فهرست منبع

Merge pull request #2322 from MerryMage/ctx-mnu

game_list: Add a context menu with "Open Save Location"  option
Merry 9 سال پیش
والد
کامیت
acc83a1c32

+ 25 - 3
src/citra_qt/game_list.cpp

@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 // Refer to the license.txt file included.
 
 
 #include <QHeaderView>
 #include <QHeaderView>
+#include <QMenu>
 #include <QThreadPool>
 #include <QThreadPool>
 #include <QVBoxLayout>
 #include <QVBoxLayout>
 #include "common/common_paths.h"
 #include "common/common_paths.h"
@@ -28,6 +29,7 @@ GameList::GameList(QWidget* parent) : QWidget{parent} {
     tree_view->setSortingEnabled(true);
     tree_view->setSortingEnabled(true);
     tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
     tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
     tree_view->setUniformRowHeights(true);
     tree_view->setUniformRowHeights(true);
+    tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
 
 
     item_model->insertColumns(0, COLUMN_COUNT);
     item_model->insertColumns(0, COLUMN_COUNT);
     item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
     item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
@@ -35,10 +37,10 @@ GameList::GameList(QWidget* parent) : QWidget{parent} {
     item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
     item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
 
 
     connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
     connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
+    connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
 
 
     // We must register all custom types with the Qt Automoc system so that we are able to use it
     // We must register all custom types with the Qt Automoc system so that we are able to use it
-    // with
-    // signals/slots. In this case, QList falls under the umbrells of custom types.
+    // with signals/slots. In this case, QList falls under the umbrells of custom types.
     qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
     qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
 
 
     layout->addWidget(tree_view);
     layout->addWidget(tree_view);
@@ -71,6 +73,23 @@ void GameList::DonePopulating() {
     tree_view->setEnabled(true);
     tree_view->setEnabled(true);
 }
 }
 
 
+void GameList::PopupContextMenu(const QPoint& menu_location) {
+    QModelIndex item = tree_view->indexAt(menu_location);
+    if (!item.isValid())
+        return;
+
+    int row = item_model->itemFromIndex(item)->row();
+    QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
+    u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong();
+
+    QMenu context_menu;
+    QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
+    open_save_location->setEnabled(program_id != 0);
+    connect(open_save_location, &QAction::triggered,
+            [&]() { emit OpenSaveFolderRequested(program_id); });
+    context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
+}
+
 void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
 void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
     if (!FileUtil::Exists(dir_path.toStdString()) ||
     if (!FileUtil::Exists(dir_path.toStdString()) ||
         !FileUtil::IsDirectory(dir_path.toStdString())) {
         !FileUtil::IsDirectory(dir_path.toStdString())) {
@@ -128,8 +147,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
             std::vector<u8> smdh;
             std::vector<u8> smdh;
             loader->ReadIcon(smdh);
             loader->ReadIcon(smdh);
 
 
+            u64 program_id = 0;
+            loader->ReadProgramId(program_id);
+
             emit EntryReady({
             emit EntryReady({
-                new GameListItemPath(QString::fromStdString(physical_name), smdh),
+                new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id),
                 new GameListItem(
                 new GameListItem(
                     QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
                     QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
                 new GameListItemSize(FileUtil::GetSize(physical_name)),
                 new GameListItemSize(FileUtil::GetSize(physical_name)),

+ 3 - 0
src/citra_qt/game_list.h

@@ -36,12 +36,15 @@ public:
 signals:
 signals:
     void GameChosen(QString game_path);
     void GameChosen(QString game_path);
     void ShouldCancelWorker();
     void ShouldCancelWorker();
+    void OpenSaveFolderRequested(u64 program_id);
 
 
 private:
 private:
     void AddEntry(const QList<QStandardItem*>& entry_items);
     void AddEntry(const QList<QStandardItem*>& entry_items);
     void ValidateEntry(const QModelIndex& item);
     void ValidateEntry(const QModelIndex& item);
     void DonePopulating();
     void DonePopulating();
 
 
+    void PopupContextMenu(const QPoint& menu_location);
+
     QTreeView* tree_view = nullptr;
     QTreeView* tree_view = nullptr;
     QStandardItemModel* item_model = nullptr;
     QStandardItemModel* item_model = nullptr;
     GameListWorker* current_worker = nullptr;
     GameListWorker* current_worker = nullptr;

+ 4 - 1
src/citra_qt/game_list_p.h

@@ -71,10 +71,13 @@ class GameListItemPath : public GameListItem {
 public:
 public:
     static const int FullPathRole = Qt::UserRole + 1;
     static const int FullPathRole = Qt::UserRole + 1;
     static const int TitleRole = Qt::UserRole + 2;
     static const int TitleRole = Qt::UserRole + 2;
+    static const int ProgramIdRole = Qt::UserRole + 3;
 
 
     GameListItemPath() : GameListItem() {}
     GameListItemPath() : GameListItem() {}
-    GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data) : GameListItem() {
+    GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data, u64 program_id)
+        : GameListItem() {
         setData(game_path, FullPathRole);
         setData(game_path, FullPathRole);
+        setData(qulonglong(program_id), ProgramIdRole);
 
 
         if (!Loader::IsValidSMDH(smdh_data)) {
         if (!Loader::IsValidSMDH(smdh_data)) {
             // SMDH is not valid, set a default icon
             // SMDH is not valid, set a default icon

+ 19 - 0
src/citra_qt/main.cpp

@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 // Refer to the license.txt file included.
 
 
+#include <cinttypes>
 #include <clocale>
 #include <clocale>
 #include <memory>
 #include <memory>
 #include <thread>
 #include <thread>
@@ -41,6 +42,7 @@
 #include "common/string_util.h"
 #include "common/string_util.h"
 #include "core/arm/disassembler/load_symbol_map.h"
 #include "core/arm/disassembler/load_symbol_map.h"
 #include "core/core.h"
 #include "core/core.h"
+#include "core/file_sys/archive_source_sd_savedata.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/loader/loader.h"
 #include "core/loader/loader.h"
 #include "core/settings.h"
 #include "core/settings.h"
@@ -171,6 +173,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
     // Setup connections
     // Setup connections
     connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)),
     connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)),
             Qt::DirectConnection);
             Qt::DirectConnection);
+    connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this,
+            SLOT(OnGameListOpenSaveFolder(u64)), Qt::DirectConnection);
     connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure()));
     connect(ui.action_Configure, SIGNAL(triggered()), this, SLOT(OnConfigure()));
     connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()),
     connect(ui.action_Load_File, SIGNAL(triggered()), this, SLOT(OnMenuLoadFile()),
             Qt::DirectConnection);
             Qt::DirectConnection);
@@ -460,6 +464,21 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
     BootGame(game_path.toStdString());
     BootGame(game_path.toStdString());
 }
 }
 
 
+void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) {
+    std::string sdmc_dir = FileUtil::GetUserPath(D_SDMC_IDX);
+    std::string path = FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(sdmc_dir, program_id);
+    QString qpath = QString::fromStdString(path);
+
+    QDir dir(qpath);
+    if (!dir.exists()) {
+        QMessageBox::critical(this, tr("Error Opening Save Folder"), tr("Folder does not exist!"));
+        return;
+    }
+
+    LOG_INFO(Frontend, "Opening save data path for program_id=%" PRIu64, program_id);
+    QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
+}
+
 void GMainWindow::OnMenuLoadFile() {
 void GMainWindow::OnMenuLoadFile() {
     QString filename =
     QString filename =
         QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path,
         QFileDialog::getOpenFileName(this, tr("Load File"), UISettings::values.roms_path,

+ 1 - 0
src/citra_qt/main.h

@@ -105,6 +105,7 @@ private slots:
     void OnStopGame();
     void OnStopGame();
     /// Called whenever a user selects a game in the game list widget.
     /// Called whenever a user selects a game in the game list widget.
     void OnGameListLoadFile(QString game_path);
     void OnGameListLoadFile(QString game_path);
+    void OnGameListOpenSaveFolder(u64 program_id);
     void OnMenuLoadFile();
     void OnMenuLoadFile();
     void OnMenuLoadSymbolMap();
     void OnMenuLoadSymbolMap();
     /// Called whenever a user selects the "File->Select Game List Root" menu item
     /// Called whenever a user selects the "File->Select Game List Root" menu item

+ 5 - 0
src/core/file_sys/archive_source_sd_savedata.cpp

@@ -90,4 +90,9 @@ ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(u64 program
     return MakeResult<ArchiveFormatInfo>(info);
     return MakeResult<ArchiveFormatInfo>(info);
 }
 }
 
 
+std::string ArchiveSource_SDSaveData::GetSaveDataPathFor(const std::string& mount_point,
+                                                         u64 program_id) {
+    return GetSaveDataPath(GetSaveDataContainerPath(mount_point), program_id);
+}
+
 } // namespace FileSys
 } // namespace FileSys

+ 2 - 0
src/core/file_sys/archive_source_sd_savedata.h

@@ -23,6 +23,8 @@ public:
     ResultCode Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info);
     ResultCode Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info);
     ResultVal<ArchiveFormatInfo> GetFormatInfo(u64 program_id) const;
     ResultVal<ArchiveFormatInfo> GetFormatInfo(u64 program_id) const;
 
 
+    static std::string GetSaveDataPathFor(const std::string& mount_point, u64 program_id);
+
 private:
 private:
     std::string mount_point;
     std::string mount_point;
 };
 };

+ 9 - 0
src/core/loader/loader.h

@@ -143,6 +143,15 @@ public:
         return ResultStatus::ErrorNotImplemented;
         return ResultStatus::ErrorNotImplemented;
     }
     }
 
 
+    /**
+     * Get the program id of the application
+     * @param out_program_id Reference to store program id into
+     * @return ResultStatus result of function
+     */
+    virtual ResultStatus ReadProgramId(u64& out_program_id) {
+        return ResultStatus::ErrorNotImplemented;
+    }
+
     /**
     /**
      * Get the RomFS of the application
      * Get the RomFS of the application
      * Since the RomFS can be huge, we return a file reference instead of copying to a buffer
      * Since the RomFS can be huge, we return a file reference instead of copying to a buffer

+ 12 - 0
src/core/loader/ncch.cpp

@@ -344,6 +344,18 @@ ResultStatus AppLoader_NCCH::ReadLogo(std::vector<u8>& buffer) {
     return LoadSectionExeFS("logo", buffer);
     return LoadSectionExeFS("logo", buffer);
 }
 }
 
 
+ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) {
+    if (!file.IsOpen())
+        return ResultStatus::Error;
+
+    ResultStatus result = LoadExeFS();
+    if (result != ResultStatus::Success)
+        return result;
+
+    out_program_id = ncch_header.program_id;
+    return ResultStatus::Success;
+}
+
 ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
 ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
                                        u64& size) {
                                        u64& size) {
     if (!file.IsOpen())
     if (!file.IsOpen())

+ 7 - 0
src/core/loader/ncch.h

@@ -219,6 +219,13 @@ public:
      */
      */
     ResultStatus ReadLogo(std::vector<u8>& buffer) override;
     ResultStatus ReadLogo(std::vector<u8>& buffer) override;
 
 
+    /**
+     * Get the program id of the application
+     * @param out_program_id Reference to store program id into
+     * @return ResultStatus result of function
+     */
+    ResultStatus ReadProgramId(u64& out_program_id) override;
+
     /**
     /**
      * Get the RomFS of the application
      * Get the RomFS of the application
      * @param romfs_file Reference to buffer to store data
      * @param romfs_file Reference to buffer to store data