From f50dcc88bf160100336c94673aa7679403405cdd Mon Sep 17 00:00:00 2001
From: MerryMage <MerryMage@users.noreply.github.com>
Date: Thu, 15 Dec 2016 09:55:03 +0000
Subject: [PATCH] game_list: Implement context menu for items in list

* Add a context menu with a "Open Save Data Location" action
---
 src/citra_qt/game_list.cpp | 28 +++++++++++++++++++++++++---
 src/citra_qt/game_list.h   |  3 +++
 src/citra_qt/game_list_p.h |  5 ++++-
 3 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp
index e536628dd..09469f3c5 100644
--- a/src/citra_qt/game_list.cpp
+++ b/src/citra_qt/game_list.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <QHeaderView>
+#include <QMenu>
 #include <QThreadPool>
 #include <QVBoxLayout>
 #include "common/common_paths.h"
@@ -28,6 +29,7 @@ GameList::GameList(QWidget* parent) : QWidget{parent} {
     tree_view->setSortingEnabled(true);
     tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
     tree_view->setUniformRowHeights(true);
+    tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
 
     item_model->insertColumns(0, COLUMN_COUNT);
     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");
 
     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
-    // 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*>");
 
     layout->addWidget(tree_view);
@@ -71,6 +73,23 @@ void GameList::DonePopulating() {
     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) {
     if (!FileUtil::Exists(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;
             loader->ReadIcon(smdh);
 
+            u64 program_id = 0;
+            loader->ReadProgramId(program_id);
+
             emit EntryReady({
-                new GameListItemPath(QString::fromStdString(physical_name), smdh),
+                new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id),
                 new GameListItem(
                     QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
                 new GameListItemSize(FileUtil::GetSize(physical_name)),
diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h
index 30b2c79a8..1abf10051 100644
--- a/src/citra_qt/game_list.h
+++ b/src/citra_qt/game_list.h
@@ -36,12 +36,15 @@ public:
 signals:
     void GameChosen(QString game_path);
     void ShouldCancelWorker();
+    void OpenSaveFolderRequested(u64 program_id);
 
 private:
     void AddEntry(const QList<QStandardItem*>& entry_items);
     void ValidateEntry(const QModelIndex& item);
     void DonePopulating();
 
+    void PopupContextMenu(const QPoint& menu_location);
+
     QTreeView* tree_view = nullptr;
     QStandardItemModel* item_model = nullptr;
     GameListWorker* current_worker = nullptr;
diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h
index 5ca3fe991..a15f06c5f 100644
--- a/src/citra_qt/game_list_p.h
+++ b/src/citra_qt/game_list_p.h
@@ -71,10 +71,13 @@ class GameListItemPath : public GameListItem {
 public:
     static const int FullPathRole = Qt::UserRole + 1;
     static const int TitleRole = Qt::UserRole + 2;
+    static const int ProgramIdRole = Qt::UserRole + 3;
 
     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(qulonglong(program_id), ProgramIdRole);
 
         if (!Loader::IsValidSMDH(smdh_data)) {
             // SMDH is not valid, set a default icon