From 73f326cd75aeb424be18ae27ea39471fb1ecea73 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 7 Aug 2025 20:45:43 +1000 Subject: [PATCH] Qt: Use header resize mode for game list column widths Simpler code, no need for workarounds in showEvent() either. --- src/core/game_list.cpp | 5 ++ src/core/game_list.h | 1 + src/duckstation-qt/gamelistwidget.cpp | 109 ++++++++++++++++++-------- src/duckstation-qt/gamelistwidget.h | 10 +-- src/duckstation-qt/mainwindow.cpp | 11 --- src/duckstation-qt/mainwindow.h | 1 - 6 files changed, 87 insertions(+), 50 deletions(-) diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index dcb6bd0c4..71128566c 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -859,6 +859,11 @@ std::unique_lock GameList::GetLock() return std::unique_lock(s_mutex); } +std::span GameList::GetEntries() +{ + return s_entries; +} + const GameList::Entry* GameList::GetEntryByIndex(u32 index) { return (index < s_entries.size()) ? &s_entries[index] : nullptr; diff --git a/src/core/game_list.h b/src/core/game_list.h index 004ddc496..76e884e40 100644 --- a/src/core/game_list.h +++ b/src/core/game_list.h @@ -93,6 +93,7 @@ bool PopulateEntryFromPath(const std::string& path, Entry* entry); // Game list access. It's the caller's responsibility to hold the lock while manipulating the entry in any way. std::unique_lock GetLock(); +std::span GetEntries(); const Entry* GetEntryByIndex(u32 index); const Entry* GetEntryForPath(std::string_view path); const Entry* GetEntryBySerial(std::string_view serial); diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp index 49926b21a..795cd2dc0 100644 --- a/src/duckstation-qt/gamelistwidget.cpp +++ b/src/duckstation-qt/gamelistwidget.cpp @@ -1423,6 +1423,10 @@ void GameListWidget::onRefreshComplete() m_ui.stack->setCurrentIndex(2); setFocusProxy(nullptr); } + else + { + m_list_view->updateDynamicColumnWidths(); + } } void GameListWidget::onSelectionModelCurrentChanged(const QModelIndex& current, const QModelIndex& previous) @@ -1572,7 +1576,6 @@ void GameListWidget::updateView(bool grid_view) { m_ui.stack->setCurrentIndex(0); setFocusProxy(m_list_view); - resizeListViewColumnsToFit(); } } @@ -1595,11 +1598,6 @@ void GameListWidget::resizeEvent(QResizeEvent* event) updateBackground(false); } -void GameListWidget::resizeListViewColumnsToFit() -{ - m_list_view->resizeColumnsToFit(); -} - const GameList::Entry* GameListWidget::getSelectedEntry() const { if (isShowingGameList()) @@ -1647,6 +1645,11 @@ GameListListView::GameListListView(GameListModel* model, GameListSortModel* sort QHeaderView* const horizontal_header = horizontalHeader(); horizontal_header->setHighlightSections(false); horizontal_header->setContextMenuPolicy(Qt::CustomContextMenu); + setFixedColumnWidths(); + updateDynamicColumnWidths(); + + horizontal_header->setSectionResizeMode(GameListModel::Column_Title, QHeaderView::ResizeMode::Stretch); + horizontal_header->setSectionResizeMode(GameListModel::Column_FileTitle, QHeaderView::ResizeMode::Stretch); verticalHeader()->hide(); @@ -1669,32 +1672,76 @@ GameListListView::GameListListView(GameListModel* model, GameListSortModel* sort GameListListView::~GameListListView() = default; -void GameListListView::resizeEvent(QResizeEvent* e) +void GameListListView::setFixedColumnWidth(int column, int width) { - QTableView::resizeEvent(e); - resizeColumnsToFit(); + horizontalHeader()->setSectionResizeMode(column, QHeaderView::Fixed); + setColumnWidth(column, width); } -void GameListListView::resizeColumnsToFit() +void GameListListView::setFixedColumnWidth(const QFontMetrics& fm, int column, int str_width, int padding) { - QtUtils::ResizeColumnsForTableView(this, { - 45, // type - 95, // serial - -1, // title - -1, // file title - 200, // developer - 200, // publisher - 200, // genre - 50, // year - 100, // players - 85, // time played - 85, // last played - 80, // file size - 80, // size - 55, // region - 100, // achievements - 100 // compatibility - }); + const int width = std::max(fm.boundingRect(m_model->getColumnDisplayName(column)).width(), str_width) + (padding * 2); + setFixedColumnWidth(column, width); +} + +void GameListListView::updateDynamicColumnWidths() +{ + s64 max_file_size = 0; + u64 max_disk_file_size = 0; + + { + const auto lock = GameList::GetLock(); + for (const GameList::Entry& entry : GameList::GetEntries()) + { + max_file_size = std::max(max_file_size, entry.file_size); + max_disk_file_size = std::max(max_disk_file_size, entry.uncompressed_size); + } + } + + const QFontMetrics fm(font()); + const auto width_for = [&fm](const QString& text) { return fm.boundingRect(text).width(); }; + + setFixedColumnWidth(fm, GameListModel::Column_FileSize, + width_for(QStringLiteral("%1 MB").arg(static_cast(max_file_size) / 1048576.0, 0, 'f', 2)), + 10); + setFixedColumnWidth( + fm, GameListModel::Column_UncompressedSize, + width_for(QStringLiteral("%1 MB").arg(static_cast(max_disk_file_size) / 1048576.0, 0, 'f', 2)), 10); +} + +void GameListListView::setFixedColumnWidths() +{ + const QFontMetrics fm(font()); + const auto width_for = [&fm](const QString& text) { return fm.boundingRect(text).width(); }; + + setFixedColumnWidth(fm, GameListModel::Column_Serial, width_for(QStringLiteral("SWWW-00000")), 4); + setFixedColumnWidth(fm, GameListModel::Column_Year, width_for(QStringLiteral("1999")), 4); + setFixedColumnWidth(fm, GameListModel::Column_Players, width_for(QStringLiteral("1 - 2")), 4); + + // Played time is a little trickier, since some locales might have longer words for "hours" and "minutes". + setFixedColumnWidth(fm, GameListModel::Column_TimePlayed, + std::max(width_for(qApp->translate("GameList", "%n seconds", "", 59)), + std::max(width_for(qApp->translate("GameList", "%n minutes", "", 59)), + width_for(qApp->translate("GameList", "%n hours", "", 1000)))), + 10); + + // And this is a monstrosity. + setFixedColumnWidth( + fm, GameListModel::Column_LastPlayed, + std::max(width_for(qApp->translate("GameList", "Today")), + std::max(width_for(qApp->translate("GameList", "Yesterday")), + std::max(width_for(qApp->translate("GameList", "Never")), + width_for(QtHost::FormatNumber(Host::NumberFormatType::ShortDate, + static_cast(QDateTime::currentSecsSinceEpoch())))))), + 10); + + setFixedColumnWidth(GameListModel::Column_Icon, 45); + setFixedColumnWidth(GameListModel::Column_Region, 55); + setFixedColumnWidth(GameListModel::Column_Achievements, 100); + setFixedColumnWidth(GameListModel::Column_Compatibility, 100); + setColumnWidth(GameListModel::Column_Developer, 200); + setColumnWidth(GameListModel::Column_Publisher, 200); + setColumnWidth(GameListModel::Column_Genre, 200); } static TinyString getColumnVisibilitySettingsKeyName(int column) @@ -1771,7 +1818,6 @@ void GameListListView::setAndSaveColumnHidden(int column, bool hidden) setColumnHidden(column, hidden); Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), !hidden); Host::CommitBaseSettingChanges(); - resizeColumnsToFit(); } void GameListListView::onHeaderSortIndicatorChanged(int, Qt::SortOrder) @@ -1791,10 +1837,7 @@ void GameListListView::onHeaderContextMenuRequested(const QPoint& point) QAction* action = menu.addAction(m_model->getColumnDisplayName(column)); action->setCheckable(true); action->setChecked(!isColumnHidden(column)); - connect(action, &QAction::triggered, [this, column](bool enabled) { - setAndSaveColumnHidden(column, !enabled); - resizeColumnsToFit(); - }); + connect(action, &QAction::triggered, [this, column](bool enabled) { setAndSaveColumnHidden(column, !enabled); }); } menu.exec(mapToGlobal(point)); diff --git a/src/duckstation-qt/gamelistwidget.h b/src/duckstation-qt/gamelistwidget.h index bac7cc9b0..e6b284c08 100644 --- a/src/duckstation-qt/gamelistwidget.h +++ b/src/duckstation-qt/gamelistwidget.h @@ -161,15 +161,16 @@ public: void setAndSaveColumnHidden(int column, bool hidden); - void resizeColumnsToFit(); - -protected: - void resizeEvent(QResizeEvent* e) override; + void updateDynamicColumnWidths(); private: void onHeaderSortIndicatorChanged(int, Qt::SortOrder); void onHeaderContextMenuRequested(const QPoint& point); + void setFixedColumnWidth(int column, int width); + void setFixedColumnWidth(const QFontMetrics& fm, int column, int str_width, int padding); + void setFixedColumnWidths(); + void loadColumnVisibilitySettings(); void loadColumnSortSettings(); void saveColumnSortSettings(); @@ -222,7 +223,6 @@ public: void initialize(QAction* actionGameList, QAction* actionGameGrid, QAction* actionMergeDiscSets, QAction* actionListShowIcons, QAction* actionGridShowTitles); - void resizeListViewColumnsToFit(); void refresh(bool invalidate_cache); void cancelRefresh(); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index aefc4f439..081c885f5 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -2681,17 +2681,6 @@ void MainWindow::openInputProfileEditor(const std::string_view name) m_input_profile_editor_window->switchProfile(name); } -void MainWindow::showEvent(QShowEvent* event) -{ - QMainWindow::showEvent(event); - - // This is a bit silly, but for some reason resizing *before* the window is shown - // gives the incorrect sizes for columns, if you set the style before setting up - // the rest of the window... so, instead, let's just force it to be resized on show. - if (isShowingGameList()) - m_game_list_widget->resizeListViewColumnsToFit(); -} - void MainWindow::closeEvent(QCloseEvent* event) { // If there's no VM, we can just exit as normal. diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 973408180..cadbad254 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -227,7 +227,6 @@ private Q_SLOTS: void openCPUDebugger(); protected: - void showEvent(QShowEvent* event) override; void closeEvent(QCloseEvent* event) override; void changeEvent(QEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override;