diff --git a/src/platform/qt/ArchiveInspector.cpp b/src/platform/qt/ArchiveInspector.cpp index acb887967..a9d346121 100644 --- a/src/platform/qt/ArchiveInspector.cpp +++ b/src/platform/qt/ArchiveInspector.cpp @@ -23,3 +23,7 @@ ArchiveInspector::ArchiveInspector(const QString& filename, QWidget* parent) VFile* ArchiveInspector::selectedVFile() const { return m_ui.archiveView->selectedVFile(); } + +QPair ArchiveInspector::selectedPath() const { + return m_ui.archiveView->selectedPath(); +} diff --git a/src/platform/qt/ArchiveInspector.h b/src/platform/qt/ArchiveInspector.h index 658a47963..c7b471335 100644 --- a/src/platform/qt/ArchiveInspector.h +++ b/src/platform/qt/ArchiveInspector.h @@ -19,6 +19,7 @@ public: ArchiveInspector(const QString& filename, QWidget* parent = nullptr); VFile* selectedVFile() const; + QPair selectedPath() const; private: Ui::ArchiveInspector m_ui; diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index eefb0a15c..6f25326c5 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -164,6 +164,10 @@ QString ConfigController::getOption(const char* key) const { return QString(mCoreConfigGetValue(&m_config, key)); } +QString ConfigController::getOption(const QString& key) const { + return getOption(key.toUtf8().constData()); +} + QVariant ConfigController::getQtOption(const QString& key, const QString& group) const { if (!group.isNull()) { m_settings->beginGroup(group); diff --git a/src/platform/qt/ConfigController.h b/src/platform/qt/ConfigController.h index a48586f5d..2c8512c5e 100644 --- a/src/platform/qt/ConfigController.h +++ b/src/platform/qt/ConfigController.h @@ -71,6 +71,7 @@ public: void updateOption(const char* key); QString getOption(const char* key) const; + QString getOption(const QString& key) const; QVariant getQtOption(const QString& key, const QString& group = QString()) const; diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 8c9f7a3a1..692de2d74 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -145,7 +145,11 @@ GameController::GameController(QObject* parent) controller->m_multiplayer->attachGame(controller); } - QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(mCoreThread*, context), Q_ARG(const QString&, controller->m_fname)); + QString path = controller->m_fname; + if (!controller->m_fsub.isEmpty()) { + path += QDir::separator() + controller->m_fsub; + } + QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(mCoreThread*, context), Q_ARG(const QString&, path)); QMetaObject::invokeMethod(controller, "startAudio"); }; @@ -369,17 +373,48 @@ void GameController::loadGame(const QString& path) { closeGame(); QFileInfo info(path); if (!info.isReadable()) { - LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); + QString fname = info.fileName(); + QString base = info.path(); + if (base.endsWith("/") || base.endsWith(QDir::separator())) { + base.chop(1); + } + VDir* dir = VDirOpenArchive(base.toUtf8().constData()); + if (dir) { + VFile* vf = dir->openFile(dir, fname.toUtf8().constData(), O_RDONLY); + if (vf) { + struct VFile* vfclone = VFileMemChunk(NULL, vf->size(vf)); + uint8_t buffer[2048]; + ssize_t read; + while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) { + vfclone->write(vfclone, buffer, read); + } + vf->close(vf); + vf = vfclone; + } + dir->close(dir); + loadGame(vf, fname, base); + } else { + LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); + } return; + } else { + m_fname = info.canonicalFilePath(); + m_fsub = QString(); } - m_fname = info.canonicalFilePath(); m_vf = nullptr; openGame(); } -void GameController::loadGame(VFile* vf, const QString& base) { +void GameController::loadGame(VFile* vf, const QString& path, const QString& base) { closeGame(); - m_fname = base; + QFileInfo info(base); + if (info.isDir()) { + m_fname = base + QDir::separator() + path; + m_fsub = QString(); + } else { + m_fname = base; + m_fsub = path; + } m_vf = vf; openGame(); } diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index f98bf92c1..7a3bfbecc 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -113,7 +113,7 @@ signals: public slots: void loadGame(const QString& path); - void loadGame(VFile* vf, const QString& base); + void loadGame(VFile* vf, const QString& path, const QString& base); void loadBIOS(int platform, const QString& path); void loadSave(const QString& path, bool temporary = true); void yankPak(); @@ -198,6 +198,7 @@ private: bool m_gameOpen; QString m_fname; + QString m_fsub; VFile* m_vf; QString m_bios; bool m_useBios; diff --git a/src/platform/qt/LibraryModel.cpp b/src/platform/qt/LibraryModel.cpp index c2837bbd5..b5cda179f 100644 --- a/src/platform/qt/LibraryModel.cpp +++ b/src/platform/qt/LibraryModel.cpp @@ -23,7 +23,7 @@ LibraryModel::LibraryModel(const QString& path, QObject* parent) s_columns["filename"] = { tr("Filename"), [](const mLibraryEntry& e) -> QString { - return e.filename; + return QString::fromUtf8(e.filename); } }; s_columns["size"] = { @@ -60,6 +60,12 @@ LibraryModel::LibraryModel(const QString& path, QObject* parent) } } }; + s_columns["location"] = { + tr("Location"), + [](const mLibraryEntry& e) -> QString { + return QString::fromUtf8(e.base); + } + }; } if (!path.isNull()) { if (s_handles.contains(path)) { @@ -75,6 +81,7 @@ LibraryModel::LibraryModel(const QString& path, QObject* parent) memset(&m_constraints, 0, sizeof(m_constraints)); m_constraints.platform = PLATFORM_NONE; m_columns.append(s_columns["filename"]); + m_columns.append(s_columns["location"]); m_columns.append(s_columns["platform"]); m_columns.append(s_columns["size"]); @@ -114,6 +121,22 @@ VFile* LibraryModel::openVFile(const QModelIndex& index) const { return mLibraryOpenVFile(m_library->library, &entry); } +QString LibraryModel::filename(const QModelIndex& index) const { + mLibraryEntry entry; + if (!entryAt(index.row(), &entry)) { + return QString(); + } + return QString::fromUtf8(entry.filename); +} + +QString LibraryModel::location(const QModelIndex& index) const { + mLibraryEntry entry; + if (!entryAt(index.row(), &entry)) { + return QString(); + } + return QString::fromUtf8(entry.base); +} + QVariant LibraryModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); diff --git a/src/platform/qt/LibraryModel.h b/src/platform/qt/LibraryModel.h index dd017e014..08956d9bb 100644 --- a/src/platform/qt/LibraryModel.h +++ b/src/platform/qt/LibraryModel.h @@ -29,6 +29,8 @@ public: bool entryAt(int row, mLibraryEntry* out) const; VFile* openVFile(const QModelIndex& index) const; + QString filename(const QModelIndex& index) const; + QString location(const QModelIndex& index) const; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; diff --git a/src/platform/qt/LibraryView.cpp b/src/platform/qt/LibraryView.cpp index 1741d9c34..fb46ba7c2 100644 --- a/src/platform/qt/LibraryView.cpp +++ b/src/platform/qt/LibraryView.cpp @@ -29,6 +29,10 @@ void LibraryView::setDirectory(const QString& filename) { m_model.constrainBase(filename); } +void LibraryView::addDirectory(const QString& filename) { + m_model.loadDirectory(filename); +} + VFile* LibraryView::selectedVFile() const { QModelIndex index = m_ui.listing->selectionModel()->currentIndex(); if (!index.isValid()) { @@ -37,6 +41,14 @@ VFile* LibraryView::selectedVFile() const { return m_model.openVFile(index); } +QPair LibraryView::selectedPath() const { + QModelIndex index = m_ui.listing->selectionModel()->currentIndex(); + if (!index.isValid()) { + return qMakePair(QString(), QString()); + } + return qMakePair(m_model.filename(index), m_model.location(index)); +} + void LibraryView::resizeColumns() { m_ui.listing->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); } diff --git a/src/platform/qt/LibraryView.h b/src/platform/qt/LibraryView.h index c0591973a..d56ea7ada 100644 --- a/src/platform/qt/LibraryView.h +++ b/src/platform/qt/LibraryView.h @@ -21,6 +21,7 @@ public: LibraryView(QWidget* parent = nullptr); VFile* selectedVFile() const; + QPair selectedPath() const; signals: void doneLoading(); @@ -28,6 +29,7 @@ signals: public slots: void setDirectory(const QString&); + void addDirectory(const QString&); private slots: void resizeColumns(); diff --git a/src/platform/qt/LibraryView.ui b/src/platform/qt/LibraryView.ui index 2d7d697ab..3cdaac550 100644 --- a/src/platform/qt/LibraryView.ui +++ b/src/platform/qt/LibraryView.ui @@ -13,7 +13,10 @@ Library - + + + 0 + @@ -34,6 +37,9 @@ false + + 20 + 0 diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 9cd49dcbb..09df40cbf 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -202,6 +202,7 @@ void SettingsView::updateConfig() { saveSetting("savestatePath", m_ui.savestatePath); saveSetting("screenshotPath", m_ui.screenshotPath); saveSetting("patchPath", m_ui.patchPath); + saveSetting("showLibrary", m_ui.showLibrary); if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1"); @@ -279,6 +280,7 @@ void SettingsView::reloadConfig() { loadSetting("savestatePath", m_ui.savestatePath); loadSetting("screenshotPath", m_ui.screenshotPath); loadSetting("patchPath", m_ui.patchPath); + loadSetting("showLibrary", m_ui.showLibrary); double fastForwardRatio = loadSetting("fastForwardRatio").toDouble(); if (fastForwardRatio <= 0) { diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index e5f7c5fcb..3957a29a2 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -45,6 +45,11 @@ Audio/Video + + + Interface + + Emulation @@ -72,7 +77,7 @@ - 0 + 1 @@ -384,6 +389,65 @@ + + + + + + Allow opposing input directions + + + + + + + Suspend screensaver + + + true + + + + + + + Pause when inactive + + + + + + + Library: + + + + + + + Show when no game open + + + + + + + Qt::Horizontal + + + + + + + false + + + Clear cache + + + + + @@ -478,38 +542,14 @@ - - - - Allow opposing input directions - - - - - - - Suspend screensaver - - - true - - - - - - - Pause when inactive - - - - + Idle loops: - + @@ -528,21 +568,21 @@ - + Qt::Horizontal - + Savestate extra data: - + Screenshot @@ -552,7 +592,7 @@ - + Save data @@ -562,7 +602,7 @@ - + Cheat codes @@ -572,14 +612,14 @@ - + Load extra data: - + Screenshot @@ -589,20 +629,27 @@ - + Save data - + Cheat codes + + + + Qt::Horizontal + + + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index ebd8c4f9b..cd7bfee78 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -103,7 +103,30 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) m_savedScale = multiplier.toInt(); i = m_savedScale; } -#ifdef M_CORE_GBA +#ifdef USE_SQLITE3 + m_libraryView = new LibraryView(this); + ConfigOption* showLibrary = m_config->addOption("showLibrary"); + showLibrary->connect([this](const QVariant& value) { + if (value.toBool()) { + if (m_controller->isLoaded()) { + m_screenWidget->layout()->addWidget(m_libraryView); + } else { + attachWidget(m_libraryView); + } + } else { + detachWidget(m_libraryView); + } + }, this); + m_config->updateOption("showLibrary"); + + connect(m_libraryView, &LibraryView::accepted, [this]() { + VFile* output = m_libraryView->selectedVFile(); + QPair path = m_libraryView->selectedPath(); + if (output) { + m_controller->loadGame(output, path.first, path.second); + } + }); +#elif defined(M_CORE_GBA) m_screenWidget->setSizeHint(QSize(VIDEO_HORIZONTAL_PIXELS * i, VIDEO_VERTICAL_PIXELS * i)); #endif m_screenWidget->setPixmap(m_logo); @@ -367,16 +390,25 @@ void Window::selectROMInArchive() { return; } ArchiveInspector* archiveInspector = new ArchiveInspector(filename); - connect(archiveInspector, &QDialog::accepted, [this, archiveInspector, filename]() { + connect(archiveInspector, &QDialog::accepted, [this, archiveInspector]() { VFile* output = archiveInspector->selectedVFile(); + QPair path = archiveInspector->selectedPath(); if (output) { - m_controller->loadGame(output, filename); + m_controller->loadGame(output, path.second, path.first); } archiveInspector->close(); }); archiveInspector->setAttribute(Qt::WA_DeleteOnClose); archiveInspector->show(); } + +void Window::addDirToLibrary() { + QString filename = GBAApp::app()->getOpenDirectoryName(this, tr("Select folder")); + if (filename.isEmpty()) { + return; + } + m_libraryView->addDirectory(filename); +} #endif void Window::replaceROM() { @@ -886,8 +918,12 @@ void Window::setupMenu(QMenuBar* menubar) { installEventFilter(m_shortcutController); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), "loadROM"); +#ifdef USE_SQLITE3 addControlledAction(fileMenu, fileMenu->addAction(tr("Load ROM in archive..."), this, SLOT(selectROMInArchive())), "loadROMInArchive"); + addControlledAction(fileMenu, fileMenu->addAction(tr("Add folder to library..."), this, SLOT(addDirToLibrary())), + "addDirToLibrary"); +#endif QAction* loadTemporarySave = new QAction(tr("Load temporary save..."), fileMenu); connect(loadTemporarySave, &QAction::triggered, [this]() { this->selectSave(true); }); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 58886a6f6..37ef814df 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -28,6 +28,7 @@ class Display; class GameController; class GDBController; class GIFView; +class LibraryView; class LogView; class ShaderSelector; class ShortcutController; @@ -59,6 +60,7 @@ public slots: void selectROM(); #ifdef USE_SQLITE3 void selectROMInArchive(); + void addDirToLibrary(); #endif void selectSave(bool temporary); void selectPatch(); @@ -191,6 +193,10 @@ private: #ifdef USE_GDB_STUB GDBController* m_gdbController; #endif + +#ifdef USE_SQLITE3 + LibraryView* m_libraryView; +#endif }; class WindowBackground : public QLabel {