QtUtils: Add ShowFileInFolder function and make GameList right-click menu actions use it.

This commit is contained in:
Jordan Woyak
2026-04-24 18:09:39 -05:00
parent d7b7c47453
commit a796486625
3 changed files with 110 additions and 34 deletions
+22 -34
View File
@@ -65,6 +65,7 @@
#include "DolphinQt/QtUtils/DoubleClickEventFilter.h" #include "DolphinQt/QtUtils/DoubleClickEventFilter.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/NonAutodismissibleMenu.h" #include "DolphinQt/QtUtils/NonAutodismissibleMenu.h"
#include "DolphinQt/QtUtils/QtUtils.h"
#include "DolphinQt/Resources.h" #include "DolphinQt/Resources.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
#include "DolphinQt/WiiUpdate.h" #include "DolphinQt/WiiUpdate.h"
@@ -722,19 +723,7 @@ void GameList::OpenContainingFolder()
if (!game) if (!game)
return; return;
// Remove everything after the last separator in the game's path, resulting in the parent QtUtils::ShowFileInFolder(game->GetFilePath());
// directory path with a trailing separator. Keeping the trailing separator prevents Windows from
// mistakenly opening a .bat or .exe file in the grandparent folder when that file has the same
// base name as the folder (See https://bugs.dolphin-emu.org/issues/12411).
std::string parent_directory_path;
SplitPath(game->GetFilePath(), &parent_directory_path, nullptr, nullptr);
if (parent_directory_path.empty())
{
return;
}
const QUrl url = QUrl::fromLocalFile(QString::fromStdString(parent_directory_path));
QDesktopServices::openUrl(url);
} }
void GameList::OpenWiiSaveFolder() void GameList::OpenWiiSaveFolder()
@@ -766,45 +755,44 @@ void GameList::OpenGCSaveFolder()
for (Slot slot : ExpansionInterface::MEMCARD_SLOTS) for (Slot slot : ExpansionInterface::MEMCARD_SLOTS)
{ {
QUrl url;
const ExpansionInterface::EXIDeviceType current_exi_device = const ExpansionInterface::EXIDeviceType current_exi_device =
Config::Get(Config::GetInfoForEXIDevice(slot)); Config::Get(Config::GetInfoForEXIDevice(slot));
switch (current_exi_device) switch (current_exi_device)
{ {
case ExpansionInterface::EXIDeviceType::MemoryCardFolder: case ExpansionInterface::EXIDeviceType::MemoryCardFolder:
{ {
std::string override_path = Config::Get(Config::GetInfoForGCIPathOverride(slot)); namespace fs = std::filesystem;
QDir dir(QString::fromStdString(override_path.empty() ?
Config::GetGCIFolderPath(slot, game->GetRegion()) :
override_path));
if (!dir.entryList({QStringLiteral("%1-%2-*.gci") std::string override_path = Config::Get(Config::GetInfoForGCIPathOverride(slot));
.arg(QString::fromStdString(game->GetMakerID())) const auto dir =
.arg(QString::fromStdString(game->GetGameID().substr(0, 4)))}) override_path.empty() ? Config::GetGCIFolderPath(slot, game->GetRegion()) : override_path;
.empty())
const auto gci_filename_prefix =
fs::path{fmt::format("{}-{}-", game->GetMakerID(), game->GetGameID().substr(0, 4))}
.native();
const auto gci_extension = fs::path{".gci"}.native();
for (const auto& entry : fs::directory_iterator(dir))
{ {
url = QUrl::fromLocalFile(dir.absolutePath()); if (entry.path().filename().native().starts_with(gci_filename_prefix) &&
entry.path().extension() == gci_extension)
{
QtUtils::ShowFileInFolder(entry.path().generic_string());
found = true;
break;
}
} }
break; break;
} }
case ExpansionInterface::EXIDeviceType::MemoryCard: case ExpansionInterface::EXIDeviceType::MemoryCard:
{ {
const std::string memcard_path = Config::GetMemcardPath(slot, game->GetRegion()); QtUtils::ShowFileInFolder(Config::GetMemcardPath(slot, game->GetRegion()));
found = true;
std::string memcard_dir;
SplitPath(memcard_path, &memcard_dir, nullptr, nullptr);
url = QUrl::fromLocalFile(QString::fromStdString(memcard_dir));
break; break;
} }
default: default:
break; break;
} }
found |= !url.isEmpty();
if (!url.isEmpty())
QDesktopServices::openUrl(url);
} }
if (!found) if (!found)
+83
View File
@@ -4,10 +4,43 @@
#include "DolphinQt/QtUtils/QtUtils.h" #include "DolphinQt/QtUtils/QtUtils.h"
#include <QDateTimeEdit> #include <QDateTimeEdit>
#include <QDesktopServices>
#include <QDir>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QProcess>
#include <QScreen> #include <QScreen>
#if defined(QT_DBUS_LIB)
#include <QDBusConnectionInterface>
#include <QDBusInterface>
#endif
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
namespace
{
void ShowFolderOfFile(std::string_view file_path)
{
// Remove everything after the last separator in the game's path, resulting in the parent
// directory path with a trailing separator. Keeping the trailing separator prevents Windows from
// mistakenly opening a .bat or .exe file in the grandparent folder when that file has the same
// base name as the folder (See https://bugs.dolphin-emu.org/issues/12411).
std::string parent_directory_path;
SplitPath(file_path, &parent_directory_path, nullptr, nullptr);
if (parent_directory_path.empty())
{
return;
}
const QUrl url = QUrl::fromLocalFile(QString::fromStdString(parent_directory_path));
QDesktopServices::openUrl(url);
}
} // namespace
namespace QtUtils namespace QtUtils
{ {
@@ -68,4 +101,54 @@ void CenterOnParentWindow(QWidget* const widget)
widget->setGeometry(QRect(pos, size)); widget->setGeometry(QRect(pos, size));
} }
void ShowFileInFolder(std::string_view file_path)
{
#if defined(QT_DBUS_LIB)
QDBusInterface dbus{QString::fromLatin1("org.freedesktop.DBus"),
QString::fromLatin1("/org/freedesktop/DBus"),
QString::fromLatin1("org.freedesktop.DBus")};
dbus.call(QString::fromLatin1("StartServiceByName"),
QString::fromLatin1("org.freedesktop.FileManager1"), 0u);
QDBusInterface iface{QString::fromLatin1("org.freedesktop.FileManager1"),
QString::fromLatin1("/org/freedesktop/FileManager1"),
QString::fromLatin1("org.freedesktop.FileManager1")};
if (iface.isValid())
{
QStringList urls;
urls << QUrl::fromLocalFile(QString::fromUtf8(file_path)).toString();
const QDBusReply<void> reply = iface.call(QString::fromLatin1("ShowItems"), urls, QString{});
if (reply.isValid())
return;
const auto& err = reply.error();
WARN_LOG_FMT(COMMON, "DBus call failed: {} - {}", err.name().toStdString(),
err.message().toStdString());
}
else
{
WARN_LOG_FMT(COMMON, "Invalid DBus interface: {}", iface.lastError().message().toStdString());
}
#elif defined(_WIN32)
if (QProcess{}.startDetached(QString::fromLatin1("explorer.exe"),
{QString::fromLatin1("/select"), QString::fromLatin1(","),
QDir::toNativeSeparators(QString::fromUtf8(file_path))}))
{
return;
}
#elif defined(__APPLE__)
if (QProcess{}.startDetached(QString::fromLatin1("open"),
{QString::fromLatin1("-R"), QString::fromUtf8(file_path)}))
{
return;
}
#endif
// Fall back on failure or unsupported platforms.
ShowFolderOfFile(file_path);
}
} // namespace QtUtils } // namespace QtUtils
+5
View File
@@ -3,6 +3,8 @@
#pragma once #pragma once
#include <string_view>
#include <QStyle> #include <QStyle>
class QDateTimeEdit; class QDateTimeEdit;
@@ -37,4 +39,7 @@ public:
QSize sizeHint() const override { return Widget::minimumSizeHint(); } QSize sizeHint() const override { return Widget::minimumSizeHint(); }
}; };
// Opens the folder of the given file (and also selects the file on supported platforms).
void ShowFileInFolder(std::string_view file_path);
} // namespace QtUtils } // namespace QtUtils