From dd1a00674d0f43ae750fc5384bb06f942bf5832b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 17 Sep 2023 00:22:39 +1000 Subject: [PATCH] Qt: PCSX2 UI fix backports --- .../controllerglobalsettingswidget.cpp | 3 +- src/duckstation-qt/gamelistmodel.cpp | 21 +++-- src/duckstation-qt/gamelistmodel.h | 8 +- src/duckstation-qt/gamelistwidget.cpp | 49 ++++++------ src/duckstation-qt/gamelistwidget.h | 6 +- src/duckstation-qt/inputbindingdialog.cpp | 2 +- src/duckstation-qt/inputbindingwidgets.cpp | 7 +- src/duckstation-qt/inputbindingwidgets.h | 2 +- src/duckstation-qt/mainwindow.cpp | 43 +++++++--- src/duckstation-qt/mainwindow.h | 1 + src/duckstation-qt/settingwidgetbinder.h | 78 ++++++++++++++----- 11 files changed, 147 insertions(+), 73 deletions(-) diff --git a/src/duckstation-qt/controllerglobalsettingswidget.cpp b/src/duckstation-qt/controllerglobalsettingswidget.cpp index d2bc7c5a5..5d45a2f84 100644 --- a/src/duckstation-qt/controllerglobalsettingswidget.cpp +++ b/src/duckstation-qt/controllerglobalsettingswidget.cpp @@ -32,7 +32,8 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, m_ui.enableXInputSource->setEnabled(false); m_ui.enableRawInput->setEnabled(false); #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", &Settings::ParseMultitapModeName, &Settings::GetMultitapModeName, Settings::DEFAULT_MULTITAP_MODE); diff --git a/src/duckstation-qt/gamelistmodel.cpp b/src/duckstation-qt/gamelistmodel.cpp index d31b701ad..a87973257 100644 --- a/src/duckstation-qt/gamelistmodel.cpp +++ b/src/duckstation-qt/gamelistmodel.cpp @@ -114,11 +114,11 @@ const char* GameListModel::getColumnName(Column col) return s_column_names[static_cast(col)]; } -GameListModel::GameListModel(QObject* parent /* = nullptr */) - : QAbstractTableModel(parent), m_cover_pixmap_cache(MIN_COVER_CACHE_SIZE) +GameListModel::GameListModel(float cover_scale, bool show_cover_titles, QObject* parent /* = nullptr */) + : QAbstractTableModel(parent), m_show_titles_for_covers(show_cover_titles) { loadCommonImages(); - setCoverScale(1.0f); + setCoverScale(cover_scale); setColumnDisplayNames(); } GameListModel::~GameListModel() = default; @@ -132,6 +132,8 @@ void GameListModel::setCoverScale(float scale) m_cover_scale = scale; m_loading_pixmap = QPixmap(getCoverArtWidth(), getCoverArtHeight()); m_loading_pixmap.fill(QColor(0, 0, 0, 0)); + + emit coverScaleChanged(); } void GameListModel::refreshCovers() @@ -150,9 +152,9 @@ void GameListModel::updateCacheSize(int width, int height) m_cover_pixmap_cache.SetMaxCapacity(static_cast(std::max(num_columns * num_rows, MIN_COVER_CACHE_SIZE))); } -void GameListModel::reloadCommonImages() +void GameListModel::reloadThemeSpecificImages() { - loadCommonImages(); + loadThemeSpecificImages(); 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(GameList::EntryType::Count); i++) m_type_pixmaps[i] = QtUtils::GetIconForEntryType(static_cast(i)).pixmap(QSize(24, 24)); for (u32 i = 0; i < static_cast(DiscRegion::Count); i++) m_region_pixmaps[i] = QtUtils::GetIconForRegion(static_cast(i)).pixmap(42, 30); +} + +void GameListModel::loadCommonImages() +{ + loadThemeSpecificImages(); for (int i = 0; i < static_cast(GameDatabase::CompatibilityRating::Count); i++) + { m_compatibility_pixmaps[i] = QtUtils::GetIconForCompatibility(static_cast(i)).pixmap(96, 24); + } m_placeholder_pixmap.load(QStringLiteral("%1/images/cover-placeholder.png").arg(QtHost::GetResourcesBasePath())); } diff --git a/src/duckstation-qt/gamelistmodel.h b/src/duckstation-qt/gamelistmodel.h index 62c71a838..ac5ef1fd3 100644 --- a/src/duckstation-qt/gamelistmodel.h +++ b/src/duckstation-qt/gamelistmodel.h @@ -45,7 +45,7 @@ public: static std::optional getColumnIdForName(std::string_view name); static const char* getColumnName(Column col); - GameListModel(QObject* parent = nullptr); + GameListModel(float cover_scale, bool show_cover_titles, QObject* parent = nullptr); ~GameListModel(); 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]; } void refresh(); + void reloadThemeSpecificImages(); bool titlesLessThan(int left_row, int right_row) const; @@ -71,10 +72,13 @@ public: int getCoverArtSpacing() const; void refreshCovers(); void updateCacheSize(int width, int height); - void reloadCommonImages(); + +Q_SIGNALS: + void coverScaleChanged(); private: void loadCommonImages(); + void loadThemeSpecificImages(); void setColumnDisplayNames(); void loadOrGenerateCover(const GameList::Entry* ge); void invalidateCoverForPath(const std::string& path); diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp index d7c12fb90..fc1613cd8 100644 --- a/src/duckstation-qt/gamelistwidget.cpp +++ b/src/duckstation-qt/gamelistwidget.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "gamelistwidget.h" @@ -20,6 +20,7 @@ #include #include #include +#include static constexpr float MIN_SCALE = 0.1f; static constexpr float MAX_SCALE = 2.0f; @@ -91,9 +92,9 @@ GameListWidget::~GameListWidget() = default; void GameListWidget::initialize() { - m_model = new GameListModel(this); - m_model->setCoverScale(Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f)); - m_model->setShowCoverTitles(Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true)); + const float cover_scale = Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f); + const bool show_cover_titles = Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true); + m_model = new GameListModel(cover_scale, show_cover_titles, this); m_model->updateCacheSize(width(), height()); 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->setModel(m_sort_model); 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->setResizeMode(QListView::Adjust); m_list_view->setUniformItemSizes(true); m_list_view->setItemAlignment(Qt::AlignHCenter); m_list_view->setContextMenuPolicy(Qt::CustomContextMenu); m_list_view->setFrameStyle(QFrame::NoFrame); - m_list_view->setSpacing(m_model->getCoverArtSpacing()); m_list_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel); - - updateListFont(); + m_list_view->verticalScrollBar()->setSingleStep(15); + onCoverScaleChanged(); connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this, &GameListWidget::onSelectionModelCurrentChanged); @@ -180,6 +180,7 @@ void GameListWidget::initialize() connect(m_list_view, &GameListGridListView::zoomOut, this, &GameListWidget::gridZoomOut); connect(m_list_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated); 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); @@ -237,6 +238,11 @@ void GameListWidget::cancelRefresh() 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) { // switch away from the placeholder while we scan, in case we find anything @@ -326,14 +332,23 @@ void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder) 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) { const float new_scale = std::clamp(m_model->getCoverScale() + delta, MIN_SCALE, MAX_SCALE); Host::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale); Host::CommitBaseSettingChanges(); m_model->setCoverScale(new_scale); - m_model->updateCacheSize(width(), height()); - updateListFont(); updateToolbar(); m_model->refresh(); @@ -356,8 +371,6 @@ void GameListWidget::gridIntScale(int int_scale) Host::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale); Host::CommitBaseSettingChanges(); m_model->setCoverScale(new_scale); - m_model->updateCacheSize(width(), height()); - updateListFont(); updateToolbar(); m_model->refresh(); @@ -416,13 +429,6 @@ void GameListWidget::setShowCoverTitles(bool enabled) emit layoutChange(); } -void GameListWidget::updateListFont() -{ - QFont font; - font.setPointSizeF(16.0f * m_model->getCoverScale()); - m_list_view->setFont(font); -} - void GameListWidget::updateToolbar() { const bool grid_view = isShowingGameGrid(); @@ -473,11 +479,6 @@ void GameListWidget::resizeTableViewColumnsToFit() }); } -void GameListWidget::reloadCommonImages() -{ - m_model->reloadCommonImages(); -} - static TinyString getColumnVisibilitySettingsKeyName(int column) { return TinyString::FromFormat("Show%s", GameListModel::getColumnName(static_cast(column))); diff --git a/src/duckstation-qt/gamelistwidget.h b/src/duckstation-qt/gamelistwidget.h index 42339cb10..979331ee7 100644 --- a/src/duckstation-qt/gamelistwidget.h +++ b/src/duckstation-qt/gamelistwidget.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once @@ -43,10 +43,10 @@ public: void initialize(); void resizeTableViewColumnsToFit(); - void reloadCommonImages(); void refresh(bool invalidate_cache); void cancelRefresh(); + void reloadThemeSpecificImages(); bool isShowingGameList() const; bool isShowingGameGrid() const; @@ -76,6 +76,7 @@ private Q_SLOTS: void onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder); void onListViewItemActivated(const QModelIndex& index); void onListViewContextMenuRequested(const QPoint& point); + void onCoverScaleChanged(); public Q_SLOTS: void showGameList(); @@ -96,7 +97,6 @@ private: void loadTableViewColumnSortSettings(); void saveTableViewColumnSortSettings(); void listZoom(float delta); - void updateListFont(); void updateToolbar(); Ui::GameListWidget m_ui; diff --git a/src/duckstation-qt/inputbindingdialog.cpp b/src/duckstation-qt/inputbindingdialog.cpp index f80225fb7..bc64b6581 100644 --- a/src/duckstation-qt/inputbindingdialog.cpp +++ b/src/duckstation-qt/inputbindingdialog.cpp @@ -135,7 +135,7 @@ void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds) { m_value_ranges.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_timer = new QTimer(this); m_input_listen_timer->setSingleShot(false); diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index 3a85c71fc..e5829e993 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -42,9 +42,10 @@ InputBindingWidget::~InputBindingWidget() 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, @@ -287,7 +288,7 @@ void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds) { m_value_ranges.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_timer = new QTimer(this); m_input_listen_timer->setSingleShot(false); diff --git a/src/duckstation-qt/inputbindingwidgets.h b/src/duckstation-qt/inputbindingwidgets.h index 57b0169ea..ae11b43b7 100644 --- a/src/duckstation-qt/inputbindingwidgets.h +++ b/src/duckstation-qt/inputbindingwidgets.h @@ -22,7 +22,7 @@ public: std::string section_name, std::string key_name); ~InputBindingWidget(); - static bool isMouseMappingEnabled(); + static bool isMouseMappingEnabled(SettingsInterface* sif); void initialize(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name, std::string key_name); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 87643e22f..6655020d8 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -1417,13 +1417,25 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry) { - QString filename = QFileDialog::getOpenFileName(this, tr("Select Cover Image"), QString(), - tr("All Cover Image Types (*.jpg *.jpeg *.png)")); + const QString filename = QDir::toNativeSeparators(QFileDialog::getOpenFileName( + this, tr("Select Cover Image"), QString(), tr("All Cover Image Types (*.jpg *.jpeg *.png *.webp)"))); if (filename.isEmpty()) 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"), tr("A cover image for this game already exists, do you wish to replace it?"), 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)) { QMessageBox::critical(this, tr("Copy Error"), tr("Failed to remove existing cover '%1'").arg(new_filename)); return; } - if (!QFile::copy(filename, new_filename)) { QMessageBox::critical(this, tr("Copy Error"), tr("Failed to copy '%1' to '%2'").arg(filename).arg(new_filename)); 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(); } @@ -2068,7 +2078,12 @@ void MainWindow::updateTheme() { updateApplicationTheme(); updateMenuSelectedTheme(); - m_game_list_widget->reloadCommonImages(); + reloadThemeSpecificImages(); +} + +void MainWindow::reloadThemeSpecificImages() +{ + m_game_list_widget->reloadThemeSpecificImages(); } void MainWindow::setStyleFromSettings() @@ -2405,6 +2420,12 @@ void MainWindow::changeEvent(QEvent* event) g_emu_thread->redrawDisplayWindow(); } + if (event->type() == QEvent::StyleChange) + { + setIconThemeFromSettings(); + reloadThemeSpecificImages(); + } + QMainWindow::changeEvent(event); } diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index ad63c0d42..ecd7bdbe9 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -239,6 +239,7 @@ private: void clearGameListEntryPlayTime(const GameList::Entry* entry); void setTheme(const QString& theme); void updateTheme(); + void reloadThemeSpecificImages(); void recreate(); void registerForDeviceNotifications(); diff --git a/src/duckstation-qt/settingwidgetbinder.h b/src/duckstation-qt/settingwidgetbinder.h index 707fd1a6f..3bba4e860 100644 --- a/src/duckstation-qt/settingwidgetbinder.h +++ b/src/duckstation-qt/settingwidgetbinder.h @@ -11,6 +11,7 @@ #include "core/settings.h" #include "common/assert.h" +#include "common/file_system.h" #include "common/path.h" #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -1030,22 +1032,20 @@ static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget, } } -template -static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget, QAbstractButton* browse_button, - QAbstractButton* open_button, QAbstractButton* reset_button, std::string section, - std::string key, std::string default_value) +static inline void BindWidgetToFolderSetting(SettingsInterface* sif, QLineEdit* widget, QAbstractButton* browse_button, + QAbstractButton* open_button, QAbstractButton* reset_button, + std::string section, std::string key, std::string default_value, + bool use_relative = true) { - using Accessor = SettingAccessor; + using Accessor = SettingAccessor; 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 (!Path::IsAbsolute(current_path)) - current_path = Path::Combine(EmuFolders::DataRoot, current_path); - + else if (use_relative && !Path::IsAbsolute(current_path)) + current_path = Path::Canonicalize(Path::Combine(EmuFolders::DataRoot, current_path)); const QString value(QString::fromStdString(current_path)); Accessor::setStringValue(widget, value); - // if we're doing per-game settings, disable the widget, we only allow folder changes in the base config if (sif) { @@ -1057,32 +1057,65 @@ static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget return; } - Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() { - const std::string new_value(Accessor::getStringValue(widget).toStdString()); + auto value_changed = [widget, section = std::move(section), key = std::move(key), default_value, use_relative]() { + const std::string new_value(widget->text().toStdString()); if (!new_value.empty()) { - std::string relative_path(Path::MakeRelative(new_value, EmuFolders::DataRoot)); - Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), relative_path.c_str()); + if (FileSystem::DirectoryExists(new_value.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 { - 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(); - g_emu_thread->updateEmuFolders(); - }); + // reset to old value + 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) { - 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( 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))))); if (path.isEmpty()) return; - Accessor::setStringValue(widget, path); + widget->setText(path); + value_changed(); }); } if (open_button) @@ -1096,9 +1129,12 @@ static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget if (reset_button) { QObject::connect(reset_button, &QAbstractButton::clicked, reset_button, - [widget, default_value = std::move(default_value)]() { - Accessor::setStringValue(widget, QString::fromStdString(default_value)); + [widget, default_value = std::move(default_value), value_changed]() { + widget->setText(QString::fromStdString(default_value)); + value_changed(); }); } + + widget->connect(widget, &QLineEdit::editingFinished, widget, std::move(value_changed)); } } // namespace SettingWidgetBinder