From 06218502ed5379555eda1504e6b05f2ef6dfb292 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 11 Sep 2019 23:34:54 -0400 Subject: fixes for ExpanderWidget removed dead code in MainWindow --- src/modinfodialogconflicts.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/modinfodialogconflicts.h') diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index a77c2ac9..ad305dfc 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -33,7 +33,7 @@ signals: private: struct Expanders { - ExpanderWidget overwrite, overwritten, nonconflict; + MOBase::ExpanderWidget overwrite, overwritten, nonconflict; }; ConflictsTab* m_tab; -- cgit v1.3.1 From d4172dc5f8c642dbbe235a86a28992af310e703a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 13:51:46 -0500 Subject: added "open with vfs" option to conflicts tab --- src/env.cpp | 157 +++++++++++++++++++++++++++++++++++++++++ src/env.h | 20 ++++++ src/modinfodialog.cpp | 23 +++++- src/modinfodialogconflicts.cpp | 39 +++++++++- src/modinfodialogconflicts.h | 2 + src/modinfodialogfwd.h | 1 + src/processrunner.cpp | 20 +++++- src/processrunner.h | 12 ++-- 8 files changed, 263 insertions(+), 11 deletions(-) (limited to 'src/modinfodialogconflicts.h') diff --git a/src/env.cpp b/src/env.cpp index f9507dc1..0098456e 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -675,6 +675,163 @@ Service getService(const QString& name) } +std::optional getAssocString(const QFileInfo& file, ASSOCSTR astr) +{ + const auto ext = L"." + file.suffix().toStdWString(); + + // getting buffer size + DWORD bufferSize = 0; + auto r = AssocQueryStringW( + ASSOCF_INIT_IGNOREUNKNOWN, astr, ext.c_str(), L"open", nullptr, &bufferSize); + + // returns S_FALSE when giving back the buffer size, so that's actually the + // expected return value + + if (r != S_FALSE || bufferSize == 0) { + if (r == HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION)) { + log::error("file '{}' has no associated executable", file.absoluteFilePath()); + } else { + log::error( + "can't get buffer size for AssocQueryStringW(), {}", + formatSystemMessage(r)); + } + return {}; + } + + // getting string + auto buffer = std::make_unique(bufferSize + 1); + std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); + + r = AssocQueryStringW( + ASSOCF_INIT_IGNOREUNKNOWN, astr, ext.c_str(), L"open", buffer.get(), &bufferSize); + + if (FAILED(r)) { + log::error( + "failed to get exe associated with '{}', {}", + file.suffix(), formatSystemMessage(r)); + + return {}; + } + + // buffer size includes the null terminator + return QString::fromWCharArray(buffer.get(), bufferSize - 1); +} + +QString formatCommandLine(const QFileInfo& targetInfo, const QString& cmd) +{ + // yeah, FormatMessage() expects at least as many arguments as there are + // placeholders and while the command for associations should typically only + // have %1, the user can actually enter anything in the registry + // + // since the maximum number of arguments is 99, this creates an array of 99 + // wchar_* where the first one (%1) points to the filename and the remaining + // 98 to "" + // + // FormatMessage() actually takes a va_list* for the arguments, but by passing + // FORMAT_MESSAGE_ARGUMENT_ARRAY, an array of DWORD_PTR can be given instead + + // 99 arguments + std::array args; + + // first one is the filename + const auto wpath = targetInfo.absoluteFilePath().toStdWString(); + args[0] = reinterpret_cast(wpath.c_str()); + + // remaining are "" + std::fill(args.begin() + 1, args.end(), reinterpret_cast(L"")); + + // must be freed with LocalFree() + wchar_t* buffer = nullptr; + + const auto wcmd = cmd.toStdWString(); + + const auto n = ::FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING, + wcmd.c_str(), 0, 0, + reinterpret_cast(&buffer), + 0, reinterpret_cast(&args[0])); + + if (n == 0 || !buffer){ + const auto e = GetLastError(); + + log::error( + "failed to format command line '{}' with path '{}', {}", + cmd, targetInfo.absoluteFilePath(), formatSystemMessage(e)); + + return {}; + } + + auto s = QString::fromWCharArray(buffer, n); + ::LocalFree(buffer); + + return s.trimmed(); +} + +std::pair splitExeAndArguments(const QString& cmd) +{ + int exeBegin = 0; + int exeEnd = -1; + + if (cmd[0] == '"'){ + // surrounded by double-quotes, so find the next one + exeBegin = 1; + exeEnd = cmd.indexOf('"', exeBegin); + + if (exeEnd == -1) { + log::error("missing terminating double-quote in command line '{}'", cmd); + return {}; + } + } else { + // no double-quotes, find the first whitespace + exeEnd = cmd.indexOf(QRegExp("\\s")); + if (exeEnd == -1) { + exeEnd = cmd.size(); + } + } + + QString exe = cmd.mid(exeBegin, exeEnd - exeBegin).trimmed(); + QString args = cmd.mid(exeEnd + 1).trimmed(); + + return {std::move(exe), std::move(args)}; +} + +Association getAssociation(const QFileInfo& targetInfo) +{ + log::debug( + "getting association for '{}', extension is '.{}'", + targetInfo.absoluteFilePath(), targetInfo.suffix()); + + const auto cmd = getAssocString(targetInfo, ASSOCSTR_COMMAND); + if (!cmd) { + return {}; + } + + log::debug("raw cmd is '{}'", *cmd); + + QString formattedCmd = formatCommandLine(targetInfo, *cmd); + if (formattedCmd.isEmpty()) { + log::error( + "command line associated with '{}' is empty", + targetInfo.absoluteFilePath()); + + return {}; + } + + log::debug("formatted cmd is '{}'", formattedCmd); + + const auto p = splitExeAndArguments(formattedCmd); + if (p.first.isEmpty()) { + return {}; + } + + log::debug("split into exe='{}' and cmd='{}'", p.first, p.second); + + return {p.first, *cmd, p.second}; +} + + // returns the filename of the given process or the current one // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) diff --git a/src/env.h b/src/env.h index dc0fd864..16f8039e 100644 --- a/src/env.h +++ b/src/env.h @@ -246,6 +246,26 @@ Service getService(const QString& name); QString toString(Service::StartType st); QString toString(Service::Status st); + +struct Association +{ + // path to the executable associated with the file + QFileInfo executable; + + // full command line associated with the file, no replacements + QString commandLine; + + // command line _without_ the executable and with placeholders such as %1 + // replaced by the given file + QString formattedCommandLine; +}; + +// returns the associated executable and command line, executable is empty on +// error +// +Association getAssociation(const QFileInfo& file); + + enum class CoreDumpTypes { Mini = 1, diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c7e071ad..f5ca1de7 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -61,10 +61,27 @@ bool canPreviewFile( return pluginContainer.previewGenerator().previewSupported(ext); } -bool canOpenFile(bool isArchive, const QString&) +bool isExecutableFilename(const QString& filename) { - // can open anything as long as it's not in an archive - return !isArchive; + static const std::set exeExtensions = { + "exe", "cmd", "bat" + }; + + const auto ext = QFileInfo(filename).suffix().toLower(); + + return exeExtensions.contains(ext); +} + +bool canRunFile(bool isArchive, const QString& filename) +{ + // can run executables that are not archives + return !isArchive && isExecutableFilename(filename); +} + +bool canOpenFile(bool isArchive, const QString& filename) +{ + // can open non-executables that are not archives + return !isArchive && !isExecutableFilename(filename); } bool canExploreFile(bool isArchive, const QString&) diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index d37f068c..58e935f2 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -81,6 +81,11 @@ public: return canUnhideFile(isArchive(), fileName()); } + bool canRun() const + { + return canRunFile(isArchive(), fileName()); + } + bool canOpen() const { return canOpenFile(isArchive(), fileName()); @@ -536,6 +541,20 @@ void ConflictsTab::openItems(QTreeView* tree) }); } +void ConflictsTab::runItemsHooked(QTreeView* tree) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for_each_in_selection(tree, [&](const ConflictItem* item) { + core().processRunner() + .setFromFile(parentWidget(), item->fileName(), true) + .setWaitForCompletion() + .run(); + + return true; + }); +} + void ConflictsTab::previewItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -571,6 +590,15 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.open); } + // run hooked + if (actions.runHooked) { + connect(actions.runHooked, &QAction::triggered, [&]{ + runItemsHooked(tree); + }); + + menu.addAction(actions.runHooked); + } + // preview if (actions.preview) { connect(actions.preview, &QAction::triggered, [&]{ @@ -633,6 +661,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) bool enableHide = true; bool enableUnhide = true; + bool enableRun = true; bool enableOpen = true; bool enablePreview = true; bool enableExplore = true; @@ -657,6 +686,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) enableHide = item->canHide(); enableUnhide = item->canUnhide(); + enableRun = item->canRun(); enableOpen = item->canOpen(); enablePreview = item->canPreview(plugin()); enableExplore = item->canExplore(); @@ -665,6 +695,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) else { // this is a multiple selection, don't show open/preview so users don't open // a thousand files + enableRun = false; enableOpen = false; enablePreview = false; @@ -709,8 +740,12 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) actions.unhide = new QAction(tr("&Unhide"), parentWidget()); actions.unhide->setEnabled(enableUnhide); - actions.open = new QAction(tr("&Open/Execute"), parentWidget()); - actions.open->setEnabled(enableOpen); + if (enableRun) { + actions.open = new QAction(tr("&Execute"), parentWidget()); + } else if (enableOpen) { + actions.open = new QAction(tr("&Open"), parentWidget()); + actions.runHooked = new QAction(tr("Open with &VFS"), parentWidget()); + } actions.preview = new QAction(tr("&Preview"), parentWidget()); actions.preview->setEnabled(enablePreview); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index ad305dfc..ebf82033 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -112,6 +112,7 @@ public: bool canHandleUnmanaged() const override; void openItems(QTreeView* tree); + void runItemsHooked(QTreeView* tree); void previewItems(QTreeView* tree); void exploreItems(QTreeView* tree); @@ -125,6 +126,7 @@ private: QAction* hide = nullptr; QAction* unhide = nullptr; QAction* open = nullptr; + QAction* runHooked = nullptr; QAction* preview = nullptr; QAction* explore = nullptr; QMenu* gotoMenu = nullptr; diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index 9ede766f..2147fc04 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -23,6 +23,7 @@ enum class ModInfoTabIDs class PluginContainer; bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); +bool canRunFile(bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); bool canExploreFile(bool isArchive, const QString& filename); bool canHideFile(bool isArchive, const QString& filename); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index b6167706..46065d69 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -3,6 +3,8 @@ #include "instancemanager.h" #include "iuserinterface.h" #include "envmodule.h" +#include "env.h" +#include #include using namespace MOBase; @@ -473,13 +475,14 @@ ProcessRunner& ProcessRunner::setWaitForCompletion( return *this; } -ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targetInfo) +ProcessRunner& ProcessRunner::setFromFile( + QWidget* parent, const QFileInfo& targetInfo, bool forceHook) { if (!parent && m_ui) { parent = m_ui->qtWidget(); } - // if the file is a .exe, start it directory; if it's anything else, ask the + // if the file is a .exe, start it directly; if it's anything else, ask the // shell to start it const auto fec = spawn::getFileExecutionContext(parent, targetInfo); @@ -497,6 +500,19 @@ ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targ case spawn::FileExecutionTypes::Other: // fall-through default: { + if (forceHook) { + auto assoc = env::getAssociation(targetInfo); + if (!assoc.executable.filePath().isEmpty()) { + setBinary(assoc.executable); + setArguments(assoc.formattedCommandLine); + setCurrentDirectory(assoc.executable.absoluteDir()); + + return *this; + } + + // if it fails, just use the regular shell open + } + m_shellOpen = targetInfo.absoluteFilePath(); // picked up by postRun() diff --git a/src/processrunner.h b/src/processrunner.h index a5099136..d576216a 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -68,10 +68,14 @@ public: ProcessRunner& setWaitForCompletion( WaitFlags flags=NoFlags, UILocker::Reasons reason=UILocker::LockUI); - // if the target is an executable file, runs that; for anything else, calls - // ShellExecute() on it - // - ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); + // - if the target is an executable file, runs it hooked + // - if the target is a file: + // - if forceHook is false, calls ShellExecute() on it + // - if forceHook is true, gets the executable associated with the file + // and runs that hooked by passing the file as an argument + // + ProcessRunner& setFromFile( + QWidget* parent, const QFileInfo& targetInfo, bool forceHook = false); ProcessRunner& setFromExecutable(const Executable& exe); ProcessRunner& setFromShortcut(const MOShortcut& shortcut); -- cgit v1.3.1 From 47b767ef2fd1071a065e546c543805f490ab3e2d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 14:36:09 -0500 Subject: added "open with vfs" to filetree --- src/modinfodialogconflicts.h | 4 -- src/modinfodialogfiletree.cpp | 115 ++++++++++++++++++++++-------------------- src/modinfodialogfiletree.h | 18 ++++--- 3 files changed, 69 insertions(+), 68 deletions(-) (limited to 'src/modinfodialogconflicts.h') diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index ebf82033..8baa62b6 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -60,10 +60,6 @@ private: void onOverwriteActivated(const QModelIndex& index); void onOverwrittenActivated(const QModelIndex& index); - - void onOverwriteTreeContext(const QPoint &pos); - void onOverwrittenTreeContext(const QPoint &pos); - void onNoConflictTreeContext(const QPoint &pos); }; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 00471a72..23d65fdb 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -24,6 +24,7 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) m_actions.newFolder = new QAction(tr("&New Folder"), ui->filetree); m_actions.open = new QAction(tr("&Open/Execute"), ui->filetree); + m_actions.runHooked = new QAction(tr("Open with &VFS"), ui->filetree); m_actions.preview = new QAction(tr("&Preview"), ui->filetree); m_actions.explore = new QAction(tr("Open in &Explorer"), ui->filetree); m_actions.rename = new QAction(tr("&Rename"), ui->filetree); @@ -33,6 +34,7 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); + connect(m_actions.runHooked, &QAction::triggered, [&]{ onRunHooked(); }); connect(m_actions.preview, &QAction::triggered, [&]{ onPreview(); }); connect(m_actions.explore, &QAction::triggered, [&]{ onExplore(); }); connect(m_actions.rename, &QAction::triggered, [&]{ onRename(); }); @@ -146,6 +148,19 @@ void FileTreeTab::onOpen() .run(); } +void FileTreeTab::onRunHooked() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; + } + + core().processRunner() + .setFromFile(parentWidget(), m_fs->filePath(selection), true) + .setWaitForCompletion() + .run(); +} + void FileTreeTab::onPreview() { auto selection = singleSelection(); @@ -342,75 +357,53 @@ void FileTreeTab::onContextMenu(const QPoint &pos) QMenu menu(ui->filetree); - bool enableNewFolder = true; - bool enableOpen = true; - bool enablePreview = true; - bool enableExplore = true; - bool enableRename = true; - bool enableDelete = true; - bool enableHide = true; - bool enableUnhide = true; + bool enableNewFolder = false; + bool enableRun = false; + bool enableOpen = false; + bool enablePreview = false; + bool enableExplore = false; + bool enableRename = false; + bool enableDelete = false; + bool enableHide = false; + bool enableUnhide = false; if (selection.size() == 0) { // no selection, only new folder and explore - enableOpen = false; - enablePreview = false; - enableRename = false; - enableDelete = false; - enableHide = false; - enableUnhide = false; + enableNewFolder = true; + enableExplore = true; } else if (selection.size() == 1) { // single selection + enableNewFolder = true; + enableRename = true; + enableDelete = true; // only enable open action if a file is selected bool hasFiles = false; - for (auto index : selection) { - if (m_fs->fileInfo(index).isFile()) { - hasFiles = true; - break; - } - } - - if (!hasFiles) { - enableOpen = false; - enablePreview = false; - } - const QString fileName = m_fs->fileName(selection[0]); - if (!canPreviewFile(plugin(), false, fileName)) { - enablePreview = false; - } - - if (!canExploreFile(false, fileName)) { - enableExplore = false; - } - - if (!canHideFile(false, fileName)) { - enableHide = false; + if (m_fs->fileInfo(selection[0]).isFile()) { + if (canRunFile(false, fileName)) { + enableRun = true; + } else if (canOpenFile(false, fileName)) { + enableOpen = true; + } } - if (!canUnhideFile(false, fileName)) { - enableUnhide = false; - } + enablePreview = canPreviewFile(plugin(), false, fileName); + enableExplore = canExploreFile(false, fileName); + enableHide = canHideFile(false, fileName); + enableUnhide = canUnhideFile(false, fileName); } else { - // this is a multiple selection, don't show open action so users don't open - // a thousand files - enableOpen = false; - enablePreview = false; - - // can't explore multiple files - enableExplore = false; - - // can't rename multiple files - enableRename = false; + // this is a multiple selection, don't show open or explore actions so users + // don't open a thousand files + enableNewFolder = true; + enablePreview = true; + enableDelete = true; if (selection.size() < max_scan_for_context_menu) { // if the number of selected items is low, checking them to accurately // show the menu items is worth it - enableHide = false; - enableUnhide = false; for (const auto& index : selection) { const QString fileName = m_fs->fileName(index); @@ -431,11 +424,14 @@ void FileTreeTab::onContextMenu(const QPoint &pos) } } - menu.addAction(m_actions.newFolder); - m_actions.newFolder->setEnabled(enableNewFolder); - - menu.addAction(m_actions.open); - m_actions.open->setEnabled(enableOpen); + if (enableRun) { + m_actions.open->setText(tr("&Execute")); + menu.addAction(m_actions.open); + } else if (enableOpen) { + m_actions.open->setText(tr("&Open")); + menu.addAction(m_actions.open); + menu.addAction(m_actions.runHooked); + } menu.addAction(m_actions.preview); m_actions.preview->setEnabled(enablePreview); @@ -443,12 +439,19 @@ void FileTreeTab::onContextMenu(const QPoint &pos) menu.addAction(m_actions.explore); m_actions.explore->setEnabled(enableExplore); + menu.addSeparator(); + + menu.addAction(m_actions.newFolder); + m_actions.newFolder->setEnabled(enableNewFolder); + menu.addAction(m_actions.rename); m_actions.rename->setEnabled(enableRename); menu.addAction(m_actions.del); m_actions.del->setEnabled(enableDelete); + menu.addSeparator(); + menu.addAction(m_actions.hide); m_actions.hide->setEnabled(enableHide); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 494a7e14..2f2e501c 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -20,14 +20,15 @@ public: private: struct Actions { - QAction *newFolder = nullptr; - QAction *open = nullptr; - QAction *preview = nullptr; - QAction *explore = nullptr; - QAction *rename = nullptr; - QAction *del = nullptr; - QAction *hide = nullptr; - QAction *unhide = nullptr; + QAction* newFolder = nullptr; + QAction* open = nullptr; + QAction* runHooked = nullptr; + QAction* preview = nullptr; + QAction* explore = nullptr; + QAction* rename = nullptr; + QAction* del = nullptr; + QAction* hide = nullptr; + QAction* unhide = nullptr; }; QFileSystemModel* m_fs; @@ -35,6 +36,7 @@ private: void onCreateDirectory(); void onOpen(); + void onRunHooked(); void onPreview(); void onExplore(); void onRename(); -- cgit v1.3.1 From 91b95d00a6fda3788e774488ebeb72b28c89e656 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Dec 2019 13:24:31 -0500 Subject: implemented previews on double-click for the data and conflicts tabs --- src/mainwindow.cpp | 90 +++++++++++++++++++++++++++++++++++++----- src/mainwindow.h | 1 + src/modinfodialogconflicts.cpp | 57 +++++++++++++++++++------- src/modinfodialogconflicts.h | 3 ++ src/modinfodialogfiletree.cpp | 50 +++++++++++------------ src/modinfodialogfiletree.h | 1 + src/settingsdialog.ui | 3 ++ 7 files changed, 154 insertions(+), 51 deletions(-) (limited to 'src/modinfodialogconflicts.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c365fb6b..01f683a7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -198,6 +198,55 @@ QString UnmanagedModName() bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); +void setDefaultActivationActionForFile(QAction* open, QAction* preview) +{ + if (!open && !preview) { + return; + } + + QFont bold, notBold; + + if (open) { + bold = open->font(); + notBold = open->font(); + } else { + bold = preview->font(); + notBold = preview->font(); + } + + notBold.setBold(false); + bold.setBold(true); + + + const auto& s = Settings::instance(); + const auto openEnabled = (open && open->isEnabled()); + const auto previewEnabled = (preview && preview->isEnabled()); + + bool doPreview = false; + + // preview is bold if the file is previewable and [the preview on double-click + // option is enabled or the file can't be opened]; open is bold if the file + // can be opened and cannot be previewed + if (previewEnabled && s.interface().doubleClicksOpenPreviews()) { + doPreview = true; + } else if (openEnabled) { + doPreview = false; + } else if (previewEnabled) { + doPreview = true; + } else { + // shouldn't happen, checked above + return; + } + + if (open) { + open->setFont(doPreview ? notBold : bold); + } + + if (preview) { + preview->setFont(doPreview ? bold : notBold); + } +} + MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore @@ -1770,7 +1819,23 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) { - openDataFile(item); + const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); + + if (isArchive || isDirectory) { + return; + } + + const QString path = item->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); + + const auto tryPreview = m_OrganizerCore.settings().interface().doubleClicksOpenPreviews(); + + if (tryPreview && m_PluginContainer.previewGenerator().previewSupported(targetInfo.suffix())) { + previewDataFile(item); + } else { + openDataFile(item); + } } bool MainWindow::refreshProfiles(bool selectProfile) @@ -5287,7 +5352,16 @@ void MainWindow::disableSelectedMods_clicked() void MainWindow::previewDataFile() { - QString fileName = QDir::fromNativeSeparators(m_ContextItem->data(0, Qt::UserRole).toString()); + if (m_ContextItem == nullptr) { + return; + } + + previewDataFile(m_ContextItem); +} + +void MainWindow::previewDataFile(QTreeWidgetItem* item) +{ + QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); m_OrganizerCore.previewFileWithAlternatives(this, fileName); } @@ -5413,6 +5487,7 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); QAction* open = nullptr; + QAction* preview = nullptr; if (canRunFile(isArchive, fileName)) { open = menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); @@ -5421,16 +5496,10 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Open with &VFS"), this, SLOT(runDataFileHooked())); } - if (open) { - auto bold = open->font(); - bold.setBold(true); - open->setFont(bold); - } - menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); + preview = menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); } if (!isArchive && !isDirectory) { @@ -5449,7 +5518,10 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Hide"), this, SLOT(hideFile())); } } + + setDefaultActivationActionForFile(open, preview); } + menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); diff --git a/src/mainwindow.h b/src/mainwindow.h index f5bab586..98573423 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -444,6 +444,7 @@ private slots: void runDataFileHooked(); void addAsExecutable(); void previewDataFile(); + void previewDataFile(QTreeWidgetItem* item); void hideFile(); void unhideFile(); void openDataOriginExplorer_clicked(); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index daa40cb3..9c7ccc8c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -12,6 +12,9 @@ using namespace MOBase; // checking whether menu items apply to them, just show all of them const std::size_t max_small_selection = 50; +// in mainwindow.cpp +void setDefaultActivationActionForFile(QAction* open, QAction* preview); + class ConflictItem { @@ -527,20 +530,43 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) } } -void ConflictsTab::openItems(QTreeView* tree) +void ConflictsTab::activateItems(QTreeView* tree) { + const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); + // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().processRunner() - .setFromFile(parentWidget(), item->fileName()) - .setWaitForCompletion() - .run(); + const auto path = item->fileName(); + + if (tryPreview && canPreviewFile(plugin(), item->isArchive(), path)) { + previewItem(item); + } else { + openItem(item); + } + + return true; + }); +} +void ConflictsTab::openItems(QTreeView* tree) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for_each_in_selection(tree, [&](const ConflictItem* item) { + openItem(item); return true; }); } +void ConflictsTab::openItem(const ConflictItem* item) +{ + core().processRunner() + .setFromFile(parentWidget(), item->fileName()) + .setWaitForCompletion() + .run(); +} + void ConflictsTab::runItemsHooked(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -560,11 +586,16 @@ void ConflictsTab::previewItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().previewFileWithAlternatives(parentWidget(), item->fileName()); + previewItem(item); return true; }); } +void ConflictsTab::previewItem(const ConflictItem* item) +{ + core().previewFileWithAlternatives(parentWidget(), item->fileName()); +} + void ConflictsTab::exploreItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -587,10 +618,6 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) openItems(tree); }); - auto bold = actions.open->font(); - bold.setBold(true); - actions.open->setFont(bold); - menu.addAction(actions.open); } @@ -654,6 +681,8 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.unhide); } + setDefaultActivationActionForFile(actions.open, actions.preview); + if (!menu.isEmpty()) { menu.exec(tree->viewport()->mapToGlobal(pos)); } @@ -828,15 +857,15 @@ GeneralConflictsTab::GeneralConflictsTab( QObject::connect( ui->overwriteTree, &QTreeView::doubleClicked, - [&](auto&&){ m_tab->openItems(ui->overwriteTree); }); + [&](auto&&){ m_tab->activateItems(ui->overwriteTree); }); QObject::connect( ui->overwrittenTree, &QTreeView::doubleClicked, - [&](auto&& item){ m_tab->openItems(ui->overwrittenTree); }); + [&](auto&& item){ m_tab->activateItems(ui->overwrittenTree); }); QObject::connect( ui->noConflictTree, &QTreeView::doubleClicked, - [&](auto&& item){ m_tab->openItems(ui->noConflictTree); }); + [&](auto&& item){ m_tab->activateItems(ui->noConflictTree); }); QObject::connect( ui->overwriteTree, &QTreeView::customContextMenuRequested, @@ -1049,7 +1078,7 @@ AdvancedConflictsTab::AdvancedConflictsTab( QObject::connect( ui->conflictsAdvancedList, &QTreeView::activated, - [&]{ m_tab->openItems(ui->conflictsAdvancedList); }); + [&]{ m_tab->activateItems(ui->conflictsAdvancedList); }); QObject::connect( ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 8baa62b6..3ac8de23 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -107,11 +107,14 @@ public: void restoreState(const Settings& s) override; bool canHandleUnmanaged() const override; + void activateItems(QTreeView* tree); void openItems(QTreeView* tree); void runItemsHooked(QTreeView* tree); void previewItems(QTreeView* tree); void exploreItems(QTreeView* tree); + void openItem(const ConflictItem* item); + void previewItem(const ConflictItem* item); void changeItemsVisibility(QTreeView* tree, bool visible); void showContextMenu(const QPoint &pos, QTreeView* tree); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index c79a5264..d1ae3823 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -14,6 +14,9 @@ namespace shell = MOBase::shell; // checking whether menu items apply to them, just show all of them const int max_scan_for_context_menu = 50; +// in mainwindow.cpp +void setDefaultActivationActionForFile(QAction* open, QAction* preview); + FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) : ModInfoDialogTab(std::move(cx)), m_fs(nullptr) { @@ -52,7 +55,7 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) ui->filetree->setEditTriggers( ui->filetree->editTriggers() & (~QAbstractItemView::DoubleClicked)); - connect(ui->filetree, &QTreeView::activated, [&](auto&&){ onOpen(); }); + connect(ui->filetree, &QTreeView::activated, [&](auto&&){ onActivated(); }); } void FileTreeTab::clear() @@ -141,7 +144,7 @@ void FileTreeTab::onCreateDirectory() ui->filetree->edit(newIndex); } -void FileTreeTab::onOpen() +void FileTreeTab::onActivated() { auto selection = singleSelection(); if (!selection.isValid()) { @@ -152,13 +155,24 @@ void FileTreeTab::onOpen() const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); if (tryPreview && canPreviewFile(plugin(), false, path)) { - core().previewFile(parentWidget(), mod().name(), path); + onPreview(); } else { - core().processRunner() - .setFromFile(parentWidget(), path) - .setWaitForCompletion() - .run(); + onOpen(); + } +} + +void FileTreeTab::onOpen() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; } + + const auto path = m_fs->filePath(selection); + core().processRunner() + .setFromFile(parentWidget(), path) + .setWaitForCompletion() + .run(); } void FileTreeTab::onRunHooked() @@ -448,27 +462,7 @@ void FileTreeTab::onContextMenu(const QPoint &pos) menu.addAction(m_actions.preview); m_actions.preview->setEnabled(enablePreview); - auto bold = m_actions.preview->font(); - bold.setBold(true); - auto notBold = m_actions.preview->font(); - notBold.setBold(false); - - // preview is bold if the file is previewable and [the preview on double-click - // option is enabled or the file can't be opened]; open is bold if the file - // can be opened and cannot be previewed - if (enablePreview && core().settings().interface().doubleClicksOpenPreviews()) { - m_actions.open->setFont(notBold); - m_actions.preview->setFont(bold); - } else if (enableOpen) { - m_actions.open->setFont(bold); - m_actions.preview->setFont(notBold); - } else if (enablePreview) { - m_actions.open->setFont(notBold); - m_actions.preview->setFont(bold); - } else { - m_actions.open->setFont(notBold); - m_actions.preview->setFont(notBold); - } + setDefaultActivationActionForFile(m_actions.open, m_actions.preview); menu.addAction(m_actions.explore); m_actions.explore->setEnabled(enableExplore); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 2f2e501c..c3c84ed4 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -35,6 +35,7 @@ private: Actions m_actions; void onCreateDirectory(); + void onActivated(); void onOpen(); void onRunHooked(); void onPreview(); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 8e175312..84ca5731 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -137,6 +137,9 @@ + + Whether double-clicking on a file opens the preview window or launches the program associated with it. This applies to the Data tab as well as the Conflicts and Filetree tabs in the mod info window. + Open previews on double-click -- cgit v1.3.1 From c9049bb07274aaefa79c9751ec40910bf8daf0ca Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Dec 2019 11:33:03 -0500 Subject: conflicts tab: run exes unhooked by default --- src/mainwindow.cpp | 3 +- src/modinfodialogconflicts.cpp | 72 +++++++++++++++++++++++------------------- src/modinfodialogconflicts.h | 5 ++- src/modinfodialogfiletree.cpp | 3 +- src/processrunner.cpp | 70 ++++++++++++++++++++++++++-------------- src/processrunner.h | 8 +++-- 6 files changed, 97 insertions(+), 64 deletions(-) (limited to 'src/modinfodialogconflicts.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 43d5b820..532d8502 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5425,7 +5425,8 @@ void MainWindow::runDataFileHooked() const QFileInfo targetInfo(path); m_OrganizerCore.processRunner() - .setFromFile(this, targetInfo, true) + .setFromFile(this, targetInfo) + .setHooked(true) .setWaitForCompletion(ProcessRunner::Refresh) .run(); } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 9c7ccc8c..81e8c7a3 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -542,45 +542,32 @@ void ConflictsTab::activateItems(QTreeView* tree) if (tryPreview && canPreviewFile(plugin(), item->isArchive(), path)) { previewItem(item); } else { - openItem(item); + openItem(item, false); } return true; }); } -void ConflictsTab::openItems(QTreeView* tree) +void ConflictsTab::openItems(QTreeView* tree, bool hooked) { // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - openItem(item); + openItem(item, hooked); return true; }); } -void ConflictsTab::openItem(const ConflictItem* item) +void ConflictsTab::openItem(const ConflictItem* item, bool hooked) { core().processRunner() .setFromFile(parentWidget(), item->fileName()) + .setHooked(hooked) .setWaitForCompletion() .run(); } -void ConflictsTab::runItemsHooked(QTreeView* tree) -{ - // the menu item is only shown for a single selection, but handle all of them - // in case this changes - for_each_in_selection(tree, [&](const ConflictItem* item) { - core().processRunner() - .setFromFile(parentWidget(), item->fileName(), true) - .setWaitForCompletion() - .run(); - - return true; - }); -} - void ConflictsTab::previewItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -615,30 +602,44 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) // open if (actions.open) { connect(actions.open, &QAction::triggered, [&]{ - openItems(tree); + openItems(tree, false); }); + } - menu.addAction(actions.open); + // preview + if (actions.preview) { + connect(actions.preview, &QAction::triggered, [&]{ + previewItems(tree); + }); + } + + if ((actions.open && actions.open->isEnabled()) && (actions.preview && actions.preview->isEnabled())) { + if (Settings::instance().interface().doubleClicksOpenPreviews()) { + menu.addAction(actions.preview); + menu.addAction(actions.open); + } else { + menu.addAction(actions.open); + menu.addAction(actions.preview); + } + } else { + if (actions.open) { + menu.addAction(actions.open); + } + + if (actions.preview) { + menu.addAction(actions.preview); + } } // run hooked if (actions.runHooked) { connect(actions.runHooked, &QAction::triggered, [&]{ - runItemsHooked(tree); + openItems(tree, true); }); menu.addAction(actions.runHooked); } - // preview - if (actions.preview) { - connect(actions.preview, &QAction::triggered, [&]{ - previewItems(tree); - }); - - menu.addAction(actions.preview); - } - // goto if (actions.gotoMenu) { menu.addMenu(actions.gotoMenu); @@ -681,9 +682,15 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.unhide); } - setDefaultActivationActionForFile(actions.open, actions.preview); - if (!menu.isEmpty()) { + if (actions.open || actions.preview || actions.runHooked) { + // bold the first option + auto* top = menu.actions()[0]; + auto f = top->font(); + f.setBold(true); + top->setFont(f); + } + menu.exec(tree->viewport()->mapToGlobal(pos)); } } @@ -769,6 +776,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) if (enableRun) { actions.open = new QAction(tr("&Execute"), parentWidget()); + actions.runHooked = new QAction(tr("Execute with &VFS"), parentWidget()); } else if (enableOpen) { actions.open = new QAction(tr("&Open"), parentWidget()); actions.runHooked = new QAction(tr("Open with &VFS"), parentWidget()); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 3ac8de23..1297e536 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -108,12 +108,11 @@ public: bool canHandleUnmanaged() const override; void activateItems(QTreeView* tree); - void openItems(QTreeView* tree); - void runItemsHooked(QTreeView* tree); + void openItems(QTreeView* tree, bool hooked); void previewItems(QTreeView* tree); void exploreItems(QTreeView* tree); - void openItem(const ConflictItem* item); + void openItem(const ConflictItem* item, bool hooked); void previewItem(const ConflictItem* item); void changeItemsVisibility(QTreeView* tree, bool visible); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index d1ae3823..0b9e8da4 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -183,7 +183,8 @@ void FileTreeTab::onRunHooked() } core().processRunner() - .setFromFile(parentWidget(), m_fs->filePath(selection), true) + .setFromFile(parentWidget(), m_fs->filePath(selection)) + .setHooked(true) .setWaitForCompletion() .run(); } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 46065d69..19aae632 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -421,8 +421,8 @@ ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : m_core(core), m_ui(ui), m_lockReason(UILocker::NoReason), m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { - // all processes started in ProcessRunner are hooked - m_sp.hooked = true; + // all processes started in ProcessRunner are hooked by default + setHooked(true); } ProcessRunner& ProcessRunner::setBinary(const QFileInfo &binary) @@ -475,8 +475,14 @@ ProcessRunner& ProcessRunner::setWaitForCompletion( return *this; } +ProcessRunner& ProcessRunner::setHooked(bool b) +{ + m_sp.hooked = b; + return *this; +} + ProcessRunner& ProcessRunner::setFromFile( - QWidget* parent, const QFileInfo& targetInfo, bool forceHook) + QWidget* parent, const QFileInfo& targetInfo) { if (!parent && m_ui) { parent = m_ui->qtWidget(); @@ -500,24 +506,8 @@ ProcessRunner& ProcessRunner::setFromFile( case spawn::FileExecutionTypes::Other: // fall-through default: { - if (forceHook) { - auto assoc = env::getAssociation(targetInfo); - if (!assoc.executable.filePath().isEmpty()) { - setBinary(assoc.executable); - setArguments(assoc.formattedCommandLine); - setCurrentDirectory(assoc.executable.absoluteDir()); - - return *this; - } - - // if it fails, just use the regular shell open - } - - m_shellOpen = targetInfo.absoluteFilePath(); - - // picked up by postRun() - m_sp.hooked = false; - + m_shellOpen = targetInfo; + setHooked(false); break; } } @@ -649,11 +639,41 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( return *this; } +bool ProcessRunner::shouldRunShell() const +{ + return !m_shellOpen.filePath().isEmpty(); +} + ProcessRunner::Results ProcessRunner::run() { + // check if setHooked() was called after setFromFile(); this needs to + // modify the settings to run the associated executable instead of using + // shell::Open() + + if (shouldRunShell() && m_sp.hooked) { + // this is a non-executable file, but it should be hooked; the associated + // executable needs to be retrieved and run instead + auto assoc = env::getAssociation(m_shellOpen); + if (!assoc.executable.filePath().isEmpty()) { + setBinary(assoc.executable); + setArguments(assoc.formattedCommandLine); + setCurrentDirectory(assoc.executable.absoluteDir()); + m_shellOpen = {}; + } else { + // if it fails, just use the regular shell open + log::error("failed to get the associated executable, running unhooked"); + m_sp.hooked = false; + } + } else if (!shouldRunShell() && !m_sp.hooked) { + // this is an executable that should not be hooked; just run it through + // the shell + m_shellOpen = m_sp.binary; + } + + std::optional r; - if (!m_shellOpen.isEmpty()) { + if (shouldRunShell()) { r = runShell(); } else { r = runBinary(); @@ -669,9 +689,11 @@ ProcessRunner::Results ProcessRunner::run() std::optional ProcessRunner::runShell() { - log::debug("executing from shell: '{}'", m_shellOpen); + const auto file = m_shellOpen.absoluteFilePath(); + + log::debug("executing from shell: '{}'", file); - auto r = shell::Open(m_shellOpen); + auto r = shell::Open(file); if (!r.success()) { return Error; } diff --git a/src/processrunner.h b/src/processrunner.h index d576216a..1bfdc465 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -67,6 +67,7 @@ public: ProcessRunner& setProfileName(const QString& profileName); ProcessRunner& setWaitForCompletion( WaitFlags flags=NoFlags, UILocker::Reasons reason=UILocker::LockUI); + ProcessRunner& setHooked(bool b); // - if the target is an executable file, runs it hooked // - if the target is a file: @@ -74,8 +75,7 @@ public: // - if forceHook is true, gets the executable associated with the file // and runs that hooked by passing the file as an argument // - ProcessRunner& setFromFile( - QWidget* parent, const QFileInfo& targetInfo, bool forceHook = false); + ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); ProcessRunner& setFromExecutable(const Executable& exe); ProcessRunner& setFromShortcut(const MOShortcut& shortcut); @@ -150,11 +150,13 @@ private: QString m_profileName; UILocker::Reasons m_lockReason; WaitFlags m_waitFlags; - QString m_shellOpen; + QFileInfo m_shellOpen; env::HandlePtr m_handle; DWORD m_exitCode; + bool shouldRunShell() const; + // runs the command in m_shellOpen; returns empty if it can be waited for // std::optional runShell(); -- cgit v1.3.1