gamelistsettingswidget.cpp (9844B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "gamelistsettingswidget.h" 5 #include "core/game_list.h" 6 #include "mainwindow.h" 7 #include "qthost.h" 8 #include "qtutils.h" 9 10 #include "common/assert.h" 11 #include "common/file_system.h" 12 #include "common/string_util.h" 13 14 #include <QtCore/QAbstractTableModel> 15 #include <QtCore/QDebug> 16 #include <QtCore/QSettings> 17 #include <QtCore/QUrl> 18 #include <QtWidgets/QCheckBox> 19 #include <QtWidgets/QFileDialog> 20 #include <QtWidgets/QHeaderView> 21 #include <QtWidgets/QMenu> 22 #include <QtWidgets/QMessageBox> 23 #include <algorithm> 24 25 GameListSettingsWidget::GameListSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QWidget(parent) 26 { 27 m_ui.setupUi(this); 28 29 m_ui.searchDirectoryList->setSelectionMode(QAbstractItemView::SingleSelection); 30 m_ui.searchDirectoryList->setSelectionBehavior(QAbstractItemView::SelectRows); 31 m_ui.searchDirectoryList->setAlternatingRowColors(true); 32 m_ui.searchDirectoryList->setShowGrid(false); 33 m_ui.searchDirectoryList->horizontalHeader()->setHighlightSections(false); 34 m_ui.searchDirectoryList->verticalHeader()->hide(); 35 m_ui.searchDirectoryList->setCurrentIndex({}); 36 m_ui.searchDirectoryList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); 37 38 connect(m_ui.searchDirectoryList, &QTableWidget::customContextMenuRequested, this, 39 &GameListSettingsWidget::onDirectoryListContextMenuRequested); 40 connect(m_ui.addSearchDirectoryButton, &QPushButton::clicked, this, 41 &GameListSettingsWidget::onAddSearchDirectoryButtonClicked); 42 connect(m_ui.removeSearchDirectoryButton, &QPushButton::clicked, this, 43 &GameListSettingsWidget::onRemoveSearchDirectoryButtonClicked); 44 connect(m_ui.searchDirectoryList, &QTableWidget::itemSelectionChanged, this, 45 &GameListSettingsWidget::onSearchDirectoriesSelectionChanged); 46 connect(m_ui.addExcludedFile, &QPushButton::clicked, this, &GameListSettingsWidget::onAddExcludedFileButtonClicked); 47 connect(m_ui.addExcludedFolder, &QPushButton::clicked, this, 48 &GameListSettingsWidget::onAddExcludedFolderButtonClicked); 49 connect(m_ui.removeExcludedPath, &QPushButton::clicked, this, 50 &GameListSettingsWidget::onRemoveExcludedPathButtonClicked); 51 connect(m_ui.excludedPaths, &QListWidget::itemSelectionChanged, this, 52 &GameListSettingsWidget::onExcludedPathsSelectionChanged); 53 connect(m_ui.rescanAllGames, &QPushButton::clicked, this, &GameListSettingsWidget::onRescanAllGamesClicked); 54 connect(m_ui.scanForNewGames, &QPushButton::clicked, this, &GameListSettingsWidget::onScanForNewGamesClicked); 55 56 refreshDirectoryList(); 57 refreshExclusionList(); 58 } 59 60 GameListSettingsWidget::~GameListSettingsWidget() = default; 61 62 bool GameListSettingsWidget::addExcludedPath(const std::string& path) 63 { 64 if (!Host::AddValueToBaseStringListSetting("GameList", "ExcludedPaths", path.c_str())) 65 return false; 66 67 Host::CommitBaseSettingChanges(); 68 m_ui.excludedPaths->addItem(QString::fromStdString(path)); 69 g_main_window->refreshGameList(false); 70 return true; 71 } 72 73 void GameListSettingsWidget::refreshExclusionList() 74 { 75 m_ui.excludedPaths->clear(); 76 77 const std::vector<std::string> paths(Host::GetBaseStringListSetting("GameList", "ExcludedPaths")); 78 for (const std::string& path : paths) 79 m_ui.excludedPaths->addItem(QString::fromStdString(path)); 80 81 m_ui.removeExcludedPath->setEnabled(false); 82 } 83 84 bool GameListSettingsWidget::event(QEvent* event) 85 { 86 bool res = QWidget::event(event); 87 88 switch (event->type()) 89 { 90 case QEvent::LayoutRequest: 91 case QEvent::Resize: 92 QtUtils::ResizeColumnsForTableView(m_ui.searchDirectoryList, {-1, 100}); 93 break; 94 95 default: 96 break; 97 } 98 99 return res; 100 } 101 102 void GameListSettingsWidget::addPathToTable(const std::string& path, bool recursive) 103 { 104 const int row = m_ui.searchDirectoryList->rowCount(); 105 m_ui.searchDirectoryList->insertRow(row); 106 107 QTableWidgetItem* item = new QTableWidgetItem(); 108 item->setText(QString::fromStdString(path)); 109 item->setFlags(item->flags() & ~(Qt::ItemIsEditable)); 110 m_ui.searchDirectoryList->setItem(row, 0, item); 111 112 QCheckBox* cb = new QCheckBox(m_ui.searchDirectoryList); 113 m_ui.searchDirectoryList->setCellWidget(row, 1, cb); 114 cb->setChecked(recursive); 115 116 connect(cb, &QCheckBox::checkStateChanged, this, [item](Qt::CheckState state) { 117 const std::string path(item->text().toStdString()); 118 if (state == Qt::Checked) 119 { 120 Host::RemoveValueFromBaseStringListSetting("GameList", "Paths", path.c_str()); 121 Host::AddValueToBaseStringListSetting("GameList", "RecursivePaths", path.c_str()); 122 } 123 else 124 { 125 Host::RemoveValueFromBaseStringListSetting("GameList", "RecursivePaths", path.c_str()); 126 Host::AddValueToBaseStringListSetting("GameList", "Paths", path.c_str()); 127 } 128 Host::CommitBaseSettingChanges(); 129 g_main_window->refreshGameList(false); 130 }); 131 } 132 133 void GameListSettingsWidget::refreshDirectoryList() 134 { 135 QSignalBlocker sb(m_ui.searchDirectoryList); 136 while (m_ui.searchDirectoryList->rowCount() > 0) 137 m_ui.searchDirectoryList->removeRow(0); 138 139 std::vector<std::string> path_list = Host::GetBaseStringListSetting("GameList", "Paths"); 140 for (const std::string& entry : path_list) 141 addPathToTable(entry, false); 142 143 path_list = Host::GetBaseStringListSetting("GameList", "RecursivePaths"); 144 for (const std::string& entry : path_list) 145 addPathToTable(entry, true); 146 147 m_ui.searchDirectoryList->sortByColumn(0, Qt::AscendingOrder); 148 m_ui.removeSearchDirectoryButton->setEnabled(false); 149 } 150 151 void GameListSettingsWidget::addSearchDirectory(const QString& path, bool recursive) 152 { 153 const std::string spath(path.toStdString()); 154 Host::RemoveValueFromBaseStringListSetting("GameList", recursive ? "Paths" : "RecursivePaths", spath.c_str()); 155 Host::AddValueToBaseStringListSetting("GameList", recursive ? "RecursivePaths" : "Paths", spath.c_str()); 156 Host::CommitBaseSettingChanges(); 157 refreshDirectoryList(); 158 g_main_window->refreshGameList(false); 159 } 160 161 void GameListSettingsWidget::removeSearchDirectory(const QString& path) 162 { 163 const std::string spath(path.toStdString()); 164 if (!Host::RemoveValueFromBaseStringListSetting("GameList", "Paths", spath.c_str()) && 165 !Host::RemoveValueFromBaseStringListSetting("GameList", "RecursivePaths", spath.c_str())) 166 { 167 return; 168 } 169 170 Host::CommitBaseSettingChanges(); 171 refreshDirectoryList(); 172 g_main_window->refreshGameList(false); 173 } 174 175 void GameListSettingsWidget::onDirectoryListContextMenuRequested(const QPoint& point) 176 { 177 QModelIndexList selection = m_ui.searchDirectoryList->selectionModel()->selectedIndexes(); 178 if (selection.size() < 1) 179 return; 180 181 const int row = selection[0].row(); 182 183 QMenu menu; 184 menu.addAction(tr("Remove"), [this]() { onRemoveSearchDirectoryButtonClicked(); }); 185 menu.addSeparator(); 186 menu.addAction(tr("Open Directory..."), [this, row]() { 187 QtUtils::OpenURL(this, QUrl::fromLocalFile(m_ui.searchDirectoryList->item(row, 0)->text())); 188 }); 189 menu.exec(m_ui.searchDirectoryList->mapToGlobal(point)); 190 } 191 192 void GameListSettingsWidget::addSearchDirectory(QWidget* parent_widget) 193 { 194 QString dir = 195 QDir::toNativeSeparators(QFileDialog::getExistingDirectory(parent_widget, tr("Select Search Directory"))); 196 197 if (dir.isEmpty()) 198 return; 199 200 QMessageBox::StandardButton selection = 201 QMessageBox::question(this, tr("Scan Recursively?"), 202 tr("Would you like to scan the directory \"%1\" recursively?\n\nScanning recursively takes " 203 "more time, but will identify files in subdirectories.") 204 .arg(dir), 205 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); 206 if (selection == QMessageBox::Cancel) 207 return; 208 209 const bool recursive = (selection == QMessageBox::Yes); 210 addSearchDirectory(dir, recursive); 211 } 212 213 void GameListSettingsWidget::onAddSearchDirectoryButtonClicked() 214 { 215 addSearchDirectory(this); 216 } 217 218 void GameListSettingsWidget::onRemoveSearchDirectoryButtonClicked() 219 { 220 const int row = m_ui.searchDirectoryList->currentRow(); 221 QTableWidgetItem* item = (row >= 0) ? m_ui.searchDirectoryList->takeItem(row, 0) : nullptr; 222 if (!item) 223 return; 224 225 removeSearchDirectory(item->text()); 226 delete item; 227 } 228 229 void GameListSettingsWidget::onSearchDirectoriesSelectionChanged() 230 { 231 m_ui.removeSearchDirectoryButton->setEnabled(m_ui.searchDirectoryList->selectionModel()->hasSelection()); 232 } 233 234 void GameListSettingsWidget::onAddExcludedFileButtonClicked() 235 { 236 QString path = 237 QDir::toNativeSeparators(QFileDialog::getOpenFileName(QtUtils::GetRootWidget(this), tr("Select Path"))); 238 if (path.isEmpty()) 239 return; 240 241 addExcludedPath(path.toStdString()); 242 } 243 244 void GameListSettingsWidget::onAddExcludedFolderButtonClicked() 245 { 246 QString path = 247 QDir::toNativeSeparators(QFileDialog::getExistingDirectory(QtUtils::GetRootWidget(this), tr("Select Directory"))); 248 if (path.isEmpty()) 249 return; 250 251 addExcludedPath(path.toStdString()); 252 } 253 254 void GameListSettingsWidget::onRemoveExcludedPathButtonClicked() 255 { 256 const int row = m_ui.excludedPaths->currentRow(); 257 QListWidgetItem* item = (row >= 0) ? m_ui.excludedPaths->takeItem(row) : 0; 258 if (!item) 259 return; 260 261 if (Host::RemoveValueFromBaseStringListSetting("GameList", "ExcludedPaths", item->text().toUtf8().constData())) 262 Host::CommitBaseSettingChanges(); 263 delete item; 264 265 g_main_window->refreshGameList(false); 266 } 267 268 void GameListSettingsWidget::onExcludedPathsSelectionChanged() 269 { 270 m_ui.removeExcludedPath->setEnabled(!m_ui.excludedPaths->selectedItems().isEmpty()); 271 } 272 273 void GameListSettingsWidget::onRescanAllGamesClicked() 274 { 275 g_main_window->refreshGameList(true); 276 } 277 278 void GameListSettingsWidget::onScanForNewGamesClicked() 279 { 280 g_main_window->refreshGameList(false); 281 }