From ada05140848761c07c4fd7f028daf41b6fe9d96a Mon Sep 17 00:00:00 2001 From: Saadi Date: Sun, 20 Sep 2026 17:44:40 +0100 Subject: [PATCH] launcher: add a game list refresh action (#683) * launcher: add a game list refresh action Dropping new games into an already-configured game folder was only picked up after restarting the launcher or re-saving the global settings dialog. Add a refresh toolbar button, an F5 shortcut, and a "Refresh game list" context-menu entry, all triggering a rescan of the game folders. * launcher: do not rescan the game list while a game is running Clearing cfgs_list destroys the running ConfigurationItem: the running state is lost and the replacement items allow editing or deleting a game whose process is still active. Skip the rescan while any item is running (CodeRabbit review finding for the refresh action). * launcher: preserve game state when refreshing the list Share one refresh action across the toolbar, keyboard shortcut, and context menu. Retain running items during rescans, update their settings for the next launch, and preserve selection, filtering, and sort order. Commit active inline editors before rebuilding rows to avoid losing comments or accessing destroyed configuration data. Tie editor callbacks to item lifetime and guard the context-menu item reference. Validation: launcher build and startup check passed; 32 Qt interaction checks passed. * launcher: remove refresh from the game context menu --------- Co-authored-by: nmzik --- .../forms/configuration_list_widget.ui | 30 +++++++ src/launcher/icons/refresh.svg | 1 + src/launcher/resources.qrc | 1 + src/launcher/src/configurationListWidget.cpp | 85 ++++++++++++++++--- 4 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 src/launcher/icons/refresh.svg 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 =