Merge pull request #2322 from MerryMage/ctx-mnu
game_list: Add a context menu with "Open Save Location" option
This commit is contained in:
commit
acc83a1c32
|
@ -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
|
// with signals/slots. In this case, QList falls under the umbrells of custom types.
|
||||||
// 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)),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
Reference in New Issue