Files
pcsx2/pcsx2-qt/GameList/GameListModel.cpp
T
RedDevilus 03f7e9e15a Qt: More flags and regions + enabling more icons
In reality it will show multiple flags like Korean flag but the flags aren't correct due to the structure in the GameDB actually being the languages more than the actual region.
At least people will have Korean flags and others for now and fix GameDB later as it's still a nightly/development cycle.

Though there are about 30 country flags included for future usage as of now.
2022-06-03 16:23:54 +01:00

566 lines
17 KiB
C++

/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "GameListModel.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include <QtCore/QDate>
#include <QtCore/QDateTime>
#include <QtGui/QGuiApplication>
#include <QtGui/QIcon>
#include <QtGui/QPainter>
static constexpr std::array<const char*, GameListModel::Column_Count> s_column_names = {
{"Type", "Code", "Title", "File Title", "CRC", "Size", "Region", "Compatibility", "Cover"}};
static constexpr int COVER_ART_WIDTH = 350;
static constexpr int COVER_ART_HEIGHT = 512;
static constexpr int COVER_ART_SPACING = 32;
static int DPRScale(int size, float dpr)
{
return static_cast<int>(static_cast<float>(size) * dpr);
}
static int DPRUnscale(int size, float dpr)
{
return static_cast<int>(static_cast<float>(size) / dpr);
}
static void resizeAndPadPixmap(QPixmap* pm, int expected_width, int expected_height, float dpr)
{
const int dpr_expected_width = DPRScale(expected_width, dpr);
const int dpr_expected_height = DPRScale(expected_height, dpr);
if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height)
return;
*pm = pm->scaled(dpr_expected_width, dpr_expected_height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height)
return;
// QPainter works in unscaled coordinates.
int xoffs = 0;
int yoffs = 0;
if (pm->width() < dpr_expected_width)
xoffs = DPRUnscale((dpr_expected_width - pm->width()) / 2, dpr);
if (pm->height() < dpr_expected_height)
yoffs = DPRUnscale((dpr_expected_height - pm->height()) / 2, dpr);
QPixmap padded_image(dpr_expected_width, dpr_expected_height);
padded_image.setDevicePixelRatio(dpr);
padded_image.fill(Qt::transparent);
QPainter painter;
if (painter.begin(&padded_image))
{
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawPixmap(xoffs, yoffs, *pm);
painter.setCompositionMode(QPainter::CompositionMode_Destination);
painter.fillRect(padded_image.rect(), QColor(0, 0, 0, 0));
painter.end();
}
*pm = padded_image;
}
static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int width, int height, float scale,
const std::string& title)
{
const float dpr = qApp->devicePixelRatio();
QPixmap pm(placeholder_pixmap.copy());
pm.setDevicePixelRatio(dpr);
if (pm.isNull())
return QPixmap();
resizeAndPadPixmap(&pm, width, height, dpr);
QPainter painter;
if (painter.begin(&pm))
{
QFont font;
font.setPointSize(std::max(static_cast<int>(32.0f * scale), 1));
painter.setFont(font);
painter.setPen(Qt::white);
const QRect text_rc(0, 0, static_cast<int>(static_cast<float>(width)),
static_cast<int>(static_cast<float>(height)));
painter.drawText(text_rc, Qt::AlignCenter | Qt::TextWordWrap, QString::fromStdString(title));
painter.end();
}
return pm;
}
std::optional<GameListModel::Column> GameListModel::getColumnIdForName(std::string_view name)
{
for (int column = 0; column < Column_Count; column++)
{
if (name == s_column_names[column])
return static_cast<Column>(column);
}
return std::nullopt;
}
const char* GameListModel::getColumnName(Column col)
{
return s_column_names[static_cast<int>(col)];
}
GameListModel::GameListModel(QObject* parent /* = nullptr */)
: QAbstractTableModel(parent)
{
loadCommonImages();
setColumnDisplayNames();
}
GameListModel::~GameListModel() = default;
void GameListModel::setCoverScale(float scale)
{
if (m_cover_scale == scale)
return;
m_cover_pixmap_cache.clear();
m_cover_scale = scale;
}
void GameListModel::refreshCovers()
{
m_cover_pixmap_cache.clear();
refresh();
}
int GameListModel::getCoverArtWidth() const
{
return std::max(static_cast<int>(static_cast<float>(COVER_ART_WIDTH) * m_cover_scale), 1);
}
int GameListModel::getCoverArtHeight() const
{
return std::max(static_cast<int>(static_cast<float>(COVER_ART_HEIGHT) * m_cover_scale), 1);
}
int GameListModel::getCoverArtSpacing() const
{
return std::max(static_cast<int>(static_cast<float>(COVER_ART_SPACING) * m_cover_scale), 1);
}
int GameListModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return static_cast<int>(GameList::GetEntryCount());
}
int GameListModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return Column_Count;
}
QVariant GameListModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return {};
const int row = index.row();
if (row < 0 || row >= static_cast<int>(GameList::GetEntryCount()))
return {};
const auto lock = GameList::GetLock();
const GameList::Entry* ge = GameList::GetEntryByIndex(row);
if (!ge)
return {};
switch (role)
{
case Qt::DisplayRole:
{
switch (index.column())
{
case Column_Serial:
return QString::fromStdString(ge->serial);
case Column_Title:
return QString::fromStdString(ge->title);
case Column_FileTitle:
{
const std::string_view file_title(Path::GetFileTitle(ge->path));
return QString::fromUtf8(file_title.data(), static_cast<int>(file_title.length()));
}
case Column_CRC:
return QStringLiteral("%1").arg(ge->crc, 8, 16, QChar('0'));
case Column_Size:
return QString("%1 MB").arg(static_cast<double>(ge->total_size) / 1048576.0, 0, 'f', 2);
case Column_Cover:
{
if (m_show_titles_for_covers)
return QString::fromStdString(ge->title);
else
return {};
}
default:
return {};
}
}
case Qt::InitialSortOrderRole:
{
switch (index.column())
{
case Column_Type:
return static_cast<int>(ge->type);
case Column_Serial:
return QString::fromStdString(ge->serial);
case Column_Title:
case Column_Cover:
return QString::fromStdString(ge->title);
case Column_FileTitle:
{
const std::string_view file_title(Path::GetFileTitle(ge->path));
return QString::fromUtf8(file_title.data(), static_cast<int>(file_title.length()));
}
case Column_CRC:
return static_cast<int>(ge->crc);
case Column_Region:
return static_cast<int>(ge->region);
case Column_Compatibility:
return static_cast<int>(ge->compatibility_rating);
case Column_Size:
return static_cast<qulonglong>(ge->total_size);
default:
return {};
}
}
case Qt::DecorationRole:
{
switch (index.column())
{
case Column_Type:
{
switch (ge->type)
{
case GameList::EntryType::PS1Disc:
case GameList::EntryType::PS2Disc:
// return ((ge->settings.GetUserSettingsCount() > 0) ? m_type_disc_with_settings_pixmap : // m_type_disc_pixmap);
return m_type_disc_pixmap;
case GameList::EntryType::Playlist:
return m_type_playlist_pixmap;
case GameList::EntryType::ELF:
default:
return m_type_exe_pixmap;
}
}
case Column_Region:
{
switch (ge->region)
{
case GameList::Region::NTSC_B:
return m_region_b_pixmap;
case GameList::Region::NTSC_C:
return m_region_c_pixmap;
case GameList::Region::NTSC_HK:
return m_region_hk_pixmap;
case GameList::Region::NTSC_J:
return m_region_jp_pixmap;
case GameList::Region::NTSC_K:
return m_region_k_pixmap;
case GameList::Region::NTSC_T:
return m_region_t_pixmap;
case GameList::Region::NTSC_U:
return m_region_us_pixmap;
case GameList::Region::PAL:
return m_region_eu_pixmap;
case GameList::Region::PAL_A:
return m_region_a_pixmap;
case GameList::Region::PAL_AF:
return m_region_af_pixmap;
case GameList::Region::PAL_AU:
return m_region_au_pixmap;
case GameList::Region::PAL_BE:
return m_region_be_pixmap;
case GameList::Region::PAL_E:
return m_region_eu_pixmap;
case GameList::Region::PAL_F:
return m_region_f_pixmap;
case GameList::Region::PAL_FI:
return m_region_fi_pixmap;
case GameList::Region::PAL_G:
return m_region_g_pixmap;
case GameList::Region::PAL_GR:
return m_region_gr_pixmap;
case GameList::Region::PAL_I:
return m_region_i_pixmap;
case GameList::Region::PAL_IN:
return m_region_in_pixmap;
case GameList::Region::PAL_M:
return m_region_eu_pixmap;
case GameList::Region::PAL_NL:
return m_region_nl_pixmap;
case GameList::Region::PAL_NO:
return m_region_no_pixmap;
case GameList::Region::PAL_P:
return m_region_p_pixmap;
case GameList::Region::PAL_R:
return m_region_r_pixmap;
case GameList::Region::PAL_S:
return m_region_s_pixmap;
case GameList::Region::PAL_SC:
return m_region_sc_pixmap;
case GameList::Region::PAL_SW:
return m_region_sw_pixmap;
case GameList::Region::PAL_SWI:
return m_region_swi_pixmap;
case GameList::Region::PAL_UK:
return m_region_uk_pixmap;
case GameList::Region::Other:
default:
return m_region_other_pixmap;
}
}
case Column_Compatibility:
{
return m_compatibility_pixmaps[static_cast<int>(
(static_cast<u32>(ge->compatibility_rating) >= GameList::CompatibilityRatingCount) ?
GameList::CompatibilityRating::Unknown :
ge->compatibility_rating)];
}
case Column_Cover:
{
auto it = m_cover_pixmap_cache.find(ge->path);
if (it != m_cover_pixmap_cache.end())
return it->second;
QPixmap image;
std::string path = GameList::GetCoverImagePathForEntry(ge);
if (!path.empty())
{
const float dpr = qApp->devicePixelRatio();
image = QPixmap(QString::fromStdString(path));
if (!image.isNull())
{
image.setDevicePixelRatio(dpr);
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), dpr);
}
}
if (image.isNull())
{
image = createPlaceholderImage(m_placeholder_pixmap, getCoverArtWidth(), getCoverArtHeight(), m_cover_scale,
ge->title);
}
m_cover_pixmap_cache.emplace(ge->path, image);
return image;
}
break;
default:
return {};
}
default:
return {};
}
}
}
QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal || role != Qt::DisplayRole || section < 0 || section >= Column_Count)
return {};
return m_column_display_names[section];
}
void GameListModel::refresh()
{
beginResetModel();
endResetModel();
}
bool GameListModel::titlesLessThan(int left_row, int right_row) const
{
if (left_row < 0 || left_row >= static_cast<int>(GameList::GetEntryCount()) || right_row < 0 ||
right_row >= static_cast<int>(GameList::GetEntryCount()))
{
return false;
}
const GameList::Entry* left = GameList::GetEntryByIndex(left_row);
const GameList::Entry* right = GameList::GetEntryByIndex(right_row);
return (StringUtil::Strcasecmp(left->title.c_str(), right->title.c_str()) < 0);
}
bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const
{
if (!left_index.isValid() || !right_index.isValid())
return false;
const int left_row = left_index.row();
const int right_row = right_index.row();
if (left_row < 0 || left_row >= static_cast<int>(GameList::GetEntryCount()) || right_row < 0 ||
right_row >= static_cast<int>(GameList::GetEntryCount()))
{
return false;
}
const auto lock = GameList::GetLock();
const GameList::Entry* left = GameList::GetEntryByIndex(left_row);
const GameList::Entry* right = GameList::GetEntryByIndex(right_row);
if (!left || !right)
return false;
switch (column)
{
case Column_Type:
{
if (left->type == right->type)
return titlesLessThan(left_row, right_row);
return static_cast<int>(left->type) < static_cast<int>(right->type);
}
case Column_Serial:
{
if (left->serial == right->serial)
return titlesLessThan(left_row, right_row);
return (StringUtil::Strcasecmp(left->serial.c_str(), right->serial.c_str()) < 0);
}
case Column_Title:
{
return titlesLessThan(left_row, right_row);
}
case Column_FileTitle:
{
const std::string_view file_title_left(Path::GetFileTitle(left->path));
const std::string_view file_title_right(Path::GetFileTitle(right->path));
if (file_title_left == file_title_right)
return titlesLessThan(left_row, right_row);
const std::size_t smallest = std::min(file_title_left.size(), file_title_right.size());
return (StringUtil::Strncasecmp(file_title_left.data(), file_title_right.data(), smallest) < 0);
}
case Column_Region:
{
if (left->region == right->region)
return titlesLessThan(left_row, right_row);
return (static_cast<int>(left->region) < static_cast<int>(right->region));
}
case Column_Compatibility:
{
if (left->compatibility_rating == right->compatibility_rating)
return titlesLessThan(left_row, right_row);
return (static_cast<int>(left->compatibility_rating) < static_cast<int>(right->compatibility_rating));
}
case Column_Size:
{
if (left->total_size == right->total_size)
return titlesLessThan(left_row, right_row);
return (left->total_size < right->total_size);
}
case Column_CRC:
{
if (left->crc == right->crc)
return titlesLessThan(left_row, right_row);
return (left->crc < right->crc);
}
default:
return false;
}
}
void GameListModel::loadCommonImages()
{
m_type_disc_pixmap = QIcon(QStringLiteral(":/icons/media-optical-24.png")).pixmap(QSize(24, 24));
m_type_disc_with_settings_pixmap = QIcon(QStringLiteral(":/icons/media-optical-gear-24.png")).pixmap(QSize(24, 24));
m_type_exe_pixmap = QIcon(QStringLiteral(":/icons/applications-system-24.png")).pixmap(QSize(24, 24));
m_type_playlist_pixmap = QIcon(QStringLiteral(":/icons/address-book-new-22.png")).pixmap(QSize(22, 22));
m_region_eu_pixmap = QIcon(QStringLiteral(":/icons/flag-eu.png")).pixmap(QSize(42, 30));
m_region_jp_pixmap = QIcon(QStringLiteral(":/icons/flag-jp.png")).pixmap(QSize(42, 30));
m_region_us_pixmap = QIcon(QStringLiteral(":/icons/flag-us.png")).pixmap(QSize(42, 30));
m_region_a_pixmap = QIcon(QStringLiteral(":/icons/flag-a.png")).pixmap(QSize(42, 30));
m_region_af_pixmap = QIcon(QStringLiteral(":/icons/flag-af.png")).pixmap(QSize(42, 30));
m_region_au_pixmap = QIcon(QStringLiteral(":/icons/flag-au.png")).pixmap(QSize(42, 30));
m_region_b_pixmap = QIcon(QStringLiteral(":/icons/flag-b.png")).pixmap(QSize(42, 30));
m_region_be_pixmap = QIcon(QStringLiteral(":/icons/flag-be.png")).pixmap(QSize(42, 30));
m_region_c_pixmap = QIcon(QStringLiteral(":/icons/flag-c.png")).pixmap(QSize(42, 30));
m_region_f_pixmap = QIcon(QStringLiteral(":/icons/flag-f.png")).pixmap(QSize(42, 30));
m_region_fi_pixmap = QIcon(QStringLiteral(":/icons/flag-fi.png")).pixmap(QSize(42, 30));
m_region_g_pixmap = QIcon(QStringLiteral(":/icons/flag-g.png")).pixmap(QSize(42, 30));
m_region_gr_pixmap = QIcon(QStringLiteral(":/icons/flag-gr.png")).pixmap(QSize(42, 30));
m_region_hk_pixmap = QIcon(QStringLiteral(":/icons/flag-hk.png")).pixmap(QSize(42, 30));
m_region_i_pixmap = QIcon(QStringLiteral(":/icons/flag-i.png")).pixmap(QSize(42, 30));
m_region_in_pixmap = QIcon(QStringLiteral(":/icons/flag-in.png")).pixmap(QSize(42, 30));
m_region_k_pixmap = QIcon(QStringLiteral(":/icons/flag-k.png")).pixmap(QSize(42, 30));
m_region_nl_pixmap = QIcon(QStringLiteral(":/icons/flag-nl.png")).pixmap(QSize(42, 30));
m_region_no_pixmap = QIcon(QStringLiteral(":/icons/flag-no.png")).pixmap(QSize(42, 30));
m_region_p_pixmap = QIcon(QStringLiteral(":/icons/flag-p.png")).pixmap(QSize(42, 30));
m_region_r_pixmap = QIcon(QStringLiteral(":/icons/flag-r.png")).pixmap(QSize(42, 30));
m_region_s_pixmap = QIcon(QStringLiteral(":/icons/flag-s.png")).pixmap(QSize(42, 30));
m_region_sc_pixmap = QIcon(QStringLiteral(":/icons/flag-sc.png")).pixmap(QSize(42, 30));
m_region_sw_pixmap = QIcon(QStringLiteral(":/icons/flag-sw.png")).pixmap(QSize(42, 30));
m_region_swi_pixmap = QIcon(QStringLiteral(":/icons/flag-swi.png")).pixmap(QSize(42, 30));
m_region_other_pixmap = QIcon(QStringLiteral(":/icons/flag-other.png")).pixmap(QSize(42, 30));
for (u32 i = 1; i < GameList::CompatibilityRatingCount; i++)
m_compatibility_pixmaps[i].load(QStringLiteral(":/icons/star-%1.png").arg(i - 1));
m_placeholder_pixmap.load(QString::fromStdString(Path::Combine(EmuFolders::Resources, "cover-placeholder.png")));
}
void GameListModel::setColumnDisplayNames()
{
m_column_display_names[Column_Type] = tr("Type");
m_column_display_names[Column_Serial] = tr("Code");
m_column_display_names[Column_Title] = tr("Title");
m_column_display_names[Column_FileTitle] = tr("File Title");
m_column_display_names[Column_CRC] = tr("CRC");
m_column_display_names[Column_Size] = tr("Size");
m_column_display_names[Column_Region] = tr("Region");
m_column_display_names[Column_Compatibility] = tr("Compatibility");
}