[SHLWAPI]
[reactos.git] / base / shell / rshell / CMenuToolbars.cpp
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 "precomp.h"
21 #include <windowsx.h>
22 #include <CommonControls.h>
23 #include <shlwapi_undoc.h>
24
25 #include "CMenuBand.h"
26 #include "CMenuToolbars.h"
27
28 WINE_DEFAULT_DEBUG_CHANNEL(CMenuToolbars);
29
30 extern "C"
31 HRESULT WINAPI SHGetImageList(
32 _In_ int iImageList,
33 _In_ REFIID riid,
34 _Out_ void **ppv
35 );
36
37 // FIXME: Enable if/when wine comctl supports this flag properly
38 //#define TBSTYLE_EX_VERTICAL 4
39
40 #define TIMERID_HOTTRACK 1
41 #define SUBCLASS_ID_MENUBAND 1
42
43 HRESULT CMenuToolbarBase::OnWinEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *theResult)
44 {
45 RECT rc;
46 HDC hdc;
47 HBRUSH bgBrush;
48 HBRUSH hotBrush;
49 NMHDR * hdr;
50 NMTBCUSTOMDRAW * cdraw;
51 NMTBHOTITEM * hot;
52 NMMOUSE * rclick;
53 NMPGCALCSIZE* csize;
54 TBBUTTONINFO btni;
55 COLORREF clrText;
56 COLORREF clrTextHighlight;
57 SIZE tbs;
58 bool isHot, isPopup;
59
60 *theResult = 0;
61 switch (uMsg)
62 {
63 case WM_COMMAND:
64 return OnCommand(wParam, lParam, theResult);
65
66 case WM_NOTIFY:
67 hdr = reinterpret_cast<LPNMHDR>(lParam);
68 switch (hdr->code)
69 {
70 case PGN_CALCSIZE:
71 csize = reinterpret_cast<LPNMPGCALCSIZE>(hdr);
72
73 GetIdealSize(tbs);
74 if (csize->dwFlag == PGF_CALCHEIGHT)
75 {
76 csize->iHeight = tbs.cy;
77 }
78 else if (csize->dwFlag == PGF_CALCWIDTH)
79 {
80 csize->iHeight = tbs.cx;
81 }
82 return S_OK;
83
84 case TBN_DROPDOWN:
85 wParam = reinterpret_cast<LPNMTOOLBAR>(hdr)->iItem;
86 return OnCommand(wParam, 0, theResult);
87
88 case TBN_HOTITEMCHANGE:
89 hot = reinterpret_cast<LPNMTBHOTITEM>(hdr);
90 return OnHotItemChange(hot);
91
92 case NM_RCLICK:
93 rclick = reinterpret_cast<LPNMMOUSE>(hdr);
94
95 return OnContextMenu(rclick);
96
97 case NM_CUSTOMDRAW:
98 cdraw = reinterpret_cast<LPNMTBCUSTOMDRAW>(hdr);
99 switch (cdraw->nmcd.dwDrawStage)
100 {
101 case CDDS_PREPAINT:
102 *theResult = CDRF_NOTIFYITEMDRAW;
103 return S_OK;
104
105 case CDDS_ITEMPREPAINT:
106
107 clrText = GetSysColor(COLOR_MENUTEXT);
108 clrTextHighlight = GetSysColor(COLOR_HIGHLIGHTTEXT);
109
110 bgBrush = GetSysColorBrush(COLOR_MENU);
111 hotBrush = GetSysColorBrush(m_useFlatMenus ? COLOR_MENUHILIGHT : COLOR_HIGHLIGHT);
112
113 rc = cdraw->nmcd.rc;
114 hdc = cdraw->nmcd.hdc;
115
116 isHot = m_hotBar == this && m_hotItem == static_cast<INT>(cdraw->nmcd.dwItemSpec);
117 isPopup = m_popupBar == this && m_popupItem == static_cast<INT>(cdraw->nmcd.dwItemSpec);
118
119 if (isHot || (m_hotItem < 0 && isPopup))
120 {
121 cdraw->nmcd.uItemState |= CDIS_HOT;
122 }
123 else
124 {
125 cdraw->nmcd.uItemState &= ~CDIS_HOT;
126 }
127
128 if (cdraw->nmcd.uItemState&CDIS_HOT)
129 {
130 FillRect(hdc, &rc, hotBrush);
131 SetTextColor(hdc, clrTextHighlight);
132 cdraw->clrText = clrTextHighlight;
133 }
134 else
135 {
136 FillRect(hdc, &rc, bgBrush);
137 SetTextColor(hdc, clrText);
138 cdraw->clrText = clrText;
139 }
140
141 cdraw->iListGap += 4;
142
143 *theResult = CDRF_NOTIFYPOSTPAINT | TBCDRF_NOBACKGROUND | TBCDRF_NOEDGES | TBCDRF_NOOFFSET | TBCDRF_NOMARK | 0x00800000; // FIXME: the last bit is Vista+, for debugging only
144 return S_OK;
145
146 case CDDS_ITEMPOSTPAINT:
147 btni.cbSize = sizeof(btni);
148 btni.dwMask = TBIF_STYLE;
149 SendMessage(hWnd, TB_GETBUTTONINFO, cdraw->nmcd.dwItemSpec, reinterpret_cast<LPARAM>(&btni));
150 if (btni.fsStyle & BTNS_DROPDOWN)
151 {
152 SelectObject(cdraw->nmcd.hdc, m_marlett);
153 WCHAR text[] = L"8";
154 SetBkMode(cdraw->nmcd.hdc, TRANSPARENT);
155 RECT rc = cdraw->nmcd.rc;
156 rc.right += 1;
157 DrawTextEx(cdraw->nmcd.hdc, text, 1, &rc, DT_NOCLIP | DT_VCENTER | DT_RIGHT | DT_SINGLELINE, NULL);
158 }
159 *theResult = TRUE;
160 return S_OK;
161 }
162 return S_OK;
163 }
164 return S_OK;
165 }
166
167 return S_FALSE;
168 }
169
170 CMenuToolbarBase::CMenuToolbarBase(CMenuBand *menuBand, BOOL usePager) :
171 m_hwnd(NULL),
172 m_useFlatMenus(FALSE),
173 m_SubclassOld(NULL),
174 m_menuBand(menuBand),
175 m_hwndToolbar(NULL),
176 m_dwMenuFlags(0),
177 m_hasIdealSize(FALSE),
178 m_usePager(usePager),
179 m_hotItem(-1),
180 m_popupItem(-1)
181 {
182 m_marlett = CreateFont(
183 0, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET,
184 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
185 DEFAULT_QUALITY, FF_DONTCARE, L"Marlett");
186 }
187
188 CMenuToolbarBase::~CMenuToolbarBase()
189 {
190 DeleteObject(m_marlett);
191 }
192
193 HRESULT CMenuToolbarBase::IsWindowOwner(HWND hwnd)
194 {
195 return (m_hwnd && m_hwnd == hwnd) ||
196 (m_hwndToolbar && m_hwndToolbar == hwnd) ? S_OK : S_FALSE;
197 }
198
199 void CMenuToolbarBase::InvalidateDraw()
200 {
201 InvalidateRect(m_hwnd, NULL, FALSE);
202 }
203
204 HRESULT CMenuToolbarBase::ShowWindow(BOOL fShow)
205 {
206 ::ShowWindow(m_hwnd, fShow ? SW_SHOW : SW_HIDE);
207
208 UpdateImageLists();
209
210 SystemParametersInfo(SPI_GETFLATMENU, 0, &m_useFlatMenus, 0);
211
212 return S_OK;
213 }
214
215 HRESULT CMenuToolbarBase::UpdateImageLists()
216 {
217 int shiml;
218 if (m_menuBand->UseBigIcons())
219 {
220 shiml = SHIL_LARGE;
221 SendMessageW(m_hwndToolbar, TB_SETPADDING, 0, MAKELPARAM(4, 0));
222 }
223 else
224 {
225 shiml = SHIL_SMALL;
226 SendMessageW(m_hwndToolbar, TB_SETPADDING, 0, MAKELPARAM(4, 4));
227 }
228
229 IImageList * piml;
230 HRESULT hr = SHGetImageList(shiml, IID_PPV_ARG(IImageList, &piml));
231 if (SUCCEEDED(hr))
232 {
233 SendMessageW(m_hwndToolbar, TB_SETIMAGELIST, 0, reinterpret_cast<LPARAM>(piml));
234 }
235 else
236 {
237 SendMessageW(m_hwndToolbar, TB_SETIMAGELIST, 0, 0);
238 }
239 return S_OK;
240 }
241
242 HRESULT CMenuToolbarBase::Close()
243 {
244 DestroyWindow(m_hwndToolbar);
245 if (m_hwndToolbar != m_hwnd)
246 DestroyWindow(m_hwnd);
247 m_hwndToolbar = NULL;
248 m_hwnd = NULL;
249 return S_OK;
250 }
251
252 HRESULT CMenuToolbarBase::CreateToolbar(HWND hwndParent, DWORD dwFlags)
253 {
254 LONG tbStyles = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
255 TBSTYLE_TOOLTIPS | TBSTYLE_TRANSPARENT | TBSTYLE_REGISTERDROP | TBSTYLE_LIST | TBSTYLE_FLAT | TBSTYLE_CUSTOMERASE |
256 CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE | CCS_TOP;
257 LONG tbExStyles = TBSTYLE_EX_DOUBLEBUFFER;
258
259 if (dwFlags & SMINIT_VERTICAL)
260 {
261 tbStyles |= CCS_VERT;
262
263 #ifdef TBSTYLE_EX_VERTICAL
264 // FIXME: Use when it works in ros (?)
265 tbExStyles |= TBSTYLE_EX_VERTICAL | WS_EX_TOOLWINDOW;
266 #endif
267 }
268
269 RECT rc;
270
271 if (!::GetClientRect(hwndParent, &rc) || (rc.left == rc.right) || (rc.top == rc.bottom))
272 {
273 rc.left = 0;
274 rc.top = 0;
275 rc.right = 1;
276 rc.bottom = 1;
277 }
278
279 HWND hwndToolbar = CreateWindowEx(
280 tbExStyles, TOOLBARCLASSNAMEW, NULL,
281 tbStyles, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
282 hwndParent, NULL, _AtlBaseModule.GetModuleInstance(), 0);
283
284 if (hwndToolbar == NULL)
285 return E_FAIL;
286
287 if (m_usePager)
288 {
289 LONG pgStyles = PGS_VERT | WS_CHILD | WS_VISIBLE;
290 LONG pgExStyles = 0;
291
292 HWND hwndPager = CreateWindowEx(
293 pgExStyles, WC_PAGESCROLLER, NULL,
294 pgStyles, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
295 hwndParent, NULL, _AtlBaseModule.GetModuleInstance(), 0);
296
297 ::SetParent(hwndToolbar, hwndPager);
298 ::SetParent(hwndPager, hwndParent);
299
300 SendMessage(hwndPager, PGM_SETCHILD, 0, reinterpret_cast<LPARAM>(hwndToolbar));
301 m_hwndToolbar = hwndToolbar;
302 m_hwnd = hwndPager;
303 }
304 else
305 {
306 ::SetParent(hwndToolbar, hwndParent);
307 m_hwndToolbar = hwndToolbar;
308 m_hwnd = hwndToolbar;
309 }
310
311 /* Identify the version of the used Common Controls DLL by sending the size of the TBBUTTON structure */
312 SendMessageW(hwndToolbar, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
313
314 //if (dwFlags & SMINIT_TOPLEVEL)
315 //{
316 // /* Hide the placeholders for the button images */
317 // SendMessageW(m_hwnd, TB_SETIMAGELIST, 0, 0);
318 //}
319 //else
320
321 SetWindowLongPtr(hwndToolbar, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
322 m_SubclassOld = (WNDPROC) SetWindowLongPtr(hwndToolbar, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(CMenuToolbarBase::s_SubclassProc));
323
324 UpdateImageLists();
325
326 return S_OK;
327 }
328
329 HRESULT CMenuToolbarBase::GetIdealSize(SIZE& size)
330 {
331 size.cx = size.cy = 0;
332
333 if (m_hwndToolbar && !m_hasIdealSize)
334 {
335 SendMessageW(m_hwndToolbar, TB_AUTOSIZE, 0, 0);
336 SendMessageW(m_hwndToolbar, TB_GETMAXSIZE, 0, reinterpret_cast<LPARAM>(&m_idealSize));
337 m_hasIdealSize = TRUE;
338 }
339
340 size = m_idealSize;
341
342 return S_OK;
343 }
344
345 HRESULT CMenuToolbarBase::SetPosSize(int x, int y, int cx, int cy)
346 {
347 if (m_hwnd != m_hwndToolbar)
348 {
349 SetWindowPos(m_hwndToolbar, NULL, x, y, cx, m_idealSize.cy, 0);
350 }
351 SetWindowPos(m_hwnd, NULL, x, y, cx, cy, 0);
352 DWORD btnSize = SendMessage(m_hwndToolbar, TB_GETBUTTONSIZE, 0, 0);
353 SendMessage(m_hwndToolbar, TB_SETBUTTONSIZE, 0, MAKELPARAM(cx, HIWORD(btnSize)));
354 return S_OK;
355 }
356
357 HRESULT CMenuToolbarBase::GetWindow(HWND *phwnd)
358 {
359 if (!phwnd)
360 return E_FAIL;
361
362 *phwnd = m_hwnd;
363
364 return S_OK;
365 }
366
367 LRESULT CALLBACK CMenuToolbarBase::s_SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
368 {
369 CMenuToolbarBase * pthis = reinterpret_cast<CMenuToolbarBase *>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
370 return pthis->SubclassProc(hWnd, uMsg, wParam, lParam);
371 }
372
373 LRESULT CMenuToolbarBase::SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
374 {
375 switch (uMsg)
376 {
377 case WM_TIMER:
378 if (wParam == TIMERID_HOTTRACK)
379 {
380 KillTimer(hWnd, TIMERID_HOTTRACK);
381
382 m_menuBand->_OnPopupSubMenu(NULL, NULL, NULL, NULL, -1);
383
384 if (HasSubMenu(m_hotItem) == S_OK)
385 {
386 PopupItem(m_hotItem);
387 }
388 }
389 }
390
391 return m_SubclassOld(hWnd, uMsg, wParam, lParam);
392 }
393
394 HRESULT CMenuToolbarBase::OnHotItemChange(const NMTBHOTITEM * hot)
395 {
396 if (hot->dwFlags & HICF_LEAVING)
397 {
398 KillTimer(m_hwndToolbar, TIMERID_HOTTRACK);
399 m_hotItem = -1;
400 m_menuBand->_OnHotItemChanged(NULL, -1);
401 m_menuBand->_MenuItemHotTrack(MPOS_CHILDTRACKING);
402 }
403 else if (m_hotItem != hot->idNew)
404 {
405 DWORD elapsed = 0;
406 SystemParametersInfo(SPI_GETMENUSHOWDELAY, 0, &elapsed, 0);
407 SetTimer(m_hwndToolbar, TIMERID_HOTTRACK, elapsed, NULL);
408
409 m_hotItem = hot->idNew;
410 m_menuBand->_OnHotItemChanged(this, m_hotItem);
411 m_menuBand->_MenuItemHotTrack(MPOS_CHILDTRACKING);
412 }
413 return S_OK;
414 }
415
416 HRESULT CMenuToolbarBase::OnHotItemChanged(CMenuToolbarBase * toolbar, INT item)
417 {
418 m_hotBar = toolbar;
419 m_hotItem = item;
420 InvalidateDraw();
421 return S_OK;
422 }
423
424 HRESULT CMenuToolbarBase::OnPopupItemChanged(CMenuToolbarBase * toolbar, INT item)
425 {
426 m_popupBar = toolbar;
427 m_popupItem = item;
428 InvalidateDraw();
429 return S_OK;
430 }
431
432 HRESULT CMenuToolbarBase::PopupSubMenu(UINT uItem, UINT index, IShellMenu* childShellMenu)
433 {
434 IBandSite* pBandSite;
435 IDeskBar* pDeskBar;
436
437 HRESULT hr = 0;
438 RECT rc = { 0 };
439
440 if (!SendMessage(m_hwndToolbar, TB_GETITEMRECT, index, reinterpret_cast<LPARAM>(&rc)))
441 return E_FAIL;
442
443 POINT a = { rc.left, rc.top };
444 POINT b = { rc.right, rc.bottom };
445
446 ClientToScreen(m_hwndToolbar, &a);
447 ClientToScreen(m_hwndToolbar, &b);
448
449 POINTL pt = { b.x - 3, a.y - 3 };
450 RECTL rcl = { a.x, a.y, b.x, b.y }; // maybe-TODO: fetch client area of deskbar?
451
452 #if USE_SYSTEM_MENUSITE
453 hr = CoCreateInstance(CLSID_MenuBandSite,
454 NULL,
455 CLSCTX_INPROC_SERVER,
456 IID_PPV_ARG(IBandSite, &pBandSite));
457 #else
458 hr = CMenuSite_Constructor(IID_PPV_ARG(IBandSite, &pBandSite));
459 #endif
460 if (FAILED_UNEXPECTEDLY(hr))
461 return hr;
462 #if WRAP_MENUSITE
463 hr = CMenuSite_Wrapper(pBandSite, IID_PPV_ARG(IBandSite, &pBandSite));
464 if (FAILED_UNEXPECTEDLY(hr))
465 return hr;
466 #endif
467
468 #if USE_SYSTEM_MENUDESKBAR
469 hr = CoCreateInstance(CLSID_MenuDeskBar,
470 NULL,
471 CLSCTX_INPROC_SERVER,
472 IID_PPV_ARG(IDeskBar, &pDeskBar));
473 #else
474 hr = CMenuDeskBar_Constructor(IID_PPV_ARG(IDeskBar, &pDeskBar));
475 #endif
476 if (FAILED_UNEXPECTEDLY(hr))
477 return hr;
478 #if WRAP_MENUDESKBAR
479 hr = CMenuDeskBar_Wrapper(pDeskBar, IID_PPV_ARG(IDeskBar, &pDeskBar));
480 if (FAILED_UNEXPECTEDLY(hr))
481 return hr;
482 #endif
483
484 hr = pDeskBar->SetClient(pBandSite);
485 if (FAILED_UNEXPECTEDLY(hr))
486 return hr;
487
488 hr = pBandSite->AddBand(childShellMenu);
489 if (FAILED_UNEXPECTEDLY(hr))
490 return hr;
491
492 CComPtr<IMenuPopup> popup;
493 hr = pDeskBar->QueryInterface(IID_PPV_ARG(IMenuPopup, &popup));
494 if (FAILED_UNEXPECTEDLY(hr))
495 return hr;
496
497 m_menuBand->_OnPopupSubMenu(popup, &pt, &rcl, this, m_popupItem);
498
499 return S_OK;
500 }
501
502 HRESULT CMenuToolbarBase::PopupSubMenu(UINT index, HMENU menu)
503 {
504 RECT rc = { 0 };
505
506 if (!SendMessage(m_hwndToolbar, TB_GETITEMRECT, index, reinterpret_cast<LPARAM>(&rc)))
507 return E_FAIL;
508
509 POINT b = { rc.right, rc.bottom };
510
511 ClientToScreen(m_hwndToolbar, &b);
512
513 HMENU popup = GetSubMenu(menu, index);
514
515 m_menuBand->_TrackSubMenuUsingTrackPopupMenu(popup, b.x, b.y);
516
517 return S_OK;
518 }
519
520 HRESULT CMenuToolbarBase::DoContextMenu(IContextMenu* contextMenu)
521 {
522 HRESULT hr;
523 HMENU hPopup = CreatePopupMenu();
524
525 if (hPopup == NULL)
526 return E_FAIL;
527
528 hr = contextMenu->QueryContextMenu(hPopup, 0, 0, UINT_MAX, CMF_NORMAL);
529 if (FAILED_UNEXPECTEDLY(hr))
530 {
531 DestroyMenu(hPopup);
532 return hr;
533 }
534
535 DWORD dwPos = GetMessagePos();
536 UINT uCommand = ::TrackPopupMenu(hPopup, TPM_RETURNCMD, GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos), 0, m_hwnd, NULL);
537 if (uCommand == 0)
538 return S_FALSE;
539
540 CMINVOKECOMMANDINFO cmi = { 0 };
541 cmi.cbSize = sizeof(cmi);
542 cmi.lpVerb = MAKEINTRESOURCEA(uCommand);
543 cmi.hwnd = m_hwnd;
544 hr = contextMenu->InvokeCommand(&cmi);
545
546 DestroyMenu(hPopup);
547 return hr;
548 }
549
550 HRESULT CMenuToolbarBase::OnCommand(WPARAM wParam, LPARAM lParam, LRESULT *theResult)
551 {
552 theResult = 0;
553 if (HasSubMenu(wParam) == S_OK)
554 {
555 KillTimer(m_hwndToolbar, TIMERID_HOTTRACK);
556 PopupItem(wParam);
557 return S_FALSE;
558 }
559 return m_menuBand->_MenuItemHotTrack(MPOS_EXECUTE);
560 }
561
562 HRESULT CMenuToolbarBase::ChangeHotItem(DWORD dwSelectType)
563 {
564 int prev = m_hotItem;
565 int index = -1;
566
567 if (dwSelectType != 0xFFFFFFFF)
568 {
569 int count = SendMessage(m_hwndToolbar, TB_BUTTONCOUNT, 0, 0);
570
571 if (m_hotItem >= 0)
572 {
573 TBBUTTONINFO info = { 0 };
574 info.cbSize = sizeof(TBBUTTONINFO);
575 info.dwMask = 0;
576 index = SendMessage(m_hwndToolbar, TB_GETBUTTONINFO, m_hotItem, reinterpret_cast<LPARAM>(&info));
577 }
578
579 if (dwSelectType == VK_HOME)
580 {
581 index = 0;
582 dwSelectType = VK_DOWN;
583 }
584 else if (dwSelectType == VK_END)
585 {
586 index = count - 1;
587 dwSelectType = VK_UP;
588 }
589 else if (index < 0)
590 {
591 if (dwSelectType == VK_UP)
592 {
593 index = count - 1;
594 }
595 else if (dwSelectType == VK_DOWN)
596 {
597 index = 0;
598 }
599 }
600 else
601 {
602 if (dwSelectType == VK_UP)
603 {
604 index--;
605 }
606 else if (dwSelectType == VK_DOWN)
607 {
608 index++;
609 }
610 }
611
612 TBBUTTON btn = { 0 };
613 while (index >= 0 && index < count)
614 {
615 DWORD res = SendMessage(m_hwndToolbar, TB_GETBUTTON, index, reinterpret_cast<LPARAM>(&btn));
616 if (!res)
617 return E_FAIL;
618
619 if (btn.dwData)
620 {
621 m_hotItem = btn.idCommand;
622 if (prev != m_hotItem)
623 {
624 SendMessage(m_hwndToolbar, TB_SETHOTITEM, index, 0);
625 return m_menuBand->_OnHotItemChanged(this, m_hotItem);
626 }
627 return S_OK;
628 }
629
630 if (dwSelectType == VK_UP)
631 {
632 index--;
633 }
634 else if (dwSelectType == VK_DOWN)
635 {
636 index++;
637 }
638 }
639 }
640
641 m_hotItem = -1;
642 if (prev != m_hotItem)
643 {
644 SendMessage(m_hwndToolbar, TB_SETHOTITEM, -1, 0);
645 m_menuBand->_OnHotItemChanged(NULL, -1);
646 }
647 return S_FALSE;
648 }
649
650 HRESULT CMenuToolbarBase::AddButton(DWORD commandId, LPCWSTR caption, BOOL hasSubMenu, INT iconId, DWORD_PTR buttonData, BOOL last)
651 {
652 TBBUTTON tbb = { 0 };
653
654 tbb.fsState = TBSTATE_ENABLED;
655 #ifndef TBSTYLE_EX_VERTICAL
656 if (!last)
657 tbb.fsState |= TBSTATE_WRAP;
658 #endif
659 tbb.fsStyle = 0;
660
661 if (hasSubMenu)
662 tbb.fsStyle |= BTNS_DROPDOWN;
663
664 tbb.iString = (INT_PTR) caption;
665 tbb.idCommand = commandId;
666
667 tbb.iBitmap = iconId;
668 tbb.dwData = buttonData;
669
670 DbgPrint("Trying to add a new button with id %d and caption '%S'...\n", commandId, caption);
671
672 if (!SendMessageW(m_hwndToolbar, TB_ADDBUTTONS, 1, reinterpret_cast<LPARAM>(&tbb)))
673 return HRESULT_FROM_WIN32(GetLastError());
674
675 return S_OK;
676 }
677
678 HRESULT CMenuToolbarBase::AddSeparator(BOOL last)
679 {
680 TBBUTTON tbb = { 0 };
681
682 tbb.fsState = TBSTATE_ENABLED;
683 #ifndef TBSTYLE_EX_VERTICAL
684 if (!last)
685 tbb.fsState |= TBSTATE_WRAP;
686 #endif
687 tbb.fsStyle = BTNS_SEP;
688 tbb.iBitmap = 0;
689
690 DbgPrint("Trying to add a new separator...\n");
691
692 if (!SendMessageW(m_hwndToolbar, TB_ADDBUTTONS, 1, reinterpret_cast<LPARAM>(&tbb)))
693 return HRESULT_FROM_WIN32(GetLastError());
694
695 return S_OK;
696 }
697
698 HRESULT CMenuToolbarBase::AddPlaceholder()
699 {
700 TBBUTTON tbb = { 0 };
701 PCWSTR MenuString = L"(Empty)";
702
703 tbb.fsState = 0;
704 tbb.fsStyle = 0;
705 tbb.iString = (INT_PTR) MenuString;
706 tbb.iBitmap = -1;
707
708 DbgPrint("Trying to add a new placeholder...\n");
709
710 if (!SendMessageW(m_hwndToolbar, TB_ADDBUTTONS, 1, reinterpret_cast<LPARAM>(&tbb)))
711 return HRESULT_FROM_WIN32(GetLastError());
712
713 return S_OK;
714 }
715
716 HRESULT CMenuToolbarBase::GetDataFromId(INT uItem, INT* pIndex, DWORD_PTR* pData)
717 {
718 TBBUTTONINFO info = { 0 };
719 info.cbSize = sizeof(TBBUTTONINFO);
720 info.dwMask = 0;
721 int index = SendMessage(m_hwndToolbar, TB_GETBUTTONINFO, uItem, reinterpret_cast<LPARAM>(&info));
722 if (index < 0)
723 return E_FAIL;
724
725 if (pIndex)
726 *pIndex = index;
727
728 if (pData)
729 {
730 TBBUTTON btn = { 0 };
731 if (!SendMessage(m_hwndToolbar, TB_GETBUTTON, index, reinterpret_cast<LPARAM>(&btn)))
732 return E_FAIL;
733 *pData = btn.dwData;
734 }
735
736 return S_OK;
737 }
738
739
740 HRESULT CMenuToolbarBase::PopupItem(INT uItem)
741 {
742 INT index;
743 DWORD_PTR dwData;
744
745 GetDataFromId(uItem, &index, &dwData);
746
747 return InternalPopupItem(uItem, index, dwData);
748 }
749
750 HRESULT CMenuToolbarBase::HasSubMenu(INT uItem)
751 {
752 INT index;
753 DWORD_PTR dwData;
754
755 GetDataFromId(uItem, &index, &dwData);
756
757 return InternalHasSubMenu(uItem, index, dwData);
758 }
759
760 CMenuStaticToolbar::CMenuStaticToolbar(CMenuBand *menuBand) :
761 CMenuToolbarBase(menuBand, FALSE),
762 m_hmenu(NULL)
763 {
764 }
765
766 HRESULT CMenuStaticToolbar::GetMenu(
767 HMENU *phmenu,
768 HWND *phwnd,
769 DWORD *pdwFlags)
770 {
771 *phmenu = m_hmenu;
772 *phwnd = NULL;
773 *pdwFlags = m_dwMenuFlags;
774
775 return S_OK;
776 }
777
778 HRESULT CMenuStaticToolbar::SetMenu(
779 HMENU hmenu,
780 HWND hwnd,
781 DWORD dwFlags)
782 {
783 m_hmenu = hmenu;
784 m_dwMenuFlags = dwFlags;
785
786 return S_OK;
787 }
788
789 HRESULT CMenuStaticToolbar::FillToolbar()
790 {
791 int i;
792 int ic = GetMenuItemCount(m_hmenu);
793
794 int count = 0;
795 for (i = 0; i < ic; i++)
796 {
797 BOOL last = i + 1 == ic;
798
799 MENUITEMINFOW info;
800
801 info.cbSize = sizeof(info);
802 info.dwTypeData = NULL;
803 info.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID;
804
805 if (!GetMenuItemInfoW(m_hmenu, i, TRUE, &info))
806 {
807 DbgPrint("Error obtaining info for menu item at pos=%d\n", i);
808 continue;
809 }
810
811 count++;
812
813 DbgPrint("Found item with fType=%x, cmdId=%d\n", info.fType, info.wID);
814
815 if (info.fType & MFT_SEPARATOR)
816 {
817 AddSeparator(last);
818 }
819 else if (!(info.fType & MFT_BITMAP))
820 {
821
822 info.cch++;
823 info.dwTypeData = (PWSTR) HeapAlloc(GetProcessHeap(), 0, (info.cch + 1) * sizeof(WCHAR));
824
825 info.fMask = MIIM_STRING | MIIM_SUBMENU | MIIM_ID;
826 GetMenuItemInfoW(m_hmenu, i, TRUE, &info);
827
828 SMINFO * sminfo = new SMINFO();
829 sminfo->dwMask = SMIM_ICON | SMIM_FLAGS;
830 // FIXME: remove before deleting the toolbar or it will leak
831
832 HRESULT hr = m_menuBand->_CallCBWithItemId(info.wID, SMC_GETINFO, 0, reinterpret_cast<LPARAM>(sminfo));
833 if (FAILED_UNEXPECTEDLY(hr))
834 return hr;
835
836 AddButton(info.wID, info.dwTypeData, info.hSubMenu != NULL, sminfo->iIcon, reinterpret_cast<DWORD_PTR>(sminfo), last);
837
838 HeapFree(GetProcessHeap(), 0, info.dwTypeData);
839 }
840 }
841
842 DbgPrint("Created toolbar with %d buttons.\n", count);
843
844 return S_OK;
845 }
846
847 HRESULT CMenuStaticToolbar::OnContextMenu(NMMOUSE * rclick)
848 {
849 CComPtr<IContextMenu> contextMenu;
850 HRESULT hr = m_menuBand->_CallCBWithItemId(rclick->dwItemSpec, SMC_GETOBJECT, reinterpret_cast<WPARAM>(&IID_IContextMenu), reinterpret_cast<LPARAM>(&contextMenu));
851 if (hr != S_OK)
852 return hr;
853
854 return DoContextMenu(contextMenu);
855 }
856
857 HRESULT CMenuStaticToolbar::OnCommand(WPARAM wParam, LPARAM lParam, LRESULT *theResult)
858 {
859 HRESULT hr;
860 hr = CMenuToolbarBase::OnCommand(wParam, lParam, theResult);
861 if (FAILED_UNEXPECTEDLY(hr))
862 return hr;
863
864 // in case the clicked item has a submenu, we do not need to execute the item
865 if (hr == S_FALSE)
866 return hr;
867
868 return m_menuBand->_CallCBWithItemId(wParam, SMC_EXEC, 0, 0);
869 }
870
871 HRESULT CMenuStaticToolbar::InternalPopupItem(INT uItem, INT index, DWORD_PTR dwData)
872 {
873 SMINFO * nfo = reinterpret_cast<SMINFO*>(dwData);
874 if (!nfo)
875 return E_FAIL;
876
877 if (nfo->dwFlags&SMIF_TRACKPOPUP)
878 {
879 return PopupSubMenu(index, m_hmenu);
880 }
881 else
882 {
883 CComPtr<IShellMenu> shellMenu;
884 HRESULT hr = m_menuBand->_CallCBWithItemId(uItem, SMC_GETOBJECT, reinterpret_cast<WPARAM>(&IID_IShellMenu), reinterpret_cast<LPARAM>(&shellMenu));
885 if (FAILED_UNEXPECTEDLY(hr))
886 return hr;
887
888 return PopupSubMenu(uItem, index, shellMenu);
889 }
890 }
891
892 HRESULT CMenuStaticToolbar::InternalHasSubMenu(INT uItem, INT index, DWORD_PTR dwData)
893 {
894 return ::GetSubMenu(m_hmenu, index) ? S_OK : S_FALSE;
895 }
896
897 CMenuSFToolbar::CMenuSFToolbar(CMenuBand * menuBand) :
898 CMenuToolbarBase(menuBand, TRUE),
899 m_shellFolder(NULL)
900 {
901 }
902
903 CMenuSFToolbar::~CMenuSFToolbar()
904 {
905 }
906
907 HRESULT CMenuSFToolbar::FillToolbar()
908 {
909 HRESULT hr;
910 int i = 0;
911 PWSTR MenuString;
912
913 IEnumIDList * eidl;
914 m_shellFolder->EnumObjects(m_hwndToolbar, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &eidl);
915
916 LPITEMIDLIST item = static_cast<LPITEMIDLIST>(CoTaskMemAlloc(sizeof(ITEMIDLIST)));
917 ULONG fetched;
918 hr = eidl->Next(1, &item, &fetched);
919 while (SUCCEEDED(hr) && fetched > 0)
920 {
921 INT index = 0;
922 INT indexOpen = 0;
923
924 STRRET sr = { STRRET_CSTR, { 0 } };
925
926 hr = m_shellFolder->GetDisplayNameOf(item, SIGDN_NORMALDISPLAY, &sr);
927 if (FAILED_UNEXPECTEDLY(hr))
928 return hr;
929
930 StrRetToStr(&sr, NULL, &MenuString);
931
932 index = SHMapPIDLToSystemImageListIndex(m_shellFolder, item, &indexOpen);
933
934 LPCITEMIDLIST itemc = item;
935
936 SFGAOF attrs = SFGAO_FOLDER;
937 hr = m_shellFolder->GetAttributesOf(1, &itemc, &attrs);
938
939 DWORD_PTR dwData = reinterpret_cast<DWORD_PTR>(ILClone(item));
940 // FIXME: remove before deleting the toolbar or it will leak
941
942 // Fetch next item already, so we know if the current one is the last
943 hr = eidl->Next(1, &item, &fetched);
944
945 AddButton(++i, MenuString, attrs & SFGAO_FOLDER, index, dwData, FAILED(hr) || fetched == 0);
946
947 CoTaskMemFree(MenuString);
948 }
949 CoTaskMemFree(item);
950
951 // If no items were added, show the "empty" placeholder
952 if (i == 0)
953 {
954 DbgPrint("The toolbar is empty, adding placeholder.\n");
955
956 return AddPlaceholder();
957 }
958
959 DbgPrint("Created toolbar with %d buttons.\n", i);
960
961 return hr;
962 }
963
964 HRESULT CMenuSFToolbar::SetShellFolder(IShellFolder *psf, LPCITEMIDLIST pidlFolder, HKEY hKey, DWORD dwFlags)
965 {
966 m_shellFolder = psf;
967 m_idList = pidlFolder;
968 m_hKey = hKey;
969 m_dwMenuFlags = dwFlags;
970 return S_OK;
971 }
972
973 HRESULT CMenuSFToolbar::GetShellFolder(DWORD *pdwFlags, LPITEMIDLIST *ppidl, REFIID riid, void **ppv)
974 {
975 HRESULT hr;
976
977 hr = m_shellFolder->QueryInterface(riid, ppv);
978 if (FAILED_UNEXPECTEDLY(hr))
979 return hr;
980
981 if (pdwFlags)
982 *pdwFlags = m_dwMenuFlags;
983
984 if (ppidl)
985 {
986 LPITEMIDLIST pidl = NULL;
987
988 if (m_idList)
989 {
990 pidl = ILClone(m_idList);
991 if (!pidl)
992 {
993 (*(IUnknown**) ppv)->Release();
994 return E_FAIL;
995 }
996 }
997
998 *ppidl = pidl;
999 }
1000
1001 return hr;
1002 }
1003
1004 HRESULT CMenuSFToolbar::OnContextMenu(NMMOUSE * rclick)
1005 {
1006 HRESULT hr;
1007 CComPtr<IContextMenu> contextMenu;
1008 LPCITEMIDLIST pidl = reinterpret_cast<LPCITEMIDLIST>(rclick->dwItemData);
1009
1010 hr = m_shellFolder->GetUIObjectOf(m_hwndToolbar, 1, &pidl, IID_IContextMenu, NULL, reinterpret_cast<VOID **>(&contextMenu));
1011 if (hr != S_OK)
1012 return hr;
1013
1014 return DoContextMenu(contextMenu);
1015 }
1016
1017 HRESULT CMenuSFToolbar::OnCommand(WPARAM wParam, LPARAM lParam, LRESULT *theResult)
1018 {
1019 HRESULT hr;
1020 hr = CMenuToolbarBase::OnCommand(wParam, lParam, theResult);
1021 if (FAILED_UNEXPECTEDLY(hr))
1022 return hr;
1023
1024 // in case the clicked item has a submenu, we do not need to execute the item
1025 if (hr == S_FALSE)
1026 return hr;
1027
1028 DWORD_PTR data;
1029 GetDataFromId(wParam, NULL, &data);
1030
1031 return m_menuBand->_CallCBWithItemPidl(reinterpret_cast<LPITEMIDLIST>(data), SMC_SFEXEC, 0, 0);
1032 }
1033
1034 HRESULT CMenuSFToolbar::InternalPopupItem(INT uItem, INT index, DWORD_PTR dwData)
1035 {
1036 HRESULT hr;
1037 UINT uId;
1038 UINT uIdAncestor;
1039 DWORD flags;
1040 CComPtr<IShellMenuCallback> psmc;
1041 CComPtr<IShellMenu> shellMenu;
1042
1043 LPITEMIDLIST pidl = reinterpret_cast<LPITEMIDLIST>(dwData);
1044
1045 if (!pidl)
1046 return E_FAIL;
1047
1048 #if USE_SYSTEM_MENUBAND
1049 hr = CoCreateInstance(CLSID_MenuBand,
1050 NULL,
1051 CLSCTX_INPROC_SERVER,
1052 IID_PPV_ARG(IShellMenu, &shellMenu));
1053 #else
1054 hr = CMenuBand_Constructor(IID_PPV_ARG(IShellMenu, &shellMenu));
1055 #endif
1056 if (FAILED_UNEXPECTEDLY(hr))
1057 return hr;
1058 #if WRAP_MENUBAND
1059 hr = CMenuBand_Wrapper(shellMenu, IID_PPV_ARG(IShellMenu, &shellMenu));
1060 if (FAILED_UNEXPECTEDLY(hr))
1061 return hr;
1062 #endif
1063
1064 m_menuBand->GetMenuInfo(&psmc, &uId, &uIdAncestor, &flags);
1065
1066 // FIXME: not sure what to use as uId/uIdAncestor here
1067 hr = shellMenu->Initialize(psmc, 0, uId, SMINIT_VERTICAL);
1068 if (FAILED_UNEXPECTEDLY(hr))
1069 return hr;
1070
1071 CComPtr<IShellFolder> childFolder;
1072 hr = m_shellFolder->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &childFolder));
1073 if (FAILED_UNEXPECTEDLY(hr))
1074 return hr;
1075
1076 hr = shellMenu->SetShellFolder(childFolder, NULL, NULL, 0);
1077 if (FAILED_UNEXPECTEDLY(hr))
1078 return hr;
1079
1080 return PopupSubMenu(uItem, index, shellMenu);
1081 }
1082
1083 HRESULT CMenuSFToolbar::InternalHasSubMenu(INT uItem, INT index, DWORD_PTR dwData)
1084 {
1085 HRESULT hr;
1086 LPCITEMIDLIST pidl = reinterpret_cast<LPITEMIDLIST>(dwData);
1087
1088 SFGAOF attrs = SFGAO_FOLDER;
1089 hr = m_shellFolder->GetAttributesOf(1, &pidl, &attrs);
1090 if (FAILED_UNEXPECTEDLY(hr))
1091 return hr;
1092
1093 return (attrs & SFGAO_FOLDER) ? S_OK : S_FALSE;
1094 }