From c1b1d00b4c4eab2596fdad6a1c65a4995f359d7d Mon Sep 17 00:00:00 2001 From: FlexBy420 <68403300+FlexBy420@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:23:28 +0200 Subject: [PATCH] Qt: Overhaul trophy manager (#19428) * Allow to search game by title or via comm id * More info columns * Seperated games list and trophy list instead of displaying both at the same time --- rpcs3/rpcs3qt/gui_settings.cpp | 6 + rpcs3/rpcs3qt/gui_settings.h | 7 + rpcs3/rpcs3qt/trophy_manager_dialog.cpp | 487 ++++++++++++++++++------ rpcs3/rpcs3qt/trophy_manager_dialog.h | 16 +- 4 files changed, 397 insertions(+), 119 deletions(-) diff --git a/rpcs3/rpcs3qt/gui_settings.cpp b/rpcs3/rpcs3qt/gui_settings.cpp index 6cae265aef..b25fc46f8e 100644 --- a/rpcs3/rpcs3qt/gui_settings.cpp +++ b/rpcs3/rpcs3qt/gui_settings.cpp @@ -83,6 +83,7 @@ namespace gui case trophy_list_columns::id: return "trophy_column_id"; case trophy_list_columns::platinum_link: return "trophy_column_platinum_link"; case trophy_list_columns::time_unlocked: return "trophy_column_time_unlocked"; + case trophy_list_columns::trophy_set: return "trophy_column_trophy_set"; case trophy_list_columns::count: return ""; } @@ -97,6 +98,11 @@ namespace gui case trophy_game_list_columns::name: return "trophy_game_column_name"; case trophy_game_list_columns::progress: return "trophy_game_column_progress"; case trophy_game_list_columns::trophies: return "trophy_game_column_trophies"; + case trophy_game_list_columns::bronze: return "trophy_game_column_bronze"; + case trophy_game_list_columns::silver: return "trophy_game_column_silver"; + case trophy_game_list_columns::gold: return "trophy_game_column_gold"; + case trophy_game_list_columns::platinum: return "trophy_game_column_platinum"; + case trophy_game_list_columns::comm_id: return "trophy_game_column_comm_id"; case trophy_game_list_columns::count: return ""; } diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index 59fa724240..39a1c4dc71 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -52,6 +52,7 @@ namespace gui id = 5, platinum_link = 6, time_unlocked = 7, + trophy_set = 8, count }; @@ -62,6 +63,11 @@ namespace gui name = 1, progress = 2, trophies = 3, + bronze = 4, + silver = 5, + gold = 6, + platinum = 7, + comm_id = 8, count }; @@ -302,6 +308,7 @@ namespace gui const gui_save tr_icon_color = gui_save(trophy, "icon_color", gl_icon_color); const gui_save tr_icon_height = gui_save(trophy, "icon_height", 75); const gui_save tr_game_iconSize = gui_save(trophy, "game_iconSize", 25); + const gui_save tr_game_icon_width = gui_save(trophy, "game_icon_width", 0); const gui_save tr_show_locked = gui_save(trophy, "show_locked", true); const gui_save tr_show_unlocked = gui_save(trophy, "show_unlocked", true); const gui_save tr_show_hidden = gui_save(trophy, "show_hidden", false); diff --git a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp index d8c3f81058..13337c6470 100644 --- a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,9 @@ #include #include #include +#include +#include +#include LOG_CHANNEL(gui_log, "GUI"); @@ -83,9 +87,10 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s // Get the currently selected user's trophy path. m_trophy_dir = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/"; - // Game chooser combo box - m_game_combo = new QComboBox(); + // Internal game selector. The visible UI uses the game table and a details page. + m_game_combo = new QComboBox(this); m_game_combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + m_game_combo->hide(); // Game progression label m_game_progress = new QLabel(tr("Progress: %1% (%2/%3)").arg(0).arg(0).arg(0)); @@ -105,7 +110,10 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s m_game_table->setEditTriggers(QAbstractItemView::NoEditTriggers); m_game_table->setColumnCount(static_cast(gui::trophy_game_list_columns::count)); m_game_table->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); - m_game_table->horizontalHeader()->setStretchLastSection(true); + m_game_table->horizontalHeader()->setSectionsMovable(true); + m_game_table->horizontalHeader()->setStretchLastSection(false); + m_game_table->horizontalHeader()->setCascadingSectionResizes(false); + m_game_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); m_game_table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); m_game_table->setContextMenuPolicy(Qt::CustomContextMenu); m_game_table->verticalHeader()->setVisible(false); @@ -123,6 +131,12 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s add_game_column(gui::trophy_game_list_columns::name); add_game_column(gui::trophy_game_list_columns::progress); add_game_column(gui::trophy_game_list_columns::trophies); + add_game_column(gui::trophy_game_list_columns::bronze); + add_game_column(gui::trophy_game_list_columns::silver); + add_game_column(gui::trophy_game_list_columns::gold); + add_game_column(gui::trophy_game_list_columns::platinum); + add_game_column(gui::trophy_game_list_columns::comm_id); + m_game_table->horizontalHeader()->setSectionResizeMode(static_cast(gui::trophy_game_list_columns::icon), QHeaderView::Fixed); // Trophy Table m_trophy_table = new game_list(); @@ -138,7 +152,10 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s m_trophy_table->setEditTriggers(QAbstractItemView::NoEditTriggers); m_trophy_table->setColumnCount(static_cast(gui::trophy_list_columns::count)); m_trophy_table->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); - m_trophy_table->horizontalHeader()->setStretchLastSection(true); + m_trophy_table->horizontalHeader()->setSectionsMovable(true); + m_trophy_table->horizontalHeader()->setStretchLastSection(false); + m_trophy_table->horizontalHeader()->setCascadingSectionResizes(false); + m_trophy_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); m_trophy_table->horizontalHeader()->setSectionResizeMode(static_cast(gui::trophy_list_columns::icon), QHeaderView::Fixed); m_trophy_table->verticalHeader()->setVisible(false); m_trophy_table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); @@ -161,12 +178,20 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s add_trophy_column(gui::trophy_list_columns::id); add_trophy_column(gui::trophy_list_columns::platinum_link); add_trophy_column(gui::trophy_list_columns::time_unlocked); + add_trophy_column(gui::trophy_list_columns::trophy_set); - m_splitter = new QSplitter(); - m_splitter->addWidget(m_game_table); - m_splitter->addWidget(m_trophy_table); + m_trophy_table->horizontalHeader()->moveSection(static_cast(gui::trophy_list_columns::trophy_set), 3); - m_game_icon_size = gui_settings::SizeFromSlider(m_game_icon_size_index); + const int saved_game_icon_width = m_gui_settings->GetValue(gui::tr_game_icon_width).toInt(); + if (saved_game_icon_width >= gui::gl_icon_size_min.width() && saved_game_icon_width <= gui::gl_icon_size_max.width()) + { + const int height = (saved_game_icon_width * gui::gl_icon_size_max.height() + gui::gl_icon_size_max.width() / 2) / gui::gl_icon_size_max.width(); + m_game_icon_size = QSize(saved_game_icon_width, height); + } + else + { + m_game_icon_size = gui_settings::SizeFromSlider(m_game_icon_size_index); + } // Checkboxes to control dialog QCheckBox* check_lock_trophy = new QCheckBox(tr("Show Not Earned Trophies")); @@ -200,72 +225,134 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s QLabel* trophy_slider_label = new QLabel(); trophy_slider_label->setText(tr("Trophy Icon Size: %0x%1").arg(m_icon_height).arg(m_icon_height)); - QLabel* game_slider_label = new QLabel(); - game_slider_label->setText(tr("Game Icon Size: %0x%1").arg(m_game_icon_size.width()).arg(m_game_icon_size.height())); - m_icon_slider = new QSlider(Qt::Horizontal); m_icon_slider->setRange(25, 225); m_icon_slider->setValue(m_icon_height); - m_game_icon_slider = new QSlider(Qt::Horizontal); - m_game_icon_slider->setRange(0, gui::gl_max_slider_pos); - m_game_icon_slider->setValue(m_game_icon_size_index); + m_game_icon_size_spin = new QSpinBox(); + m_game_icon_size_spin->setRange(gui::gl_icon_size_min.width(), gui::gl_icon_size_max.width()); + m_game_icon_size_spin->setValue(m_game_icon_size.width()); + m_game_icon_size_spin->setSuffix(tr(" px")); + m_game_icon_size_spin->setToolTip(tr("Width of game icons in pixels. The height is adjusted automatically to keep the icon aspect ratio.")); // LAYOUTS - QGroupBox* choose_game = new QGroupBox(tr("Choose Game")); - QVBoxLayout* choose_layout = new QVBoxLayout(); - choose_layout->addWidget(m_game_combo); - choose_game->setLayout(choose_layout); - - QGroupBox* trophy_info = new QGroupBox(tr("Trophy Info")); - QVBoxLayout* info_layout = new QVBoxLayout(); - info_layout->addWidget(m_game_progress); - trophy_info->setLayout(info_layout); - - QGroupBox* show_settings = new QGroupBox(tr("Trophy View Options")); - QVBoxLayout* settings_layout = new QVBoxLayout(); - settings_layout->addWidget(check_lock_trophy); - settings_layout->addWidget(check_unlock_trophy); - settings_layout->addWidget(check_hidden_trophy); - settings_layout->addWidget(check_bronze_trophy); - settings_layout->addWidget(check_silver_trophy); - settings_layout->addWidget(check_gold_trophy); - settings_layout->addWidget(check_platinum_trophy); + QGroupBox* show_settings = new QGroupBox(tr("Filters")); + QGridLayout* settings_layout = new QGridLayout(); + settings_layout->addWidget(check_lock_trophy, 0, 0); + settings_layout->addWidget(check_unlock_trophy, 0, 1); + settings_layout->addWidget(check_hidden_trophy, 0, 2); + settings_layout->addWidget(check_bronze_trophy, 0, 3); + settings_layout->addWidget(check_silver_trophy, 1, 0); + settings_layout->addWidget(check_gold_trophy, 1, 1); + settings_layout->addWidget(check_platinum_trophy, 1, 2); show_settings->setLayout(settings_layout); QGroupBox* icon_settings = new QGroupBox(tr("Icon Options")); QVBoxLayout* slider_layout = new QVBoxLayout(); slider_layout->addWidget(trophy_slider_label); slider_layout->addWidget(m_icon_slider); - slider_layout->addWidget(game_slider_label); - slider_layout->addWidget(m_game_icon_slider); icon_settings->setLayout(slider_layout); - QGroupBox* rpcn_settings = new QGroupBox(tr("RPCN")); - QVBoxLayout* rpcn_layout = new QVBoxLayout(); m_btn_sync_all_trophies = new QPushButton(tr("Sync All to RPCN")); + + // Game list page + QWidget* game_page = new QWidget(); + QVBoxLayout* game_page_layout = new QVBoxLayout(game_page); + game_page_layout->setContentsMargins(0, 0, 0, 0); + + QHBoxLayout* game_toolbar = new QHBoxLayout(); + m_game_search = new QLineEdit(); + m_game_search->setClearButtonEnabled(true); + m_game_search->setPlaceholderText(tr("Search games by title or Communication ID...")); + QPushButton* btn_open_game = new QPushButton(tr("View Trophies")); QPushButton* btn_delete_online_trophies = new QPushButton(tr("Delete Online Trophies")); - rpcn_layout->addWidget(m_btn_sync_all_trophies); - rpcn_layout->addWidget(btn_delete_online_trophies); - rpcn_settings->setLayout(rpcn_layout); + game_toolbar->addWidget(m_game_search, 1); + game_toolbar->addWidget(new QLabel(tr("Game Icon Size:"))); + game_toolbar->addWidget(m_game_icon_size_spin); + game_toolbar->addWidget(btn_open_game); + game_toolbar->addSpacing(12); + game_toolbar->addWidget(m_btn_sync_all_trophies); + game_toolbar->addWidget(btn_delete_online_trophies); - QVBoxLayout* options_layout = new QVBoxLayout(); - options_layout->addWidget(choose_game); - options_layout->addWidget(trophy_info); - options_layout->addWidget(show_settings); - options_layout->addWidget(icon_settings); - options_layout->addWidget(rpcn_settings); - options_layout->addStretch(); + game_page_layout->addLayout(game_toolbar); + game_page_layout->addWidget(m_game_table, 1); - QHBoxLayout* all_layout = new QHBoxLayout(this); - all_layout->addLayout(options_layout); - all_layout->addWidget(m_splitter); - all_layout->setStretch(1, 1); + // Trophy details page + QWidget* trophy_page = new QWidget(); + QVBoxLayout* trophy_page_layout = new QVBoxLayout(trophy_page); + trophy_page_layout->setContentsMargins(0, 0, 0, 0); + + QHBoxLayout* trophy_header = new QHBoxLayout(); + QPushButton* btn_back_to_games = new QPushButton(tr("Back to Games")); + + m_game_title = new QLabel(); + m_game_title->setObjectName("trophy_manager_game_title"); + QFont title_font = m_game_title->font(); + title_font.setBold(true); + if (title_font.pointSizeF() > 0.0) + { + title_font.setPointSizeF(title_font.pointSizeF() + 4.0); + } + m_game_title->setFont(title_font); + m_game_title->setTextInteractionFlags(Qt::TextSelectableByMouse); + + m_game_communication_id = new QLabel(); + m_game_communication_id->setObjectName("trophy_manager_game_communication_id"); + m_game_communication_id->setTextInteractionFlags(Qt::TextSelectableByMouse); + + QVBoxLayout* game_identity_layout = new QVBoxLayout(); + game_identity_layout->setContentsMargins(0, 0, 0, 0); + game_identity_layout->setSpacing(2); + game_identity_layout->addWidget(m_game_title); + game_identity_layout->addWidget(m_game_communication_id); + + QFont progress_font = m_game_progress->font(); + progress_font.setBold(true); + m_game_progress->setFont(progress_font); + + trophy_header->addWidget(btn_back_to_games, 0, Qt::AlignTop); + trophy_header->addLayout(game_identity_layout, 1); + trophy_header->addWidget(m_game_progress, 0, Qt::AlignVCenter); + + QHBoxLayout* trophy_options_layout = new QHBoxLayout(); + trophy_options_layout->addWidget(show_settings); + trophy_options_layout->addWidget(icon_settings); + trophy_options_layout->addStretch(); + + trophy_page_layout->addLayout(trophy_header); + trophy_page_layout->addLayout(trophy_options_layout); + trophy_page_layout->addWidget(m_trophy_table, 1); + + m_stack = new QStackedWidget(); + m_stack->addWidget(game_page); + m_stack->addWidget(trophy_page); + m_stack->setCurrentWidget(game_page); + + QVBoxLayout* all_layout = new QVBoxLayout(this); + all_layout->addWidget(m_stack); setLayout(all_layout); // Make connects connect(m_btn_sync_all_trophies, &QAbstractButton::clicked, this, &trophy_manager_dialog::SyncAllOnlineTrophies); connect(btn_delete_online_trophies, &QAbstractButton::clicked, this, &trophy_manager_dialog::DeleteOnlineTrophies); + connect(m_game_search, &QLineEdit::textChanged, this, &trophy_manager_dialog::ApplyGameFilter); + connect(m_game_search, &QLineEdit::returnPressed, this, [this]() + { + const int row = m_game_table->currentRow(); + if (row >= 0 && !m_game_table->isRowHidden(row)) + { + OpenGameFromRow(row); + } + }); + connect(btn_back_to_games, &QAbstractButton::clicked, this, &trophy_manager_dialog::ShowGameList); + connect(btn_open_game, &QAbstractButton::clicked, this, [this]() + { + const int row = m_game_table->currentRow(); + if (row >= 0) + { + OpenGameFromRow(row); + } + }); UpdateOnlineTrophySyncCooldown(); connect(m_icon_slider, &QSlider::valueChanged, this, [this, trophy_slider_label](int val) @@ -296,33 +383,12 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s } }); - connect(m_game_icon_slider, &QSlider::valueChanged, this, [this, game_slider_label](int val) + connect(m_game_icon_size_spin, &QSpinBox::valueChanged, this, [this](int width) { - m_game_icon_size_index = val; - m_game_icon_size = gui_settings::SizeFromSlider(val); - if (game_slider_label) - { - game_slider_label->setText(tr("Game Icon Size: %0x%1").arg(m_game_icon_size.width()).arg(m_game_icon_size.height())); - } + const int height = (width * gui::gl_icon_size_max.height() + gui::gl_icon_size_max.width() / 2) / gui::gl_icon_size_max.width(); + m_game_icon_size = QSize(width, height); + m_gui_settings->SetValue(gui::tr_game_icon_width, width); ResizeGameIcons(); - if (m_save_game_icon_size) - { - m_save_game_icon_size = false; - m_gui_settings->SetValue(gui::tr_game_iconSize, val); - } - }); - - connect(m_game_icon_slider, &QSlider::sliderReleased, this, [this]() - { - m_gui_settings->SetValue(gui::tr_game_iconSize, m_game_icon_slider->value()); - }); - - connect(m_game_icon_slider, &QSlider::actionTriggered, this, [this](int action) - { - if (action != QAbstractSlider::SliderNoAction && action != QAbstractSlider::SliderMove) - { // we only want to save on mouseclicks or slider release (the other connect handles this) - m_save_game_icon_size = true; // actionTriggered happens before the value was changed - } }); connect(check_lock_trophy, &QCheckBox::clicked, this, [this](bool checked) @@ -376,26 +442,10 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s connect(m_trophy_table, &QTableWidget::customContextMenuRequested, this, &trophy_manager_dialog::ShowTrophyTableContextMenu); - connect(m_game_combo, &QComboBox::currentTextChanged, this, [this] - { - PopulateTrophyTable(); - ApplyFilter(); - }); - connect(m_game_table, &QTableWidget::customContextMenuRequested, this, &trophy_manager_dialog::ShowGameTableContextMenu); - - connect(m_game_table, &QTableWidget::itemSelectionChanged, this, [this] + connect(m_game_table, &QTableWidget::cellDoubleClicked, this, [this](int row, int) { - if (m_game_table->selectedItems().isEmpty()) - { - return; - } - QTableWidgetItem* item = m_game_table->item(m_game_table->selectedItems().first()->row(), static_cast(gui::trophy_game_list_columns::name)); - if (!item) - { - return; - } - m_game_combo->setCurrentText(item->text()); + OpenGameFromRow(row); }); connect(this, &trophy_manager_dialog::TrophyIconReady, this, [this](int index, const QPixmap& pixmap) @@ -835,6 +885,7 @@ QString trophy_manager_dialog::get_trophy_header_text(int col) const case gui::trophy_list_columns::id: return tr("ID"); case gui::trophy_list_columns::platinum_link: return tr("Platinum Relevant"); case gui::trophy_list_columns::time_unlocked: return tr("Time Unlocked"); + case gui::trophy_list_columns::trophy_set: return tr("Trophy Set"); case gui::trophy_list_columns::count: break; } return {}; @@ -852,6 +903,7 @@ QString trophy_manager_dialog::get_trophy_action_text(int col) const case gui::trophy_list_columns::id: return tr("Show IDs"); case gui::trophy_list_columns::platinum_link: return tr("Show Platinum Relevant"); case gui::trophy_list_columns::time_unlocked: return tr("Show Time Unlocked"); + case gui::trophy_list_columns::trophy_set: return tr("Show Trophy Set"); case gui::trophy_list_columns::count: break; } return {}; @@ -862,9 +914,14 @@ QString trophy_manager_dialog::get_gamelist_header_text(int col) const switch (static_cast(col)) { case gui::trophy_game_list_columns::icon: return tr("Icon"); - case gui::trophy_game_list_columns::name: return tr("Game"); + case gui::trophy_game_list_columns::name: return tr("Title"); case gui::trophy_game_list_columns::progress: return tr("Progress"); case gui::trophy_game_list_columns::trophies: return tr("Trophies"); + case gui::trophy_game_list_columns::bronze: return tr("Bronze"); + case gui::trophy_game_list_columns::silver: return tr("Silver"); + case gui::trophy_game_list_columns::gold: return tr("Gold"); + case gui::trophy_game_list_columns::platinum: return tr("Platinum"); + case gui::trophy_game_list_columns::comm_id: return tr("Communication ID"); case gui::trophy_game_list_columns::count: break; } return {}; @@ -875,9 +932,14 @@ QString trophy_manager_dialog::get_gamelist_action_text(int col) const switch (static_cast(col)) { case gui::trophy_game_list_columns::icon: return tr("Show Icons"); - case gui::trophy_game_list_columns::name: return tr("Show Games"); + case gui::trophy_game_list_columns::name: return tr("Show Titles"); case gui::trophy_game_list_columns::progress: return tr("Show Progress"); case gui::trophy_game_list_columns::trophies: return tr("Show Trophies"); + case gui::trophy_game_list_columns::bronze: return tr("Show Bronze"); + case gui::trophy_game_list_columns::silver: return tr("Show Silver"); + case gui::trophy_game_list_columns::gold: return tr("Show Gold"); + case gui::trophy_game_list_columns::platinum: return tr("Show Platinum"); + case gui::trophy_game_list_columns::comm_id: return tr("Show Communication ID"); case gui::trophy_game_list_columns::count: break; } return {}; @@ -977,14 +1039,30 @@ void trophy_manager_dialog::RepaintUI(bool restore_layout) resize(QGuiApplication::primaryScreen()->availableSize() * 0.7); } - if (restore_layout && !m_splitter->restoreState(m_gui_settings->GetValue(gui::tr_splitterState).toByteArray())) + if (m_current_game_index >= 0) { - const int width_left = m_splitter->width() * 0.4; - const int width_right = m_splitter->width() - width_left; - m_splitter->setSizes({ width_left, width_right }); + const int combo_index = m_game_combo->findData(m_current_game_index); + if (combo_index >= 0 && static_cast(m_current_game_index) < m_trophies_db.size() && m_trophies_db[m_current_game_index]) + { + m_game_combo->setCurrentIndex(combo_index); + m_game_title->setText(QString::fromStdString(m_trophies_db[m_current_game_index]->game_name).simplified()); + m_game_communication_id->setText(tr("Communication ID: %1").arg(QString::fromStdString(m_trophies_db[m_current_game_index]->communication_id))); + PopulateTrophyTable(); + } + else + { + m_current_game_index = -1; + ShowGameList(); + } + } + else + { + m_trophy_table->clear_list(); + m_trophy_table->setRowCount(0); + m_game_title->clear(); + m_game_communication_id->clear(); + m_game_progress->setText(tr("Progress: %1% (%2/%3)").arg(0).arg(0).arg(0)); } - - PopulateTrophyTable(); const QByteArray game_table_state = m_gui_settings->GetValue(gui::tr_games_state).toByteArray(); if (restore_layout && !m_game_table->horizontalHeader()->restoreState(game_table_state) && m_game_table->rowCount()) @@ -1000,6 +1078,18 @@ void trophy_manager_dialog::RepaintUI(bool restore_layout) //m_trophy_table->horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents); } + m_game_table->horizontalHeader()->setSectionsMovable(true); + m_game_table->horizontalHeader()->setStretchLastSection(false); + m_game_table->horizontalHeader()->setCascadingSectionResizes(false); + m_game_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); + m_game_table->horizontalHeader()->setSectionResizeMode(static_cast(gui::trophy_game_list_columns::icon), QHeaderView::Fixed); + + m_trophy_table->horizontalHeader()->setSectionsMovable(true); + m_trophy_table->horizontalHeader()->setStretchLastSection(false); + m_trophy_table->horizontalHeader()->setCascadingSectionResizes(false); + m_trophy_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); + m_trophy_table->horizontalHeader()->setSectionResizeMode(static_cast(gui::trophy_list_columns::icon), QHeaderView::Fixed); + if (restore_layout) { // Make sure the actions and the headers are synced @@ -1018,16 +1108,26 @@ void trophy_manager_dialog::RepaintUI(bool restore_layout) void trophy_manager_dialog::HandleRepaintUiRequest() { const QSize window_size = size(); - const QByteArray splitter_state = m_splitter->saveState(); const QByteArray game_table_state = m_game_table->horizontalHeader()->saveState(); const QByteArray trophy_table_state = m_trophy_table->horizontalHeader()->saveState(); RepaintUI(false); - m_splitter->restoreState(splitter_state); m_game_table->horizontalHeader()->restoreState(game_table_state); m_trophy_table->horizontalHeader()->restoreState(trophy_table_state); + m_game_table->horizontalHeader()->setSectionsMovable(true); + m_game_table->horizontalHeader()->setStretchLastSection(false); + m_game_table->horizontalHeader()->setCascadingSectionResizes(false); + m_game_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); + m_game_table->horizontalHeader()->setSectionResizeMode(static_cast(gui::trophy_game_list_columns::icon), QHeaderView::Fixed); + + m_trophy_table->horizontalHeader()->setSectionsMovable(true); + m_trophy_table->horizontalHeader()->setStretchLastSection(false); + m_trophy_table->horizontalHeader()->setCascadingSectionResizes(false); + m_trophy_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); + m_trophy_table->horizontalHeader()->setSectionResizeMode(static_cast(gui::trophy_list_columns::icon), QHeaderView::Fixed); + // Make sure the actions and the headers are synced m_game_table->sync_header_actions(m_game_column_acts, [this](int col) { return m_gui_settings->GetTrophyGamelistColVisibility(static_cast(col)); }); m_trophy_table->sync_header_actions(m_trophy_column_acts, [this](int col) { return m_gui_settings->GetTrophylistColVisibility(static_cast(col)); }); @@ -1278,6 +1378,85 @@ void trophy_manager_dialog::ApplyFilter() ReadjustTrophyTable(); } +void trophy_manager_dialog::ApplyGameFilter(const QString& text) +{ + if (!m_game_table) + { + return; + } + + const QString filter = text.trimmed(); + int first_match = -1; + for (int row = 0; row < m_game_table->rowCount(); ++row) + { + const QTableWidgetItem* name_item = m_game_table->item(row, static_cast(gui::trophy_game_list_columns::name)); + const QTableWidgetItem* comm_id_item = m_game_table->item(row, static_cast(gui::trophy_game_list_columns::comm_id)); + const bool matches = filter.isEmpty() + || (name_item && name_item->text().contains(filter, Qt::CaseInsensitive)) + || (comm_id_item && comm_id_item->text().contains(filter, Qt::CaseInsensitive)); + m_game_table->setRowHidden(row, !matches); + if (matches && first_match < 0) + { + first_match = row; + } + } + + const int current_row = m_game_table->currentRow(); + if (first_match >= 0 && (current_row < 0 || m_game_table->isRowHidden(current_row))) + { + m_game_table->setCurrentCell(first_match, static_cast(gui::trophy_game_list_columns::name)); + } +} + +void trophy_manager_dialog::OpenGameFromRow(int row) +{ + if (row < 0 || row >= m_game_table->rowCount()) + { + return; + } + + const QTableWidgetItem* icon_item = m_game_table->item(row, static_cast(gui::trophy_game_list_columns::icon)); + if (!icon_item) + { + return; + } + + const int db_index = icon_item->data(GameUserRole::GameIndex).toInt(); + if (db_index < 0 || static_cast(db_index) >= m_trophies_db.size() || !m_trophies_db[db_index]) + { + return; + } + + const int combo_index = m_game_combo->findData(db_index); + if (combo_index < 0) + { + return; + } + + m_game_combo->setCurrentIndex(combo_index); + m_current_game_index = db_index; + m_game_title->setText(QString::fromStdString(m_trophies_db[db_index]->game_name).simplified()); + m_game_communication_id->setText(tr("Communication ID: %1").arg(QString::fromStdString(m_trophies_db[db_index]->communication_id))); + PopulateTrophyTable(); + ApplyFilter(); + m_stack->setCurrentIndex(1); + m_trophy_table->setFocus(); +} + +void trophy_manager_dialog::ShowGameList() +{ + if (!m_stack) + { + return; + } + + m_stack->setCurrentIndex(0); + if (m_game_search) + { + m_game_search->setFocus(); + } +} + void trophy_manager_dialog::ShowTrophyTableContextMenu(const QPoint& pos) { const int row = m_trophy_table->currentRow(); @@ -1502,6 +1681,8 @@ void trophy_manager_dialog::StartTrophyLoadThreads() { WaitAndAbortGameRepaintThreads(); WaitAndAbortTrophyRepaintThreads(); + m_current_game_index = -1; + ShowGameList(); m_trophies_db.clear(); @@ -1604,9 +1785,27 @@ void trophy_manager_dialog::PopulateGameTable() { const int all_trophies = m_trophies_db[i]->trop_usr->GetTrophiesCount(); const int unlocked_trophies = m_trophies_db[i]->trop_usr->GetUnlockedTrophiesCount(); - const int percentage = 100 * unlocked_trophies / all_trophies; + const int percentage = all_trophies > 0 ? 100 * unlocked_trophies / all_trophies : 0; const QString progress = tr("%0% (%1/%2)").arg(percentage).arg(unlocked_trophies).arg(all_trophies); const QString name = QString::fromStdString(m_trophies_db[i]->game_name).simplified(); + const QString communication_id = QString::fromStdString(m_trophies_db[i]->communication_id); + + int bronze_trophies = 0; + int silver_trophies = 0; + int gold_trophies = 0; + int platinum_trophies = 0; + + for (int trophy_id = 0; trophy_id < all_trophies; ++trophy_id) + { + switch (m_trophies_db[i]->trop_usr->GetTrophyGrade(trophy_id)) + { + case SCE_NP_TROPHY_GRADE_BRONZE: ++bronze_trophies; break; + case SCE_NP_TROPHY_GRADE_SILVER: ++silver_trophies; break; + case SCE_NP_TROPHY_GRADE_GOLD: ++gold_trophies; break; + case SCE_NP_TROPHY_GRADE_PLATINUM: ++platinum_trophies; break; + default: break; + } + } custom_table_widget_item* icon_item = new custom_table_widget_item; icon_item->setData(Qt::DecorationRole, placeholder); @@ -1618,6 +1817,11 @@ void trophy_manager_dialog::PopulateGameTable() m_game_table->setItem(i, static_cast(gui::trophy_game_list_columns::name), new custom_table_widget_item(name)); m_game_table->setItem(i, static_cast(gui::trophy_game_list_columns::progress), new custom_table_widget_item(progress, Qt::UserRole, percentage)); m_game_table->setItem(i, static_cast(gui::trophy_game_list_columns::trophies), new custom_table_widget_item(QString::number(all_trophies), Qt::UserRole, all_trophies)); + m_game_table->setItem(i, static_cast(gui::trophy_game_list_columns::bronze), new custom_table_widget_item(QString::number(bronze_trophies), Qt::UserRole, bronze_trophies)); + m_game_table->setItem(i, static_cast(gui::trophy_game_list_columns::silver), new custom_table_widget_item(QString::number(silver_trophies), Qt::UserRole, silver_trophies)); + m_game_table->setItem(i, static_cast(gui::trophy_game_list_columns::gold), new custom_table_widget_item(QString::number(gold_trophies), Qt::UserRole, gold_trophies)); + m_game_table->setItem(i, static_cast(gui::trophy_game_list_columns::platinum), new custom_table_widget_item(QString::number(platinum_trophies), Qt::UserRole, platinum_trophies)); + m_game_table->setItem(i, static_cast(gui::trophy_game_list_columns::comm_id), new custom_table_widget_item(communication_id)); m_game_combo->addItem(name, i); } @@ -1627,6 +1831,7 @@ void trophy_manager_dialog::PopulateGameTable() m_game_combo->setCurrentIndex(0); m_game_table->setSortingEnabled(true); // Enable sorting only after using setItem calls + ApplyGameFilter(m_game_search ? m_game_search->text() : QString{}); ResizeGameIcons(); @@ -1677,6 +1882,40 @@ void trophy_manager_dialog::PopulateTrophyTable() gui_log.error("Populating Trophy Manager UI failed (root is null): %s %s", data->game_name, data->path); } + struct trophy_group_info + { + QString name; + QString detail; + }; + + std::unordered_map trophy_groups; + for (std::shared_ptr group = trophy_base ? trophy_base->GetChildren() : nullptr; group; group = group->GetNext()) + { + if (group->GetName() != "group") + { + continue; + } + + trophy_group_info info; + for (std::shared_ptr child = group->GetChildren(); child; child = child->GetNext()) + { + if (child->GetName() == "name") + { + info.name = QString::fromStdString(child->GetNodeContent()).simplified(); + } + else if (child->GetName() == "detail") + { + info.detail = QString::fromStdString(child->GetNodeContent()).simplified(); + } + } + + const std::string group_id = group->GetAttribute("id"); + if (!group_id.empty()) + { + trophy_groups.emplace(group_id, std::move(info)); + } + } + int i = 0; for (std::shared_ptr n = trophy_base ? trophy_base->GetChildren() : nullptr; n; n = n->GetNext()) { @@ -1733,6 +1972,24 @@ void trophy_manager_dialog::PopulateTrophyTable() const QString unlockstate = data->trop_usr->GetTrophyUnlockState(trophy_id) ? tr("Earned") : tr("Not Earned"); + QString trophy_set = tr("Base Game"); + QString trophy_set_detail; + const std::string group_id = n->GetAttribute("gid"); + if (!group_id.empty()) + { + if (const auto group = trophy_groups.find(group_id); group != trophy_groups.cend()) + { + trophy_set = group->second.name.isEmpty() + ? tr("Group %1").arg(QString::fromStdString(group_id)) + : group->second.name; + trophy_set_detail = group->second.detail; + } + else + { + trophy_set = tr("Group %1").arg(QString::fromStdString(group_id)); + } + } + custom_table_widget_item* icon_item = new custom_table_widget_item(); icon_item->setData(Qt::UserRole, hidden, true); icon_item->setData(Qt::DecorationRole, placeholder); @@ -1740,6 +1997,12 @@ void trophy_manager_dialog::PopulateTrophyTable() custom_table_widget_item* type_item = new custom_table_widget_item(trophy_type); type_item->setData(Qt::UserRole, static_cast(details.trophyGrade), true); + custom_table_widget_item* trophy_set_item = new custom_table_widget_item(trophy_set); + if (!trophy_set_detail.isEmpty()) + { + trophy_set_item->setToolTip(trophy_set_detail); + } + m_trophy_table->setItem(i, static_cast(gui::trophy_list_columns::icon), icon_item); m_trophy_table->setItem(i, static_cast(gui::trophy_list_columns::name), new custom_table_widget_item(QString::fromStdString(details.name))); m_trophy_table->setItem(i, static_cast(gui::trophy_list_columns::description), new custom_table_widget_item(QString::fromStdString(details.description))); @@ -1748,6 +2011,7 @@ void trophy_manager_dialog::PopulateTrophyTable() m_trophy_table->setItem(i, static_cast(gui::trophy_list_columns::id), new custom_table_widget_item(QString::number(trophy_id), Qt::UserRole, trophy_id)); m_trophy_table->setItem(i, static_cast(gui::trophy_list_columns::platinum_link), new custom_table_widget_item(platinum_relevant, Qt::UserRole, platinum_link_id)); m_trophy_table->setItem(i, static_cast(gui::trophy_list_columns::time_unlocked), new custom_table_widget_item(datetime, Qt::UserRole, QVariant::fromValue(tick))); + m_trophy_table->setItem(i, static_cast(gui::trophy_list_columns::trophy_set), trophy_set_item); ++i; } @@ -1767,9 +2031,6 @@ void trophy_manager_dialog::ReadjustGameTable() const // Resize and fixate icon column m_game_table->resizeColumnToContents(static_cast(gui::trophy_game_list_columns::icon)); m_game_table->horizontalHeader()->setSectionResizeMode(static_cast(gui::trophy_game_list_columns::icon), QHeaderView::Fixed); - - // Shorten the last section to remove horizontal scrollbar if possible - m_game_table->resizeColumnToContents(static_cast(gui::trophy_game_list_columns::count) - 1); } void trophy_manager_dialog::ReadjustTrophyTable() const @@ -1781,9 +2042,7 @@ void trophy_manager_dialog::ReadjustTrophyTable() const // Resize and fixate icon column m_trophy_table->resizeColumnToContents(static_cast(gui::trophy_list_columns::icon)); - - // Shorten the last section to remove horizontal scrollbar if possible - m_trophy_table->resizeColumnToContents(static_cast(gui::trophy_list_columns::count) - 1); + m_trophy_table->horizontalHeader()->setSectionResizeMode(static_cast(gui::trophy_list_columns::icon), QHeaderView::Fixed); } bool trophy_manager_dialog::eventFilter(QObject *object, QEvent *event) @@ -1835,10 +2094,9 @@ bool trophy_manager_dialog::eventFilter(QObject *object, QEvent *event) m_save_icon_height = true; m_icon_slider->setSliderPosition(zoom_val + m_icon_slider->value()); } - else if (m_game_icon_slider && (is_game_table || is_game_scroll)) + else if (m_game_icon_size_spin && (is_game_table || is_game_scroll)) { - m_save_game_icon_size = true; - m_game_icon_slider->setSliderPosition(zoom_val + m_game_icon_slider->value()); + m_game_icon_size_spin->setValue(m_game_icon_size_spin->value() + zoom_val * 4); } return true; } @@ -1850,7 +2108,6 @@ void trophy_manager_dialog::closeEvent(QCloseEvent *event) { // Save gui settings m_gui_settings->SetValue(gui::tr_geometry, saveGeometry(), false); - m_gui_settings->SetValue(gui::tr_splitterState, m_splitter->saveState(), false); m_gui_settings->SetValue(gui::tr_games_state, m_game_table->horizontalHeader()->saveState(), false); m_gui_settings->SetValue(gui::tr_trophy_state, m_trophy_table->horizontalHeader()->saveState(), true); diff --git a/rpcs3/rpcs3qt/trophy_manager_dialog.h b/rpcs3/rpcs3qt/trophy_manager_dialog.h index 5190cd2908..33d90b6512 100644 --- a/rpcs3/rpcs3qt/trophy_manager_dialog.h +++ b/rpcs3/rpcs3qt/trophy_manager_dialog.h @@ -9,7 +9,8 @@ #include #include #include -#include +#include +#include #include #include @@ -18,6 +19,7 @@ class game_list; class QPushButton; +class QLineEdit; class gui_settings; class TROPUSRLoader; @@ -53,6 +55,7 @@ private Q_SLOTS: void ResizeGameIcons(); void ResizeTrophyIcons(); void ApplyFilter(); + void ApplyGameFilter(const QString& text); void ShowTrophyTableContextMenu(const QPoint& pos); void ShowGameTableContextMenu(const QPoint& pos); void DeleteOnlineTrophies(); @@ -88,6 +91,8 @@ private: Takes results from LoadTrophyFolderToDB and puts it into the UI. */ void PopulateTrophyTable(); + void OpenGameFromRow(int row); + void ShowGameList(); void ReadjustGameTable() const; void ReadjustTrophyTable() const; @@ -113,7 +118,10 @@ private: std::mutex m_trophies_db_mtx; QComboBox* m_game_combo; //! Lets you choose a game QLabel* m_game_progress; //! Shows you the current game's progress - QSplitter* m_splitter; //! Contains the game and trophy tables + QLabel* m_game_title = nullptr; + QLabel* m_game_communication_id = nullptr; + QLineEdit* m_game_search = nullptr; + QStackedWidget* m_stack = nullptr; game_list* m_trophy_table; //! UI element to display trophy stuff. game_list* m_game_table; //! UI element to display games. QPushButton* m_btn_sync_all_trophies = nullptr; @@ -136,7 +144,7 @@ private: int m_game_icon_size_index = 25; QSize m_game_icon_size = QSize(m_game_icon_size_index, m_game_icon_size_index); - bool m_save_game_icon_size = false; - QSlider* m_game_icon_slider = nullptr; + QSpinBox* m_game_icon_size_spin = nullptr; QColor m_game_icon_color; + int m_current_game_index = -1; };