Use a proxy model for the GameList.

This lets us sort by the underlying integers while only displaying the
icons. Currently, in both DolphinQt and DolphinQt2, we display both the
icon and the integer, but cut off the column width to not show the
integer. We also currently sort by the size's formatted string, not by
the size itself, which leads to "1 MB" sorting to less than "2 KB". This
commit fixes these issues.

In the future, we can use the filter methods here to allow for
searching for games.
This commit is contained in:
spxtr 2015-11-28 10:00:03 -08:00
parent 5a1729877e
commit 59bdeb411c
7 changed files with 153 additions and 89 deletions

View File

@ -13,6 +13,7 @@ set(SRCS
GameList/GameList.cpp
GameList/GameTracker.cpp
GameList/GameListModel.cpp
GameList/GameListProxyModel.cpp
)
list(APPEND LIBS core uicommon)

View File

@ -6,12 +6,14 @@
#include "Core/ConfigManager.h"
#include "DolphinQt2/GameList/GameList.h"
#include "DolphinQt2/GameList/GameListProxyModel.h"
GameList::GameList(QWidget* parent): QStackedWidget(parent)
{
m_model = new GameListModel(this);
m_proxy = new QSortFilterProxyModel(this);
m_proxy = new GameListProxyModel(this);
m_proxy->setSourceModel(m_model);
m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
MakeTableView();
MakeListView();
@ -36,31 +38,25 @@ void GameList::MakeTableView()
m_table->setSortingEnabled(true);
m_table->setCurrentIndex(QModelIndex());
// These fixed column widths make it so that the DisplayRole is cut
// off, which lets us see the icon but sort by the actual value.
// It's a bit of a hack. To do it right we need to subclass
// QSortFilterProxyModel and not show those items.
// FIXME These icon image are overly wide and should be cut down to size,
// then we can remove these lines.
m_table->setColumnWidth(GameListModel::COL_PLATFORM, 52);
m_table->setColumnWidth(GameListModel::COL_COUNTRY, 38);
m_table->setColumnWidth(GameListModel::COL_RATING, 52);
// This column is for the icon view. Hide it.
m_table->setColumnHidden(GameListModel::COL_LARGE_ICON, true);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_PLATFORM, QHeaderView::Fixed);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_COUNTRY, QHeaderView::Fixed);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_ID, QHeaderView::ResizeToContents);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_TITLE, QHeaderView::Stretch);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_MAKER, QHeaderView::ResizeToContents);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_SIZE, QHeaderView::ResizeToContents);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_DESCRIPTION, QHeaderView::Stretch);
m_table->horizontalHeader()->setSectionResizeMode(
GameListModel::COL_RATING, QHeaderView::Fixed);
QHeaderView* header = m_table->horizontalHeader();
header->setSectionResizeMode(GameListModel::COL_PLATFORM, QHeaderView::Fixed);
header->setSectionResizeMode(GameListModel::COL_COUNTRY, QHeaderView::Fixed);
header->setSectionResizeMode(GameListModel::COL_ID, QHeaderView::ResizeToContents);
header->setSectionResizeMode(GameListModel::COL_BANNER, QHeaderView::ResizeToContents);
header->setSectionResizeMode(GameListModel::COL_TITLE, QHeaderView::Stretch);
header->setSectionResizeMode(GameListModel::COL_MAKER, QHeaderView::Stretch);
header->setSectionResizeMode(GameListModel::COL_SIZE, QHeaderView::ResizeToContents);
header->setSectionResizeMode(GameListModel::COL_DESCRIPTION, QHeaderView::Stretch);
header->setSectionResizeMode(GameListModel::COL_RATING, QHeaderView::Fixed);
}
void GameList::MakeListView()

View File

@ -5,25 +5,6 @@
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/GameList/GameListModel.h"
static QString FormatSize(qint64 size)
{
QStringList units{
QStringLiteral("KB"),
QStringLiteral("MB"),
QStringLiteral("GB"),
QStringLiteral("TB")
};
QStringListIterator i(units);
QString unit = QStringLiteral("B");
double num = (double) size;
while (num > 1024.0 && i.hasNext())
{
unit = i.next();
num /= 1024.0;
}
return QStringLiteral("%1 %2").arg(QString::number(num, 'f', 1)).arg(unit);
}
GameListModel::GameListModel(QObject* parent)
: QAbstractTableModel(parent)
{
@ -38,46 +19,22 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
return QVariant();
QSharedPointer<GameFile> game = m_games[index.row()];
if (index.column() == COL_PLATFORM && role == Qt::DecorationRole)
return QVariant(Resources::GetPlatform(game->GetPlatform()));
else if (index.column() == COL_PLATFORM && role == Qt::DisplayRole)
return QVariant(game->GetPlatform());
else if (index.column() == COL_TITLE && role == Qt::DecorationRole)
return QVariant(game->GetBanner());
else if (index.column() == COL_TITLE && role == Qt::DisplayRole)
return QVariant(game->GetLongName());
else if (index.column() == COL_ID && role == Qt::DisplayRole)
return QVariant(game->GetUniqueID());
else if (index.column() == COL_DESCRIPTION && role == Qt::DisplayRole)
return QVariant(game->GetDescription());
else if (index.column() == COL_MAKER && role == Qt::DisplayRole)
return QVariant(game->GetCompany());
// FIXME this sorts lexicographically, not by size.
else if (index.column() == COL_SIZE && role == Qt::DisplayRole)
return QVariant(FormatSize(game->GetFileSize()));
else if (index.column() == COL_COUNTRY && role == Qt::DecorationRole)
return QVariant(Resources::GetCountry(game->GetCountry()));
else if (index.column() == COL_COUNTRY && role == Qt::DisplayRole)
return QVariant(game->GetCountry());
else if (index.column() == COL_RATING && role == Qt::DecorationRole)
return QVariant(Resources::GetRating(game->GetRating()));
else if (index.column() == COL_RATING && role == Qt::DisplayRole)
return QVariant(game->GetRating());
else if (index.column() == COL_LARGE_ICON && role == Qt::DecorationRole)
return QVariant(game->GetBanner().scaled(144, 48));
else if (index.column() == COL_LARGE_ICON && role == Qt::DisplayRole)
return QVariant(game->GetLongName());
else
return QVariant();
if (role == Qt::DisplayRole)
{
switch (index.column())
{
case COL_PLATFORM: return game->GetPlatform();
case COL_BANNER: return game->GetBanner();
case COL_TITLE: return game->GetLongName();
case COL_ID: return game->GetUniqueID();
case COL_DESCRIPTION: return game->GetDescription();
case COL_MAKER: return game->GetCompany();
case COL_SIZE: return game->GetFileSize();
case COL_COUNTRY: return game->GetCountry();
case COL_RATING: return game->GetRating();
}
}
return QVariant();
}
QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int role) const
@ -87,14 +44,15 @@ QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int
switch (section)
{
case COL_TITLE: return QVariant(tr("Title"));
case COL_ID: return QVariant(tr("ID"));
case COL_DESCRIPTION: return QVariant(tr("Description"));
case COL_MAKER: return QVariant(tr("Maker"));
case COL_SIZE: return QVariant(tr("Size"));
case COL_RATING: return QVariant(tr("Quality"));
default: return QVariant();
case COL_TITLE: return tr("Title");
case COL_ID: return tr("ID");
case COL_BANNER: return tr("Banner");
case COL_DESCRIPTION: return tr("Description");
case COL_MAKER: return tr("Maker");
case COL_SIZE: return tr("Size");
case COL_RATING: return tr("Quality");
}
return QVariant();
}
int GameListModel::rowCount(const QModelIndex& parent) const

View File

@ -30,6 +30,7 @@ public:
{
COL_PLATFORM = 0,
COL_ID,
COL_BANNER,
COL_TITLE,
COL_DESCRIPTION,
COL_MAKER,

View File

@ -0,0 +1,87 @@
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/GameList/GameListModel.h"
#include "DolphinQt2/GameList/GameListProxyModel.h"
static constexpr QSize NORMAL_BANNER_SIZE(96, 32);
static constexpr QSize LARGE_BANNER_SIZE(144, 48);
// Convert an integer size to a friendly string representation.
static QString FormatSize(qint64 size)
{
QStringList units{
QStringLiteral("KB"),
QStringLiteral("MB"),
QStringLiteral("GB"),
QStringLiteral("TB")
};
QStringListIterator i(units);
QString unit = QStringLiteral("B");
double num = (double) size;
while (num > 1024.0 && i.hasNext())
{
unit = i.next();
num /= 1024.0;
}
return QStringLiteral("%1 %2").arg(QString::number(num, 'f', 1)).arg(unit);
}
GameListProxyModel::GameListProxyModel(QObject* parent)
: QSortFilterProxyModel(parent)
{
}
QVariant GameListProxyModel::data(const QModelIndex& i, int role) const
{
QModelIndex source_index = mapToSource(i);
QVariant source_data = sourceModel()->data(source_index, Qt::DisplayRole);
if (role == Qt::DisplayRole)
{
switch (i.column())
{
// Sort by the integer but display the formatted string.
case GameListModel::COL_SIZE:
return FormatSize(source_data.toULongLong());
// These fall through to the underlying model.
case GameListModel::COL_ID:
case GameListModel::COL_TITLE:
case GameListModel::COL_DESCRIPTION:
case GameListModel::COL_MAKER:
return source_data;
// Show the title in the display role of the icon view.
case GameListModel::COL_LARGE_ICON:
return data(index(i.row(), GameListModel::COL_TITLE), Qt::DisplayRole);
}
}
else if (role == Qt::DecorationRole)
{
switch (i.column())
{
// Show icons in the decoration roles. This lets us sort by the
// underlying ints, but display just the icons without doing any
// fixed-width hacks.
case GameListModel::COL_PLATFORM:
return Resources::GetPlatform(source_data.toInt());
case GameListModel::COL_BANNER:
return source_data.value<QPixmap>().scaled(
NORMAL_BANNER_SIZE,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
case GameListModel::COL_COUNTRY:
return Resources::GetCountry(source_data.toInt());
case GameListModel::COL_RATING:
return Resources::GetRating(source_data.toInt());
// Show a scaled icon in the decoration role of the icon view.
case GameListModel::COL_LARGE_ICON:
return data(index(i.row(), GameListModel::COL_BANNER), Qt::DecorationRole)
.value<QPixmap>().scaled(
LARGE_BANNER_SIZE,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
}
}
return QVariant();
}

View File

@ -0,0 +1,19 @@
// Copyright 2015 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <QSortFilterProxyModel>
// This subclass of QSortFilterProxyModel transforms the raw GameFile data
// into presentable icons, and allows for sorting and filtering.
// For instance, the GameListModel exposes country as an integer, so this
// class converts that into a flag, while still allowing sorting on the
// underlying integer.
class GameListProxyModel final : public QSortFilterProxyModel
{
Q_OBJECT
public:
GameListProxyModel(QObject* parent = nullptr);
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
};

View File

@ -71,6 +71,7 @@ void MainWindow::AddTableColumnsMenu(QMenu* view_menu)
QStringList col_names{
tr("Platform"),
tr("ID"),
tr("Banner"),
tr("Title"),
tr("Description"),
tr("Maker"),
@ -78,12 +79,13 @@ void MainWindow::AddTableColumnsMenu(QMenu* view_menu)
tr("Country"),
tr("Quality")
};
// TODO we'll need to update SConfig with another column. Then we can clean this
// up significantly.
// TODO we'll need to update SConfig with the extra columns. Then we can
// clean this up significantly.
QList<bool> show_cols{
SConfig::GetInstance().m_showSystemColumn,
SConfig::GetInstance().m_showIDColumn,
SConfig::GetInstance().m_showBannerColumn,
true,
false,
SConfig::GetInstance().m_showMakerColumn,
SConfig::GetInstance().m_showSizeColumn,