Qt: PCSX2 UI fix backports

This commit is contained in:
Stenzek 2023-09-17 00:22:39 +10:00
parent 546f73e36a
commit dd1a00674d
11 changed files with 147 additions and 73 deletions

View file

@ -32,7 +32,8 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
m_ui.enableXInputSource->setEnabled(false); m_ui.enableXInputSource->setEnabled(false);
m_ui.enableRawInput->setEnabled(false); m_ui.enableRawInput->setEnabled(false);
#endif #endif
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping", false); ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping",
false);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.multitapMode, "ControllerPorts", "MultitapMode", SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.multitapMode, "ControllerPorts", "MultitapMode",
&Settings::ParseMultitapModeName, &Settings::GetMultitapModeName, &Settings::ParseMultitapModeName, &Settings::GetMultitapModeName,
Settings::DEFAULT_MULTITAP_MODE); Settings::DEFAULT_MULTITAP_MODE);

View file

@ -114,11 +114,11 @@ const char* GameListModel::getColumnName(Column col)
return s_column_names[static_cast<int>(col)]; return s_column_names[static_cast<int>(col)];
} }
GameListModel::GameListModel(QObject* parent /* = nullptr */) GameListModel::GameListModel(float cover_scale, bool show_cover_titles, QObject* parent /* = nullptr */)
: QAbstractTableModel(parent), m_cover_pixmap_cache(MIN_COVER_CACHE_SIZE) : QAbstractTableModel(parent), m_show_titles_for_covers(show_cover_titles)
{ {
loadCommonImages(); loadCommonImages();
setCoverScale(1.0f); setCoverScale(cover_scale);
setColumnDisplayNames(); setColumnDisplayNames();
} }
GameListModel::~GameListModel() = default; GameListModel::~GameListModel() = default;
@ -132,6 +132,8 @@ void GameListModel::setCoverScale(float scale)
m_cover_scale = scale; m_cover_scale = scale;
m_loading_pixmap = QPixmap(getCoverArtWidth(), getCoverArtHeight()); m_loading_pixmap = QPixmap(getCoverArtWidth(), getCoverArtHeight());
m_loading_pixmap.fill(QColor(0, 0, 0, 0)); m_loading_pixmap.fill(QColor(0, 0, 0, 0));
emit coverScaleChanged();
} }
void GameListModel::refreshCovers() void GameListModel::refreshCovers()
@ -150,9 +152,9 @@ void GameListModel::updateCacheSize(int width, int height)
m_cover_pixmap_cache.SetMaxCapacity(static_cast<int>(std::max(num_columns * num_rows, MIN_COVER_CACHE_SIZE))); m_cover_pixmap_cache.SetMaxCapacity(static_cast<int>(std::max(num_columns * num_rows, MIN_COVER_CACHE_SIZE)));
} }
void GameListModel::reloadCommonImages() void GameListModel::reloadThemeSpecificImages()
{ {
loadCommonImages(); loadThemeSpecificImages();
refresh(); refresh();
} }
@ -585,17 +587,24 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r
} }
} }
void GameListModel::loadCommonImages() void GameListModel::loadThemeSpecificImages()
{ {
for (u32 i = 0; i < static_cast<u32>(GameList::EntryType::Count); i++) for (u32 i = 0; i < static_cast<u32>(GameList::EntryType::Count); i++)
m_type_pixmaps[i] = QtUtils::GetIconForEntryType(static_cast<GameList::EntryType>(i)).pixmap(QSize(24, 24)); m_type_pixmaps[i] = QtUtils::GetIconForEntryType(static_cast<GameList::EntryType>(i)).pixmap(QSize(24, 24));
for (u32 i = 0; i < static_cast<u32>(DiscRegion::Count); i++) for (u32 i = 0; i < static_cast<u32>(DiscRegion::Count); i++)
m_region_pixmaps[i] = QtUtils::GetIconForRegion(static_cast<DiscRegion>(i)).pixmap(42, 30); m_region_pixmaps[i] = QtUtils::GetIconForRegion(static_cast<DiscRegion>(i)).pixmap(42, 30);
}
void GameListModel::loadCommonImages()
{
loadThemeSpecificImages();
for (int i = 0; i < static_cast<int>(GameDatabase::CompatibilityRating::Count); i++) for (int i = 0; i < static_cast<int>(GameDatabase::CompatibilityRating::Count); i++)
{
m_compatibility_pixmaps[i] = m_compatibility_pixmaps[i] =
QtUtils::GetIconForCompatibility(static_cast<GameDatabase::CompatibilityRating>(i)).pixmap(96, 24); QtUtils::GetIconForCompatibility(static_cast<GameDatabase::CompatibilityRating>(i)).pixmap(96, 24);
}
m_placeholder_pixmap.load(QStringLiteral("%1/images/cover-placeholder.png").arg(QtHost::GetResourcesBasePath())); m_placeholder_pixmap.load(QStringLiteral("%1/images/cover-placeholder.png").arg(QtHost::GetResourcesBasePath()));
} }

View file

@ -45,7 +45,7 @@ public:
static std::optional<Column> getColumnIdForName(std::string_view name); static std::optional<Column> getColumnIdForName(std::string_view name);
static const char* getColumnName(Column col); static const char* getColumnName(Column col);
GameListModel(QObject* parent = nullptr); GameListModel(float cover_scale, bool show_cover_titles, QObject* parent = nullptr);
~GameListModel(); ~GameListModel();
int rowCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@ -56,6 +56,7 @@ public:
ALWAYS_INLINE const QString& getColumnDisplayName(int column) { return m_column_display_names[column]; } ALWAYS_INLINE const QString& getColumnDisplayName(int column) { return m_column_display_names[column]; }
void refresh(); void refresh();
void reloadThemeSpecificImages();
bool titlesLessThan(int left_row, int right_row) const; bool titlesLessThan(int left_row, int right_row) const;
@ -71,10 +72,13 @@ public:
int getCoverArtSpacing() const; int getCoverArtSpacing() const;
void refreshCovers(); void refreshCovers();
void updateCacheSize(int width, int height); void updateCacheSize(int width, int height);
void reloadCommonImages();
Q_SIGNALS:
void coverScaleChanged();
private: private:
void loadCommonImages(); void loadCommonImages();
void loadThemeSpecificImages();
void setColumnDisplayNames(); void setColumnDisplayNames();
void loadOrGenerateCover(const GameList::Entry* ge); void loadOrGenerateCover(const GameList::Entry* ge);
void invalidateCoverForPath(const std::string& path); void invalidateCoverForPath(const std::string& path);

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "gamelistwidget.h" #include "gamelistwidget.h"
@ -20,6 +20,7 @@
#include <QtGui/QWheelEvent> #include <QtGui/QWheelEvent>
#include <QtWidgets/QHeaderView> #include <QtWidgets/QHeaderView>
#include <QtWidgets/QMenu> #include <QtWidgets/QMenu>
#include <QtWidgets/QScrollBar>
static constexpr float MIN_SCALE = 0.1f; static constexpr float MIN_SCALE = 0.1f;
static constexpr float MAX_SCALE = 2.0f; static constexpr float MAX_SCALE = 2.0f;
@ -91,9 +92,9 @@ GameListWidget::~GameListWidget() = default;
void GameListWidget::initialize() void GameListWidget::initialize()
{ {
m_model = new GameListModel(this); const float cover_scale = Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f);
m_model->setCoverScale(Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f)); const bool show_cover_titles = Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true);
m_model->setShowCoverTitles(Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true)); m_model = new GameListModel(cover_scale, show_cover_titles, this);
m_model->updateCacheSize(width(), height()); m_model->updateCacheSize(width(), height());
m_sort_model = new GameListSortModel(m_model); m_sort_model = new GameListSortModel(m_model);
@ -162,17 +163,16 @@ void GameListWidget::initialize()
m_list_view = new GameListGridListView(m_ui.stack); m_list_view = new GameListGridListView(m_ui.stack);
m_list_view->setModel(m_sort_model); m_list_view->setModel(m_sort_model);
m_list_view->setModelColumn(GameListModel::Column_Cover); m_list_view->setModelColumn(GameListModel::Column_Cover);
m_list_view->setSelectionMode(QAbstractItemView::ExtendedSelection); m_list_view->setSelectionMode(QAbstractItemView::SingleSelection);
m_list_view->setViewMode(QListView::IconMode); m_list_view->setViewMode(QListView::IconMode);
m_list_view->setResizeMode(QListView::Adjust); m_list_view->setResizeMode(QListView::Adjust);
m_list_view->setUniformItemSizes(true); m_list_view->setUniformItemSizes(true);
m_list_view->setItemAlignment(Qt::AlignHCenter); m_list_view->setItemAlignment(Qt::AlignHCenter);
m_list_view->setContextMenuPolicy(Qt::CustomContextMenu); m_list_view->setContextMenuPolicy(Qt::CustomContextMenu);
m_list_view->setFrameStyle(QFrame::NoFrame); m_list_view->setFrameStyle(QFrame::NoFrame);
m_list_view->setSpacing(m_model->getCoverArtSpacing());
m_list_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel); m_list_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
m_list_view->verticalScrollBar()->setSingleStep(15);
updateListFont(); onCoverScaleChanged();
connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this, connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
&GameListWidget::onSelectionModelCurrentChanged); &GameListWidget::onSelectionModelCurrentChanged);
@ -180,6 +180,7 @@ void GameListWidget::initialize()
connect(m_list_view, &GameListGridListView::zoomOut, this, &GameListWidget::gridZoomOut); connect(m_list_view, &GameListGridListView::zoomOut, this, &GameListWidget::gridZoomOut);
connect(m_list_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated); connect(m_list_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated);
connect(m_list_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested); connect(m_list_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested);
connect(m_model, &GameListModel::coverScaleChanged, this, &GameListWidget::onCoverScaleChanged);
m_ui.stack->insertWidget(1, m_list_view); m_ui.stack->insertWidget(1, m_list_view);
@ -237,6 +238,11 @@ void GameListWidget::cancelRefresh()
AssertMsg(!m_refresh_thread, "Game list thread should be unreferenced by now"); AssertMsg(!m_refresh_thread, "Game list thread should be unreferenced by now");
} }
void GameListWidget::reloadThemeSpecificImages()
{
m_model->reloadThemeSpecificImages();
}
void GameListWidget::onRefreshProgress(const QString& status, int current, int total) void GameListWidget::onRefreshProgress(const QString& status, int current, int total)
{ {
// switch away from the placeholder while we scan, in case we find anything // switch away from the placeholder while we scan, in case we find anything
@ -326,14 +332,23 @@ void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder)
saveTableViewColumnSortSettings(); saveTableViewColumnSortSettings();
} }
void GameListWidget::onCoverScaleChanged()
{
m_model->updateCacheSize(width(), height());
m_list_view->setSpacing(m_model->getCoverArtSpacing());
QFont font;
font.setPointSizeF(16.0f * m_model->getCoverScale());
m_list_view->setFont(font);
}
void GameListWidget::listZoom(float delta) void GameListWidget::listZoom(float delta)
{ {
const float new_scale = std::clamp(m_model->getCoverScale() + delta, MIN_SCALE, MAX_SCALE); const float new_scale = std::clamp(m_model->getCoverScale() + delta, MIN_SCALE, MAX_SCALE);
Host::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale); Host::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale);
Host::CommitBaseSettingChanges(); Host::CommitBaseSettingChanges();
m_model->setCoverScale(new_scale); m_model->setCoverScale(new_scale);
m_model->updateCacheSize(width(), height());
updateListFont();
updateToolbar(); updateToolbar();
m_model->refresh(); m_model->refresh();
@ -356,8 +371,6 @@ void GameListWidget::gridIntScale(int int_scale)
Host::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale); Host::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale);
Host::CommitBaseSettingChanges(); Host::CommitBaseSettingChanges();
m_model->setCoverScale(new_scale); m_model->setCoverScale(new_scale);
m_model->updateCacheSize(width(), height());
updateListFont();
updateToolbar(); updateToolbar();
m_model->refresh(); m_model->refresh();
@ -416,13 +429,6 @@ void GameListWidget::setShowCoverTitles(bool enabled)
emit layoutChange(); emit layoutChange();
} }
void GameListWidget::updateListFont()
{
QFont font;
font.setPointSizeF(16.0f * m_model->getCoverScale());
m_list_view->setFont(font);
}
void GameListWidget::updateToolbar() void GameListWidget::updateToolbar()
{ {
const bool grid_view = isShowingGameGrid(); const bool grid_view = isShowingGameGrid();
@ -473,11 +479,6 @@ void GameListWidget::resizeTableViewColumnsToFit()
}); });
} }
void GameListWidget::reloadCommonImages()
{
m_model->reloadCommonImages();
}
static TinyString getColumnVisibilitySettingsKeyName(int column) static TinyString getColumnVisibilitySettingsKeyName(int column)
{ {
return TinyString::FromFormat("Show%s", GameListModel::getColumnName(static_cast<GameListModel::Column>(column))); return TinyString::FromFormat("Show%s", GameListModel::getColumnName(static_cast<GameListModel::Column>(column)));

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once #pragma once
@ -43,10 +43,10 @@ public:
void initialize(); void initialize();
void resizeTableViewColumnsToFit(); void resizeTableViewColumnsToFit();
void reloadCommonImages();
void refresh(bool invalidate_cache); void refresh(bool invalidate_cache);
void cancelRefresh(); void cancelRefresh();
void reloadThemeSpecificImages();
bool isShowingGameList() const; bool isShowingGameList() const;
bool isShowingGameGrid() const; bool isShowingGameGrid() const;
@ -76,6 +76,7 @@ private Q_SLOTS:
void onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder); void onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder);
void onListViewItemActivated(const QModelIndex& index); void onListViewItemActivated(const QModelIndex& index);
void onListViewContextMenuRequested(const QPoint& point); void onListViewContextMenuRequested(const QPoint& point);
void onCoverScaleChanged();
public Q_SLOTS: public Q_SLOTS:
void showGameList(); void showGameList();
@ -96,7 +97,6 @@ private:
void loadTableViewColumnSortSettings(); void loadTableViewColumnSortSettings();
void saveTableViewColumnSortSettings(); void saveTableViewColumnSortSettings();
void listZoom(float delta); void listZoom(float delta);
void updateListFont();
void updateToolbar(); void updateToolbar();
Ui::GameListWidget m_ui; Ui::GameListWidget m_ui;

View file

@ -135,7 +135,7 @@ void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds)
{ {
m_value_ranges.clear(); m_value_ranges.clear();
m_new_bindings.clear(); m_new_bindings.clear();
m_mouse_mapping_enabled = InputBindingWidget::isMouseMappingEnabled(); m_mouse_mapping_enabled = InputBindingWidget::isMouseMappingEnabled(m_sif);
m_input_listen_start_position = QCursor::pos(); m_input_listen_start_position = QCursor::pos();
m_input_listen_timer = new QTimer(this); m_input_listen_timer = new QTimer(this);
m_input_listen_timer->setSingleShot(false); m_input_listen_timer->setSingleShot(false);

View file

@ -42,9 +42,10 @@ InputBindingWidget::~InputBindingWidget()
Q_ASSERT(!isListeningForInput()); Q_ASSERT(!isListeningForInput());
} }
bool InputBindingWidget::isMouseMappingEnabled() bool InputBindingWidget::isMouseMappingEnabled(SettingsInterface* sif)
{ {
return Host::GetBaseBoolSettingValue("UI", "EnableMouseMapping", false); return sif ? sif->GetBoolValue("UI", "EnableMouseMapping", false) :
Host::GetBaseBoolSettingValue("UI", "EnableMouseMapping", false);
} }
void InputBindingWidget::initialize(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name, void InputBindingWidget::initialize(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name,
@ -287,7 +288,7 @@ void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
{ {
m_value_ranges.clear(); m_value_ranges.clear();
m_new_bindings.clear(); m_new_bindings.clear();
m_mouse_mapping_enabled = isMouseMappingEnabled(); m_mouse_mapping_enabled = isMouseMappingEnabled(m_sif);
m_input_listen_start_position = QCursor::pos(); m_input_listen_start_position = QCursor::pos();
m_input_listen_timer = new QTimer(this); m_input_listen_timer = new QTimer(this);
m_input_listen_timer->setSingleShot(false); m_input_listen_timer->setSingleShot(false);

View file

@ -22,7 +22,7 @@ public:
std::string section_name, std::string key_name); std::string section_name, std::string key_name);
~InputBindingWidget(); ~InputBindingWidget();
static bool isMouseMappingEnabled(); static bool isMouseMappingEnabled(SettingsInterface* sif);
void initialize(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name, void initialize(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name,
std::string key_name); std::string key_name);

View file

@ -1417,13 +1417,25 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry) void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
{ {
QString filename = QFileDialog::getOpenFileName(this, tr("Select Cover Image"), QString(), const QString filename = QDir::toNativeSeparators(QFileDialog::getOpenFileName(
tr("All Cover Image Types (*.jpg *.jpeg *.png)")); this, tr("Select Cover Image"), QString(), tr("All Cover Image Types (*.jpg *.jpeg *.png *.webp)")));
if (filename.isEmpty()) if (filename.isEmpty())
return; return;
if (!GameList::GetCoverImagePathForEntry(entry).empty()) const QString old_filename = QString::fromStdString(GameList::GetCoverImagePathForEntry(entry));
const QString new_filename =
QString::fromStdString(GameList::GetNewCoverImagePathForEntry(entry, filename.toUtf8().constData(), false));
if (new_filename.isEmpty())
return;
if (!old_filename.isEmpty())
{ {
if (QFileInfo(old_filename) == QFileInfo(filename))
{
QMessageBox::critical(this, tr("Copy Error"), tr("You must select a different file to the current cover image."));
return;
}
if (QMessageBox::question(this, tr("Cover Already Exists"), if (QMessageBox::question(this, tr("Cover Already Exists"),
tr("A cover image for this game already exists, do you wish to replace it?"), tr("A cover image for this game already exists, do you wish to replace it?"),
QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
@ -1432,23 +1444,21 @@ void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
} }
} }
QString new_filename =
QString::fromStdString(GameList::GetNewCoverImagePathForEntry(entry, filename.toStdString().c_str(), false));
if (new_filename.isEmpty())
return;
if (QFile::exists(new_filename) && !QFile::remove(new_filename)) if (QFile::exists(new_filename) && !QFile::remove(new_filename))
{ {
QMessageBox::critical(this, tr("Copy Error"), tr("Failed to remove existing cover '%1'").arg(new_filename)); QMessageBox::critical(this, tr("Copy Error"), tr("Failed to remove existing cover '%1'").arg(new_filename));
return; return;
} }
if (!QFile::copy(filename, new_filename)) if (!QFile::copy(filename, new_filename))
{ {
QMessageBox::critical(this, tr("Copy Error"), tr("Failed to copy '%1' to '%2'").arg(filename).arg(new_filename)); QMessageBox::critical(this, tr("Copy Error"), tr("Failed to copy '%1' to '%2'").arg(filename).arg(new_filename));
return; return;
} }
if (!old_filename.isEmpty() && old_filename != new_filename && !QFile::remove(old_filename))
{
QMessageBox::critical(this, tr("Copy Error"), tr("Failed to remove '%1'").arg(old_filename));
return;
}
m_game_list_widget->refreshGridCovers(); m_game_list_widget->refreshGridCovers();
} }
@ -2068,7 +2078,12 @@ void MainWindow::updateTheme()
{ {
updateApplicationTheme(); updateApplicationTheme();
updateMenuSelectedTheme(); updateMenuSelectedTheme();
m_game_list_widget->reloadCommonImages(); reloadThemeSpecificImages();
}
void MainWindow::reloadThemeSpecificImages()
{
m_game_list_widget->reloadThemeSpecificImages();
} }
void MainWindow::setStyleFromSettings() void MainWindow::setStyleFromSettings()
@ -2405,6 +2420,12 @@ void MainWindow::changeEvent(QEvent* event)
g_emu_thread->redrawDisplayWindow(); g_emu_thread->redrawDisplayWindow();
} }
if (event->type() == QEvent::StyleChange)
{
setIconThemeFromSettings();
reloadThemeSpecificImages();
}
QMainWindow::changeEvent(event); QMainWindow::changeEvent(event);
} }

View file

@ -239,6 +239,7 @@ private:
void clearGameListEntryPlayTime(const GameList::Entry* entry); void clearGameListEntryPlayTime(const GameList::Entry* entry);
void setTheme(const QString& theme); void setTheme(const QString& theme);
void updateTheme(); void updateTheme();
void reloadThemeSpecificImages();
void recreate(); void recreate();
void registerForDeviceNotifications(); void registerForDeviceNotifications();

View file

@ -11,6 +11,7 @@
#include "core/settings.h" #include "core/settings.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/file_system.h"
#include "common/path.h" #include "common/path.h"
#include <QtCore/QtCore> #include <QtCore/QtCore>
@ -22,6 +23,7 @@
#include <QtWidgets/QFileDialog> #include <QtWidgets/QFileDialog>
#include <QtWidgets/QLineEdit> #include <QtWidgets/QLineEdit>
#include <QtWidgets/QMenu> #include <QtWidgets/QMenu>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QSlider> #include <QtWidgets/QSlider>
#include <QtWidgets/QSpinBox> #include <QtWidgets/QSpinBox>
#include <optional> #include <optional>
@ -1030,22 +1032,20 @@ static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget,
} }
} }
template<typename WidgetType> static inline void BindWidgetToFolderSetting(SettingsInterface* sif, QLineEdit* widget, QAbstractButton* browse_button,
static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget, QAbstractButton* browse_button, QAbstractButton* open_button, QAbstractButton* reset_button,
QAbstractButton* open_button, QAbstractButton* reset_button, std::string section, std::string section, std::string key, std::string default_value,
std::string key, std::string default_value) bool use_relative = true)
{ {
using Accessor = SettingAccessor<WidgetType>; using Accessor = SettingAccessor<QLineEdit>;
std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str())); std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str()));
if (current_path.empty()) if (current_path.empty())
current_path = default_value; current_path = default_value;
else if (!Path::IsAbsolute(current_path)) else if (use_relative && !Path::IsAbsolute(current_path))
current_path = Path::Combine(EmuFolders::DataRoot, current_path); current_path = Path::Canonicalize(Path::Combine(EmuFolders::DataRoot, current_path));
const QString value(QString::fromStdString(current_path)); const QString value(QString::fromStdString(current_path));
Accessor::setStringValue(widget, value); Accessor::setStringValue(widget, value);
// if we're doing per-game settings, disable the widget, we only allow folder changes in the base config // if we're doing per-game settings, disable the widget, we only allow folder changes in the base config
if (sif) if (sif)
{ {
@ -1057,32 +1057,65 @@ static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget
return; return;
} }
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() { auto value_changed = [widget, section = std::move(section), key = std::move(key), default_value, use_relative]() {
const std::string new_value(Accessor::getStringValue(widget).toStdString()); const std::string new_value(widget->text().toStdString());
if (!new_value.empty()) if (!new_value.empty())
{ {
std::string relative_path(Path::MakeRelative(new_value, EmuFolders::DataRoot)); if (FileSystem::DirectoryExists(new_value.c_str()) ||
Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), relative_path.c_str()); QMessageBox::question(
QtUtils::GetRootWidget(widget), qApp->translate("SettingWidgetBinder", "Confirm Folder"),
qApp
->translate(
"SettingWidgetBinder",
"The chosen directory does not currently exist:\n\n%1\n\nDo you want to create this directory?")
.arg(QString::fromStdString(new_value)),
QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
{
if (use_relative)
{
const std::string relative_path(Path::MakeRelative(new_value, EmuFolders::DataRoot));
Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), relative_path.c_str());
}
else
{
Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), new_value.c_str());
}
Host::CommitBaseSettingChanges();
g_emu_thread->updateEmuFolders();
return;
}
} }
else else
{ {
Host::DeleteBaseSettingValue(section.c_str(), key.c_str()); QMessageBox::critical(QtUtils::GetRootWidget(widget), qApp->translate("SettingWidgetBinder", "Error"),
qApp->translate("SettingWidgetBinder", "Folder path cannot be empty."));
} }
Host::CommitBaseSettingChanges(); // reset to old value
g_emu_thread->updateEmuFolders(); std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str()));
}); if (current_path.empty())
current_path = default_value;
else if (use_relative && !Path::IsAbsolute(current_path))
current_path = Path::Canonicalize(Path::Combine(EmuFolders::DataRoot, current_path));
widget->setText(QString::fromStdString(current_path));
};
if (browse_button) if (browse_button)
{ {
QObject::connect(browse_button, &QAbstractButton::clicked, browse_button, [widget, key]() { QObject::connect(browse_button, &QAbstractButton::clicked, browse_button, [widget, key, value_changed]() {
const QString path(QDir::toNativeSeparators(QFileDialog::getExistingDirectory( const QString path(QDir::toNativeSeparators(QFileDialog::getExistingDirectory(
QtUtils::GetRootWidget(widget), QtUtils::GetRootWidget(widget),
// It seems that the latter half should show the types of folders that can be selected within Settings ->
// Folders, but right now it's broken. It would be best for localization purposes to duplicate this into
// multiple lines, each per type of folder.
qApp->translate("SettingWidgetBinder", "Select folder for %1").arg(QString::fromStdString(key))))); qApp->translate("SettingWidgetBinder", "Select folder for %1").arg(QString::fromStdString(key)))));
if (path.isEmpty()) if (path.isEmpty())
return; return;
Accessor::setStringValue(widget, path); widget->setText(path);
value_changed();
}); });
} }
if (open_button) if (open_button)
@ -1096,9 +1129,12 @@ static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget
if (reset_button) if (reset_button)
{ {
QObject::connect(reset_button, &QAbstractButton::clicked, reset_button, QObject::connect(reset_button, &QAbstractButton::clicked, reset_button,
[widget, default_value = std::move(default_value)]() { [widget, default_value = std::move(default_value), value_changed]() {
Accessor::setStringValue(widget, QString::fromStdString(default_value)); widget->setText(QString::fromStdString(default_value));
value_changed();
}); });
} }
widget->connect(widget, &QLineEdit::editingFinished, widget, std::move(value_changed));
} }
} // namespace SettingWidgetBinder } // namespace SettingWidgetBinder