GameListWidget: Overhaul serialization of table header state
Some checks are pending
🐧 Linux Builds / AppImage (push) Waiting to run
🐧 Linux Builds / Flatpak (push) Waiting to run
🍎 MacOS Builds / Defaults (push) Waiting to run
🖥️ Windows Builds / Lint VS Project Files (push) Waiting to run
🖥️ Windows Builds / SSE4 (push) Blocked by required conditions
🖥️ Windows Builds / AVX2 (push) Blocked by required conditions
🖥️ Windows Builds / CMake (push) Waiting to run

This commit is contained in:
TheTechnician27 2025-11-18 07:15:24 -06:00 committed by lightningterror
parent c8dffccaa7
commit 5a60259ef5
2 changed files with 140 additions and 95 deletions

View File

@ -45,6 +45,25 @@ static const char* SUPPORTED_FORMATS_STRING = QT_TRANSLATE_NOOP(GameListWidget,
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;
static constexpr GameListModel::Column DEFAULT_SORT_COLUMN = GameListModel::Column_Title;
static constexpr int DEFAULT_SORT_INDEX = static_cast<int>(DEFAULT_SORT_COLUMN);
static constexpr Qt::SortOrder DEFAULT_SORT_ORDER = Qt::AscendingOrder;
static constexpr std::array<int, GameListModel::Column_Count> DEFAULT_COLUMN_WIDTHS = {{
55, // type
85, // code
-1, // title
-1, // file title
75, // crc
95, // time played
90, // last played
80, // size
60, // region
120 // compatibility
}};
static_assert(static_cast<int>(DEFAULT_COLUMN_WIDTHS.size()) <= GameListModel::Column_Count,
"Game List: More default column widths than column types.");
class GameListSortModel final : public QSortFilterProxyModel class GameListSortModel final : public QSortFilterProxyModel
{ {
public: public:
@ -235,6 +254,7 @@ void GameListWidget::initialize()
m_table_view = new QTableView(m_ui.stack); m_table_view = new QTableView(m_ui.stack);
m_table_view->setModel(m_sort_model); m_table_view->setModel(m_sort_model);
m_table_view->setSortingEnabled(true); m_table_view->setSortingEnabled(true);
m_table_view->horizontalHeader()->setSectionsMovable(true);
m_table_view->setSelectionMode(QAbstractItemView::SingleSelection); m_table_view->setSelectionMode(QAbstractItemView::SingleSelection);
m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table_view->setContextMenuPolicy(Qt::CustomContextMenu); m_table_view->setContextMenuPolicy(Qt::CustomContextMenu);
@ -252,18 +272,28 @@ void GameListWidget::initialize()
m_table_view->setItemDelegateForColumn(8, new GameListIconStyleDelegate(this)); m_table_view->setItemDelegateForColumn(8, new GameListIconStyleDelegate(this));
m_table_view->setItemDelegateForColumn(9, new GameListIconStyleDelegate(this)); m_table_view->setItemDelegateForColumn(9, new GameListIconStyleDelegate(this));
loadTableViewColumnVisibilitySettings();
loadTableViewColumnSortSettings();
connect(m_table_view->selectionModel(), &QItemSelectionModel::currentChanged, this, connect(m_table_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
&GameListWidget::onSelectionModelCurrentChanged); &GameListWidget::onSelectionModelCurrentChanged);
connect(m_table_view, &QTableView::activated, this, &GameListWidget::onTableViewItemActivated); connect(m_table_view, &QTableView::activated, this, &GameListWidget::onTableViewItemActivated);
connect(m_table_view, &QTableView::customContextMenuRequested, this, connect(m_table_view, &QTableView::customContextMenuRequested, this,
&GameListWidget::onTableViewContextMenuRequested); &GameListWidget::onTableViewContextMenuRequested);
connect(m_table_view->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, connect(m_table_view->horizontalHeader(), &QHeaderView::customContextMenuRequested, this,
&GameListWidget::onTableViewHeaderContextMenuRequested); &GameListWidget::onTableViewHeaderContextMenuRequested);
// Save state when header state changes (hiding and showing handled within onTableViewHeaderContextMenuRequested).
connect(m_table_view->horizontalHeader(), &QHeaderView::sectionMoved, this, &GameListWidget::onTableHeaderStateChanged);
connect(m_table_view->horizontalHeader(), &QHeaderView::sectionResized, this, &GameListWidget::onTableHeaderStateChanged);
connect(m_table_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this, connect(m_table_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this,
&GameListWidget::saveTableViewColumnSortSettings); [this](const int column, const Qt::SortOrder sort_order) { GameListWidget::saveSortSettings(column, sort_order); GameListWidget::onTableHeaderStateChanged(); });
// Load the last session's header state or create a new one.
if (Host::ContainsBaseSettingValue("GameListTableView", "HeaderState"))
loadTableHeaderState();
else
applyTableHeaderDefaults();
// After header state load to account for user-specified sort.
m_table_view->setSortingEnabled(true);
m_ui.stack->insertWidget(0, m_table_view); m_ui.stack->insertWidget(0, m_table_view);
@ -525,18 +555,24 @@ void GameListWidget::onListViewContextMenuRequested(const QPoint& point)
void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point) void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point)
{ {
QMenu menu; QMenu menu;
QHeaderView* header = m_table_view->horizontalHeader();
if (!header)
return;
int column_visual = 0;
for (int column = 0; column < GameListModel::Column_Count; column++) for (int column = 0; column < GameListModel::Column_Count; column++)
{ {
// The "cover" column is the game grid and cannot be hidden.
if (column == GameListModel::Column_Cover) if (column == GameListModel::Column_Cover)
continue; continue;
QAction* action = menu.addAction(m_model->getColumnDisplayName(column)); column_visual = header->visualIndex(column);
QAction* action = menu.addAction(m_model->getColumnDisplayName(column_visual));
action->setCheckable(true); action->setCheckable(true);
action->setChecked(!m_table_view->isColumnHidden(column)); action->setChecked(!m_table_view->isColumnHidden(column_visual));
connect(action, &QAction::toggled, [this, column](bool enabled) { connect(action, &QAction::toggled, [this, column_visual](bool enabled) {
m_table_view->setColumnHidden(column, !enabled); m_table_view->setColumnHidden(column_visual, !enabled);
saveTableViewColumnVisibilitySettings(column); onTableHeaderStateChanged();
resizeTableViewColumnsToFit(); resizeTableViewColumnsToFit();
}); });
} }
@ -697,91 +733,100 @@ bool GameListWidget::event(QEvent* event)
void GameListWidget::resizeTableViewColumnsToFit() void GameListWidget::resizeTableViewColumnsToFit()
{ {
QtUtils::ResizeColumnsForTableView(m_table_view, { QtUtils::ResizeColumnsForTableView(m_table_view, {
55, // type DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Type],
85, // code DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Serial],
-1, // title DEFAULT_COLUMN_WIDTHS[GameListModel::Column_FileTitle],
-1, // file title DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Type],
75, // crc DEFAULT_COLUMN_WIDTHS[GameListModel::Column_CRC],
95, // time played DEFAULT_COLUMN_WIDTHS[GameListModel::Column_TimePlayed],
90, // last played DEFAULT_COLUMN_WIDTHS[GameListModel::Column_LastPlayed],
80, // size DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Size],
60, // region DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Region],
120, // compatibility DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Compatibility],
}); });
} }
static std::string getColumnVisibilitySettingsKeyName(int column) void GameListWidget::loadTableHeaderState()
{ {
return StringUtil::StdStringFromFormat("Show%s", QHeaderView* header = m_table_view->horizontalHeader();
if (!header)
return;
// Decode Base64 string from settings to QByteArray state.
const std::string state_setting = Host::GetBaseStringSettingValue("GameListTableView", "HeaderState");
if (state_setting.empty())
return;
QSignalBlocker blocker(header);
header->restoreState(QByteArray::fromBase64(QByteArray::fromStdString(state_setting)));
}
void GameListWidget::onTableHeaderStateChanged()
{
QHeaderView* header = m_table_view->horizontalHeader();
if (!header)
return;
// Encode QByteArray state as Base64 string for storage.
Host::SetBaseStringSettingValue("GameListTableView", "HeaderState", header->saveState().toBase64());
}
void GameListWidget::applyTableHeaderDefaults()
{
QHeaderView* header = m_table_view->horizontalHeader();
if (!header)
return;
{
QSignalBlocker blocker(header);
header->hideSection(GameListModel::Column_FileTitle);
header->hideSection(GameListModel::Column_CRC);
header->hideSection(GameListModel::Column_Cover);
for (int column = 0; column < GameListModel::Column_Count; column++)
{
if (column == GameListModel::Column_Cover)
continue;
header->resizeSection(column, DEFAULT_COLUMN_WIDTHS[column]);
}
header->setSortIndicator(DEFAULT_SORT_INDEX, DEFAULT_SORT_ORDER);
}
Host::SetBaseStringSettingValue("GameListTableView", "HeaderState", header->saveState().toBase64());
}
// TODO (Tech): Create a button for this in the minibar. Currently unused.
void GameListWidget::resetTableHeaderToDefault()
{
QHeaderView* header = m_table_view->horizontalHeader();
if (!header)
return;
{
QSignalBlocker blocker(header);
for (int column = 0; column < GameListModel::Column_Count; column++)
{
if (column == GameListModel::Column_Cover)
continue;
// Reset size, position, and visibility.
header->resizeSection(column, DEFAULT_COLUMN_WIDTHS[column]);
header->moveSection(header->visualIndex(column), column);
header->setSectionHidden(column,
column == GameListModel::Column_CRC || column == GameListModel::Column_FileTitle);
}
header->hideSection(GameListModel::Column_Cover);
header->setSortIndicator(DEFAULT_SORT_INDEX, DEFAULT_SORT_ORDER);
}
Host::SetBaseStringSettingValue("GameListTableView", "HeaderState", header->saveState().toBase64());
}
void GameListWidget::saveSortSettings(const int column, const Qt::SortOrder sort_order)
{
Host::SetBaseStringSettingValue("GameListTableView", "SortColumn",
GameListModel::getColumnName(static_cast<GameListModel::Column>(column))); GameListModel::getColumnName(static_cast<GameListModel::Column>(column)));
} Host::SetBaseBoolSettingValue("GameListTableView", "SortDescending", static_cast<bool>(sort_order));
void GameListWidget::loadTableViewColumnVisibilitySettings()
{
static constexpr std::array<bool, GameListModel::Column_Count> DEFAULT_VISIBILITY = {{
true, // type
true, // code
true, // title
false, // file title
false, // crc
true, // time played
true, // last played
true, // size
true, // region
true // compatibility
}};
for (int column = 0; column < GameListModel::Column_Count; column++)
{
const bool visible = Host::GetBaseBoolSettingValue(
"GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), DEFAULT_VISIBILITY[column]);
m_table_view->setColumnHidden(column, !visible);
}
}
void GameListWidget::saveTableViewColumnVisibilitySettings()
{
for (int column = 0; column < GameListModel::Column_Count; column++)
{
const bool visible = !m_table_view->isColumnHidden(column);
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), visible);
Host::CommitBaseSettingChanges();
}
}
void GameListWidget::saveTableViewColumnVisibilitySettings(int column)
{
const bool visible = !m_table_view->isColumnHidden(column);
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), visible);
Host::CommitBaseSettingChanges();
}
void GameListWidget::loadTableViewColumnSortSettings()
{
const GameListModel::Column DEFAULT_SORT_COLUMN = GameListModel::Column_Type;
const bool DEFAULT_SORT_DESCENDING = false;
const GameListModel::Column sort_column =
GameListModel::getColumnIdForName(Host::GetBaseStringSettingValue("GameListTableView", "SortColumn"))
.value_or(DEFAULT_SORT_COLUMN);
const bool sort_descending =
Host::GetBaseBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING);
const Qt::SortOrder sort_order = sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder;
m_sort_model->sort(sort_column, sort_order);
if (QHeaderView* hv = m_table_view->horizontalHeader())
hv->setSortIndicator(sort_column, sort_order);
}
void GameListWidget::saveTableViewColumnSortSettings(const int sort_column, const Qt::SortOrder sort_order)
{
if (sort_column >= 0 && sort_column < GameListModel::Column_Count)
{
Host::SetBaseStringSettingValue(
"GameListTableView", "SortColumn", GameListModel::getColumnName(static_cast<GameListModel::Column>(sort_column)));
}
Host::SetBaseBoolSettingValue("GameListTableView", "SortDescending", sort_order == Qt::DescendingOrder);
Host::CommitBaseSettingChanges();
} }
std::optional<GameList::Entry> GameListWidget::getSelectedEntry() const std::optional<GameList::Entry> GameListWidget::getSelectedEntry() const

View File

@ -85,6 +85,7 @@ private Q_SLOTS:
void onListViewItemActivated(const QModelIndex& index); void onListViewItemActivated(const QModelIndex& index);
void onListViewContextMenuRequested(const QPoint& point); void onListViewContextMenuRequested(const QPoint& point);
void onCoverScaleChanged(); void onCoverScaleChanged();
void onTableHeaderStateChanged();
public Q_SLOTS: public Q_SLOTS:
void showGameList(); void showGameList();
@ -102,11 +103,10 @@ protected:
bool event(QEvent* event) override; bool event(QEvent* event) override;
private: private:
void loadTableViewColumnVisibilitySettings(); void loadTableHeaderState();
void saveTableViewColumnVisibilitySettings(); void applyTableHeaderDefaults();
void saveTableViewColumnVisibilitySettings(int column); void resetTableHeaderToDefault();
void loadTableViewColumnSortSettings(); void saveSortSettings(int column, Qt::SortOrder sort_order);
void saveTableViewColumnSortSettings(const int sort_column, const Qt::SortOrder sort_order);
void listZoom(float delta); void listZoom(float delta);
void updateToolbar(); void updateToolbar();