launcher: add Import game button

Add a prominent Import button to ConfigurationListWidget that lets
users select a game folder (containing eboot.bin) or a folder
containing multiple games via QFileDialog. Handles:

- Single game folder selected: adds its parent to game_dirs (Scan
  searches subdirectories, so parent is needed)
- Folder with multiple games: adds the selected folder
- Deduplication via PathKey/NormalizeGameDirectories, writes
  settings, rescans, and selects the imported game if found
- Shows info/warning dialogs when no games are found

UI: QToolButton with SP_DirOpenIcon and text Import beside the
existing toolbar, styled with a blue background for visibility.
Logic in ConfigurationListWidget::import_game().
This commit is contained in:
nacimoualla
2026-08-29 13:08:31 +01:00
parent 6155ce825c
commit 2f670ef95c
3 changed files with 140 additions and 29 deletions
+57 -29
View File
@@ -119,36 +119,64 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QToolButton" name="delete_button"> <widget class="QToolButton" name="delete_button">
<property name="minimumSize"> <property name="minimumSize">
<size>
<width>36</width>
<height>36</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Delete configuration&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size> <size>
<width>32</width> <width>36</width>
<height>32</height> <height>36</height>
</size> </size>
</property> </property>
<property name="autoRaise"> <property name="toolTip">
<bool>true</bool> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Delete configuration&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> <property name="styleSheet">
</item> <string notr="true"/>
<item> </property>
<spacer name="horizontalSpacer"> <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="import_button">
<property name="minimumSize">
<size>
<width>96</width>
<height>36</height>
</size>
</property>
<property name="toolTip">
<string>Import game folder (containing eboot.bin)</string>
</property>
<property name="text">
<string>Import</string>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
@@ -63,6 +63,7 @@ protected slots:
void edit_global_settings(); void edit_global_settings();
void edit_input_mapping(); void edit_input_mapping();
void run_configuration(); void run_configuration();
void import_game();
void list_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); void list_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
void list_itemDoubleClicked(QTreeWidgetItem* witem, int column); void list_itemDoubleClicked(QTreeWidgetItem* witem, int column);
void show_context_menu(const QPoint& pos); void show_context_menu(const QPoint& pos);
@@ -21,6 +21,7 @@
#include <QDialog> #include <QDialog>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QFileDialog>
#include <QFileInfo> #include <QFileInfo>
#include <QHeaderView> #include <QHeaderView>
#include <QIcon> #include <QIcon>
@@ -234,6 +235,11 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
m_ui->input_mapping_button->setToolTip(tr("Edit global input mapping")); m_ui->input_mapping_button->setToolTip(tr("Edit global input mapping"));
m_ui->edit_button->setIcon(QIcon(QStringLiteral(":/icons/edit-configuration.svg"))); m_ui->edit_button->setIcon(QIcon(QStringLiteral(":/icons/edit-configuration.svg")));
m_ui->delete_button->setIcon(QIcon(QStringLiteral(":/icons/remove-configuration.svg"))); m_ui->delete_button->setIcon(QIcon(QStringLiteral(":/icons/remove-configuration.svg")));
m_ui->import_button->setIcon(style()->standardIcon(QStyle::SP_DirOpenIcon));
m_ui->import_button->setStyleSheet(QStringLiteral(
"QToolButton { background: rgba(32,118,210,90); border: 1px solid rgba(80,160,255,120); "
"border-radius: 5px; padding: 3px 8px; color: #eef4ff; }"
"QToolButton:hover { background: rgba(32,118,210,140); border-color: rgba(80,160,255,180); }"));
m_ui->delete_button->setEnabled(false); m_ui->delete_button->setEnabled(false);
m_ui->edit_button->setEnabled(false); m_ui->edit_button->setEnabled(false);
@@ -259,6 +265,8 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
&ConfigurationListWidget::edit_configuration); &ConfigurationListWidget::edit_configuration);
connect(m_ui->delete_button, &QToolButton::clicked, this, connect(m_ui->delete_button, &QToolButton::clicked, this,
&ConfigurationListWidget::delete_configuartion); &ConfigurationListWidget::delete_configuartion);
connect(m_ui->import_button, &QToolButton::clicked, this,
&ConfigurationListWidget::import_game);
connect(m_ui->cfgs_list, &QTreeWidget::currentItemChanged, this, connect(m_ui->cfgs_list, &QTreeWidget::currentItemChanged, this,
&ConfigurationListWidget::list_currentItemChanged); &ConfigurationListWidget::list_currentItemChanged);
connect(m_ui->cfgs_list, &QTreeWidget::itemDoubleClicked, this, connect(m_ui->cfgs_list, &QTreeWidget::itemDoubleClicked, this,
@@ -731,6 +739,80 @@ void ConfigurationListWidget::edit_input_mapping() {
} }
} }
void ConfigurationListWidget::import_game() {
const QString selected = QFileDialog::getExistingDirectory(
this, tr("Select game folder (containing eboot.bin) or folder containing games"),
QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (selected.isEmpty()) {
return;
}
QDir dir(selected);
QString dir_to_add;
QString expected_game_path;
if (dir.exists(QStringLiteral("eboot.bin"))) {
// Selected a single game folder — add its parent so Scan finds it
// (Scan only searches subdirectories of game_dirs, not the dirs themselves)
dir_to_add = QDir::cleanPath(dir.absolutePath() + QStringLiteral("/.."));
expected_game_path = QDir::cleanPath(dir.absolutePath());
} else {
dir_to_add = QDir::cleanPath(dir.absolutePath());
}
const QString normalized = NormalizeGameDirectory(dir_to_add);
if (normalized.isEmpty()) {
return;
}
const QString key = PathKey(normalized);
bool already_exists = false;
for (const auto& existing: m_game_dirs) {
if (PathKey(existing) == key) {
already_exists = true;
break;
}
}
if (!already_exists) {
m_game_dirs.append(normalized);
m_game_dirs = NormalizeGameDirectories(m_game_dirs);
WriteSettings();
}
const int before_count = m_ui->cfgs_list->topLevelItemCount();
ScanGameDirectory();
if (!expected_game_path.isEmpty()) {
const QString expected_key = PathKey(expected_game_path);
for (int i = 0; i < m_ui->cfgs_list->topLevelItemCount(); ++i) {
auto* item = static_cast<ConfigurationItem*>(m_ui->cfgs_list->topLevelItem(i));
if (PathKey(item->GetInfo().game_path) == expected_key) {
m_ui->cfgs_list->setCurrentItem(item);
SelectItem(item);
break;
}
}
}
if (m_ui->cfgs_list->topLevelItemCount() == 0) {
QMessageBox::information(this, tr("Import game"),
tr("No games found in the selected folder.\n\n"
"Make sure the folder contains a game with eboot.bin "
"or subfolders with games."));
} else if (!expected_game_path.isEmpty() && m_ui->cfgs_list->topLevelItemCount() == before_count) {
// Single game import didn't appear
const int after_count = m_ui->cfgs_list->topLevelItemCount();
if (after_count == before_count) {
QMessageBox::warning(this, tr("Import game"),
tr("The selected game folder was added, but no game was detected.\n\n"
"Expected eboot.bin at: %1")
.arg(expected_game_path));
}
}
}
void ConfigurationListWidget::ClearCustomSettings(ConfigurationItem* item) { void ConfigurationListWidget::ClearCustomSettings(ConfigurationItem* item) {
delete m_custom_infos.take(item->GetInfo().game_path); delete m_custom_infos.take(item->GetInfo().game_path);
} }