#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 #include #include game_list_table::game_list_table(game_list_frame* frame, std::shared_ptr 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()->setSingleStep(20); horizontalScrollBar()->setSingleStep(20); verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); verticalHeader()->setVisible(false); horizontalHeader()->setHighlightSections(false); horizontalHeader()->setSortIndicatorShown(true); horizontalHeader()->setStretchLastSection(true); horizontalHeader()->setDefaultSectionSize(150); horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); setContextMenuPolicy(Qt::CustomContextMenu); setAlternatingRowColors(true); setColumnCount(static_cast(gui::game_list_columns::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(game->item)->row(), static_cast(gui::game_list_columns::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(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(static_cast(gui::game_list_columns::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(static_cast(gui::game_list_columns::icon), icon_column_width); // Save new header state horizontalHeader()->restoreState(horizontalHeader()->saveState()); } 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(static_cast(gui::game_list_columns::icon)); // Shorten the last section to remove horizontal scrollbar if possible resizeColumnToContents(static_cast(gui::game_list_columns::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 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 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(static_cast(gui::game_list_columns::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(static_cast(gui::game_list_columns::icon)); } // Fixate icon column horizontalHeader()->setSectionResizeMode(static_cast(gui::game_list_columns::icon), QHeaderView::Fixed); // Shorten the last section to remove horizontal scrollbar if possible resizeColumnToContents(static_cast(gui::game_list_columns::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, static_cast(gui::game_list_columns::name))) { if (const QTableWidgetItem* serial_item = item(row, static_cast(gui::game_list_columns::serial)); serial_item && serial_item->text() == serial) { title_item->setIcon(game_list_base::GetCustomConfigIcon(game)); } } } } void game_list_table::populate( const std::vector& game_data, const QMap& notes_map, const QMap& title_map, const std::string& selected_item_id, bool play_hover_movies) { clear_list(); setRowCount(::narrow(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 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::min(), ::std::numeric_limits::max()); const bool ok_top = !unknown && try_to_float(&top_ver, game->compat.latest_version.toStdString(), ::std::numeric_limits::min(), ::std::numeric_limits::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, static_cast(gui::game_list_columns::icon), icon_item); setItem(row, static_cast(gui::game_list_columns::name), title_item); setItem(row, static_cast(gui::game_list_columns::serial), serial_item); setItem(row, static_cast(gui::game_list_columns::firmware), new custom_table_widget_item(game->info.fw)); setItem(row, static_cast(gui::game_list_columns::version), new custom_table_widget_item(app_version)); setItem(row, static_cast(gui::game_list_columns::category), new custom_table_widget_item(game->localized_category)); setItem(row, static_cast(gui::game_list_columns::path), new custom_table_widget_item(game->info.path)); setItem(row, static_cast(gui::game_list_columns::move), new custom_table_widget_item((supports_move ? tr("Supported") : tr("Not Supported")).toStdString(), Qt::UserRole, !supports_move)); setItem(row, static_cast(gui::game_list_columns::resolution), new custom_table_widget_item(Localized::GetStringFromU32(game->info.resolution, localized.resolution.mode, true))); setItem(row, static_cast(gui::game_list_columns::sound), new custom_table_widget_item(Localized::GetStringFromU32(game->info.sound_format, localized.sound.format, true))); setItem(row, static_cast(gui::game_list_columns::parental), new custom_table_widget_item(Localized::GetStringFromU32(game->info.parental_lvl, localized.parental.level), Qt::UserRole, game->info.parental_lvl)); setItem(row, static_cast(gui::game_list_columns::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, static_cast(gui::game_list_columns::playtime), new custom_table_widget_item(elapsed_ms == 0 ? tr("Never played") : localized.GetVerboseTimeByMs(elapsed_ms), Qt::UserRole, elapsed_ms)); setItem(row, static_cast(gui::game_list_columns::compat), compat_item); setItem(row, static_cast(gui::game_list_columns::dir_size), new custom_table_widget_item(game_size != umax ? gui::utils::format_byte_size(game_size) : tr("Unknown"), Qt::UserRole, QVariant::fromValue(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_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(); }