Qt: Begin library view
jump to
@@ -23,3 +23,7 @@
VFile* ArchiveInspector::selectedVFile() const { return m_ui.archiveView->selectedVFile(); } + +QPair<QString, QString> ArchiveInspector::selectedPath() const { + return m_ui.archiveView->selectedPath(); +}
@@ -19,6 +19,7 @@ public:
ArchiveInspector(const QString& filename, QWidget* parent = nullptr); VFile* selectedVFile() const; + QPair<QString, QString> selectedPath() const; private: Ui::ArchiveInspector m_ui;
@@ -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);
@@ -71,6 +71,7 @@ ConfigOption* addOption(const char* key);
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;
@@ -145,7 +145,11 @@ if (controller->m_multiplayer) {
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(); }
@@ -113,7 +113,7 @@ void postLog(int level, int category, const QString& log);
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 @@
bool m_gameOpen; QString m_fname; + QString m_fsub; VFile* m_vf; QString m_bios; bool m_useBios;
@@ -23,7 +23,7 @@ if (s_columns.empty()) {
s_columns["filename"] = { tr("Filename"), [](const mLibraryEntry& e) -> QString { - return e.filename; + return QString::fromUtf8(e.filename); } }; s_columns["size"] = {@@ -60,6 +60,12 @@ return tr("?");
} } }; + 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 @@ }
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"]);@@ -112,6 +119,22 @@ if (!entryAt(index.row(), &entry)) {
return nullptr; } 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 {
@@ -29,6 +29,8 @@ virtual ~LibraryModel();
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;
@@ -29,12 +29,24 @@ m_model.loadDirectory(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()) { return nullptr; } return m_model.openVFile(index); +} + +QPair<QString, QString> 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() {
@@ -21,6 +21,7 @@ public:
LibraryView(QWidget* parent = nullptr); VFile* selectedVFile() const; + QPair<QString, QString> selectedPath() const; signals: void doneLoading();@@ -28,6 +29,7 @@ void accepted();
public slots: void setDirectory(const QString&); + void addDirectory(const QString&); private slots: void resizeColumns();
@@ -13,7 +13,10 @@ </property>
<property name="windowTitle"> <string>Library</string> </property> - <layout class="QGridLayout" name="gridLayout"> + <layout class="QGridLayout"> + <property name="margin"> + <number>0</number> + </property> <item row="0" column="0"> <widget class="QTableView" name="listing"> <property name="editTriggers">@@ -33,6 +36,9 @@ <bool>true</bool>
</attribute> <attribute name="verticalHeaderVisible"> <bool>false</bool> + </attribute> + <attribute name="verticalHeaderDefaultSectionSize"> + <number>20</number> </attribute> <attribute name="verticalHeaderMinimumSectionSize"> <number>0</number>
@@ -202,6 +202,7 @@ saveSetting("savegamePath", m_ui.savegamePath);
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 @@ loadSetting("savegamePath", m_ui.savegamePath);
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) {
@@ -47,6 +47,11 @@ </property>
</item> <item> <property name="text"> + <string>Interface</string> + </property> + </item> + <item> + <property name="text"> <string>Emulation</string> </property> </item>@@ -72,7 +77,7 @@ </item>
<item row="1" column="1"> <widget class="QStackedWidget" name="stackedWidget"> <property name="currentIndex"> - <number>0</number> + <number>1</number> </property> <widget class="QWidget" name="av"> <layout class="QFormLayout" name="formLayout">@@ -384,6 +389,65 @@ </widget>
</item> </layout> </widget> + <widget class="QWidget" name="interface_2"> + <layout class="QFormLayout" name="formLayout_4"> + <item row="3" column="1"> + <widget class="QCheckBox" name="allowOpposingDirections"> + <property name="text"> + <string>Allow opposing input directions</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QCheckBox" name="suspendScreensaver"> + <property name="text"> + <string>Suspend screensaver</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="pauseOnFocusLost"> + <property name="text"> + <string>Pause when inactive</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Library:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="showLibrary"> + <property name="text"> + <string>Show when no game open</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="Line" name="line_8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="clearCache"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Clear cache</string> + </property> + </widget> + </item> + </layout> + </widget> <widget class="QWidget" name="emulation"> <layout class="QFormLayout" name="formLayout_2"> <property name="fieldGrowthPolicy">@@ -478,38 +542,14 @@ <enum>Qt::Horizontal</enum>
</property> </widget> </item> - <item row="5" column="1"> - <widget class="QCheckBox" name="allowOpposingDirections"> - <property name="text"> - <string>Allow opposing input directions</string> - </property> - </widget> - </item> - <item row="6" column="1"> - <widget class="QCheckBox" name="suspendScreensaver"> - <property name="text"> - <string>Suspend screensaver</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="7" column="1"> - <widget class="QCheckBox" name="pauseOnFocusLost"> - <property name="text"> - <string>Pause when inactive</string> - </property> - </widget> - </item> - <item row="8" column="0"> + <item row="5" column="0"> <widget class="QLabel" name="label_15"> <property name="text"> <string>Idle loops:</string> </property> </widget> </item> - <item row="8" column="1"> + <item row="5" column="1"> <widget class="QComboBox" name="idleOptimization"> <item> <property name="text">@@ -528,21 +568,21 @@ </property>
</item> </widget> </item> - <item row="9" column="0" colspan="2"> + <item row="6" column="0" colspan="2"> <widget class="Line" name="line_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> - <item row="10" column="0"> + <item row="7" column="0"> <widget class="QLabel" name="label_24"> <property name="text"> <string>Savestate extra data:</string> </property> </widget> </item> - <item row="10" column="1"> + <item row="7" column="1"> <widget class="QCheckBox" name="saveStateScreenshot"> <property name="text"> <string>Screenshot</string>@@ -552,7 +592,7 @@ <bool>true</bool>
</property> </widget> </item> - <item row="11" column="1"> + <item row="8" column="1"> <widget class="QCheckBox" name="saveStateSave"> <property name="text"> <string>Save data</string>@@ -562,7 +602,7 @@ <bool>true</bool>
</property> </widget> </item> - <item row="12" column="1"> + <item row="9" column="1"> <widget class="QCheckBox" name="saveStateCheats"> <property name="text"> <string>Cheat codes</string>@@ -572,14 +612,14 @@ <bool>true</bool>
</property> </widget> </item> - <item row="13" column="0"> + <item row="11" column="0"> <widget class="QLabel" name="label_25"> <property name="text"> <string>Load extra data:</string> </property> </widget> </item> - <item row="13" column="1"> + <item row="11" column="1"> <widget class="QCheckBox" name="loadStateScreenshot"> <property name="text"> <string>Screenshot</string>@@ -589,17 +629,24 @@ <bool>true</bool>
</property> </widget> </item> - <item row="14" column="1"> + <item row="12" column="1"> <widget class="QCheckBox" name="loadStateSave"> <property name="text"> <string>Save data</string> </property> </widget> </item> - <item row="15" column="1"> + <item row="13" column="1"> <widget class="QCheckBox" name="loadStateCheats"> <property name="text"> <string>Cheat codes</string> + </property> + </widget> + </item> + <item row="10" column="0" colspan="2"> + <widget class="Line" name="line_9"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> </widget> </item>
@@ -103,7 +103,30 @@ if (!multiplier.isNull()) {
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<QString, QString> 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,15 +390,24 @@ if (filename.isEmpty()) {
return; } ArchiveInspector* archiveInspector = new ArchiveInspector(filename); - connect(archiveInspector, &QDialog::accepted, [this, archiveInspector, filename]() { + connect(archiveInspector, &QDialog::accepted, [this, archiveInspector]() { VFile* output = archiveInspector->selectedVFile(); + QPair<QString, QString> 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@@ -886,8 +918,12 @@ m_shortcutController->addMenu(fileMenu);
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); });
@@ -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();@@ -190,6 +192,10 @@ #endif
#ifdef USE_GDB_STUB GDBController* m_gdbController; +#endif + +#ifdef USE_SQLITE3 + LibraryView* m_libraryView; #endif };