mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
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 <Nmzik@mail.ru>
This commit is contained in:
@@ -44,6 +44,28 @@
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QToolButton" name="refresh_button">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>36</width>
|
||||
<height>36</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="global_settings_button">
|
||||
<property name="minimumSize">
|
||||
@@ -245,6 +267,14 @@
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
<action name="refresh_action">
|
||||
<property name="text">
|
||||
<string>Refresh game list</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Rescan game folders for new games</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="#000" d="M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 1 0 7.73 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
|
||||
|
After Width: | Height: | Size: 232 B |
@@ -3,6 +3,7 @@
|
||||
<file alias="edit-configuration.svg">icons/edit-configuration.svg</file>
|
||||
<file alias="global-settings.svg">icons/global-settings.svg</file>
|
||||
<file alias="input-mapping.svg">icons/input-mapping.svg</file>
|
||||
<file alias="refresh.svg">icons/refresh.svg</file>
|
||||
<file alias="remove-configuration.svg">icons/remove-configuration.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/styles">
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <QAbstractItemModel>
|
||||
#include <QAbstractItemView>
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QCoreApplication>
|
||||
#include <QCursor>
|
||||
@@ -26,11 +27,13 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QKeySequence>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
@@ -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<QString, ConfigurationItem*> running_items;
|
||||
for (int index = m_ui->cfgs_list->topLevelItemCount() - 1; index >= 0; index--) {
|
||||
auto* item = static_cast<ConfigurationItem*>(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<QString> 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<ConfigurationItem*>(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<ConfigurationItem>(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 =
|
||||
|
||||
Reference in New Issue
Block a user