From 789bab97be41cb5413cbc832a06729a10e2238eb Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 30 Mar 2026 12:39:46 +0200 Subject: [PATCH] Qt: add some sorting options to the screenshot manager --- rpcs3/rpcs3qt/main_window.cpp | 4 +- rpcs3/rpcs3qt/qt_utils.cpp | 5 + rpcs3/rpcs3qt/screenshot_item.cpp | 39 +++- rpcs3/rpcs3qt/screenshot_item.h | 17 +- rpcs3/rpcs3qt/screenshot_manager_dialog.cpp | 197 +++++++++++++++----- rpcs3/rpcs3qt/screenshot_manager_dialog.h | 34 +++- 6 files changed, 232 insertions(+), 64 deletions(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index b4776d2fc1..130434f0c2 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -3119,9 +3119,9 @@ void main_window::CreateConnects() m_game_list_frame->Refresh(true); // New user may have different games unlocked. }); - connect(ui->actionManage_Screenshots, &QAction::triggered, this, [] + connect(ui->actionManage_Screenshots, &QAction::triggered, this, [this] { - screenshot_manager_dialog* screenshot_manager = new screenshot_manager_dialog(); + screenshot_manager_dialog* screenshot_manager = new screenshot_manager_dialog(m_game_list_frame ? m_game_list_frame->GetGameInfo() : std::vector{}); screenshot_manager->show(); }); diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index 7c0760f3a8..ede9f6be9a 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -279,6 +279,11 @@ namespace gui exp_img.setDevicePixelRatio(device_pixel_ratio); exp_img.fill(Qt::transparent); + if (pixmap.isNull()) + { + return exp_img; + } + // Load scaled pixmap pixmap = pixmap.scaled(icon_size, Qt::KeepAspectRatio, mode); diff --git a/rpcs3/rpcs3qt/screenshot_item.cpp b/rpcs3/rpcs3qt/screenshot_item.cpp index 2d24c7cb36..2897cf968c 100644 --- a/rpcs3/rpcs3qt/screenshot_item.cpp +++ b/rpcs3/rpcs3qt/screenshot_item.cpp @@ -4,26 +4,37 @@ #include -screenshot_item::screenshot_item(QWidget* parent) +screenshot_item::screenshot_item(QWidget* parent, QSize icon_size, const QString& icon_path, const QPixmap& placeholder) : flow_widget_item(parent) + , m_icon_path(icon_path) + , m_icon_size(icon_size) { + setToolTip(icon_path); + cb_on_first_visibility = [this]() { m_thread.reset(QThread::create([this]() { thread_base::set_name("Screenshot item"); - const QPixmap pixmap = gui::utils::get_aligned_pixmap(icon_path, icon_size, 1.0, Qt::SmoothTransformation, gui::utils::align_h::center, gui::utils::align_v::center); + const QPixmap src_icon = QPixmap(m_icon_path); + if (src_icon.isNull()) return; + + const QPixmap pixmap = gui::utils::get_aligned_pixmap(src_icon, m_icon_size, 1.0, Qt::SmoothTransformation, gui::utils::align_h::center, gui::utils::align_v::center); Q_EMIT signal_icon_update(pixmap); })); m_thread->start(); }; - label = new QLabel(this); + m_label = new QLabel(this); + m_label->setPixmap(placeholder); + QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(label); + layout->addWidget(m_label); setLayout(layout); + + connect(this, &screenshot_item::signal_icon_update, this, &screenshot_item::update_icon, Qt::ConnectionType::QueuedConnection); } screenshot_item::~screenshot_item() @@ -33,3 +44,23 @@ screenshot_item::~screenshot_item() m_thread->wait(); } } + +void screenshot_item::update_icon(const QPixmap& pixmap) +{ + if (m_label) + { + m_label->setPixmap(pixmap); + } +} + +void screenshot_item::mouseDoubleClickEvent(QMouseEvent* ev) +{ + flow_widget_item::mouseDoubleClickEvent(ev); + + if (!ev) return; + + if (ev->button() == Qt::LeftButton) + { + Q_EMIT signal_icon_preview(m_icon_path); + } +} diff --git a/rpcs3/rpcs3qt/screenshot_item.h b/rpcs3/rpcs3qt/screenshot_item.h index 36f0232b1f..df223783f3 100644 --- a/rpcs3/rpcs3qt/screenshot_item.h +++ b/rpcs3/rpcs3qt/screenshot_item.h @@ -3,22 +3,29 @@ #include "flow_widget_item.h" #include #include +#include class screenshot_item : public flow_widget_item { Q_OBJECT public: - screenshot_item(QWidget* parent); + screenshot_item(QWidget* parent, QSize icon_size, const QString& icon_path, const QPixmap& placeholder); virtual ~screenshot_item(); - QString icon_path; - QSize icon_size; - QLabel* label{}; - private: + QLabel* m_label{}; + QString m_icon_path; + QSize m_icon_size; std::unique_ptr m_thread; +protected: + void mouseDoubleClickEvent(QMouseEvent* ev) override; + Q_SIGNALS: void signal_icon_update(const QPixmap& pixmap); + void signal_icon_preview(const QString& path); + +public Q_SLOTS: + void update_icon(const QPixmap& pixmap); }; diff --git a/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp b/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp index 3825dfd747..d99ce35605 100644 --- a/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp @@ -10,13 +10,15 @@ #include #include #include +#include #include #include #include LOG_CHANNEL(gui_log, "GUI"); -screenshot_manager_dialog::screenshot_manager_dialog(QWidget* parent) : QDialog(parent) +screenshot_manager_dialog::screenshot_manager_dialog(const std::vector& games, QWidget* parent) + : QDialog(parent), m_games(games) { setWindowTitle(tr("Screenshots")); setAttribute(Qt::WA_DeleteOnClose); @@ -28,11 +30,50 @@ screenshot_manager_dialog::screenshot_manager_dialog(QWidget* parent) : QDialog( m_placeholder = QPixmap(m_icon_size); m_placeholder.fill(Qt::gray); - connect(this, &screenshot_manager_dialog::signal_icon_preview, this, &screenshot_manager_dialog::show_preview); - connect(this, &screenshot_manager_dialog::signal_entry_parsed, this, &screenshot_manager_dialog::add_entry); + connect(this, &screenshot_manager_dialog::signal_entry_parsed, this, &screenshot_manager_dialog::add_entry, Qt::ConnectionType::QueuedConnection); - QVBoxLayout* layout = new QVBoxLayout; + m_combo_sort_filter = new QComboBox(); + m_combo_sort_filter->setSizeAdjustPolicy(QComboBox::AdjustToContents); + m_combo_sort_filter->addItem(tr("Sort by Game"), static_cast(sort_filter::game)); + m_combo_sort_filter->addItem(tr("Sort by Date"), static_cast(sort_filter::date)); + connect(m_combo_sort_filter, &QComboBox::currentIndexChanged, this, [this](int /*index*/){ reload(); }); + + m_combo_type_filter = new QComboBox(); + m_combo_type_filter->setSizeAdjustPolicy(QComboBox::AdjustToContents); + m_combo_type_filter->addItem(tr("All Screenshots"), static_cast(type_filter::all)); + m_combo_type_filter->addItem(tr("RPCS3 Screenshots"), static_cast(type_filter::rpcs3)); + m_combo_type_filter->addItem(tr("Cell Screenshots"), static_cast(type_filter::cell)); + connect(m_combo_type_filter, &QComboBox::currentIndexChanged, this, [this](int /*index*/){ reload(); }); + + m_combo_game_filter = new QComboBox(); + m_combo_game_filter->setSizeAdjustPolicy(QComboBox::AdjustToContents); + m_combo_game_filter->addItem(tr("All Games"), QString()); + connect(m_combo_game_filter, &QComboBox::currentIndexChanged, this, [this](int /*index*/){ reload(); }); + + QHBoxLayout* sort_layout = new QHBoxLayout(); + sort_layout->addWidget(m_combo_sort_filter); + QGroupBox* gb_sort = new QGroupBox(tr("Sort")); + gb_sort->setLayout(sort_layout); + + QHBoxLayout* type_layout = new QHBoxLayout(); + type_layout->addWidget(m_combo_type_filter); + QGroupBox* gb_type = new QGroupBox(tr("Filter Type")); + gb_type->setLayout(type_layout); + + QHBoxLayout* game_layout = new QHBoxLayout(); + game_layout->addWidget(m_combo_game_filter); + QGroupBox* gb_game = new QGroupBox(tr("Filter Game")); + gb_game->setLayout(game_layout); + + QHBoxLayout* top_layout = new QHBoxLayout(); + top_layout->addWidget(gb_sort); + top_layout->addWidget(gb_type); + top_layout->addWidget(gb_game); + top_layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum)); + + QVBoxLayout* layout = new QVBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); + layout->addLayout(top_layout); layout->addWidget(m_flow_widget); setLayout(layout); @@ -47,14 +88,8 @@ screenshot_manager_dialog::~screenshot_manager_dialog() void screenshot_manager_dialog::add_entry(const QString& path) { - screenshot_item* item = new screenshot_item(m_flow_widget); - ensure(item->label); - item->setToolTip(path); - item->installEventFilter(this); - item->label->setPixmap(m_placeholder); - item->icon_path = path; - item->icon_size = m_icon_size; - connect(item, &screenshot_item::signal_icon_update, this, &screenshot_manager_dialog::update_icon); + screenshot_item* item = new screenshot_item(m_flow_widget, m_icon_size, path, m_placeholder); + connect(item, &screenshot_item::signal_icon_preview, this, &screenshot_manager_dialog::show_preview); m_flow_widget->add_widget(item); } @@ -65,28 +100,74 @@ void screenshot_manager_dialog::show_preview(const QString& path) preview->show(); } -void screenshot_manager_dialog::update_icon(const QPixmap& pixmap) -{ - if (screenshot_item* item = static_cast(QObject::sender())) - { - if (item->label) - { - item->label->setPixmap(pixmap); - } - } -} - void screenshot_manager_dialog::reload() { m_abort_parsing = true; + m_parsing_watcher.disconnect(); gui::utils::stop_future_watcher(m_parsing_watcher, true); - const std::string screenshot_path_qt = fs::get_config_dir() + "screenshots/"; - const std::string screenshot_path_cell = rpcs3::utils::get_hdd0_dir() + "/photo/"; + const type_filter t_filter = static_cast(m_combo_type_filter->currentData().toInt()); + const sort_filter s_filter = static_cast(m_combo_sort_filter->currentData().toInt()); + const QString game_filter = m_combo_game_filter->currentData().toString(); + + const std::string screenshot_path_rpcs3 = fs::get_config_dir() + "screenshots/"; + const std::string screenshot_path_cell = rpcs3::utils::get_hdd0_dir() + "/photo/"; + + std::vector folders; + switch (t_filter) + { + case type_filter::all: + folders.push_back(screenshot_path_rpcs3); + folders.push_back(screenshot_path_cell); + break; + case type_filter::rpcs3: + folders.push_back(screenshot_path_rpcs3); + break; + case type_filter::cell: + folders.push_back(screenshot_path_cell); + break; + } m_flow_widget->clear(); + m_game_folders.clear(); m_abort_parsing = false; - m_parsing_watcher.setFuture(QtConcurrent::map(m_parsing_threads, [this, screenshot_path_qt, screenshot_path_cell](int index) + + connect(&m_parsing_watcher, &QFutureWatcher::finished, this, [this]() + { + std::vector> games; + for (const auto& [dirname, paths] : m_game_folders) + { + const std::string serial = dirname.toStdString(); + std::string text = serial; + for (const auto& game : m_games) + { + if (game && game->info.serial == serial) + { + text = fmt::format("%s (%s)", game->info.name, serial); + break; + } + } + games.push_back(std::pair(dirname, QString::fromStdString(text))); + } + + std::sort(games.begin(), games.end(), [](const std::pair& l, const std::pair& r) + { + return l.second < r.second; + }); + + const QString old_filter = m_combo_game_filter->currentData().toString(); + m_combo_game_filter->blockSignals(true); + m_combo_game_filter->clear(); + m_combo_game_filter->addItem(tr("All Games"), QString()); + for (const auto& [dirname, text] : games) + { + m_combo_game_filter->addItem(text, dirname); + } + m_combo_game_filter->setCurrentIndex(m_combo_game_filter->findData(old_filter)); + m_combo_game_filter->blockSignals(false); + }); + + m_parsing_watcher.setFuture(QtConcurrent::map(m_parsing_threads, [this, folders, game_filter, s_filter](int index) { if (index != 0) { @@ -95,26 +176,68 @@ void screenshot_manager_dialog::reload() const QStringList filter{ QStringLiteral("*.png") }; - for (const std::string& path : { screenshot_path_qt, screenshot_path_cell }) + for (const std::string& folder : folders) { if (m_abort_parsing) { return; } - if (path.empty()) + if (folder.empty()) { gui_log.error("Screenshot manager: Trying to load screenshots from empty path!"); continue; } - QDirIterator dir_iter(QString::fromStdString(path), filter, QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + QDirIterator dir_iter(QString::fromStdString(folder), filter, QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while (dir_iter.hasNext() && !m_abort_parsing) { - Q_EMIT signal_entry_parsed(dir_iter.next()); + QFileInfo info(dir_iter.next()); + const QString dirname = info.dir().dirName(); + m_game_folders[dirname].push_back(std::move(info)); } } + + switch (s_filter) + { + case sort_filter::game: + { + for (const auto& [dirname, infos] : m_game_folders) + { + if (game_filter.isEmpty() || game_filter == dirname) + { + for (const QFileInfo& info : infos) + { + Q_EMIT signal_entry_parsed(info.filePath()); + } + } + } + break; + } + case sort_filter::date: + { + std::vector sorted_infos; + for (const auto& [dirname, infos] : m_game_folders) + { + if (game_filter.isEmpty() || game_filter == dirname) + { + sorted_infos.insert(sorted_infos.end(), infos.begin(), infos.end()); + } + } + + std::sort(sorted_infos.begin(), sorted_infos.end(), [](const QFileInfo& a, const QFileInfo& b) + { + return a.lastModified() < b.lastModified(); + }); + + for (const QFileInfo& info : sorted_infos) + { + Q_EMIT signal_entry_parsed(info.filePath()); + } + break; + } + } })); } @@ -123,17 +246,3 @@ void screenshot_manager_dialog::showEvent(QShowEvent* event) QDialog::showEvent(event); reload(); } - -bool screenshot_manager_dialog::eventFilter(QObject* watched, QEvent* event) -{ - if (event && event->type() == QEvent::MouseButtonDblClick && static_cast(event)->button() == Qt::LeftButton) - { - if (screenshot_item* item = static_cast(watched)) - { - Q_EMIT signal_icon_preview(item->icon_path); - return true; - } - } - - return false; -} diff --git a/rpcs3/rpcs3qt/screenshot_manager_dialog.h b/rpcs3/rpcs3qt/screenshot_manager_dialog.h index ab4d7c13a8..a236eb5e0d 100644 --- a/rpcs3/rpcs3qt/screenshot_manager_dialog.h +++ b/rpcs3/rpcs3qt/screenshot_manager_dialog.h @@ -1,30 +1,29 @@ #pragma once #include "flow_widget.h" +#include "gui_game_info.h" +#include #include +#include #include #include #include -#include +#include #include +#include +#include class screenshot_manager_dialog : public QDialog { Q_OBJECT public: - screenshot_manager_dialog(QWidget* parent = nullptr); + screenshot_manager_dialog(const std::vector& games, QWidget* parent = nullptr); ~screenshot_manager_dialog(); - bool eventFilter(QObject* watched, QEvent* event) override; - Q_SIGNALS: void signal_entry_parsed(const QString& path); - void signal_icon_preview(const QString& path); - -public Q_SLOTS: - void update_icon(const QPixmap& pixmap); private Q_SLOTS: void add_entry(const QString& path); @@ -36,11 +35,28 @@ protected: private: void reload(); + enum class type_filter + { + all, + rpcs3, + cell + }; + + enum class sort_filter + { + game, + date + }; + + std::vector m_games; bool m_abort_parsing = false; const std::array m_parsing_threads{0}; QFutureWatcher m_parsing_watcher; flow_widget* m_flow_widget = nullptr; - + QComboBox* m_combo_sort_filter = nullptr; + QComboBox* m_combo_game_filter = nullptr; + QComboBox* m_combo_type_filter = nullptr; QSize m_icon_size; QPixmap m_placeholder; + std::map> m_game_folders; };