1 /*
2 * Shell Menu Band
3 *
4 * Copyright 2014 David Quintana
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20 #include "shellmenu.h"
21 #include <commoncontrols.h>
22 #include <shlwapi_undoc.h>
23 #include <uxtheme.h>
24 #include <vssym32.h>
25
26 #include "CMenuBand.h"
27 #include "CMenuToolbars.h"
28
29 #define IDS_MENU_EMPTY 34561
30
31 WINE_DEFAULT_DEBUG_CHANNEL(CMenuToolbars);
32
33 // FIXME: Enable if/when wine comctl supports this flag properly
34 #define USE_TBSTYLE_EX_VERTICAL 0
35
36 // User-defined timer ID used while hot-tracking around the menu
37 #define TIMERID_HOTTRACK 1
38
OnWinEventWrap(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)39 LRESULT CMenuToolbarBase::OnWinEventWrap(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
40 {
41 LRESULT lr;
42 bHandled = OnWinEvent(m_hWnd, uMsg, wParam, lParam, &lr) != S_FALSE;
43 return lr;
44 }
45
OnWinEvent(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam,LRESULT * theResult)46 HRESULT CMenuToolbarBase::OnWinEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *theResult)
47 {
48 NMHDR * hdr;
49 HRESULT hr;
50 LRESULT result;
51
52 if (theResult)
53 *theResult = 0;
54 switch (uMsg)
55 {
56 case WM_COMMAND:
57 //return OnCommand(wParam, lParam, theResult);
58 return S_OK;
59
60 case WM_NOTIFY:
61 hdr = reinterpret_cast<LPNMHDR>(lParam);
62 switch (hdr->code)
63 {
64 case TBN_DELETINGBUTTON:
65 return OnDeletingButton(reinterpret_cast<LPNMTOOLBAR>(hdr));
66
67 case PGN_CALCSIZE:
68 return OnPagerCalcSize(reinterpret_cast<LPNMPGCALCSIZE>(hdr));
69
70 case TBN_DROPDOWN:
71 return ProcessClick(reinterpret_cast<LPNMTOOLBAR>(hdr)->iItem);
72
73 case TBN_HOTITEMCHANGE:
74 //return OnHotItemChange(reinterpret_cast<LPNMTBHOTITEM>(hdr), theResult);
75 return S_OK;
76
77 case NM_CUSTOMDRAW:
78 hr = OnCustomDraw(reinterpret_cast<LPNMTBCUSTOMDRAW>(hdr), &result);
79 if (theResult)
80 *theResult = result;
81 return hr;
82
83 case TBN_GETINFOTIP:
84 return OnGetInfoTip(reinterpret_cast<LPNMTBGETINFOTIP>(hdr));
85
86 // Silence unhandled items so that they don't print as unknown
87 case RBN_CHILDSIZE:
88 return S_OK;
89
90 case TTN_GETDISPINFO:
91 return S_OK;
92
93 case NM_RELEASEDCAPTURE:
94 break;
95
96 case NM_CLICK:
97 case NM_RDOWN:
98 case NM_LDOWN:
99 break;
100
101 case TBN_GETDISPINFO:
102 break;
103
104 case TBN_BEGINDRAG:
105 case TBN_ENDDRAG:
106 break;
107
108 case NM_TOOLTIPSCREATED:
109 break;
110
111 case TBN_DRAGOUT: return S_FALSE;
112
113 default:
114 TRACE("WM_NOTIFY unknown code %d, %d\n", hdr->code, hdr->idFrom);
115 return S_OK;
116 }
117 return S_FALSE;
118 case WM_WININICHANGE:
119 if (wParam == SPI_SETFLATMENU)
120 {
121 SystemParametersInfo(SPI_GETFLATMENU, 0, &m_useFlatMenus, 0);
122 }
123 }
124
125 return S_FALSE;
126 }
127
DisableMouseTrack(BOOL bDisable)128 HRESULT CMenuToolbarBase::DisableMouseTrack(BOOL bDisable)
129 {
130 if (m_disableMouseTrack != bDisable)
131 {
132 m_disableMouseTrack = bDisable;
133 TRACE("DisableMouseTrack %d\n", bDisable);
134 }
135 return S_OK;
136 }
137
OnPagerCalcSize(LPNMPGCALCSIZE csize)138 HRESULT CMenuToolbarBase::OnPagerCalcSize(LPNMPGCALCSIZE csize)
139 {
140 SIZE tbs;
141 GetSizes(NULL, &tbs, NULL);
142 if (csize->dwFlag == PGF_CALCHEIGHT)
143 {
144 csize->iHeight = tbs.cy;
145 }
146 else if (csize->dwFlag == PGF_CALCWIDTH)
147 {
148 csize->iWidth = tbs.cx;
149 }
150 return S_OK;
151 }
152
OnCustomDraw(LPNMTBCUSTOMDRAW cdraw,LRESULT * theResult)153 HRESULT CMenuToolbarBase::OnCustomDraw(LPNMTBCUSTOMDRAW cdraw, LRESULT * theResult)
154 {
155 bool isHot, isPopup, isActive;
156 TBBUTTONINFO btni;
157
158 switch (cdraw->nmcd.dwDrawStage)
159 {
160 case CDDS_PREPAINT:
161 *theResult = CDRF_NOTIFYITEMDRAW;
162 return S_OK;
163
164 case CDDS_ITEMPREPAINT:
165
166 HWND tlw;
167 m_menuBand->_GetTopLevelWindow(&tlw);
168
169 // The item with an active submenu gets the CHECKED flag.
170 isHot = m_hotBar == this && (int) cdraw->nmcd.dwItemSpec == m_hotItem;
171 isPopup = m_popupBar == this && (int) cdraw->nmcd.dwItemSpec == m_popupItem;
172 isActive = (GetForegroundWindow() == tlw) || (m_popupBar == this);
173
174 if (m_hotItem < 0 && isPopup)
175 isHot = TRUE;
176
177 if ((m_useFlatMenus && isHot) || (m_initFlags & SMINIT_VERTICAL))
178 {
179 COLORREF clrText;
180 HBRUSH bgBrush;
181 RECT rc = cdraw->nmcd.rc;
182 HDC hdc = cdraw->nmcd.hdc;
183
184 // Remove HOT and CHECKED flags (will restore HOT if necessary)
185 cdraw->nmcd.uItemState &= ~(CDIS_HOT | CDIS_CHECKED);
186
187 // Decide on the colors
188 if (isHot)
189 {
190 cdraw->nmcd.uItemState |= CDIS_HOT;
191
192 clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
193 bgBrush = GetSysColorBrush(m_useFlatMenus ? COLOR_MENUHILIGHT : COLOR_HIGHLIGHT);
194 }
195 else
196 {
197 clrText = GetSysColor(COLOR_MENUTEXT);
198 bgBrush = GetSysColorBrush(COLOR_MENU);
199 }
200
201 // Paint the background color with the selected color
202 FillRect(hdc, &rc, bgBrush);
203
204 // Set the text color in advance, this color will be assigned when the ITEMPOSTPAINT triggers
205 SetTextColor(hdc, clrText);
206
207 // Set the text color, will be used by the internal drawing code
208 cdraw->clrText = clrText;
209 cdraw->iListGap += 4;
210
211 // Tell the default drawing code we don't want any fanciness, not even a background.
212 *theResult = CDRF_NOTIFYPOSTPAINT | TBCDRF_NOBACKGROUND | TBCDRF_NOEDGES | TBCDRF_NOOFFSET | TBCDRF_NOMARK | 0x00800000; // FIXME: the last bit is Vista+, useful for debugging only
213 }
214 else
215 {
216 // Set the text color, will be used by the internal drawing code
217 cdraw->clrText = GetSysColor(isActive ? COLOR_MENUTEXT : COLOR_GRAYTEXT);
218
219 // Remove HOT and CHECKED flags (will restore HOT if necessary)
220 cdraw->nmcd.uItemState &= ~CDIS_HOT;
221
222 // Decide on the colors
223 if (isHot)
224 {
225 cdraw->nmcd.uItemState |= CDIS_HOT;
226 }
227
228 *theResult = 0;
229 }
230
231 return S_OK;
232
233 case CDDS_ITEMPOSTPAINT:
234
235 // Fetch the button style
236 btni.cbSize = sizeof(btni);
237 btni.dwMask = TBIF_STYLE;
238 GetButtonInfo(cdraw->nmcd.dwItemSpec, &btni);
239
240 // Check if we need to draw a submenu arrow
241 if (btni.fsStyle & BTNS_DROPDOWN)
242 {
243 // TODO: Support RTL text modes by drawing a leftwards arrow aligned to the left of the control
244
245 // "8" is the rightwards dropdown arrow in the Marlett font
246 WCHAR text [] = L"8";
247
248 // Configure the font to draw with Marlett, keeping the current background color as-is
249 HGDIOBJ hFontOld = SelectObject(cdraw->nmcd.hdc, m_marlett);
250 SetBkMode(cdraw->nmcd.hdc, TRANSPARENT);
251
252 // Tweak the alignment by 1 pixel so the menu draws like the Windows start menu.
253 RECT rc = cdraw->nmcd.rc;
254 rc.right += 1;
255
256 // The arrow is drawn at the right of the item's rect, aligned vertically.
257 DrawTextEx(cdraw->nmcd.hdc, text, 1, &rc, DT_NOCLIP | DT_VCENTER | DT_RIGHT | DT_SINGLELINE, NULL);
258
259 SelectObject(cdraw->nmcd.hdc, hFontOld);
260 }
261 *theResult = TRUE;
262 return S_OK;
263
264 default:
265 *theResult = 0L;
266 break;
267 }
268 return S_OK;
269 }
270
CMenuToolbarBase(CMenuBand * menuBand,BOOL usePager)271 CMenuToolbarBase::CMenuToolbarBase(CMenuBand *menuBand, BOOL usePager) :
272 m_pager(WC_PAGESCROLLER, this),
273 m_useFlatMenus(FALSE),
274 m_disableMouseTrack(FALSE),
275 m_timerEnabled(FALSE),
276 m_menuBand(menuBand),
277 m_dwMenuFlags(0),
278 m_hasSizes(FALSE),
279 m_usePager(usePager),
280 m_hotBar(NULL),
281 m_hotItem(-1),
282 m_popupBar(NULL),
283 m_popupItem(-1),
284 m_isTrackingPopup(FALSE),
285 m_cancelingPopup(FALSE)
286 {
287 m_idealSize.cx = 0;
288 m_idealSize.cy = 0;
289 m_itemSize.cx = 0;
290 m_itemSize.cy = 0;
291 m_marlett = CreateFont(
292 0, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET,
293 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
294 DEFAULT_QUALITY, FF_DONTCARE, L"Marlett");
295 }
296
~CMenuToolbarBase()297 CMenuToolbarBase::~CMenuToolbarBase()
298 {
299 if (m_hWnd)
300 {
301 ClearToolbar();
302 DestroyWindow();
303 }
304
305 if (m_pager.m_hWnd)
306 m_pager.DestroyWindow();
307
308 DeleteObject(m_marlett);
309 }
310
InvalidateDraw()311 void CMenuToolbarBase::InvalidateDraw()
312 {
313 InvalidateRect(NULL, FALSE);
314 }
315
ShowDW(BOOL fShow)316 HRESULT CMenuToolbarBase::ShowDW(BOOL fShow)
317 {
318 if (m_hWnd == NULL)
319 return S_FALSE;
320
321 ShowWindow(fShow ? SW_SHOW : SW_HIDE);
322
323 // Ensure that the right image list is assigned to the toolbar
324 UpdateImageLists();
325
326 // For custom-drawing
327 SystemParametersInfo(SPI_GETFLATMENU, 0, &m_useFlatMenus, 0);
328
329 return S_OK;
330 }
331
UpdateImageLists()332 HRESULT CMenuToolbarBase::UpdateImageLists()
333 {
334 if ((m_initFlags & (SMINIT_TOPLEVEL | SMINIT_VERTICAL)) == SMINIT_TOPLEVEL) // not vertical.
335 {
336 // No image list, prevents the buttons from having a margin at the left side
337 SetImageList(NULL);
338 return S_OK;
339 }
340
341 // Assign the correct imagelist and padding based on the current icon size
342
343 int shiml;
344 if (m_menuBand->UseBigIcons())
345 {
346 shiml = SHIL_LARGE;
347 SetPadding(4, 0);
348 }
349 else
350 {
351 shiml = SHIL_SMALL;
352 SetPadding(4, 4);
353 }
354
355 IImageList * piml;
356 HRESULT hr = SHGetImageList(shiml, IID_PPV_ARG(IImageList, &piml));
357 if (FAILED_UNEXPECTEDLY(hr))
358 {
359 SetImageList(NULL);
360 }
361 else
362 {
363 SetImageList((HIMAGELIST)piml);
364 }
365 return S_OK;
366 }
367
Close()368 HRESULT CMenuToolbarBase::Close()
369 {
370 if (m_hWnd)
371 DestroyWindow();
372
373 if (m_pager.m_hWnd)
374 m_pager.DestroyWindow();
375
376 return S_OK;
377 }
378
CreateToolbar(HWND hwndParent,DWORD dwFlags)379 HRESULT CMenuToolbarBase::CreateToolbar(HWND hwndParent, DWORD dwFlags)
380 {
381 LONG tbStyles = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
382 TBSTYLE_TOOLTIPS | TBSTYLE_TRANSPARENT | TBSTYLE_REGISTERDROP | TBSTYLE_LIST | TBSTYLE_FLAT | TBSTYLE_CUSTOMERASE |
383 CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE | CCS_TOP;
384 LONG tbExStyles = TBSTYLE_EX_DOUBLEBUFFER | WS_EX_TOOLWINDOW;
385
386 if (dwFlags & SMINIT_VERTICAL)
387 {
388 // Activate vertical semantics
389 tbStyles |= CCS_VERT;
390
391 #if USE_TBSTYLE_EX_VERTICAL
392 tbExStyles |= TBSTYLE_EX_VERTICAL;
393 #endif
394 }
395
396 m_initFlags = dwFlags;
397
398 // Get a temporary rect to use while creating the toolbar window.
399 // Ensure that it is not a null rect.
400 RECT rc;
401 if (!::GetClientRect(hwndParent, &rc) ||
402 (rc.left == rc.right) ||
403 (rc.top == rc.bottom))
404 {
405 rc.left = 0;
406 rc.top = 0;
407 rc.right = 1;
408 rc.bottom = 1;
409 }
410
411 // HACK & FIXME: CORE-17505
412 HWND toolbar = CToolbar::Create(hwndParent, tbStyles, tbExStyles);
413 m_hWnd = NULL;
414 SubclassWindow(toolbar);
415
416 SetWindowTheme(m_hWnd, L"", L"");
417
418 SystemParametersInfo(SPI_GETFLATMENU, 0, &m_useFlatMenus, 0);
419
420 m_menuBand->AdjustForTheme(m_useFlatMenus);
421
422 // If needed, create the pager.
423 if (m_usePager)
424 {
425 LONG pgStyles = PGS_VERT | WS_CHILD | WS_VISIBLE;
426 LONG pgExStyles = 0;
427
428 HWND hwndPager = CreateWindowEx(
429 pgExStyles, WC_PAGESCROLLER, NULL,
430 pgStyles, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
431 hwndParent, NULL, _AtlBaseModule.GetModuleInstance(), 0);
432
433 m_pager.SubclassWindow(hwndPager);
434
435 ::SetParent(m_hWnd, hwndPager);
436
437 m_pager.SendMessageW(PGM_SETCHILD, 0, reinterpret_cast<LPARAM>(m_hWnd));
438 }
439
440 // Configure the image lists
441 UpdateImageLists();
442
443 return S_OK;
444 }
445
GetSizes(SIZE * pMinSize,SIZE * pMaxSize,SIZE * pIntegralSize)446 HRESULT CMenuToolbarBase::GetSizes(SIZE* pMinSize, SIZE* pMaxSize, SIZE* pIntegralSize)
447 {
448 if (pMinSize)
449 *pMinSize = m_idealSize;
450 if (pMaxSize)
451 *pMaxSize = m_idealSize;
452 if (pIntegralSize)
453 *pIntegralSize = m_itemSize;
454
455 if (m_hasSizes)
456 return S_OK;
457
458 TRACE("Sizes out of date, recalculating.\n");
459
460 if (!m_hWnd)
461 {
462 return S_OK;
463 }
464
465 // Obtain the ideal size, to be used as min and max
466 GetMaxSize(&m_idealSize);
467 GetIdealSize((m_initFlags & SMINIT_VERTICAL) != 0, &m_idealSize);
468
469 TRACE("Ideal Size: (%d, %d) for %d buttons\n", m_idealSize, GetButtonCount());
470
471 // Obtain the button size, to be used as the integral size
472 DWORD size = GetButtonSize();
473 m_itemSize.cx = GET_X_LPARAM(size);
474 m_itemSize.cy = GET_Y_LPARAM(size);
475 m_hasSizes = TRUE;
476
477 if (pMinSize)
478 *pMinSize = m_idealSize;
479 if (pMaxSize)
480 *pMaxSize = m_idealSize;
481 if (pIntegralSize)
482 *pIntegralSize = m_itemSize;
483
484 return S_OK;
485 }
486
SetPosSize(int x,int y,int cx,int cy)487 HRESULT CMenuToolbarBase::SetPosSize(int x, int y, int cx, int cy)
488 {
489 // Update the toolbar or pager to fit the requested rect
490 // If we have a pager, set the toolbar height to the ideal height of the toolbar
491 if (m_pager.m_hWnd)
492 {
493 SetWindowPos(NULL, x, y, cx, m_idealSize.cy, 0);
494 m_pager.SetWindowPos(NULL, x, y, cx, cy, 0);
495 }
496 else
497 {
498 SetWindowPos(NULL, x, y, cx, cy, 0);
499 }
500
501 // In a vertical menu, resize the buttons to fit the width
502 if (m_initFlags & SMINIT_VERTICAL)
503 {
504 DWORD btnSize = GetButtonSize();
505 SetButtonSize(cx, GET_Y_LPARAM(btnSize));
506 }
507
508 return S_OK;
509 }
510
IsWindowOwner(HWND hwnd)511 HRESULT CMenuToolbarBase::IsWindowOwner(HWND hwnd)
512 {
513 if (m_hWnd && m_hWnd == hwnd) return S_OK;
514 if (m_pager.m_hWnd && m_pager.m_hWnd == hwnd) return S_OK;
515 return S_FALSE;
516 }
517
GetWindow(HWND * phwnd)518 HRESULT CMenuToolbarBase::GetWindow(HWND *phwnd)
519 {
520 if (!phwnd)
521 return E_FAIL;
522
523 if (m_pager.m_hWnd)
524 *phwnd = m_pager.m_hWnd;
525 else
526 *phwnd = m_hWnd;
527
528 return S_OK;
529 }
530
OnGetInfoTip(NMTBGETINFOTIP * tip)531 HRESULT CMenuToolbarBase::OnGetInfoTip(NMTBGETINFOTIP * tip)
532 {
533 INT index;
534 DWORD_PTR dwData;
535
536 INT iItem = tip->iItem;
537
538 GetDataFromId(iItem, &index, &dwData);
539
540 return InternalGetTooltip(iItem, index, dwData, tip->pszText, tip->cchTextMax);
541 }
542
OnPopupTimer(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)543 HRESULT CMenuToolbarBase::OnPopupTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
544 {
545 if (wParam != TIMERID_HOTTRACK)
546 {
547 bHandled = FALSE;
548 return 0;
549 }
550
551 KillTimer(TIMERID_HOTTRACK);
552
553 if (!m_timerEnabled)
554 return 0;
555
556 m_timerEnabled = FALSE;
557
558 if (m_hotItem < 0)
559 return 0;
560
561 // Returns S_FALSE if the current item did not show a submenu
562 HRESULT hr = PopupItem(m_hotItem, FALSE);
563 if (hr != S_FALSE)
564 return 0;
565
566 // If we didn't switch submenus, cancel the current popup regardless
567 if (m_popupBar)
568 {
569 HRESULT hr = CancelCurrentPopup();
570 if (FAILED_UNEXPECTEDLY(hr))
571 return 0;
572 }
573
574 return 0;
575 }
576
KillPopupTimer()577 HRESULT CMenuToolbarBase::KillPopupTimer()
578 {
579 if (m_timerEnabled)
580 {
581 m_timerEnabled = FALSE;
582 KillTimer(TIMERID_HOTTRACK);
583 return S_OK;
584 }
585 return S_FALSE;
586 }
587
ChangeHotItem(CMenuToolbarBase * toolbar,INT item,DWORD dwFlags)588 HRESULT CMenuToolbarBase::ChangeHotItem(CMenuToolbarBase * toolbar, INT item, DWORD dwFlags)
589 {
590 // Ignore the change if it already matches the stored info
591 if (m_hotBar == toolbar && m_hotItem == item)
592 return S_FALSE;
593
594 // Prevent a change of hot item if the change was triggered by the mouse,
595 // and mouse tracking is disabled.
596 if (m_disableMouseTrack && dwFlags & HICF_MOUSE)
597 {
598 TRACE("Hot item change prevented by DisableMouseTrack\n");
599 return S_OK;
600 }
601
602 // Notify the toolbar if the hot-tracking left this toolbar
603 if (m_hotBar == this && toolbar != this)
604 {
605 SetHotItem(-1);
606 }
607
608 TRACE("Hot item changed from %p %p, to %p %p\n", m_hotBar, m_hotItem, toolbar, item);
609 m_hotBar = toolbar;
610 m_hotItem = item;
611
612 if (m_hotBar == this)
613 {
614 if (m_isTrackingPopup && !(m_initFlags & SMINIT_VERTICAL))
615 {
616 // If the menubar has an open submenu, switch to the new item's submenu immediately
617 PopupItem(m_hotItem, FALSE);
618 }
619 else if (dwFlags & HICF_MOUSE)
620 {
621 // Vertical menus show/hide the submenu after a delay,
622 // but only with the mouse.
623 if (m_initFlags & SMINIT_VERTICAL)
624 {
625 DWORD elapsed = 0;
626 SystemParametersInfo(SPI_GETMENUSHOWDELAY, 0, &elapsed, 0);
627 SetTimer(TIMERID_HOTTRACK, elapsed);
628 m_timerEnabled = TRUE;
629 TRACE("SetTimer called with m_hotItem=%d\n", m_hotItem);
630 }
631 }
632 else
633 {
634 TBBUTTONINFO info;
635 info.cbSize = sizeof(info);
636 info.dwMask = 0;
637
638 int index = GetButtonInfo(item, &info);
639
640 SetHotItem(index);
641 }
642 }
643
644 InvalidateDraw();
645 return S_OK;
646 }
647
ChangePopupItem(CMenuToolbarBase * toolbar,INT item)648 HRESULT CMenuToolbarBase::ChangePopupItem(CMenuToolbarBase * toolbar, INT item)
649 {
650 // Ignore the change if it already matches the stored info
651 if (m_popupBar == toolbar && m_popupItem == item)
652 return S_FALSE;
653
654 // Notify the toolbar if the popup-tracking this toolbar
655 if (m_popupBar == this && toolbar != this)
656 {
657 CheckButton(m_popupItem, FALSE);
658 m_isTrackingPopup = FALSE;
659 }
660
661 m_popupBar = toolbar;
662 m_popupItem = item;
663
664 if (m_popupBar == this)
665 {
666 CheckButton(m_popupItem, TRUE);
667 }
668
669 InvalidateDraw();
670 return S_OK;
671 }
672
IsTrackedItem(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)673 LRESULT CMenuToolbarBase::IsTrackedItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
674 {
675 TBBUTTON btn;
676 INT idx = (INT)wParam;
677
678 if (m_hotBar != this)
679 return S_FALSE;
680
681 if (idx < 0)
682 return S_FALSE;
683
684 if (!GetButton(idx, &btn))
685 return E_FAIL;
686
687 if (m_hotItem == btn.idCommand)
688 return S_OK;
689
690 if (m_popupItem == btn.idCommand)
691 return S_OK;
692
693 return S_FALSE;
694 }
695
ChangeTrackedItem(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)696 LRESULT CMenuToolbarBase::ChangeTrackedItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
697 {
698 TBBUTTON btn;
699 BOOL wasTracking = LOWORD(lParam);
700 BOOL mouse = HIWORD(lParam);
701 INT idx = (INT)wParam;
702
703 if (idx < 0)
704 {
705 m_isTrackingPopup = FALSE;
706 return m_menuBand->_ChangeHotItem(NULL, -1, HICF_MOUSE);
707 }
708
709 if (!GetButton(idx, &btn))
710 return E_FAIL;
711
712 TRACE("ChangeTrackedItem %d, %d\n", idx, wasTracking);
713 m_isTrackingPopup = wasTracking;
714 return m_menuBand->_ChangeHotItem(this, btn.idCommand, mouse ? HICF_MOUSE : 0);
715 }
716
PopupSubMenu(UINT iItem,UINT index,IShellMenu * childShellMenu,BOOL keyInitiated)717 HRESULT CMenuToolbarBase::PopupSubMenu(UINT iItem, UINT index, IShellMenu* childShellMenu, BOOL keyInitiated)
718 {
719 // Calculate the submenu position and exclude area
720 RECT rc = { 0 };
721
722 if (!GetItemRect(index, &rc))
723 return E_FAIL;
724
725 POINT a = { rc.left, rc.top };
726 POINT b = { rc.right, rc.bottom };
727
728 ClientToScreen(&a);
729 ClientToScreen(&b);
730
731 POINTL pt = { a.x, b.y };
732 RECTL rcl = { a.x, a.y, b.x, b.y };
733
734 if (m_initFlags & SMINIT_VERTICAL)
735 {
736 // FIXME: Hardcoding this here feels hacky.
737 if (IsAppThemed())
738 {
739 pt.x = b.x - 1;
740 pt.y = a.y - 1;
741 }
742 else
743 {
744 pt.x = b.x - 3;
745 pt.y = a.y - 3;
746 }
747 }
748
749 // Display the submenu
750 m_isTrackingPopup = TRUE;
751
752 m_menuBand->_ChangePopupItem(this, iItem);
753 m_menuBand->_OnPopupSubMenu(childShellMenu, &pt, &rcl, keyInitiated);
754
755 return S_OK;
756 }
757
PopupSubMenu(UINT iItem,UINT index,HMENU menu)758 HRESULT CMenuToolbarBase::PopupSubMenu(UINT iItem, UINT index, HMENU menu)
759 {
760 // Calculate the submenu position and exclude area
761 RECT rc = { 0 };
762
763 if (!GetItemRect(index, &rc))
764 return E_FAIL;
765
766 POINT a = { rc.left, rc.top };
767 POINT b = { rc.right, rc.bottom };
768
769 ClientToScreen(&a);
770 ClientToScreen(&b);
771
772 POINT pt = { a.x, b.y };
773 RECT rcl = { a.x, a.y, b.x, b.y };
774
775 if (m_initFlags & SMINIT_VERTICAL)
776 {
777 pt.x = b.x;
778 pt.y = a.y;
779 }
780
781 HMENU popup = GetSubMenu(menu, index);
782
783 // Display the submenu
784 m_isTrackingPopup = TRUE;
785 m_menuBand->_ChangePopupItem(this, iItem);
786 m_menuBand->_TrackSubMenu(popup, pt.x, pt.y, rcl);
787 m_menuBand->_ChangePopupItem(NULL, -1);
788 m_isTrackingPopup = FALSE;
789
790 return S_OK;
791 }
792
TrackContextMenu(IContextMenu * contextMenu,POINT pt)793 HRESULT CMenuToolbarBase::TrackContextMenu(IContextMenu* contextMenu, POINT pt)
794 {
795 // Cancel submenus
796 m_menuBand->_KillPopupTimers();
797 if (m_popupBar)
798 m_menuBand->_CancelCurrentPopup();
799
800 // Display the context menu
801 return m_menuBand->_TrackContextMenu(contextMenu, pt.x, pt.y);
802 }
803
BeforeCancelPopup()804 HRESULT CMenuToolbarBase::BeforeCancelPopup()
805 {
806 m_cancelingPopup = TRUE;
807 TRACE("BeforeCancelPopup\n");
808 return S_OK;
809 }
810
ProcessClick(INT iItem)811 HRESULT CMenuToolbarBase::ProcessClick(INT iItem)
812 {
813 if (m_disableMouseTrack)
814 {
815 TRACE("Item click prevented by DisableMouseTrack\n");
816 return S_OK;
817 }
818
819 // If a button is clicked while a submenu was open, cancel the submenu.
820 if (!(m_initFlags & SMINIT_VERTICAL) && m_isTrackingPopup)
821 {
822 TRACE("OnCommand cancelled because it was tracking submenu.\n");
823 return S_FALSE;
824 }
825
826 if (PopupItem(iItem, FALSE) == S_OK)
827 {
828 TRACE("PopupItem returned S_OK\n");
829 return S_FALSE;
830 }
831
832 TRACE("Executing...\n");
833
834 return m_menuBand->_MenuItemSelect(MPOS_EXECUTE);
835 }
836
ProcessContextMenu(INT iItem)837 HRESULT CMenuToolbarBase::ProcessContextMenu(INT iItem)
838 {
839 INT index;
840 DWORD_PTR data;
841
842 GetDataFromId(iItem, &index, &data);
843
844 DWORD pos = GetMessagePos();
845 POINT pt = { GET_X_LPARAM(pos), GET_Y_LPARAM(pos) };
846
847 return InternalContextMenu(iItem, index, data, pt);
848 }
849
MenuBarMouseDown(INT iIndex,BOOL isLButton)850 HRESULT CMenuToolbarBase::MenuBarMouseDown(INT iIndex, BOOL isLButton)
851 {
852 TBBUTTON btn;
853
854 GetButton(iIndex, &btn);
855
856 if ((m_initFlags & SMINIT_VERTICAL)
857 || m_popupBar
858 || m_cancelingPopup)
859 {
860 m_cancelingPopup = FALSE;
861 return S_OK;
862 }
863
864 return ProcessClick(btn.idCommand);
865 }
866
MenuBarMouseUp(INT iIndex,BOOL isLButton)867 HRESULT CMenuToolbarBase::MenuBarMouseUp(INT iIndex, BOOL isLButton)
868 {
869 TBBUTTON btn;
870
871 m_cancelingPopup = FALSE;
872
873 if (!(m_initFlags & SMINIT_VERTICAL))
874 return S_OK;
875
876 GetButton(iIndex, &btn);
877
878 if (isLButton)
879 return ProcessClick(btn.idCommand);
880 else
881 return ProcessContextMenu(btn.idCommand);
882 }
883
PrepareExecuteItem(INT iItem)884 HRESULT CMenuToolbarBase::PrepareExecuteItem(INT iItem)
885 {
886 this->m_menuBand->_KillPopupTimers();
887
888 m_executeItem = iItem;
889 return GetDataFromId(iItem, &m_executeIndex, &m_executeData);
890 }
891
ExecuteItem()892 HRESULT CMenuToolbarBase::ExecuteItem()
893 {
894 return InternalExecuteItem(m_executeItem, m_executeItem, m_executeData);
895 }
896
KeyboardItemChange(DWORD dwSelectType)897 HRESULT CMenuToolbarBase::KeyboardItemChange(DWORD dwSelectType)
898 {
899 int prev = m_hotItem;
900 int index = -1;
901
902 if (dwSelectType != 0xFFFFFFFF)
903 {
904 int count = GetButtonCount();
905
906 if (dwSelectType == VK_HOME)
907 {
908 index = 0;
909 dwSelectType = VK_DOWN;
910 }
911 else if (dwSelectType == VK_END)
912 {
913 index = count - 1;
914 dwSelectType = VK_UP;
915 }
916 else
917 {
918 if (m_hotItem >= 0)
919 {
920 TBBUTTONINFO info = { 0 };
921 info.cbSize = sizeof(TBBUTTONINFO);
922 info.dwMask = 0;
923 index = GetButtonInfo(m_hotItem, &info);
924 }
925
926 if (index < 0)
927 {
928 if (dwSelectType == VK_UP)
929 {
930 index = count - 1;
931 }
932 else if (dwSelectType == VK_DOWN)
933 {
934 index = 0;
935 }
936 }
937 else
938 {
939 if (dwSelectType == VK_UP)
940 {
941 index--;
942 }
943 else if (dwSelectType == VK_DOWN)
944 {
945 index++;
946 }
947 }
948 }
949
950 TBBUTTON btn = { 0 };
951 while (index >= 0 && index < count)
952 {
953 DWORD res = GetButton(index, &btn);
954 if (!res)
955 return E_FAIL;
956
957 if (btn.dwData)
958 {
959 if (prev != btn.idCommand)
960 {
961 TRACE("Setting Hot item to %d\n", index);
962 if (!(m_initFlags & SMINIT_VERTICAL) && m_isTrackingPopup)
963 {
964 HWND tlw;
965 m_menuBand->_GetTopLevelWindow(&tlw);
966 ::SendMessageW(tlw, WM_CANCELMODE, 0, 0);
967 PostMessageW(WM_USER_CHANGETRACKEDITEM, index, MAKELPARAM(m_isTrackingPopup, FALSE));
968 }
969 else
970 m_menuBand->_ChangeHotItem(this, btn.idCommand, 0);
971 }
972 return S_OK;
973 }
974
975 if (dwSelectType == VK_UP)
976 {
977 index--;
978 }
979 else if (dwSelectType == VK_DOWN)
980 {
981 index++;
982 }
983 }
984
985 return S_FALSE;
986 }
987
988 if (prev != -1)
989 {
990 TRACE("Setting Hot item to null\n");
991 m_menuBand->_ChangeHotItem(NULL, -1, 0);
992 }
993
994 return S_FALSE;
995 }
996
AddButton(DWORD commandId,LPCWSTR caption,BOOL hasSubMenu,INT iconId,DWORD_PTR buttonData,BOOL last)997 HRESULT CMenuToolbarBase::AddButton(DWORD commandId, LPCWSTR caption, BOOL hasSubMenu, INT iconId, DWORD_PTR buttonData, BOOL last)
998 {
999 TBBUTTON tbb = { 0 };
1000
1001 tbb.fsState = TBSTATE_ENABLED;
1002 #if !USE_TBSTYLE_EX_VERTICAL
1003 if (!last && (m_initFlags & SMINIT_VERTICAL))
1004 tbb.fsState |= TBSTATE_WRAP;
1005 #endif
1006 tbb.fsStyle = BTNS_CHECKGROUP;
1007
1008 if (hasSubMenu && (m_initFlags & SMINIT_VERTICAL))
1009 tbb.fsStyle |= BTNS_DROPDOWN;
1010
1011 if (!(m_initFlags & SMINIT_VERTICAL))
1012 tbb.fsStyle |= BTNS_AUTOSIZE;
1013
1014 tbb.iString = (INT_PTR) caption;
1015 tbb.idCommand = commandId;
1016
1017 tbb.iBitmap = iconId;
1018 tbb.dwData = buttonData;
1019
1020 m_hasSizes = FALSE;
1021
1022 if (!AddButtons(1, &tbb))
1023 return HRESULT_FROM_WIN32(GetLastError());
1024 return S_OK;
1025 }
1026
AddSeparator(BOOL last)1027 HRESULT CMenuToolbarBase::AddSeparator(BOOL last)
1028 {
1029 TBBUTTON tbb = { 0 };
1030
1031 tbb.fsState = TBSTATE_ENABLED;
1032 #if !USE_TBSTYLE_EX_VERTICAL
1033 if (!last && (m_initFlags & SMINIT_VERTICAL))
1034 tbb.fsState |= TBSTATE_WRAP;
1035 #endif
1036 tbb.fsStyle = BTNS_SEP;
1037 tbb.iBitmap = 0;
1038
1039 m_hasSizes = FALSE;
1040
1041 if (!AddButtons(1, &tbb))
1042 return HRESULT_FROM_WIN32(GetLastError());
1043
1044 return S_OK;
1045 }
1046
AddPlaceholder()1047 HRESULT CMenuToolbarBase::AddPlaceholder()
1048 {
1049 TBBUTTON tbb = { 0 };
1050 WCHAR MenuString[128];
1051
1052 LoadStringW(GetModuleHandle(L"shell32.dll"), IDS_MENU_EMPTY, MenuString, _countof(MenuString));
1053
1054 tbb.fsState = 0;
1055 tbb.fsStyle = 0;
1056 tbb.iString = (INT_PTR) MenuString;
1057 tbb.iBitmap = -1;
1058
1059 m_hasSizes = FALSE;
1060
1061 if (!AddButtons(1, &tbb))
1062 return HRESULT_FROM_WIN32(GetLastError());
1063
1064 return S_OK;
1065 }
1066
ClearToolbar()1067 HRESULT CMenuToolbarBase::ClearToolbar()
1068 {
1069 while (DeleteButton(0))
1070 {
1071 // empty;
1072 }
1073 m_hasSizes = FALSE;
1074 return S_OK;
1075 }
1076
GetDataFromId(INT iItem,INT * pIndex,DWORD_PTR * pData)1077 HRESULT CMenuToolbarBase::GetDataFromId(INT iItem, INT* pIndex, DWORD_PTR* pData)
1078 {
1079 if (pData)
1080 *pData = NULL;
1081
1082 if (pIndex)
1083 *pIndex = -1;
1084
1085 if (iItem < 0)
1086 return S_OK;
1087
1088 TBBUTTONINFO info = { 0 };
1089
1090 info.cbSize = sizeof(TBBUTTONINFO);
1091 info.dwMask = TBIF_COMMAND | TBIF_LPARAM;
1092
1093 int index = GetButtonInfo(iItem, &info);
1094 if (index < 0)
1095 return E_FAIL;
1096
1097 if (pIndex)
1098 *pIndex = index;
1099
1100 if (pData)
1101 *pData = info.lParam;
1102
1103 return S_OK;
1104 }
1105
CancelCurrentPopup()1106 HRESULT CMenuToolbarBase::CancelCurrentPopup()
1107 {
1108 return m_menuBand->_CancelCurrentPopup();
1109 }
1110
PopupItem(INT iItem,BOOL keyInitiated)1111 HRESULT CMenuToolbarBase::PopupItem(INT iItem, BOOL keyInitiated)
1112 {
1113 INT index;
1114 DWORD_PTR dwData;
1115
1116 if (iItem < 0)
1117 return S_OK;
1118
1119 if (m_popupBar == this && m_popupItem == iItem)
1120 return S_OK;
1121
1122 GetDataFromId(iItem, &index, &dwData);
1123
1124 HRESULT hr = InternalHasSubMenu(iItem, index, dwData);
1125 if (hr != S_OK)
1126 return hr;
1127
1128 if (m_popupBar)
1129 {
1130 HRESULT hr = CancelCurrentPopup();
1131 if (FAILED_UNEXPECTEDLY(hr))
1132 return hr;
1133 }
1134
1135 if (!(m_initFlags & SMINIT_VERTICAL))
1136 {
1137 TRACE("PopupItem non-vertical %d %d\n", index, iItem);
1138 m_menuBand->_ChangeHotItem(this, iItem, 0);
1139 }
1140
1141 return InternalPopupItem(iItem, index, dwData, keyInitiated);
1142 }
1143
CMenuStaticToolbar(CMenuBand * menuBand)1144 CMenuStaticToolbar::CMenuStaticToolbar(CMenuBand *menuBand) :
1145 CMenuToolbarBase(menuBand, FALSE),
1146 m_hmenu(NULL),
1147 m_hwndMenu(NULL)
1148 {
1149 }
1150
~CMenuStaticToolbar()1151 CMenuStaticToolbar::~CMenuStaticToolbar()
1152 {
1153 }
1154
GetMenu(_Out_opt_ HMENU * phmenu,_Out_opt_ HWND * phwnd,_Out_opt_ DWORD * pdwFlags)1155 HRESULT CMenuStaticToolbar::GetMenu(
1156 _Out_opt_ HMENU *phmenu,
1157 _Out_opt_ HWND *phwnd,
1158 _Out_opt_ DWORD *pdwFlags)
1159 {
1160 if (phmenu)
1161 *phmenu = m_hmenu;
1162 if (phwnd)
1163 *phwnd = m_hwndMenu;
1164 if (pdwFlags)
1165 *pdwFlags = m_dwMenuFlags;
1166
1167 return S_OK;
1168 }
1169
SetMenu(HMENU hmenu,HWND hwnd,DWORD dwFlags)1170 HRESULT CMenuStaticToolbar::SetMenu(
1171 HMENU hmenu,
1172 HWND hwnd,
1173 DWORD dwFlags)
1174 {
1175 m_hmenu = hmenu;
1176 m_hwndMenu = hwnd;
1177 m_dwMenuFlags = dwFlags;
1178
1179 if (IsWindow())
1180 ClearToolbar();
1181
1182 return S_OK;
1183 }
1184
FillToolbar(BOOL clearFirst)1185 HRESULT CMenuStaticToolbar::FillToolbar(BOOL clearFirst)
1186 {
1187 int i;
1188 int ic = GetMenuItemCount(m_hmenu);
1189
1190 if (clearFirst)
1191 {
1192 ClearToolbar();
1193 }
1194
1195 int count = 0;
1196 for (i = 0; i < ic; i++)
1197 {
1198 BOOL last = i + 1 == ic;
1199
1200 MENUITEMINFOW info;
1201
1202 info.cbSize = sizeof(info);
1203 info.dwTypeData = NULL;
1204 info.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID;
1205
1206 if (!GetMenuItemInfoW(m_hmenu, i, TRUE, &info))
1207 {
1208 TRACE("Error obtaining info for menu item at pos=%d\n", i);
1209 continue;
1210 }
1211
1212 count++;
1213
1214 if (info.fType & MFT_SEPARATOR)
1215 {
1216 AddSeparator(last);
1217 }
1218 else if (!(info.fType & MFT_BITMAP))
1219 {
1220 info.cch++;
1221 info.dwTypeData = (PWSTR) HeapAlloc(GetProcessHeap(), 0, (info.cch + 1) * sizeof(WCHAR));
1222
1223 info.fMask = MIIM_STRING | MIIM_SUBMENU | MIIM_ID;
1224 GetMenuItemInfoW(m_hmenu, i, TRUE, &info);
1225
1226 SMINFO * sminfo = new SMINFO();
1227 sminfo->dwMask = SMIM_ICON | SMIM_FLAGS;
1228
1229 HRESULT hr = m_menuBand->_CallCBWithItemId(info.wID, SMC_GETINFO, 0, reinterpret_cast<LPARAM>(sminfo));
1230 if (FAILED_UNEXPECTEDLY(hr))
1231 {
1232 delete sminfo;
1233 return hr;
1234 }
1235
1236 AddButton(info.wID, info.dwTypeData, info.hSubMenu != NULL, sminfo->iIcon, reinterpret_cast<DWORD_PTR>(sminfo), last);
1237
1238 HeapFree(GetProcessHeap(), 0, info.dwTypeData);
1239 }
1240 }
1241
1242 return S_OK;
1243 }
1244
InternalGetTooltip(INT iItem,INT index,DWORD_PTR dwData,LPWSTR pszText,INT cchTextMax)1245 HRESULT CMenuStaticToolbar::InternalGetTooltip(INT iItem, INT index, DWORD_PTR dwData, LPWSTR pszText, INT cchTextMax)
1246 {
1247 //SMINFO * info = reinterpret_cast<SMINFO*>(dwData);
1248 UNIMPLEMENTED;
1249 return E_NOTIMPL;
1250 }
1251
OnDeletingButton(const NMTOOLBAR * tb)1252 HRESULT CMenuStaticToolbar::OnDeletingButton(const NMTOOLBAR * tb)
1253 {
1254 delete reinterpret_cast<SMINFO*>(tb->tbButton.dwData);
1255 return S_OK;
1256 }
1257
InternalContextMenu(INT iItem,INT index,DWORD_PTR dwData,POINT pt)1258 HRESULT CMenuStaticToolbar::InternalContextMenu(INT iItem, INT index, DWORD_PTR dwData, POINT pt)
1259 {
1260 CComPtr<IContextMenu> contextMenu;
1261 HRESULT hr = m_menuBand->_CallCBWithItemId(iItem, SMC_GETOBJECT,
1262 reinterpret_cast<WPARAM>(&IID_IContextMenu), reinterpret_cast<LPARAM>(&contextMenu));
1263 if (hr != S_OK)
1264 return hr;
1265
1266 return TrackContextMenu(contextMenu, pt);
1267 }
1268
InternalExecuteItem(INT iItem,INT index,DWORD_PTR data)1269 HRESULT CMenuStaticToolbar::InternalExecuteItem(INT iItem, INT index, DWORD_PTR data)
1270 {
1271 return m_menuBand->_CallCBWithItemId(iItem, SMC_EXEC, 0, 0);
1272 }
1273
InternalPopupItem(INT iItem,INT index,DWORD_PTR dwData,BOOL keyInitiated)1274 HRESULT CMenuStaticToolbar::InternalPopupItem(INT iItem, INT index, DWORD_PTR dwData, BOOL keyInitiated)
1275 {
1276 SMINFO * nfo = reinterpret_cast<SMINFO*>(dwData);
1277 if (!nfo)
1278 return E_FAIL;
1279
1280 if (nfo->dwFlags&SMIF_TRACKPOPUP)
1281 {
1282 return PopupSubMenu(iItem, index, m_hmenu);
1283 }
1284 else
1285 {
1286 CComPtr<IShellMenu> shellMenu;
1287 HRESULT hr = m_menuBand->_CallCBWithItemId(iItem, SMC_GETOBJECT, reinterpret_cast<WPARAM>(&IID_IShellMenu), reinterpret_cast<LPARAM>(&shellMenu));
1288 if (FAILED_UNEXPECTEDLY(hr))
1289 return hr;
1290
1291 return PopupSubMenu(iItem, index, shellMenu, keyInitiated);
1292 }
1293 }
1294
InternalHasSubMenu(INT iItem,INT index,DWORD_PTR dwData)1295 HRESULT CMenuStaticToolbar::InternalHasSubMenu(INT iItem, INT index, DWORD_PTR dwData)
1296 {
1297 return ::GetSubMenu(m_hmenu, index) ? S_OK : S_FALSE;
1298 }
1299
CMenuSFToolbar(CMenuBand * menuBand)1300 CMenuSFToolbar::CMenuSFToolbar(CMenuBand * menuBand) :
1301 CMenuToolbarBase(menuBand, TRUE),
1302 m_shellFolder(NULL),
1303 m_idList(NULL),
1304 m_hKey(NULL)
1305 {
1306 }
1307
~CMenuSFToolbar()1308 CMenuSFToolbar::~CMenuSFToolbar()
1309 {
1310 }
1311
PidlListSort(void * item1,void * item2,LPARAM lParam)1312 int CALLBACK PidlListSort(void* item1, void* item2, LPARAM lParam)
1313 {
1314 IShellFolder * psf = (IShellFolder*) lParam;
1315 PCUIDLIST_RELATIVE pidl1 = (PCUIDLIST_RELATIVE) item1;
1316 PCUIDLIST_RELATIVE pidl2 = (PCUIDLIST_RELATIVE) item2;
1317 HRESULT hr = psf->CompareIDs(0, pidl1, pidl2);
1318 if (FAILED(hr))
1319 {
1320 // No way to cancel, so sort to equal.
1321 return 0;
1322 }
1323 return (int)(short)LOWORD(hr);
1324 }
1325
IsPidlPrograms(LPCITEMIDLIST pidlTarget)1326 static BOOL IsPidlPrograms(LPCITEMIDLIST pidlTarget)
1327 {
1328 WCHAR szTarget[MAX_PATH], szPath[MAX_PATH];
1329 if (!SHGetPathFromIDListW(pidlTarget, szTarget))
1330 return FALSE;
1331 SHGetSpecialFolderPathW(NULL, szPath, CSIDL_COMMON_PROGRAMS, FALSE);
1332 if (lstrcmpiW(szTarget, szPath) == 0)
1333 return TRUE;
1334 SHGetSpecialFolderPathW(NULL, szPath, CSIDL_PROGRAMS, FALSE);
1335 return (lstrcmpiW(szTarget, szPath) == 0);
1336 }
1337
FillToolbar(BOOL clearFirst)1338 HRESULT CMenuSFToolbar::FillToolbar(BOOL clearFirst)
1339 {
1340 HRESULT hr;
1341
1342 CComPtr<IEnumIDList> eidl;
1343 hr = m_shellFolder->EnumObjects(GetToolbar(), SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &eidl);
1344 if (FAILED_UNEXPECTEDLY(hr))
1345 return hr;
1346
1347 HDPA dpaSort = DPA_Create(10);
1348
1349 LPITEMIDLIST item = NULL;
1350 hr = eidl->Next(1, &item, NULL);
1351 while (hr == S_OK)
1352 {
1353 if (m_menuBand->_CallCBWithItemPidl(item, 0x10000000, 0, 0) == S_FALSE)
1354 {
1355 DPA_AppendPtr(dpaSort, item);
1356 }
1357 else
1358 {
1359 CoTaskMemFree(item);
1360 }
1361
1362 hr = eidl->Next(1, &item, NULL);
1363 }
1364
1365 // If no items were added, show the "empty" placeholder
1366 if (DPA_GetPtrCount(dpaSort) == 0)
1367 {
1368 DPA_Destroy(dpaSort);
1369 return AddPlaceholder();
1370 }
1371
1372 TRACE("FillToolbar added %d items to the DPA\n", DPA_GetPtrCount(dpaSort));
1373
1374 DPA_Sort(dpaSort, PidlListSort, (LPARAM) m_shellFolder.p);
1375
1376 BOOL StartMenuAdminTools = SHRegGetBoolUSValueW(
1377 L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced",
1378 L"StartMenuAdminTools", FALSE, TRUE);
1379
1380 BOOL bMustHideAdminTools = IsPidlPrograms(m_idList) && !StartMenuAdminTools;
1381 TRACE("StartMenuAdminTools: %d\n", StartMenuAdminTools);
1382 TRACE("bMustHideAdminTools: %d\n", bMustHideAdminTools);
1383
1384 WCHAR szAdminTools[MAX_PATH];
1385 if (bMustHideAdminTools)
1386 {
1387 LoadStringW(GetModuleHandleW(L"shell32.dll"), IDS_ADMINISTRATIVETOOLS,
1388 szAdminTools, _countof(szAdminTools));
1389 }
1390
1391 for (int i = 0; i<DPA_GetPtrCount(dpaSort);)
1392 {
1393 item = (LPITEMIDLIST)DPA_GetPtr(dpaSort, i);
1394
1395 STRRET sr = { STRRET_CSTR };
1396 hr = m_shellFolder->GetDisplayNameOf(item, SIGDN_NORMALDISPLAY, &sr);
1397 if (FAILED_UNEXPECTEDLY(hr))
1398 {
1399 DPA_Destroy(dpaSort);
1400 return hr;
1401 }
1402
1403 PWSTR MenuString;
1404 StrRetToStr(&sr, NULL, &MenuString);
1405
1406 if (bMustHideAdminTools && lstrcmpiW(MenuString, szAdminTools) == 0)
1407 {
1408 ++i;
1409 CoTaskMemFree(MenuString);
1410 continue;
1411 }
1412
1413 INT indexOpen = 0;
1414 INT index = SHMapPIDLToSystemImageListIndex(m_shellFolder, item, &indexOpen);
1415
1416 LPCITEMIDLIST itemc = item;
1417
1418 SFGAOF attrs = SFGAO_FOLDER;
1419 hr = m_shellFolder->GetAttributesOf(1, &itemc, &attrs);
1420
1421 DWORD_PTR dwData = reinterpret_cast<DWORD_PTR>(item);
1422
1423 // Fetch next item already, so we know if the current one is the last
1424 i++;
1425
1426 AddButton(i, MenuString, attrs & SFGAO_FOLDER, index, dwData, i >= DPA_GetPtrCount(dpaSort));
1427
1428 CoTaskMemFree(MenuString);
1429 }
1430
1431 DPA_Destroy(dpaSort);
1432 return hr;
1433 }
1434
InternalGetTooltip(INT iItem,INT index,DWORD_PTR dwData,LPWSTR pszText,INT cchTextMax)1435 HRESULT CMenuSFToolbar::InternalGetTooltip(INT iItem, INT index, DWORD_PTR dwData, LPWSTR pszText, INT cchTextMax)
1436 {
1437 //ITEMIDLIST * pidl = reinterpret_cast<LPITEMIDLIST>(dwData);
1438 UNIMPLEMENTED;
1439 return E_NOTIMPL;
1440 }
1441
OnDeletingButton(const NMTOOLBAR * tb)1442 HRESULT CMenuSFToolbar::OnDeletingButton(const NMTOOLBAR * tb)
1443 {
1444 ILFree(reinterpret_cast<LPITEMIDLIST>(tb->tbButton.dwData));
1445 return S_OK;
1446 }
1447
SetShellFolder(IShellFolder * psf,LPCITEMIDLIST pidlFolder,HKEY hKey,DWORD dwFlags)1448 HRESULT CMenuSFToolbar::SetShellFolder(IShellFolder *psf, LPCITEMIDLIST pidlFolder, HKEY hKey, DWORD dwFlags)
1449 {
1450 m_shellFolder = psf;
1451 m_idList = ILClone(pidlFolder);
1452 m_hKey = hKey;
1453 m_dwMenuFlags = dwFlags;
1454
1455 if (IsWindow())
1456 ClearToolbar();
1457
1458 return S_OK;
1459 }
1460
GetShellFolder(DWORD * pdwFlags,LPITEMIDLIST * ppidl,REFIID riid,void ** ppv)1461 HRESULT CMenuSFToolbar::GetShellFolder(DWORD *pdwFlags, LPITEMIDLIST *ppidl, REFIID riid, void **ppv)
1462 {
1463 HRESULT hr;
1464
1465 hr = m_shellFolder->QueryInterface(riid, ppv);
1466 if (FAILED_UNEXPECTEDLY(hr))
1467 return hr;
1468
1469 if (pdwFlags)
1470 *pdwFlags = m_dwMenuFlags;
1471
1472 if (ppidl)
1473 {
1474 LPITEMIDLIST pidl = NULL;
1475
1476 if (m_idList)
1477 {
1478 pidl = ILClone(m_idList);
1479 if (!pidl)
1480 {
1481 ERR("ILClone failed!\n");
1482 (*reinterpret_cast<IUnknown**>(ppv))->Release();
1483 return E_FAIL;
1484 }
1485 }
1486
1487 *ppidl = pidl;
1488 }
1489
1490 return hr;
1491 }
1492
InternalContextMenu(INT iItem,INT index,DWORD_PTR dwData,POINT pt)1493 HRESULT CMenuSFToolbar::InternalContextMenu(INT iItem, INT index, DWORD_PTR dwData, POINT pt)
1494 {
1495 HRESULT hr;
1496 CComPtr<IContextMenu> contextMenu = NULL;
1497 LPCITEMIDLIST pidl = reinterpret_cast<LPCITEMIDLIST>(dwData);
1498
1499 hr = m_shellFolder->GetUIObjectOf(GetToolbar(), 1, &pidl, IID_NULL_PPV_ARG(IContextMenu, &contextMenu));
1500 if (FAILED_UNEXPECTEDLY(hr))
1501 {
1502 return hr;
1503 }
1504
1505 hr = TrackContextMenu(contextMenu, pt);
1506
1507 return hr;
1508 }
1509
InternalExecuteItem(INT iItem,INT index,DWORD_PTR data)1510 HRESULT CMenuSFToolbar::InternalExecuteItem(INT iItem, INT index, DWORD_PTR data)
1511 {
1512 return m_menuBand->_CallCBWithItemPidl(reinterpret_cast<LPITEMIDLIST>(data), SMC_SFEXEC, 0, 0);
1513 }
1514
InternalPopupItem(INT iItem,INT index,DWORD_PTR dwData,BOOL keyInitiated)1515 HRESULT CMenuSFToolbar::InternalPopupItem(INT iItem, INT index, DWORD_PTR dwData, BOOL keyInitiated)
1516 {
1517 HRESULT hr;
1518 UINT uId;
1519 UINT uIdAncestor;
1520 DWORD flags;
1521 CComPtr<IShellMenuCallback> psmc;
1522 CComPtr<IShellMenu> shellMenu;
1523
1524 LPITEMIDLIST pidl = reinterpret_cast<LPITEMIDLIST>(dwData);
1525
1526 if (!pidl)
1527 return E_FAIL;
1528
1529 hr = CMenuBand_CreateInstance(IID_PPV_ARG(IShellMenu, &shellMenu));
1530 if (FAILED_UNEXPECTEDLY(hr))
1531 return hr;
1532
1533 m_menuBand->GetMenuInfo(&psmc, &uId, &uIdAncestor, &flags);
1534
1535 // FIXME: not sure what to use as uId/uIdAncestor here
1536 hr = shellMenu->Initialize(psmc, 0, uId, SMINIT_VERTICAL);
1537 if (FAILED_UNEXPECTEDLY(hr))
1538 return hr;
1539
1540 CComPtr<IShellFolder> childFolder;
1541 hr = m_shellFolder->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &childFolder));
1542 if (FAILED_UNEXPECTEDLY(hr))
1543 return hr;
1544
1545 hr = shellMenu->SetShellFolder(childFolder, NULL, NULL, SMSET_TOP);
1546 if (FAILED_UNEXPECTEDLY(hr))
1547 return hr;
1548
1549 return PopupSubMenu(iItem, index, shellMenu, keyInitiated);
1550 }
1551
InternalHasSubMenu(INT iItem,INT index,DWORD_PTR dwData)1552 HRESULT CMenuSFToolbar::InternalHasSubMenu(INT iItem, INT index, DWORD_PTR dwData)
1553 {
1554 HRESULT hr;
1555 LPCITEMIDLIST pidl = reinterpret_cast<LPITEMIDLIST>(dwData);
1556
1557 SFGAOF attrs = SFGAO_FOLDER;
1558 hr = m_shellFolder->GetAttributesOf(1, &pidl, &attrs);
1559 if (FAILED_UNEXPECTEDLY(hr))
1560 return hr;
1561
1562 return (attrs & SFGAO_FOLDER) ? S_OK : S_FALSE;
1563 }
1564