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.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);

View file

@ -114,11 +114,11 @@ const char* GameListModel::getColumnName(Column col)
return s_column_names[static_cast<int>(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<int>(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<u32>(GameList::EntryType::Count); i++)
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++)
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++)
{
m_compatibility_pixmaps[i] =
QtUtils::GetIconForCompatibility(static_cast<GameDatabase::CompatibilityRating>(i)).pixmap(96, 24);
}
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 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);

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)
#include "gamelistwidget.h"
@ -20,6 +20,7 @@
#include <QtGui/QWheelEvent>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QMenu>
#include <QtWidgets/QScrollBar>
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<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)
#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;

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);
}

View file

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

View file

@ -11,6 +11,7 @@
#include "core/settings.h"
#include "common/assert.h"
#include "common/file_system.h"
#include "common/path.h"
#include <QtCore/QtCore>
@ -22,6 +23,7 @@
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QSlider>
#include <QtWidgets/QSpinBox>
#include <optional>
@ -1030,22 +1032,20 @@ static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget,
}
}
template<typename WidgetType>
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<WidgetType>;
using Accessor = SettingAccessor<QLineEdit>;
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