mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-02 21:11:25 +12:00
This will allow us to properly style the grid and also remove the need to refresh the whole grid on a window resize
415 lines
14 KiB
C++
415 lines
14 KiB
C++
#include "stdafx.h"
|
|
#include "game_list_table.h"
|
|
#include "game_list_delegate.h"
|
|
#include "game_list_frame.h"
|
|
#include "gui_settings.h"
|
|
#include "localized.h"
|
|
#include "custom_table_widget_item.h"
|
|
#include "persistent_settings.h"
|
|
#include "qt_utils.h"
|
|
|
|
#include "Emu/vfs_config.h"
|
|
#include "Utilities/StrUtil.h"
|
|
|
|
#include <QApplication>
|
|
#include <QHeaderView>
|
|
#include <QScrollBar>
|
|
#include <QStringBuilder>
|
|
|
|
game_list_table::game_list_table(game_list_frame* frame, std::shared_ptr<persistent_settings> persistent_settings)
|
|
: game_list(), m_game_list_frame(frame), m_persistent_settings(std::move(persistent_settings))
|
|
{
|
|
m_is_list_layout = true;
|
|
|
|
setShowGrid(false);
|
|
setItemDelegate(new game_list_delegate(this));
|
|
setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
setSelectionMode(QAbstractItemView::SingleSelection);
|
|
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
|
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
|
|
verticalScrollBar()->installEventFilter(this);
|
|
verticalScrollBar()->setSingleStep(20);
|
|
horizontalScrollBar()->setSingleStep(20);
|
|
verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
|
|
verticalHeader()->setVisible(false);
|
|
horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
horizontalHeader()->setHighlightSections(false);
|
|
horizontalHeader()->setSortIndicatorShown(true);
|
|
horizontalHeader()->setStretchLastSection(true);
|
|
horizontalHeader()->setDefaultSectionSize(150);
|
|
horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
|
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
|
setAlternatingRowColors(true);
|
|
installEventFilter(this);
|
|
setColumnCount(gui::column_count);
|
|
setMouseTracking(true);
|
|
|
|
connect(this, &game_list_table::size_on_disk_ready, this, [this](const game_info& game)
|
|
{
|
|
if (!game || !game->item) return;
|
|
if (QTableWidgetItem* size_item = item(static_cast<movie_item*>(game->item)->row(), gui::column_dir_size))
|
|
{
|
|
const u64& game_size = game->info.size_on_disk;
|
|
size_item->setText(game_size != umax ? gui::utils::format_byte_size(game_size) : tr("Unknown"));
|
|
size_item->setData(Qt::UserRole, QVariant::fromValue<qulonglong>(game_size));
|
|
}
|
|
});
|
|
|
|
connect(this, &game_list::IconReady, this, [this](const game_info& game)
|
|
{
|
|
if (!game || !game->item) return;
|
|
game->item->call_icon_func();
|
|
});
|
|
}
|
|
|
|
void game_list_table::restore_layout(const QByteArray& state)
|
|
{
|
|
// Resize to fit and get the ideal icon column width
|
|
resize_columns_to_contents();
|
|
const int icon_column_width = columnWidth(gui::column_icon);
|
|
|
|
// Restore header layout from last session
|
|
if (!horizontalHeader()->restoreState(state) && rowCount())
|
|
{
|
|
// Nothing to do
|
|
}
|
|
|
|
// Make sure no columns are squished
|
|
fix_narrow_columns();
|
|
|
|
// Make sure that the icon column is large enough for the actual items.
|
|
// This is important if the list appeared as empty when closing the software before.
|
|
horizontalHeader()->resizeSection(gui::column_icon, icon_column_width);
|
|
|
|
// Save new header state
|
|
horizontalHeader()->restoreState(horizontalHeader()->saveState());
|
|
}
|
|
|
|
void game_list_table::fix_narrow_columns()
|
|
{
|
|
QApplication::processEvents();
|
|
|
|
// handle columns (other than the icon column) that have zero width after showing them (stuck between others)
|
|
for (int col = 1; col < columnCount(); ++col)
|
|
{
|
|
if (isColumnHidden(col))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (columnWidth(col) <= horizontalHeader()->minimumSectionSize())
|
|
{
|
|
setColumnWidth(col, horizontalHeader()->minimumSectionSize());
|
|
}
|
|
}
|
|
}
|
|
|
|
void game_list_table::resize_columns_to_contents(int spacing)
|
|
{
|
|
verticalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);
|
|
horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);
|
|
|
|
// Make non-icon columns slighty bigger for better visuals
|
|
for (int i = 1; i < columnCount(); i++)
|
|
{
|
|
if (isColumnHidden(i))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int size = horizontalHeader()->sectionSize(i) + spacing;
|
|
horizontalHeader()->resizeSection(i, size);
|
|
}
|
|
}
|
|
|
|
void game_list_table::adjust_icon_column()
|
|
{
|
|
// Fixate vertical header and row height
|
|
verticalHeader()->setMinimumSectionSize(m_icon_size.height());
|
|
verticalHeader()->setMaximumSectionSize(m_icon_size.height());
|
|
|
|
// Resize the icon column
|
|
resizeColumnToContents(gui::column_icon);
|
|
|
|
// Shorten the last section to remove horizontal scrollbar if possible
|
|
resizeColumnToContents(gui::column_count - 1);
|
|
}
|
|
|
|
void game_list_table::sort(int game_count, int sort_column, Qt::SortOrder col_sort_order)
|
|
{
|
|
// Back-up old header sizes to handle unwanted column resize in case of zero search results
|
|
const int old_row_count = rowCount();
|
|
const int old_game_count = game_count;
|
|
|
|
std::vector<int> column_widths(columnCount());
|
|
for (int i = 0; i < columnCount(); i++)
|
|
{
|
|
column_widths[i] = columnWidth(i);
|
|
}
|
|
|
|
// Sorting resizes hidden columns, so unhide them as a workaround
|
|
std::vector<int> columns_to_hide;
|
|
|
|
for (int i = 0; i < columnCount(); i++)
|
|
{
|
|
if (isColumnHidden(i))
|
|
{
|
|
setColumnHidden(i, false);
|
|
columns_to_hide.push_back(i);
|
|
}
|
|
}
|
|
|
|
// Sort the list by column and sort order
|
|
sortByColumn(sort_column, col_sort_order);
|
|
|
|
// Hide columns again
|
|
for (int col : columns_to_hide)
|
|
{
|
|
setColumnHidden(col, true);
|
|
}
|
|
|
|
// Don't resize the columns if no game is shown to preserve the header settings
|
|
if (!rowCount())
|
|
{
|
|
for (int i = 0; i < columnCount(); i++)
|
|
{
|
|
setColumnWidth(i, column_widths[i]);
|
|
}
|
|
|
|
horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed);
|
|
return;
|
|
}
|
|
|
|
// Fixate vertical header and row height
|
|
verticalHeader()->setMinimumSectionSize(m_icon_size.height());
|
|
verticalHeader()->setMaximumSectionSize(m_icon_size.height());
|
|
resizeRowsToContents();
|
|
|
|
// Resize columns if the game list was empty before
|
|
if (!old_row_count && !old_game_count)
|
|
{
|
|
resize_columns_to_contents();
|
|
}
|
|
else
|
|
{
|
|
resizeColumnToContents(gui::column_icon);
|
|
}
|
|
|
|
// Fixate icon column
|
|
horizontalHeader()->setSectionResizeMode(gui::column_icon, QHeaderView::Fixed);
|
|
|
|
// Shorten the last section to remove horizontal scrollbar if possible
|
|
resizeColumnToContents(gui::column_count - 1);
|
|
}
|
|
|
|
void game_list_table::set_custom_config_icon(const game_info& game)
|
|
{
|
|
if (!game)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const QString serial = QString::fromStdString(game->info.serial);
|
|
|
|
for (int row = 0; row < rowCount(); ++row)
|
|
{
|
|
if (QTableWidgetItem* title_item = item(row, gui::column_name))
|
|
{
|
|
if (const QTableWidgetItem* serial_item = item(row, gui::column_serial); serial_item && serial_item->text() == serial)
|
|
{
|
|
title_item->setIcon(game_list_base::GetCustomConfigIcon(game));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void game_list_table::populate(
|
|
const std::vector<game_info>& game_data,
|
|
const QMap<QString, QString>& notes_map,
|
|
const QMap<QString, QString>& title_map,
|
|
const std::string& selected_item_id,
|
|
bool play_hover_movies)
|
|
{
|
|
clear_list();
|
|
|
|
setRowCount(::narrow<int>(game_data.size()));
|
|
|
|
// Default locale. Uses current Qt application language.
|
|
const QLocale locale{};
|
|
const Localized localized;
|
|
|
|
const QString game_icon_path = play_hover_movies ? QString::fromStdString(fs::get_config_dir() + "/Icons/game_icons/") : "";
|
|
const std::string dev_flash = g_cfg_vfs.get_dev_flash();
|
|
|
|
int row = 0;
|
|
int index = -1;
|
|
int selected_row = -1;
|
|
|
|
for (const auto& game : game_data)
|
|
{
|
|
index++;
|
|
|
|
const QString serial = QString::fromStdString(game->info.serial);
|
|
const QString title = title_map.value(serial, QString::fromStdString(game->info.name));
|
|
const QString notes = notes_map.value(serial);
|
|
|
|
// Icon
|
|
custom_table_widget_item* icon_item = new custom_table_widget_item;
|
|
game->item = icon_item;
|
|
|
|
icon_item->set_icon_func([this, icon_item, game](int)
|
|
{
|
|
if (!icon_item || !game)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (std::shared_ptr<QMovie> movie = icon_item->movie(); movie && icon_item->get_active())
|
|
{
|
|
icon_item->setData(Qt::DecorationRole, movie->currentPixmap().scaled(m_icon_size, Qt::KeepAspectRatio));
|
|
}
|
|
else
|
|
{
|
|
std::lock_guard lock(icon_item->pixmap_mutex);
|
|
|
|
icon_item->setData(Qt::DecorationRole, game->pxmap);
|
|
|
|
if (!game->has_hover_gif)
|
|
{
|
|
game->pxmap = {};
|
|
}
|
|
|
|
if (movie)
|
|
{
|
|
movie->stop();
|
|
}
|
|
}
|
|
});
|
|
|
|
icon_item->set_size_calc_func([this, game, cancel = icon_item->size_on_disk_loading_aborted(), dev_flash]()
|
|
{
|
|
if (game && game->info.size_on_disk == umax && (!cancel || !cancel->load()))
|
|
{
|
|
if (game->info.path.starts_with(dev_flash))
|
|
{
|
|
// Do not report size of apps inside /dev_flash (it does not make sense to do so)
|
|
game->info.size_on_disk = 0;
|
|
}
|
|
else
|
|
{
|
|
game->info.size_on_disk = fs::get_dir_size(game->info.path, 1, cancel.get());
|
|
}
|
|
|
|
if (!cancel || !cancel->load())
|
|
{
|
|
Q_EMIT size_on_disk_ready(game);
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (play_hover_movies && game->has_hover_gif)
|
|
{
|
|
icon_item->init_movie(game_icon_path % serial % "/hover.gif");
|
|
}
|
|
|
|
icon_item->setData(Qt::UserRole, index, true);
|
|
icon_item->setData(gui::custom_roles::game_role, QVariant::fromValue(game));
|
|
|
|
// Title
|
|
custom_table_widget_item* title_item = new custom_table_widget_item(title);
|
|
title_item->setIcon(game_list_base::GetCustomConfigIcon(game));
|
|
|
|
// Serial
|
|
custom_table_widget_item* serial_item = new custom_table_widget_item(game->info.serial);
|
|
|
|
if (!notes.isEmpty())
|
|
{
|
|
const QString tool_tip = tr("%0 [%1]\n\nNotes:\n%2").arg(title).arg(serial).arg(notes);
|
|
title_item->setToolTip(tool_tip);
|
|
serial_item->setToolTip(tool_tip);
|
|
}
|
|
|
|
// Move Support (http://www.psdevwiki.com/ps3/PARAM.SFO#ATTRIBUTE)
|
|
const bool supports_move = game->info.attr & 0x800000;
|
|
|
|
// Compatibility
|
|
custom_table_widget_item* compat_item = new custom_table_widget_item;
|
|
compat_item->setText(game->compat.text % (game->compat.date.isEmpty() ? QStringLiteral("") : " (" % game->compat.date % ")"));
|
|
compat_item->setData(Qt::UserRole, game->compat.index, true);
|
|
compat_item->setToolTip(game->compat.tooltip);
|
|
if (!game->compat.color.isEmpty())
|
|
{
|
|
compat_item->setData(Qt::DecorationRole, gui::utils::circle_pixmap(game->compat.color, devicePixelRatioF() * 2));
|
|
}
|
|
|
|
// Version
|
|
QString app_version = QString::fromStdString(game_list::GetGameVersion(game));
|
|
|
|
if (game->info.bootable && !game->compat.latest_version.isEmpty())
|
|
{
|
|
f64 top_ver = 0.0, app_ver = 0.0;
|
|
const bool unknown = app_version == localized.category.unknown;
|
|
const bool ok_app = !unknown && try_to_float(&app_ver, app_version.toStdString(), ::std::numeric_limits<s32>::min(), ::std::numeric_limits<s32>::max());
|
|
const bool ok_top = !unknown && try_to_float(&top_ver, game->compat.latest_version.toStdString(), ::std::numeric_limits<s32>::min(), ::std::numeric_limits<s32>::max());
|
|
|
|
// If the app is bootable and the compat database contains info about the latest patch version:
|
|
// add a hint for available software updates if the app version is unknown or lower than the latest version.
|
|
if (unknown || (ok_top && ok_app && top_ver > app_ver))
|
|
{
|
|
app_version = tr("%0 (Update available: %1)").arg(app_version, game->compat.latest_version);
|
|
}
|
|
}
|
|
|
|
// Playtimes
|
|
const quint64 elapsed_ms = m_persistent_settings->GetPlaytime(serial);
|
|
|
|
// Last played (support outdated values)
|
|
QDateTime last_played;
|
|
const QString last_played_str = m_persistent_settings->GetLastPlayed(serial);
|
|
|
|
if (!last_played_str.isEmpty())
|
|
{
|
|
last_played = QDateTime::fromString(last_played_str, gui::persistent::last_played_date_format);
|
|
|
|
if (!last_played.isValid())
|
|
{
|
|
last_played = QDateTime::fromString(last_played_str, gui::persistent::last_played_date_format_old);
|
|
}
|
|
}
|
|
|
|
const u64 game_size = game->info.size_on_disk;
|
|
|
|
setItem(row, gui::column_icon, icon_item);
|
|
setItem(row, gui::column_name, title_item);
|
|
setItem(row, gui::column_serial, serial_item);
|
|
setItem(row, gui::column_firmware, new custom_table_widget_item(game->info.fw));
|
|
setItem(row, gui::column_version, new custom_table_widget_item(app_version));
|
|
setItem(row, gui::column_category, new custom_table_widget_item(game->localized_category));
|
|
setItem(row, gui::column_path, new custom_table_widget_item(game->info.path));
|
|
setItem(row, gui::column_move, new custom_table_widget_item((supports_move ? tr("Supported") : tr("Not Supported")).toStdString(), Qt::UserRole, !supports_move));
|
|
setItem(row, gui::column_resolution, new custom_table_widget_item(Localized::GetStringFromU32(game->info.resolution, localized.resolution.mode, true)));
|
|
setItem(row, gui::column_sound, new custom_table_widget_item(Localized::GetStringFromU32(game->info.sound_format, localized.sound.format, true)));
|
|
setItem(row, gui::column_parental, new custom_table_widget_item(Localized::GetStringFromU32(game->info.parental_lvl, localized.parental.level), Qt::UserRole, game->info.parental_lvl));
|
|
setItem(row, gui::column_last_play, new custom_table_widget_item(locale.toString(last_played, last_played >= QDateTime::currentDateTime().addDays(-7) ? gui::persistent::last_played_date_with_time_of_day_format : gui::persistent::last_played_date_format_new), Qt::UserRole, last_played));
|
|
setItem(row, gui::column_playtime, new custom_table_widget_item(elapsed_ms == 0 ? tr("Never played") : localized.GetVerboseTimeByMs(elapsed_ms), Qt::UserRole, elapsed_ms));
|
|
setItem(row, gui::column_compat, compat_item);
|
|
setItem(row, gui::column_dir_size, new custom_table_widget_item(game_size != umax ? gui::utils::format_byte_size(game_size) : tr("Unknown"), Qt::UserRole, QVariant::fromValue<qulonglong>(game_size)));
|
|
|
|
if (selected_item_id == game->info.path + game->info.icon_path)
|
|
{
|
|
selected_row = row;
|
|
}
|
|
|
|
row++;
|
|
}
|
|
|
|
selectRow(selected_row);
|
|
}
|
|
|
|
void game_list_table::repaint_icons(QList<game_info>& game_data, const QColor& icon_color, const QSize& icon_size, qreal device_pixel_ratio)
|
|
{
|
|
game_list_base::repaint_icons(game_data, icon_color, icon_size, device_pixel_ratio);
|
|
adjust_icon_column();
|
|
}
|