diff --git a/src/launcher/forms/configuration_list_widget.ui b/src/launcher/forms/configuration_list_widget.ui
index da469e489..46c193c53 100644
--- a/src/launcher/forms/configuration_list_widget.ui
+++ b/src/launcher/forms/configuration_list_widget.ui
@@ -44,6 +44,28 @@
4
+ -
+
+
+
+ 36
+ 36
+
+
+
+
+
+
+
+ 32
+ 32
+
+
+
+ true
+
+
+
-
@@ -245,6 +267,14 @@
+
+
+ Refresh game list
+
+
+ Rescan game folders for new games
+
+
diff --git a/src/launcher/icons/refresh.svg b/src/launcher/icons/refresh.svg
new file mode 100644
index 000000000..33f894d22
--- /dev/null
+++ b/src/launcher/icons/refresh.svg
@@ -0,0 +1 @@
+
diff --git a/src/launcher/resources.qrc b/src/launcher/resources.qrc
index 7ddfa3f0b..b0b056259 100644
--- a/src/launcher/resources.qrc
+++ b/src/launcher/resources.qrc
@@ -3,6 +3,7 @@
icons/edit-configuration.svg
icons/global-settings.svg
icons/input-mapping.svg
+ icons/refresh.svg
icons/remove-configuration.svg
diff --git a/src/launcher/src/configurationListWidget.cpp b/src/launcher/src/configurationListWidget.cpp
index 088a37a80..408212a95 100644
--- a/src/launcher/src/configurationListWidget.cpp
+++ b/src/launcher/src/configurationListWidget.cpp
@@ -13,6 +13,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -26,11 +27,13 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -201,6 +204,10 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->setupUi(this);
ConfigureGameList(m_ui);
+ m_ui->refresh_action->setShortcuts(QKeySequence::Refresh);
+ m_ui->refresh_button->setDefaultAction(m_ui->refresh_action);
+ addAction(m_ui->refresh_action);
+
UpdateToolbarIcons();
m_ui->global_settings_button->setToolTip(tr("Edit global settings and game folders"));
m_ui->input_mapping_button->setToolTip(tr("Edit global input mapping"));
@@ -221,7 +228,10 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->cfgs_list->setColumnWidth(GAME_PATH_COLUMN, 320);
m_ui->cfgs_list->setColumnWidth(GAME_STATUS_COLUMN, 150);
m_ui->cfgs_list->setColumnWidth(GAME_COMMENT_COLUMN, 240);
+ m_ui->cfgs_list->sortItems(GAME_NAME_COLUMN, Qt::AscendingOrder);
+ connect(m_ui->refresh_action, &QAction::triggered, this,
+ &ConfigurationListWidget::ScanGameDirectory);
connect(m_ui->global_settings_button, &QToolButton::clicked, this,
&ConfigurationListWidget::edit_global_settings);
connect(m_ui->input_mapping_button, &QToolButton::clicked, this,
@@ -276,6 +286,8 @@ void ConfigurationListWidget::UpdateToolbarIcons() {
button->setIcon(QIcon(pixmap));
};
+ set_icon(m_ui->refresh_button, QStringLiteral(":/icons/refresh.svg"));
+ m_ui->refresh_action->setIcon(m_ui->refresh_button->icon());
set_icon(m_ui->global_settings_button, QStringLiteral(":/icons/global-settings.svg"));
set_icon(m_ui->input_mapping_button, QStringLiteral(":/icons/input-mapping.svg"));
set_icon(m_ui->edit_button, QStringLiteral(":/icons/edit-configuration.svg"));
@@ -564,11 +576,32 @@ bool ConfigurationListWidget::HasValidGameDirectory() const {
}
void ConfigurationListWidget::ScanGameDirectory() {
+ // Commit an active inline editor before destroying its row or replacing its data.
+ if (auto* focused = QApplication::focusWidget();
+ focused != nullptr && m_ui->cfgs_list->isAncestorOf(focused)) {
+ m_ui->cfgs_list->setFocus();
+ }
+
+ const auto selected_path =
+ m_selected_item != nullptr ? m_selected_item->GetInfo().game_path : QString();
+ const bool sorting_enabled = m_ui->cfgs_list->isSortingEnabled();
+ const int sort_column = m_ui->cfgs_list->sortColumn();
+ const auto sort_order = m_ui->cfgs_list->header()->sortIndicatorOrder();
+ const QSignalBlocker blocker(m_ui->cfgs_list);
+ m_ui->cfgs_list->setSortingEnabled(false);
m_selected_item = nullptr;
- m_ui->cfgs_list->clear();
- m_ui->cfgs_list->SetBackgroundImage({});
- m_ui->edit_button->setEnabled(false);
- m_ui->delete_button->setEnabled(false);
+
+ // MainDialog tracks the running item, so keep its identity even if its
+ // directory is no longer present. Other rows can be rebuilt from disk.
+ QMap running_items;
+ for (int index = m_ui->cfgs_list->topLevelItemCount() - 1; index >= 0; index--) {
+ auto* item = static_cast(m_ui->cfgs_list->topLevelItem(index));
+ if (item->IsRunning()) {
+ running_items.insert(PathKey(item->GetInfo().game_path), item);
+ } else {
+ delete item;
+ }
+ }
const QString eboot_name = QStringLiteral("eboot.bin");
QSet found_games;
@@ -618,10 +651,21 @@ void ConfigurationListWidget::ScanGameDirectory() {
info->game_comment = compatibility->comment;
}
+ if (auto* item = running_items.value(game_key); item != nullptr) {
+ // The process already has its own configuration copy. Refresh the
+ // row's settings for the next launch without losing its running state.
+ const QSignalBlocker status_blocker(item->GetStatusCombo());
+ item->GetInfo().CopyFrom(*info);
+ item->Update();
+ item->SetCompatibilityEditable(m_compatibility->IsLocal() &&
+ !item->GetInfo().title_id.trimmed().isEmpty());
+ continue;
+ }
+
auto* item = new ConfigurationItem(std::move(info), m_ui->cfgs_list);
item->SetCompatibilityEditable(m_compatibility->IsLocal() &&
!item->GetInfo().title_id.trimmed().isEmpty());
- connect(item->GetStatusCombo(), &QComboBox::currentIndexChanged, this,
+ connect(item->GetStatusCombo(), &QComboBox::currentIndexChanged, item,
[this, item](int /*index*/) {
if (!m_compatibility->IsLocal()) {
return;
@@ -638,7 +682,7 @@ void ConfigurationListWidget::ScanGameDirectory() {
m_ui->cfgs_list->setCurrentItem(item);
SelectItem(item);
});
- connect(item->GetCommentEdit(), &QLineEdit::editingFinished, this, [this, item]() {
+ connect(item->GetCommentEdit(), &QLineEdit::editingFinished, item, [this, item]() {
if (!m_compatibility->IsLocal()) {
return;
}
@@ -664,8 +708,22 @@ void ConfigurationListWidget::ScanGameDirectory() {
}
}
- m_ui->cfgs_list->sortItems(GAME_NAME_COLUMN, Qt::AscendingOrder);
+ m_ui->cfgs_list->setSortingEnabled(sorting_enabled);
+ if (sorting_enabled) {
+ m_ui->cfgs_list->sortItems(sort_column, sort_order);
+ }
filter_configurations(m_ui->search_line_edit->text());
+
+ ConfigurationItem* selected_item = nullptr;
+ for (int index = 0; index < m_ui->cfgs_list->topLevelItemCount(); index++) {
+ auto* item = static_cast(m_ui->cfgs_list->topLevelItem(index));
+ if (!item->isHidden() && item->GetInfo().game_path == selected_path) {
+ selected_item = item;
+ break;
+ }
+ }
+ m_ui->cfgs_list->setCurrentItem(selected_item);
+ SelectItem(selected_item);
}
void ConfigurationListWidget::edit_configuration() {
@@ -871,12 +929,13 @@ void ConfigurationListWidget::show_context_menu(const QPoint& pos) {
connect(action_view_trophies, &QAction::triggered, this,
&ConfigurationListWidget::ViewTrophies);
QAction* action_patches = menu.addAction(tr("Cheats (experimental)..."));
- connect(action_patches, &QAction::triggered, this, [this, item]() {
- if (item != nullptr) {
- auto* dialog = new PatchesDialog(item->GetInfo(), this);
- dialog->show();
- }
- });
+ connect(action_patches, &QAction::triggered, this,
+ [this, item = QPointer(item)]() {
+ if (item != nullptr) {
+ auto* dialog = new PatchesDialog(item->GetInfo(), this);
+ dialog->show();
+ }
+ });
action_patches->setVisible(item != nullptr &&
PatchesDialog::IsSupportedTitleId(item->GetInfo().title_id));
QAction* action_remove_save_data =