From abdf98bbfe5b9a5635a158d4554e1b6e1155789b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 Jan 2020 23:50:00 -0500 Subject: status bar messages --- src/envshell.cpp | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++---- src/pch.h | 1 + 2 files changed, 139 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/envshell.cpp b/src/envshell.cpp index f7033fba..ca392a9c 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -2,6 +2,7 @@ #include "env.h" #include #include +#include namespace env { @@ -26,8 +27,8 @@ public: class WndProcFilter : public QAbstractNativeEventFilter { public: - WndProcFilter(IContextMenu* cm) - : m_cm2(nullptr), m_cm3(nullptr) + WndProcFilter(QMainWindow* mw, IContextMenu* cm) + : m_mw(mw), m_cm(cm), m_cm2(nullptr), m_cm3(nullptr) { IContextMenu2* cm2 = nullptr; if (SUCCEEDED(cm->QueryInterface(IID_IContextMenu2, (void**)&cm2))) { @@ -40,10 +41,23 @@ public: } } + ~WndProcFilter() + { + if (auto* sb=m_mw->statusBar()) { + sb->clearMessage(); + } + } + bool nativeEventFilter(const QByteArray& type, void* m, long* lresultOut) override { + MSG* msg = (MSG*)m; + + if (msg->message == WM_MENUSELECT) { + HANDLE_WM_MENUSELECT(msg->hwnd, msg->wParam, msg->lParam, onMenuSelect); + return true; + } + if (m_cm3) { - MSG* msg = (MSG*)m; LRESULT lresult = 0; const auto r = m_cm3->HandleMenuMsg2( @@ -59,8 +73,6 @@ public: } if (m_cm2) { - MSG* msg = (MSG*)m; - const auto r = m_cm2->HandleMenuMsg( msg->message, msg->wParam, msg->lParam); @@ -77,8 +89,104 @@ public: } private: + QMainWindow* m_mw; + IContextMenu* m_cm; COMPtr m_cm2; COMPtr m_cm3; + + // adapted from + // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 + // + void onMenuSelect( + HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags) + { + if (m_cm && item >= QCM_FIRST && item <= QCM_LAST) { + WCHAR szBuf[MAX_PATH]; + + const auto r = IContextMenu_GetCommandString( + m_cm, item - QCM_FIRST, GCS_HELPTEXTW, NULL, szBuf, MAX_PATH); + + if (FAILED(r)) { + lstrcpynW(szBuf, L"No help available.", MAX_PATH); + } + + if (m_mw) { + if (auto* sb=m_mw->statusBar()) { + sb->showMessage(QString::fromWCharArray(szBuf)); + } + } + } + } + + // adapted from + // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 + // + HRESULT IContextMenu_GetCommandString( + IContextMenu *pcm, UINT_PTR idCmd, UINT uFlags, + UINT *pwReserved, LPWSTR pszName, UINT cchMax) + { + // Callers are expected to be using Unicode. + if (!(uFlags & GCS_UNICODE)) { + return E_INVALIDARG; + } + + // Some context menu handlers have off-by-one bugs and will + // overflow the output buffer. Let’s artificially reduce the + // buffer size so a one-character overflow won’t corrupt memory. + if (cchMax <= 1) { + return E_FAIL; + } + + cchMax--; + + // First try the Unicode message. Preset the output buffer + // with a known value because some handlers return S_OK without + // doing anything. + pszName[0] = L'\0'; + + HRESULT hr = pcm->GetCommandString( + idCmd, uFlags, pwReserved, (LPSTR)pszName, cchMax); + + if (SUCCEEDED(hr) && pszName[0] == L'\0') { + // Rats, a buggy IContextMenu handler that returned success + // even though it failed. + hr = E_NOTIMPL; + } + + if (FAILED(hr)) { + // try again with ANSI – pad the buffer with one extra character + // to compensate for context menu handlers that overflow by + // one character. + LPSTR pszAnsi = (LPSTR)LocalAlloc( + LMEM_FIXED, (cchMax + 1) * sizeof(CHAR)); + + if (pszAnsi) { + pszAnsi[0] = '\0'; + + hr = pcm->GetCommandString( + idCmd, uFlags & ~GCS_UNICODE, pwReserved, pszAnsi, cchMax); + + if (SUCCEEDED(hr) && pszAnsi[0] == '\0') { + // Rats, a buggy IContextMenu handler that returned success + // even though it failed. + hr = E_NOTIMPL; + } + + if (SUCCEEDED(hr)) { + if (MultiByteToWideChar(CP_ACP, 0, pszAnsi, -1, pszName, cchMax) == 0) { + hr = E_FAIL; + } + } + + LocalFree(pszAnsi); + + } else { + hr = E_OUTOFMEMORY; + } + } + + return hr; + } }; @@ -145,16 +253,20 @@ HMenuPtr createMenu(IContextMenu* cm) return HMenuPtr(hmenu); } -int runMenu(IContextMenu* cm, HWND hwnd, HMENU menu, const QPoint& p) +int runMenu(QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p) { - auto filter = std::make_unique(cm); + const auto hwnd = (HWND)mw->winId(); + + auto filter = std::make_unique(mw, cm); QCoreApplication::instance()->installNativeEventFilter(filter.get()); return TrackPopupMenuEx(menu, TPM_RETURNCMD, p.x(), p.y(), hwnd, nullptr); } -void invoke(HWND hwnd, const QPoint& p, int cmd, IContextMenu* cm) +void invoke(QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm) { + const auto hwnd = (HWND)mw->winId(); + CMINVOKECOMMANDINFOEX info = {}; info.cbSize = sizeof(info); @@ -184,24 +296,39 @@ void invoke(HWND hwnd, const QPoint& p, int cmd, IContextMenu* cm) } } +QMainWindow* getMainWindow(QWidget* w) +{ + QWidget* p = w; + + while (p) { + if (auto* mw=dynamic_cast(p)) { + return mw; + } + + p = p->parentWidget(); + } + + return nullptr; +} + void showShellMenu(QWidget* parent, const QFileInfo& file, const QPoint& pos) { const auto path = QDir::toNativeSeparators(file.absoluteFilePath()); try { + auto* mw = getMainWindow(parent); auto idl = getIDL(path.toStdWString().c_str()); auto [sf, childIdl] = getShellFolder(idl.get()); auto cm = getContextMenu(sf.get(), childIdl); auto hmenu = createMenu(cm.get()); - auto hwnd = (HWND)parent->window()->winId(); - const int cmd = runMenu(cm.get(), hwnd, hmenu.get(), pos); + const int cmd = runMenu(mw, cm.get(), hmenu.get(), pos); if (cmd <= 0) { return; } - invoke(hwnd, pos, cmd - QCM_FIRST, cm.get()); + invoke(mw, pos, cmd - QCM_FIRST, cm.get()); } catch(MenuFailed& e) { diff --git a/src/pch.h b/src/pch.h index 8e8d33f7..030ee634 100644 --- a/src/pch.h +++ b/src/pch.h @@ -38,6 +38,7 @@ #include #include #include +#include // boost #include -- cgit v1.3.1