diff options
| author | isanae <14251494+isanae@users.noreply.github.com> | 2020-01-20 23:25:53 -0500 |
|---|---|---|
| committer | isanae <14251494+isanae@users.noreply.github.com> | 2020-02-04 03:33:21 -0500 |
| commit | dd4bd5b17ddedcaf64df09f7a10d34267b8834c3 (patch) | |
| tree | 3e7d7ced274d7279070b0aebf84b0b8f889dc9a2 /src | |
| parent | 2d4216a4f040f157b710eb0132f4049b4d69bddb (diff) | |
shift+right click for shell menu
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/env.h | 34 | ||||
| -rw-r--r-- | src/envshell.cpp | 212 | ||||
| -rw-r--r-- | src/envshell.h | 14 | ||||
| -rw-r--r-- | src/filetree.cpp | 18 | ||||
| -rw-r--r-- | src/filetree.h | 2 |
6 files changed, 282 insertions, 1 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5199954d..ad8f74c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -135,6 +135,7 @@ SET(organizer_SRCS envmetrics.cpp envmodule.cpp envsecurity.cpp + envshell.cpp envshortcut.cpp envwindows.cpp colortable.cpp @@ -264,6 +265,7 @@ SET(organizer_HDRS envmetrics.h envmodule.h envsecurity.h + envshell.h envshortcut.h envwindows.h colortable.h @@ -393,6 +395,7 @@ set(env envmetrics envmodule envsecurity + envshell envshortcut envwindows ) @@ -30,6 +30,23 @@ struct DesktopDCReleaser using DesktopDCPtr = std::unique_ptr<HDC, DesktopDCReleaser>; +// used by HMenuPtr, calls DestroyMenu() as the deleter +// +struct HMenuFreer +{ + using pointer = HMENU; + + void operator()(HMENU h) + { + if (h != 0) { + ::DestroyMenu(h); + } + } +}; + +using HMenuPtr = std::unique_ptr<HMENU, HMenuFreer>; + + // used by LibraryPtr, calls FreeLibrary as the deleter // struct LibraryFreer @@ -94,6 +111,23 @@ template <class T> using LocalPtr = std::unique_ptr<T, LocalFreer<T>>; +// used by the CoTaskMemPtr, calls CoTaskMemFree() as the deleter +// +template <class T> +struct CoTaskMemFreer +{ + using pointer = T; + + void operator()(T p) + { + ::CoTaskMemFree(p); + } +}; + +template <class T> +using CoTaskMemPtr = std::unique_ptr<T, CoTaskMemFreer<T>>; + + // creates a console in the constructor and destroys it in the destructor, // also redirects standard streams // diff --git a/src/envshell.cpp b/src/envshell.cpp new file mode 100644 index 00000000..f7033fba --- /dev/null +++ b/src/envshell.cpp @@ -0,0 +1,212 @@ +#include "envshell.h" +#include "env.h" +#include <log.h> +#include <utility.h> + +namespace env +{ + +using namespace MOBase; + +const int QCM_FIRST = 1; +const int QCM_LAST = 0x7ff; + +class MenuFailed : public std::runtime_error +{ +public: + MenuFailed(HRESULT r, const std::string& what) + : runtime_error(fmt::format( + "{}, {}", + what, QString::fromStdWString(formatSystemMessage(r)).toStdString())) + { + } +}; + + +class WndProcFilter : public QAbstractNativeEventFilter +{ +public: + WndProcFilter(IContextMenu* cm) + : m_cm2(nullptr), m_cm3(nullptr) + { + IContextMenu2* cm2 = nullptr; + if (SUCCEEDED(cm->QueryInterface(IID_IContextMenu2, (void**)&cm2))) { + m_cm2.reset(cm2); + } + + IContextMenu3* cm3 = nullptr; + if (SUCCEEDED(cm->QueryInterface(IID_IContextMenu3, (void**)&cm3))) { + m_cm3.reset(cm3); + } + } + + bool nativeEventFilter(const QByteArray& type, void* m, long* lresultOut) override + { + if (m_cm3) { + MSG* msg = (MSG*)m; + LRESULT lresult = 0; + + const auto r = m_cm3->HandleMenuMsg2( + msg->message, msg->wParam, msg->lParam, &lresult); + + if (SUCCEEDED(r)) { + if (lresultOut) { + *lresultOut = lresult; + } + + return true; + } + } + + if (m_cm2) { + MSG* msg = (MSG*)m; + + const auto r = m_cm2->HandleMenuMsg( + msg->message, msg->wParam, msg->lParam); + + if (SUCCEEDED(r)) { + if (lresultOut) { + *lresultOut = 0; + } + + return true; + } + } + + return false; + } + +private: + COMPtr<IContextMenu2> m_cm2; + COMPtr<IContextMenu3> m_cm3; +}; + + + +CoTaskMemPtr<LPITEMIDLIST> getIDL(const wchar_t* path) +{ + LPITEMIDLIST pidl; + SFGAOF sfgao; + + const auto r = SHParseDisplayName(path, nullptr, &pidl, 0, &sfgao); + + if (FAILED(r)) { + throw MenuFailed(r, "SHParseDisplayName failed"); + } + + return CoTaskMemPtr<LPITEMIDLIST>(pidl); +} + +std::pair<COMPtr<IShellFolder>, LPCITEMIDLIST> getShellFolder(LPITEMIDLIST idl) +{ + IShellFolder* psf = nullptr; + LPCITEMIDLIST pidlChild = nullptr; + + const auto r = SHBindToParent( + idl, IID_IShellFolder, reinterpret_cast<void**>(&psf), &pidlChild); + + if (FAILED(r)) { + throw MenuFailed(r, "SHBindToParent failed"); + } + + return {COMPtr<IShellFolder>(psf), pidlChild}; +} + +COMPtr<IContextMenu> getContextMenu(IShellFolder* psf, LPCITEMIDLIST idl) +{ + IContextMenu* pcm = nullptr; + + const auto r = psf->GetUIObjectOf( + 0, 1, &idl, IID_IContextMenu, nullptr, + reinterpret_cast<void**>(&pcm)); + + if (FAILED(r)) { + throw MenuFailed(r, "GetUIObjectOf failed"); + } + + return COMPtr<IContextMenu>(pcm); +} + +HMenuPtr createMenu(IContextMenu* cm) +{ + HMENU hmenu = CreatePopupMenu(); + if (!hmenu) { + const auto e = GetLastError(); + throw MenuFailed(e, "CreatePopupMenu failed"); + } + + const auto r = cm->QueryContextMenu( + hmenu, 0, QCM_FIRST, QCM_LAST, CMF_EXTENDEDVERBS); + + if (FAILED(r)) { + throw MenuFailed(r, "QueryContextMenu failed"); + } + + return HMenuPtr(hmenu); +} + +int runMenu(IContextMenu* cm, HWND hwnd, HMENU menu, const QPoint& p) +{ + auto filter = std::make_unique<WndProcFilter>(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) +{ + CMINVOKECOMMANDINFOEX info = {}; + + info.cbSize = sizeof(info); + info.fMask = CMIC_MASK_UNICODE | CMIC_MASK_PTINVOKE; + info.hwnd = hwnd; + info.lpVerb = MAKEINTRESOURCEA(cmd); + info.lpVerbW = MAKEINTRESOURCEW(cmd); + info.nShow = SW_SHOWNORMAL; + info.ptInvoke = {p.x(), p.y()}; + + // note: this calls the query version because the Qt even loop hasn't run + // yet and shift is still considered pressed + const auto m = QApplication::queryKeyboardModifiers(); + + if (m & Qt::ShiftModifier) { + info.fMask |= CMIC_MASK_SHIFT_DOWN; + } + + if (m & Qt::ControlModifier) { + info.fMask |= CMIC_MASK_CONTROL_DOWN; + } + + const auto r = cm->InvokeCommand((CMINVOKECOMMANDINFO*)&info); + + if (FAILED(r)) { + throw MenuFailed(r, fmt::format("InvokeCommand failed, verb={}", cmd)); + } +} + +void showShellMenu(QWidget* parent, const QFileInfo& file, const QPoint& pos) +{ + const auto path = QDir::toNativeSeparators(file.absoluteFilePath()); + + try + { + 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); + if (cmd <= 0) { + return; + } + + invoke(hwnd, pos, cmd - QCM_FIRST, cm.get()); + } + catch(MenuFailed& e) + { + log::error("can't create shell menu for '{}': {}", path, e.what()); + } +} + +} // namespace diff --git a/src/envshell.h b/src/envshell.h new file mode 100644 index 00000000..f30495e0 --- /dev/null +++ b/src/envshell.h @@ -0,0 +1,14 @@ +#ifndef ENV_SHELL_H +#define ENV_SHELL_H + +#include <QFileInfo> +#include <QPoint> + +namespace env +{ + +void showShellMenu(QWidget* parent, const QFileInfo& file, const QPoint& pos); + +} + +#endif // ENV_SHELL_H diff --git a/src/filetree.cpp b/src/filetree.cpp index 71a49200..3c99ab05 100644 --- a/src/filetree.cpp +++ b/src/filetree.cpp @@ -2,6 +2,7 @@ #include "filetreemodel.h" #include "filetreeitem.h" #include "organizercore.h" +#include "envshell.h" #include <log.h> using namespace MOShared; @@ -438,6 +439,23 @@ void FileTree::onExpandedChanged(const QModelIndex& index, bool expanded) void FileTree::onContextMenu(const QPoint &pos) { + const auto m = QApplication::keyboardModifiers(); + + if (m & Qt::ShiftModifier) { + if (auto* item=singleSelection()) { + if (!item->isDirectory()) { + const auto file = m_core.directoryStructure()->searchFile( + item->dataRelativeFilePath().toStdWString(), nullptr); + + if (file) { + const QFileInfo fi(QString::fromStdWString(file->getFullPath())); + env::showShellMenu(m_tree, fi, m_tree->viewport()->mapToGlobal(pos)); + return; + } + } + } + } + QMenu menu; if (auto* item=singleSelection()) { diff --git a/src/filetree.h b/src/filetree.h index 1a17354f..40b5b2ff 100644 --- a/src/filetree.h +++ b/src/filetree.h @@ -50,7 +50,7 @@ private: FileTreeItem* singleSelection(); void onExpandedChanged(const QModelIndex& index, bool expanded); void onContextMenu(const QPoint &pos); - + void showShellMenu(const MOShared::FileEntry& file, QPoint pos); void addDirectoryMenus(QMenu& menu, FileTreeItem& item); void addFileMenus(QMenu& menu, const MOShared::FileEntry& file, int originID); |
