// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: GPL-3.0+ #include #include #include #include #include #include #include #include #include #include #include "GameListSettingsWidget.h" #include "MainWindow.h" #include "QtHost.h" #include "QtUtils.h" #include "SettingWidgetBinder.h" GameListSettingsWidget::GameListSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QWidget(parent) { SettingsInterface* sif = dialog->getSettingsInterface(); m_ui.setupUi(this); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.preferEnglishGameList, "UI", "PreferEnglishGameList", false); connect(m_ui.preferEnglishGameList, &QCheckBox::checkStateChanged, this, [this]{ emit preferEnglishGameListChanged(); }); dialog->registerWidgetHelp(m_ui.preferEnglishGameList, tr("Prefer English Titles"), tr("Unchecked"), tr("For games with both a title in the game's native language and one in English, prefer the English title.")); m_ui.searchDirectoryList->setSelectionMode(QAbstractItemView::SingleSelection); m_ui.searchDirectoryList->setSelectionBehavior(QAbstractItemView::SelectRows); m_ui.searchDirectoryList->setAlternatingRowColors(true); m_ui.searchDirectoryList->setShowGrid(false); m_ui.searchDirectoryList->horizontalHeader()->setHighlightSections(false); m_ui.searchDirectoryList->verticalHeader()->hide(); m_ui.searchDirectoryList->setCurrentIndex({}); m_ui.searchDirectoryList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); connect(m_ui.searchDirectoryList, &QTableWidget::customContextMenuRequested, this, &GameListSettingsWidget::onDirectoryListContextMenuRequested); connect(m_ui.searchDirectoryList, &QTableWidget::itemSelectionChanged, this, &GameListSettingsWidget::onDirectoryListSelectionChanged); connect(m_ui.addSearchDirectoryButton, &QPushButton::clicked, this, &GameListSettingsWidget::onAddSearchDirectoryButtonClicked); connect(m_ui.removeSearchDirectoryButton, &QPushButton::clicked, this, &GameListSettingsWidget::onRemoveSearchDirectoryButtonClicked); connect(m_ui.addExcludedFile, &QPushButton::clicked, this, &GameListSettingsWidget::onAddExcludedFileButtonClicked); connect(m_ui.addExcludedPath, &QPushButton::clicked, this, &GameListSettingsWidget::onAddExcludedPathButtonClicked); connect(m_ui.removeExcludedPath, &QPushButton::clicked, this, &GameListSettingsWidget::onRemoveExcludedPathButtonClicked); connect(m_ui.excludedPaths, &QListWidget::itemSelectionChanged, this, &GameListSettingsWidget::onExcludedPathsSelectionChanged); connect(m_ui.rescanAllGames, &QPushButton::clicked, this, &GameListSettingsWidget::onRescanAllGamesClicked); connect(m_ui.scanForNewGames, &QPushButton::clicked, this, &GameListSettingsWidget::onScanForNewGamesClicked); refreshDirectoryList(); refreshExclusionList(); } GameListSettingsWidget::~GameListSettingsWidget() = default; bool GameListSettingsWidget::addExcludedPath(const std::string& path) { if (!Host::AddBaseValueToStringList("GameList", "ExcludedPaths", path.c_str())) return false; Host::CommitBaseSettingChanges(); m_ui.excludedPaths->addItem(QString::fromStdString(path)); g_main_window->refreshGameList(false); return true; } void GameListSettingsWidget::refreshExclusionList() { m_ui.excludedPaths->clear(); const std::vector paths(Host::GetBaseStringListSetting("GameList", "ExcludedPaths")); for (const std::string& path : paths) m_ui.excludedPaths->addItem(QString::fromStdString(path)); m_ui.removeExcludedPath->setEnabled(false); } bool GameListSettingsWidget::event(QEvent* event) { bool res = QWidget::event(event); switch (event->type()) { case QEvent::LayoutRequest: case QEvent::Resize: QtUtils::ResizeColumnsForTableView(m_ui.searchDirectoryList, {-1, 100}); break; default: break; } return res; } void GameListSettingsWidget::addPathToTable(const std::string& path, bool recursive) { const int row = m_ui.searchDirectoryList->rowCount(); m_ui.searchDirectoryList->insertRow(row); QTableWidgetItem* item = new QTableWidgetItem(); item->setText(QString::fromStdString(path)); item->setFlags(item->flags() & ~(Qt::ItemIsEditable)); m_ui.searchDirectoryList->setItem(row, 0, item); QCheckBox* cb = new QCheckBox(m_ui.searchDirectoryList); m_ui.searchDirectoryList->setCellWidget(row, 1, cb); cb->setChecked(recursive); connect(cb, &QCheckBox::checkStateChanged, this, [item](Qt::CheckState state) { const std::string path(item->text().toStdString()); if (state == Qt::Checked) { Host::RemoveBaseValueFromStringList("GameList", "Paths", path.c_str()); Host::AddBaseValueToStringList("GameList", "RecursivePaths", path.c_str()); } else { Host::RemoveBaseValueFromStringList("GameList", "RecursivePaths", path.c_str()); Host::AddBaseValueToStringList("GameList", "Paths", path.c_str()); } Host::CommitBaseSettingChanges(); }); } void GameListSettingsWidget::refreshDirectoryList() { QSignalBlocker sb(m_ui.searchDirectoryList); while (m_ui.searchDirectoryList->rowCount() > 0) m_ui.searchDirectoryList->removeRow(0); std::vector path_list = Host::GetBaseStringListSetting("GameList", "Paths"); for (const std::string& entry : path_list) addPathToTable(entry, false); path_list = Host::GetBaseStringListSetting("GameList", "RecursivePaths"); for (const std::string& entry : path_list) addPathToTable(entry, true); m_ui.searchDirectoryList->sortByColumn(0, Qt::AscendingOrder); m_ui.removeSearchDirectoryButton->setEnabled(false); } void GameListSettingsWidget::addSearchDirectory(const QString& path, bool recursive) { const std::string spath(path.toStdString()); Host::RemoveBaseValueFromStringList("GameList", recursive ? "Paths" : "RecursivePaths", spath.c_str()); Host::AddBaseValueToStringList("GameList", recursive ? "RecursivePaths" : "Paths", spath.c_str()); Host::CommitBaseSettingChanges(); refreshDirectoryList(); g_main_window->refreshGameList(false); } void GameListSettingsWidget::removeSearchDirectory(const QString& path) { const std::string spath(path.toStdString()); if (!Host::RemoveBaseValueFromStringList("GameList", "Paths", spath.c_str()) && !Host::RemoveBaseValueFromStringList("GameList", "RecursivePaths", spath.c_str())) { return; } Host::CommitBaseSettingChanges(); refreshDirectoryList(); g_main_window->refreshGameList(false); } void GameListSettingsWidget::onDirectoryListContextMenuRequested(const QPoint& point) { QModelIndexList selection = m_ui.searchDirectoryList->selectionModel()->selectedIndexes(); if (selection.size() < 1) return; const int row = selection[0].row(); QMenu menu; menu.addAction(tr("Remove"), [this]() { onRemoveSearchDirectoryButtonClicked(); }); menu.addSeparator(); menu.addAction(tr("Open Directory..."), [this, row]() { QtUtils::OpenURL(this, QUrl::fromLocalFile(m_ui.searchDirectoryList->item(row, 0)->text())); }); menu.exec(m_ui.searchDirectoryList->mapToGlobal(point)); } void GameListSettingsWidget::onDirectoryListSelectionChanged() { m_ui.removeSearchDirectoryButton->setEnabled(m_ui.searchDirectoryList->selectionModel()->hasSelection()); } void GameListSettingsWidget::addSearchDirectory(QWidget* parent_widget) { QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(parent_widget, tr("Select Search Directory"))); if (dir.isEmpty()) return; QMessageBox::StandardButton selection = QMessageBox::question(this, tr("Scan Recursively?"), tr("Would you like to scan the directory \"%1\" recursively?\n\nScanning recursively takes " "more time, but will identify files in subdirectories.") .arg(dir), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); if (selection == QMessageBox::Cancel) return; const bool recursive = (selection == QMessageBox::Yes); addSearchDirectory(dir, recursive); } void GameListSettingsWidget::onAddSearchDirectoryButtonClicked() { addSearchDirectory(this); } void GameListSettingsWidget::onRemoveSearchDirectoryButtonClicked() { const int row = m_ui.searchDirectoryList->currentRow(); QTableWidgetItem* item = (row >= 0) ? m_ui.searchDirectoryList->takeItem(row, 0) : nullptr; if (!item) return; removeSearchDirectory(item->text()); delete item; } void GameListSettingsWidget::onAddExcludedFileButtonClicked() { QString path = QDir::toNativeSeparators(QFileDialog::getOpenFileName(QtUtils::GetRootWidget(this), tr("Select File"))); if (path.isEmpty()) return; addExcludedPath(path.toStdString()); } void GameListSettingsWidget::onAddExcludedPathButtonClicked() { QString path = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(QtUtils::GetRootWidget(this), tr("Select Directory"))); if (path.isEmpty()) return; addExcludedPath(path.toStdString()); } void GameListSettingsWidget::onRemoveExcludedPathButtonClicked() { const int row = m_ui.excludedPaths->currentRow(); QListWidgetItem* item = (row >= 0) ? m_ui.excludedPaths->takeItem(row) : 0; if (!item) return; if (Host::RemoveBaseValueFromStringList("GameList", "ExcludedPaths", item->text().toUtf8().constData())) Host::CommitBaseSettingChanges(); delete item; g_main_window->refreshGameList(false); } void GameListSettingsWidget::onExcludedPathsSelectionChanged() { m_ui.removeExcludedPath->setEnabled(!m_ui.excludedPaths->selectedItems().isEmpty()); } void GameListSettingsWidget::onRescanAllGamesClicked() { g_main_window->refreshGameList(true); } void GameListSettingsWidget::onScanForNewGamesClicked() { g_main_window->refreshGameList(false); }