summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/env.h34
-rw-r--r--src/envshell.cpp212
-rw-r--r--src/envshell.h14
-rw-r--r--src/filetree.cpp18
-rw-r--r--src/filetree.h2
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
)
diff --git a/src/env.h b/src/env.h
index 16f8039e..5c7492c6 100644
--- a/src/env.h
+++ b/src/env.h
@@ -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);