Qt: add some sorting options to the screenshot manager

This commit is contained in:
Megamouse 2026-03-30 12:39:46 +02:00
parent 3e60bd2aa6
commit 789bab97be
6 changed files with 232 additions and 64 deletions

View file

@ -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<game_info>{});
screenshot_manager->show();
});

View file

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

View file

@ -4,26 +4,37 @@
#include <QVBoxLayout>
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);
}
}

View file

@ -3,22 +3,29 @@
#include "flow_widget_item.h"
#include <QLabel>
#include <QThread>
#include <QMouseEvent>
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<QThread> 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);
};

View file

@ -10,13 +10,15 @@
#include <QApplication>
#include <QDir>
#include <QDirIterator>
#include <QGroupBox>
#include <QScreen>
#include <QVBoxLayout>
#include <QtConcurrent>
LOG_CHANNEL(gui_log, "GUI");
screenshot_manager_dialog::screenshot_manager_dialog(QWidget* parent) : QDialog(parent)
screenshot_manager_dialog::screenshot_manager_dialog(const std::vector<game_info>& 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<int>(sort_filter::game));
m_combo_sort_filter->addItem(tr("Sort by Date"), static_cast<int>(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<int>(type_filter::all));
m_combo_type_filter->addItem(tr("RPCS3 Screenshots"), static_cast<int>(type_filter::rpcs3));
m_combo_type_filter->addItem(tr("Cell Screenshots"), static_cast<int>(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<screenshot_item*>(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<type_filter>(m_combo_type_filter->currentData().toInt());
const sort_filter s_filter = static_cast<sort_filter>(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<std::string> 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<void>::finished, this, [this]()
{
std::vector<std::pair<QString, QString>> 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<QString, QString>& l, const std::pair<QString, QString>& 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<QFileInfo> 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<QMouseEvent*>(event)->button() == Qt::LeftButton)
{
if (screenshot_item* item = static_cast<screenshot_item*>(watched))
{
Q_EMIT signal_icon_preview(item->icon_path);
return true;
}
}
return false;
}

View file

@ -1,30 +1,29 @@
#pragma once
#include "flow_widget.h"
#include "gui_game_info.h"
#include <QComboBox>
#include <QDialog>
#include <QFileInfo>
#include <QFutureWatcher>
#include <QPixmap>
#include <QSize>
#include <QEvent>
#include <QShowEvent>
#include <array>
#include <vector>
#include <map>
class screenshot_manager_dialog : public QDialog
{
Q_OBJECT
public:
screenshot_manager_dialog(QWidget* parent = nullptr);
screenshot_manager_dialog(const std::vector<game_info>& 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<game_info> m_games;
bool m_abort_parsing = false;
const std::array<int, 1> m_parsing_threads{0};
QFutureWatcher<void> 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<QString, std::vector<QFileInfo>> m_game_folders;
};