mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-25 15:19:42 -06:00
Move DolphinQt2 to DolphinQt
This commit is contained in:
846
Source/Core/DolphinQt/GameList/GameList.cpp
Normal file
846
Source/Core/DolphinQt/GameList/GameList.cpp
Normal file
@ -0,0 +1,846 @@
|
||||
// Copyright 2015 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/GameList/GameList.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QErrorMessage>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QFrame>
|
||||
#include <QHeaderView>
|
||||
#include <QKeyEvent>
|
||||
#include <QMap>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QScrollBar>
|
||||
#include <QUrl>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/DVD/DVDInterface.h"
|
||||
#include "Core/HW/WiiSave.h"
|
||||
#include "Core/WiiUtils.h"
|
||||
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
|
||||
#include "DolphinQt/Config/PropertiesDialog.h"
|
||||
#include "DolphinQt/GameList/GridProxyModel.h"
|
||||
#include "DolphinQt/GameList/ListProxyModel.h"
|
||||
#include "DolphinQt/QtUtils/ActionHelper.h"
|
||||
#include "DolphinQt/QtUtils/DoubleClickEventFilter.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
#include "DolphinQt/WiiUpdate.h"
|
||||
|
||||
static bool CompressCB(const std::string&, float, void*);
|
||||
|
||||
GameList::GameList(QWidget* parent) : QStackedWidget(parent)
|
||||
{
|
||||
m_model = Settings::Instance().GetGameListModel();
|
||||
m_list_proxy = new ListProxyModel(this);
|
||||
m_list_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_list_proxy->setSortRole(Qt::InitialSortOrderRole);
|
||||
m_list_proxy->setSourceModel(m_model);
|
||||
m_grid_proxy = new GridProxyModel(this);
|
||||
m_grid_proxy->setSourceModel(m_model);
|
||||
|
||||
MakeListView();
|
||||
MakeGridView();
|
||||
MakeEmptyView();
|
||||
|
||||
connect(m_list, &QTableView::doubleClicked, this, &GameList::GameSelected);
|
||||
connect(m_grid, &QListView::doubleClicked, this, &GameList::GameSelected);
|
||||
connect(m_model, &QAbstractItemModel::rowsInserted, this, &GameList::ConsiderViewChange);
|
||||
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &GameList::ConsiderViewChange);
|
||||
|
||||
addWidget(m_list);
|
||||
addWidget(m_grid);
|
||||
addWidget(m_empty);
|
||||
m_prefer_list = Settings::Instance().GetPreferredView();
|
||||
ConsiderViewChange();
|
||||
}
|
||||
|
||||
void GameList::MakeListView()
|
||||
{
|
||||
m_list = new QTableView(this);
|
||||
m_list->setModel(m_list_proxy);
|
||||
|
||||
m_list->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
m_list->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_list->setAlternatingRowColors(true);
|
||||
m_list->setShowGrid(false);
|
||||
m_list->setSortingEnabled(true);
|
||||
m_list->setCurrentIndex(QModelIndex());
|
||||
m_list->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_list->setWordWrap(false);
|
||||
m_list->verticalHeader()->setDefaultSectionSize(m_list->verticalHeader()->defaultSectionSize() *
|
||||
1.25);
|
||||
|
||||
connect(m_list, &QTableView::customContextMenuRequested, this, &GameList::ShowContextMenu);
|
||||
connect(m_list->selectionModel(), &QItemSelectionModel::selectionChanged,
|
||||
[this](const QItemSelection&, const QItemSelection&) {
|
||||
emit SelectionChanged(GetSelectedGame());
|
||||
});
|
||||
|
||||
QHeaderView* hor_header = m_list->horizontalHeader();
|
||||
|
||||
hor_header->restoreState(
|
||||
Settings::GetQSettings().value(QStringLiteral("tableheader/state")).toByteArray());
|
||||
|
||||
connect(hor_header, &QHeaderView::sortIndicatorChanged, this, &GameList::OnHeaderViewChanged);
|
||||
connect(hor_header, &QHeaderView::sectionCountChanged, this, &GameList::OnHeaderViewChanged);
|
||||
connect(hor_header, &QHeaderView::sectionMoved, this, &GameList::OnHeaderViewChanged);
|
||||
connect(hor_header, &QHeaderView::sectionResized, this, &GameList::OnSectionResized);
|
||||
|
||||
if (!Settings::GetQSettings().contains(QStringLiteral("tableheader/state")))
|
||||
m_list->sortByColumn(GameListModel::COL_TITLE, Qt::AscendingOrder);
|
||||
|
||||
hor_header->setSectionResizeMode(GameListModel::COL_PLATFORM, QHeaderView::Fixed);
|
||||
hor_header->setSectionResizeMode(GameListModel::COL_BANNER, QHeaderView::Fixed);
|
||||
hor_header->setSectionResizeMode(GameListModel::COL_TITLE, QHeaderView::Interactive);
|
||||
hor_header->setSectionResizeMode(GameListModel::COL_DESCRIPTION, QHeaderView::Interactive);
|
||||
hor_header->setSectionResizeMode(GameListModel::COL_MAKER, QHeaderView::Interactive);
|
||||
hor_header->setSectionResizeMode(GameListModel::COL_ID, QHeaderView::Fixed);
|
||||
hor_header->setSectionResizeMode(GameListModel::COL_COUNTRY, QHeaderView::Fixed);
|
||||
hor_header->setSectionResizeMode(GameListModel::COL_SIZE, QHeaderView::Fixed);
|
||||
hor_header->setSectionResizeMode(GameListModel::COL_FILE_NAME, QHeaderView::Interactive);
|
||||
|
||||
// There's some odd platform-specific behavior with default minimum section size
|
||||
hor_header->setMinimumSectionSize(38);
|
||||
|
||||
// Cells have 3 pixels of padding, so the width of these needs to be image width + 6. Banners are
|
||||
// 96 pixels wide, platform and country icons are 32 pixels wide.
|
||||
m_list->setColumnWidth(GameListModel::COL_BANNER, 102);
|
||||
m_list->setColumnWidth(GameListModel::COL_PLATFORM, 38);
|
||||
m_list->setColumnWidth(GameListModel::COL_COUNTRY, 38);
|
||||
m_list->setColumnWidth(GameListModel::COL_SIZE, 85);
|
||||
m_list->setColumnWidth(GameListModel::COL_ID, 70);
|
||||
|
||||
UpdateColumnVisibility();
|
||||
|
||||
m_list->verticalHeader()->hide();
|
||||
m_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_list->setFrameStyle(QFrame::NoFrame);
|
||||
|
||||
hor_header->setSectionsMovable(true);
|
||||
hor_header->setHighlightSections(false);
|
||||
}
|
||||
|
||||
GameList::~GameList()
|
||||
{
|
||||
Settings::GetQSettings().setValue(QStringLiteral("tableheader/state"),
|
||||
m_list->horizontalHeader()->saveState());
|
||||
}
|
||||
|
||||
void GameList::UpdateColumnVisibility()
|
||||
{
|
||||
m_list->setColumnHidden(GameListModel::COL_PLATFORM, !SConfig::GetInstance().m_showSystemColumn);
|
||||
m_list->setColumnHidden(GameListModel::COL_BANNER, !SConfig::GetInstance().m_showBannerColumn);
|
||||
m_list->setColumnHidden(GameListModel::COL_TITLE, !SConfig::GetInstance().m_showTitleColumn);
|
||||
m_list->setColumnHidden(GameListModel::COL_DESCRIPTION,
|
||||
!SConfig::GetInstance().m_showDescriptionColumn);
|
||||
m_list->setColumnHidden(GameListModel::COL_MAKER, !SConfig::GetInstance().m_showMakerColumn);
|
||||
m_list->setColumnHidden(GameListModel::COL_ID, !SConfig::GetInstance().m_showIDColumn);
|
||||
m_list->setColumnHidden(GameListModel::COL_COUNTRY, !SConfig::GetInstance().m_showRegionColumn);
|
||||
m_list->setColumnHidden(GameListModel::COL_SIZE, !SConfig::GetInstance().m_showSizeColumn);
|
||||
m_list->setColumnHidden(GameListModel::COL_FILE_NAME,
|
||||
!SConfig::GetInstance().m_showFileNameColumn);
|
||||
}
|
||||
|
||||
void GameList::MakeEmptyView()
|
||||
{
|
||||
m_empty = new QLabel(this);
|
||||
m_empty->setText(tr("Dolphin could not find any GameCube/Wii ISOs or WADs.\n"
|
||||
"Double-click here to set a games directory..."));
|
||||
m_empty->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
|
||||
auto event_filter = new DoubleClickEventFilter{};
|
||||
m_empty->installEventFilter(event_filter);
|
||||
connect(event_filter, &DoubleClickEventFilter::doubleClicked, [this] {
|
||||
auto current_dir = QDir::currentPath();
|
||||
auto dir = QFileDialog::getExistingDirectory(this, tr("Select a Directory"), current_dir);
|
||||
if (!dir.isEmpty())
|
||||
Settings::Instance().AddPath(dir);
|
||||
});
|
||||
}
|
||||
|
||||
void GameList::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
OnHeaderViewChanged();
|
||||
}
|
||||
|
||||
void GameList::MakeGridView()
|
||||
{
|
||||
m_grid = new QListView(this);
|
||||
m_grid->setModel(m_grid_proxy);
|
||||
m_grid->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
m_grid->setViewMode(QListView::IconMode);
|
||||
m_grid->setResizeMode(QListView::Adjust);
|
||||
m_grid->setUniformItemSizes(true);
|
||||
m_grid->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_grid->setFrameStyle(QFrame::NoFrame);
|
||||
connect(m_grid, &QTableView::customContextMenuRequested, this, &GameList::ShowContextMenu);
|
||||
connect(m_grid->selectionModel(), &QItemSelectionModel::selectionChanged,
|
||||
[this](const QItemSelection&, const QItemSelection&) {
|
||||
emit SelectionChanged(GetSelectedGame());
|
||||
});
|
||||
}
|
||||
|
||||
void GameList::ShowContextMenu(const QPoint&)
|
||||
{
|
||||
if (!GetSelectedGame())
|
||||
return;
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
if (HasMultipleSelected())
|
||||
{
|
||||
bool wii_saves = true;
|
||||
bool compress = false;
|
||||
bool decompress = false;
|
||||
|
||||
for (const auto& game : GetSelectedGames())
|
||||
{
|
||||
DiscIO::Platform platform = game->GetPlatform();
|
||||
|
||||
if (platform == DiscIO::Platform::GameCubeDisc || platform == DiscIO::Platform::WiiDisc)
|
||||
{
|
||||
const auto blob_type = game->GetBlobType();
|
||||
if (blob_type == DiscIO::BlobType::GCZ)
|
||||
decompress = true;
|
||||
else if (blob_type == DiscIO::BlobType::PLAIN)
|
||||
compress = true;
|
||||
}
|
||||
|
||||
if (platform != DiscIO::Platform::WiiWAD && platform != DiscIO::Platform::WiiDisc)
|
||||
wii_saves = false;
|
||||
}
|
||||
|
||||
if (compress)
|
||||
AddAction(menu, tr("Compress selected ISOs..."), this, [this] { CompressISO(false); });
|
||||
if (decompress)
|
||||
AddAction(menu, tr("Decompress selected ISOs..."), this, [this] { CompressISO(true); });
|
||||
if (compress || decompress)
|
||||
menu->addSeparator();
|
||||
|
||||
if (wii_saves)
|
||||
{
|
||||
AddAction(menu, tr("Export Wii saves (Experimental)"), this, &GameList::ExportWiiSave);
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
AddAction(menu, tr("Delete selected files..."), this, &GameList::DeleteFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto game = GetSelectedGame();
|
||||
DiscIO::Platform platform = game->GetPlatform();
|
||||
|
||||
if (platform != DiscIO::Platform::ELFOrDOL)
|
||||
{
|
||||
AddAction(menu, tr("&Properties"), this, &GameList::OpenProperties);
|
||||
AddAction(menu, tr("&Wiki"), this, &GameList::OpenWiki);
|
||||
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
if (platform == DiscIO::Platform::GameCubeDisc || platform == DiscIO::Platform::WiiDisc)
|
||||
{
|
||||
AddAction(menu, tr("Set as &default ISO"), this, &GameList::SetDefaultISO);
|
||||
const auto blob_type = game->GetBlobType();
|
||||
|
||||
if (blob_type == DiscIO::BlobType::GCZ)
|
||||
AddAction(menu, tr("Decompress ISO..."), this, [this] { CompressISO(true); });
|
||||
else if (blob_type == DiscIO::BlobType::PLAIN)
|
||||
AddAction(menu, tr("Compress ISO..."), this, [this] { CompressISO(false); });
|
||||
|
||||
QAction* change_disc = AddAction(menu, tr("Change &Disc"), this, &GameList::ChangeDisc);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, change_disc,
|
||||
[change_disc] { change_disc->setEnabled(Core::IsRunning()); });
|
||||
change_disc->setEnabled(Core::IsRunning());
|
||||
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
if (platform == DiscIO::Platform::WiiDisc)
|
||||
{
|
||||
auto* perform_disc_update = AddAction(menu, tr("Perform System Update"), this, [this] {
|
||||
WiiUpdate::PerformDiscUpdate(GetSelectedGame()->GetFilePath(), this);
|
||||
});
|
||||
perform_disc_update->setEnabled(!Core::IsRunning() || !SConfig::GetInstance().bWii);
|
||||
}
|
||||
|
||||
if (platform == DiscIO::Platform::WiiWAD)
|
||||
{
|
||||
QAction* wad_install_action = new QAction(tr("Install to the NAND"), menu);
|
||||
QAction* wad_uninstall_action = new QAction(tr("Uninstall from the NAND"), menu);
|
||||
|
||||
connect(wad_install_action, &QAction::triggered, this, &GameList::InstallWAD);
|
||||
connect(wad_uninstall_action, &QAction::triggered, this, &GameList::UninstallWAD);
|
||||
|
||||
for (QAction* a : {wad_install_action, wad_uninstall_action})
|
||||
{
|
||||
a->setEnabled(!Core::IsRunning());
|
||||
menu->addAction(a);
|
||||
}
|
||||
if (!Core::IsRunning())
|
||||
wad_uninstall_action->setEnabled(WiiUtils::IsTitleInstalled(game->GetTitleID()));
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, menu,
|
||||
[=](Core::State state) {
|
||||
wad_install_action->setEnabled(state == Core::State::Uninitialized);
|
||||
wad_uninstall_action->setEnabled(state == Core::State::Uninitialized &&
|
||||
WiiUtils::IsTitleInstalled(game->GetTitleID()));
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
if (platform == DiscIO::Platform::WiiWAD || platform == DiscIO::Platform::WiiDisc)
|
||||
{
|
||||
AddAction(menu, tr("Open Wii &save folder"), this, &GameList::OpenSaveFolder);
|
||||
AddAction(menu, tr("Export Wii save (Experimental)"), this, &GameList::ExportWiiSave);
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
AddAction(menu, tr("Open &containing folder"), this, &GameList::OpenContainingFolder);
|
||||
AddAction(menu, tr("Delete File..."), this, &GameList::DeleteFile);
|
||||
|
||||
QAction* netplay_host = new QAction(tr("Host with NetPlay"), menu);
|
||||
|
||||
connect(netplay_host, &QAction::triggered, [this, game] {
|
||||
emit NetPlayHost(QString::fromStdString(game->GetUniqueIdentifier()));
|
||||
});
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, menu, [=](Core::State state) {
|
||||
netplay_host->setEnabled(state == Core::State::Uninitialized);
|
||||
});
|
||||
netplay_host->setEnabled(!Core::IsRunning());
|
||||
|
||||
menu->addAction(netplay_host);
|
||||
}
|
||||
|
||||
menu->exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void GameList::OpenProperties()
|
||||
{
|
||||
PropertiesDialog* properties = new PropertiesDialog(this, *GetSelectedGame());
|
||||
|
||||
connect(properties, &PropertiesDialog::OpenGeneralSettings, this, &GameList::OpenGeneralSettings);
|
||||
|
||||
properties->show();
|
||||
}
|
||||
|
||||
void GameList::ExportWiiSave()
|
||||
{
|
||||
const QString export_dir = QFileDialog::getExistingDirectory(
|
||||
this, tr("Select Export Directory"), QString::fromStdString(File::GetUserPath(D_USER_IDX)),
|
||||
QFileDialog::ShowDirsOnly);
|
||||
if (export_dir.isEmpty())
|
||||
return;
|
||||
|
||||
QList<std::string> failed;
|
||||
for (const auto& game : GetSelectedGames())
|
||||
{
|
||||
if (!WiiSave::Export(game->GetTitleID(), export_dir.toStdString()))
|
||||
failed.push_back(game->GetName());
|
||||
}
|
||||
|
||||
if (!failed.isEmpty())
|
||||
{
|
||||
QString failed_str;
|
||||
for (const std::string& str : failed)
|
||||
failed_str.append(QStringLiteral("\n")).append(QString::fromStdString(str));
|
||||
QMessageBox::critical(this, tr("Save Export"),
|
||||
tr("Failed to export the following save files:") + failed_str);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::information(this, tr("Save Export"), tr("Successfully exported save files"));
|
||||
}
|
||||
}
|
||||
|
||||
void GameList::OpenWiki()
|
||||
{
|
||||
QString game_id = QString::fromStdString(GetSelectedGame()->GetGameID());
|
||||
QString url = QStringLiteral("https://wiki.dolphin-emu.org/index.php?title=").append(game_id);
|
||||
QDesktopServices::openUrl(QUrl(url));
|
||||
}
|
||||
|
||||
void GameList::CompressISO(bool decompress)
|
||||
{
|
||||
auto files = GetSelectedGames();
|
||||
|
||||
bool wii_warning_given = false;
|
||||
for (QMutableListIterator<std::shared_ptr<const UICommon::GameFile>> it(files); it.hasNext();)
|
||||
{
|
||||
auto file = it.next();
|
||||
|
||||
if ((file->GetPlatform() != DiscIO::Platform::GameCubeDisc &&
|
||||
file->GetPlatform() != DiscIO::Platform::WiiDisc) ||
|
||||
(decompress && file->GetBlobType() != DiscIO::BlobType::GCZ) ||
|
||||
(!decompress && file->GetBlobType() != DiscIO::BlobType::PLAIN))
|
||||
{
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!wii_warning_given && !decompress && file->GetPlatform() == DiscIO::Platform::WiiDisc)
|
||||
{
|
||||
QMessageBox wii_warning(this);
|
||||
wii_warning.setIcon(QMessageBox::Warning);
|
||||
wii_warning.setWindowTitle(tr("Confirm"));
|
||||
wii_warning.setText(tr("Are you sure?"));
|
||||
wii_warning.setInformativeText(tr(
|
||||
"Compressing a Wii disc image will irreversibly change the compressed copy by removing "
|
||||
"padding data. Your disc image will still work. Continue?"));
|
||||
wii_warning.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (wii_warning.exec() == QMessageBox::No)
|
||||
return;
|
||||
|
||||
wii_warning_given = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (files.size() == 0)
|
||||
return; // We shouldn't get here normally...
|
||||
|
||||
QString dst_dir;
|
||||
QString dst_path;
|
||||
|
||||
if (files.size() > 1)
|
||||
{
|
||||
dst_dir = QFileDialog::getExistingDirectory(
|
||||
this,
|
||||
decompress ? tr("Select where you want to save the decompressed images") :
|
||||
tr("Select where you want to save the compressed images"),
|
||||
QFileInfo(QString::fromStdString(GetSelectedGame()->GetFilePath())).dir().absolutePath());
|
||||
|
||||
if (dst_dir.isEmpty())
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst_path = QFileDialog::getSaveFileName(
|
||||
this,
|
||||
decompress ? tr("Select where you want to save the decompressed image") :
|
||||
tr("Select where you want to save the compressed image"),
|
||||
QFileInfo(QString::fromStdString(GetSelectedGame()->GetFilePath()))
|
||||
.dir()
|
||||
.absoluteFilePath(
|
||||
QFileInfo(QString::fromStdString(files[0]->GetFilePath())).completeBaseName())
|
||||
.append(decompress ? QStringLiteral(".gcm") : QStringLiteral(".gcz")),
|
||||
decompress ? tr("Uncompressed GC/Wii images (*.iso *.gcm)") :
|
||||
tr("Compressed GC/Wii images (*.gcz)"));
|
||||
}
|
||||
|
||||
for (const auto& file : files)
|
||||
{
|
||||
const auto original_path = file->GetFilePath();
|
||||
if (files.size() > 1)
|
||||
{
|
||||
dst_path =
|
||||
QDir(dst_dir)
|
||||
.absoluteFilePath(QFileInfo(QString::fromStdString(original_path)).completeBaseName())
|
||||
.append(decompress ? QStringLiteral(".gcm") : QStringLiteral(".gcz"));
|
||||
QFileInfo dst_info = QFileInfo(dst_path);
|
||||
if (dst_info.exists())
|
||||
{
|
||||
QMessageBox confirm_replace(this);
|
||||
confirm_replace.setIcon(QMessageBox::Warning);
|
||||
confirm_replace.setWindowTitle(tr("Confirm"));
|
||||
confirm_replace.setText(tr("The file %1 already exists.\n"
|
||||
"Do you wish to replace it?")
|
||||
.arg(dst_info.fileName()));
|
||||
confirm_replace.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (confirm_replace.exec() == QMessageBox::No)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
QProgressDialog progress_dialog(decompress ? tr("Decompressing...") : tr("Compressing..."),
|
||||
tr("Abort"), 0, 100, this);
|
||||
progress_dialog.setWindowModality(Qt::WindowModal);
|
||||
progress_dialog.setWindowFlags(progress_dialog.windowFlags() &
|
||||
~Qt::WindowContextHelpButtonHint);
|
||||
progress_dialog.setWindowTitle(tr("Progress"));
|
||||
|
||||
bool good;
|
||||
|
||||
if (decompress)
|
||||
{
|
||||
if (files.size() > 1)
|
||||
progress_dialog.setLabelText(tr("Decompressing...") + QStringLiteral("\n") +
|
||||
QFileInfo(QString::fromStdString(original_path)).fileName());
|
||||
good = DiscIO::DecompressBlobToFile(original_path, dst_path.toStdString(), &CompressCB,
|
||||
&progress_dialog);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (files.size() > 1)
|
||||
progress_dialog.setLabelText(tr("Compressing...") + QStringLiteral("\n") +
|
||||
QFileInfo(QString::fromStdString(original_path)).fileName());
|
||||
good = DiscIO::CompressFileToBlob(original_path, dst_path.toStdString(),
|
||||
file->GetPlatform() == DiscIO::Platform::WiiDisc ? 1 : 0,
|
||||
16384, &CompressCB, &progress_dialog);
|
||||
}
|
||||
|
||||
if (!good)
|
||||
{
|
||||
QErrorMessage(this).showMessage(tr("Dolphin failed to complete the requested action."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QMessageBox(QMessageBox::Information, tr("Success"),
|
||||
decompress ? tr("Successfully decompressed %n image(s).", "", files.size()) :
|
||||
tr("Successfully compressed %n image(s).", "", files.size()),
|
||||
QMessageBox::Ok, this)
|
||||
.exec();
|
||||
}
|
||||
|
||||
void GameList::InstallWAD()
|
||||
{
|
||||
QMessageBox result_dialog(this);
|
||||
|
||||
const bool success = WiiUtils::InstallWAD(GetSelectedGame()->GetFilePath());
|
||||
|
||||
result_dialog.setIcon(success ? QMessageBox::Information : QMessageBox::Critical);
|
||||
result_dialog.setWindowTitle(success ? tr("Success") : tr("Failure"));
|
||||
result_dialog.setText(success ? tr("Successfully installed this title to the NAND.") :
|
||||
tr("Failed to install this title to the NAND."));
|
||||
result_dialog.exec();
|
||||
}
|
||||
|
||||
void GameList::UninstallWAD()
|
||||
{
|
||||
QMessageBox warning_dialog(this);
|
||||
|
||||
warning_dialog.setIcon(QMessageBox::Information);
|
||||
warning_dialog.setWindowTitle(tr("Confirm"));
|
||||
warning_dialog.setText(tr("Uninstalling the WAD will remove the currently installed version of "
|
||||
"this title from the NAND without deleting its save data. Continue?"));
|
||||
warning_dialog.setStandardButtons(QMessageBox::No | QMessageBox::Yes);
|
||||
|
||||
if (warning_dialog.exec() == QMessageBox::No)
|
||||
return;
|
||||
|
||||
QMessageBox result_dialog(this);
|
||||
|
||||
const bool success = WiiUtils::UninstallTitle(GetSelectedGame()->GetTitleID());
|
||||
|
||||
result_dialog.setIcon(success ? QMessageBox::Information : QMessageBox::Critical);
|
||||
result_dialog.setWindowTitle(success ? tr("Success") : tr("Failure"));
|
||||
result_dialog.setText(success ? tr("Successfully removed this title from the NAND.") :
|
||||
tr("Failed to remove this title from the NAND."));
|
||||
result_dialog.exec();
|
||||
}
|
||||
|
||||
void GameList::SetDefaultISO()
|
||||
{
|
||||
Settings::Instance().SetDefaultGame(
|
||||
QDir::toNativeSeparators(QString::fromStdString(GetSelectedGame()->GetFilePath())));
|
||||
}
|
||||
|
||||
void GameList::OpenContainingFolder()
|
||||
{
|
||||
QUrl url = QUrl::fromLocalFile(
|
||||
QFileInfo(QString::fromStdString(GetSelectedGame()->GetFilePath())).dir().absolutePath());
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
|
||||
void GameList::OpenSaveFolder()
|
||||
{
|
||||
QUrl url = QUrl::fromLocalFile(QString::fromStdString(GetSelectedGame()->GetWiiFSPath()));
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
|
||||
void GameList::DeleteFile()
|
||||
{
|
||||
QMessageBox confirm_dialog(this);
|
||||
|
||||
confirm_dialog.setIcon(QMessageBox::Warning);
|
||||
confirm_dialog.setWindowTitle(tr("Confirm"));
|
||||
confirm_dialog.setText(tr("Are you sure you want to delete this file?"));
|
||||
confirm_dialog.setInformativeText(tr("This cannot be undone!"));
|
||||
confirm_dialog.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
|
||||
|
||||
if (confirm_dialog.exec() == QMessageBox::Yes)
|
||||
{
|
||||
for (const auto& game : GetSelectedGames())
|
||||
{
|
||||
bool deletion_successful = false;
|
||||
|
||||
while (!deletion_successful)
|
||||
{
|
||||
deletion_successful = File::Delete(game->GetFilePath());
|
||||
|
||||
if (deletion_successful)
|
||||
{
|
||||
m_model->RemoveGame(game->GetFilePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox error_dialog(this);
|
||||
|
||||
error_dialog.setIcon(QMessageBox::Critical);
|
||||
error_dialog.setWindowTitle(tr("Failure"));
|
||||
error_dialog.setText(tr("Failed to delete the selected file."));
|
||||
error_dialog.setInformativeText(tr("Check whether you have the permissions required to "
|
||||
"delete the file or whether it's still in use."));
|
||||
error_dialog.setStandardButtons(QMessageBox::Retry | QMessageBox::Abort);
|
||||
|
||||
if (error_dialog.exec() == QMessageBox::Abort)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!deletion_successful)
|
||||
break; // Something is wrong, so we should abort the whole thing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameList::ChangeDisc()
|
||||
{
|
||||
Core::RunAsCPUThread([this] { DVDInterface::ChangeDisc(GetSelectedGame()->GetFilePath()); });
|
||||
}
|
||||
|
||||
std::shared_ptr<const UICommon::GameFile> GameList::GetSelectedGame() const
|
||||
{
|
||||
QAbstractItemView* view;
|
||||
QSortFilterProxyModel* proxy;
|
||||
if (currentWidget() == m_list)
|
||||
{
|
||||
view = m_list;
|
||||
proxy = m_list_proxy;
|
||||
}
|
||||
else
|
||||
{
|
||||
view = m_grid;
|
||||
proxy = m_grid_proxy;
|
||||
}
|
||||
QItemSelectionModel* sel_model = view->selectionModel();
|
||||
if (sel_model->hasSelection())
|
||||
{
|
||||
QModelIndex model_index = proxy->mapToSource(sel_model->selectedIndexes()[0]);
|
||||
return m_model->GetGameFile(model_index.row());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<std::shared_ptr<const UICommon::GameFile>> GameList::GetSelectedGames() const
|
||||
{
|
||||
QAbstractItemView* view;
|
||||
QSortFilterProxyModel* proxy;
|
||||
if (currentWidget() == m_list)
|
||||
{
|
||||
view = m_list;
|
||||
proxy = m_list_proxy;
|
||||
}
|
||||
else
|
||||
{
|
||||
view = m_grid;
|
||||
proxy = m_grid_proxy;
|
||||
}
|
||||
QList<std::shared_ptr<const UICommon::GameFile>> selected_list;
|
||||
QItemSelectionModel* sel_model = view->selectionModel();
|
||||
if (sel_model->hasSelection())
|
||||
{
|
||||
QModelIndexList index_list =
|
||||
currentWidget() == m_list ? sel_model->selectedRows() : sel_model->selectedIndexes();
|
||||
for (const auto& index : index_list)
|
||||
{
|
||||
QModelIndex model_index = proxy->mapToSource(index);
|
||||
selected_list.push_back(m_model->GetGameFile(model_index.row()));
|
||||
}
|
||||
}
|
||||
return selected_list;
|
||||
}
|
||||
|
||||
bool GameList::HasMultipleSelected() const
|
||||
{
|
||||
return currentWidget() == m_list ? m_list->selectionModel()->selectedRows().size() > 1 :
|
||||
m_grid->selectionModel()->selectedIndexes().size() > 1;
|
||||
}
|
||||
|
||||
void GameList::SetPreferredView(bool list)
|
||||
{
|
||||
m_prefer_list = list;
|
||||
Settings::Instance().SetPreferredView(list);
|
||||
ConsiderViewChange();
|
||||
}
|
||||
|
||||
void GameList::ConsiderViewChange()
|
||||
{
|
||||
if (m_model->rowCount(QModelIndex()) > 0)
|
||||
{
|
||||
if (m_prefer_list)
|
||||
setCurrentWidget(m_list);
|
||||
else
|
||||
setCurrentWidget(m_grid);
|
||||
}
|
||||
else
|
||||
{
|
||||
setCurrentWidget(m_empty);
|
||||
}
|
||||
}
|
||||
void GameList::keyReleaseEvent(QKeyEvent* event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Return && GetSelectedGame() != nullptr)
|
||||
emit GameSelected();
|
||||
else
|
||||
QStackedWidget::keyReleaseEvent(event);
|
||||
}
|
||||
|
||||
void GameList::OnColumnVisibilityToggled(const QString& row, bool visible)
|
||||
{
|
||||
static const QMap<QString, int> rowname_to_col_index = {
|
||||
{tr("Platform"), GameListModel::COL_PLATFORM},
|
||||
{tr("Banner"), GameListModel::COL_BANNER},
|
||||
{tr("Title"), GameListModel::COL_TITLE},
|
||||
{tr("Description"), GameListModel::COL_DESCRIPTION},
|
||||
{tr("Maker"), GameListModel::COL_MAKER},
|
||||
{tr("File Name"), GameListModel::COL_FILE_NAME},
|
||||
{tr("Game ID"), GameListModel::COL_ID},
|
||||
{tr("Region"), GameListModel::COL_COUNTRY},
|
||||
{tr("File Size"), GameListModel::COL_SIZE}};
|
||||
|
||||
m_list->setColumnHidden(rowname_to_col_index[row], !visible);
|
||||
}
|
||||
|
||||
void GameList::OnGameListVisibilityChanged()
|
||||
{
|
||||
m_list_proxy->invalidate();
|
||||
m_grid_proxy->invalidate();
|
||||
}
|
||||
|
||||
static bool CompressCB(const std::string& text, float percent, void* ptr)
|
||||
{
|
||||
if (ptr == nullptr)
|
||||
return false;
|
||||
|
||||
auto* progress_dialog = static_cast<QProgressDialog*>(ptr);
|
||||
|
||||
progress_dialog->setValue(percent * 100);
|
||||
return !progress_dialog->wasCanceled();
|
||||
}
|
||||
|
||||
void GameList::OnSectionResized(int index, int, int)
|
||||
{
|
||||
auto* hor_header = m_list->horizontalHeader();
|
||||
|
||||
std::vector<int> sections;
|
||||
|
||||
const int vis_index = hor_header->visualIndex(index);
|
||||
const int col_count = hor_header->count() - hor_header->hiddenSectionCount();
|
||||
|
||||
bool last = true;
|
||||
|
||||
for (int i = vis_index + 1; i < col_count; i++)
|
||||
{
|
||||
const int logical_index = hor_header->logicalIndex(i);
|
||||
if (hor_header->sectionResizeMode(logical_index) != QHeaderView::Interactive)
|
||||
continue;
|
||||
|
||||
last = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!last)
|
||||
{
|
||||
for (int i = 0; i < vis_index; i++)
|
||||
{
|
||||
const int logical_index = hor_header->logicalIndex(i);
|
||||
if (hor_header->sectionResizeMode(logical_index) != QHeaderView::Interactive)
|
||||
continue;
|
||||
|
||||
hor_header->setSectionResizeMode(logical_index, QHeaderView::Fixed);
|
||||
sections.push_back(i);
|
||||
}
|
||||
|
||||
OnHeaderViewChanged();
|
||||
|
||||
for (int i : sections)
|
||||
{
|
||||
hor_header->setSectionResizeMode(hor_header->logicalIndex(i), QHeaderView::Interactive);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OnHeaderViewChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void GameList::OnHeaderViewChanged()
|
||||
{
|
||||
static bool block = false;
|
||||
|
||||
if (block)
|
||||
return;
|
||||
|
||||
block = true;
|
||||
|
||||
UpdateColumnVisibility();
|
||||
|
||||
// So here's the deal: Qt's way of resizing stuff around stretched columns sucks ass
|
||||
// That's why instead of using Stretch, we'll just make resizable columns take all the available
|
||||
// space ourselves!
|
||||
|
||||
int available_width = width() - style()->pixelMetric(QStyle::PM_ScrollBarExtent);
|
||||
int previous_width = 0;
|
||||
|
||||
std::vector<int> candidate_columns;
|
||||
|
||||
// Iterate through all columns
|
||||
for (int i = 0; i < GameListModel::NUM_COLS; i++)
|
||||
{
|
||||
if (m_list->isColumnHidden(i))
|
||||
continue;
|
||||
|
||||
if (m_list->horizontalHeader()->sectionResizeMode(i) == QHeaderView::Fixed)
|
||||
{
|
||||
available_width -= m_list->columnWidth(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
candidate_columns.push_back(i);
|
||||
previous_width += m_list->columnWidth(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int column : candidate_columns)
|
||||
{
|
||||
int column_width = static_cast<int>(
|
||||
std::max(5.f, std::ceil(available_width * (static_cast<float>(m_list->columnWidth(column)) /
|
||||
previous_width))));
|
||||
|
||||
m_list->setColumnWidth(column, column_width);
|
||||
}
|
||||
|
||||
block = false;
|
||||
}
|
||||
|
||||
void GameList::SetSearchTerm(const QString& term)
|
||||
{
|
||||
m_model->SetSearchTerm(term);
|
||||
|
||||
m_list_proxy->invalidate();
|
||||
m_grid_proxy->invalidate();
|
||||
|
||||
UpdateColumnVisibility();
|
||||
}
|
83
Source/Core/DolphinQt/GameList/GameList.h
Normal file
83
Source/Core/DolphinQt/GameList/GameList.h
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright 2015 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QLabel>
|
||||
#include <QListView>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStackedWidget>
|
||||
#include <QTableView>
|
||||
|
||||
#include "DolphinQt/GameList/GameListModel.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
class GameList final : public QStackedWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GameList(QWidget* parent = nullptr);
|
||||
~GameList();
|
||||
|
||||
std::shared_ptr<const UICommon::GameFile> GetSelectedGame() const;
|
||||
QList<std::shared_ptr<const UICommon::GameFile>> GetSelectedGames() const;
|
||||
bool HasMultipleSelected() const;
|
||||
|
||||
void SetListView() { SetPreferredView(true); }
|
||||
void SetGridView() { SetPreferredView(false); }
|
||||
void SetViewColumn(int col, bool view) { m_list->setColumnHidden(col, !view); }
|
||||
void SetSearchTerm(const QString& term);
|
||||
|
||||
void OnColumnVisibilityToggled(const QString& row, bool visible);
|
||||
void OnGameListVisibilityChanged();
|
||||
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
signals:
|
||||
void GameSelected();
|
||||
void NetPlayHost(const QString& game_id);
|
||||
void SelectionChanged(std::shared_ptr<const UICommon::GameFile> game_file);
|
||||
void OpenGeneralSettings();
|
||||
|
||||
private:
|
||||
void ShowContextMenu(const QPoint&);
|
||||
void OpenContainingFolder();
|
||||
void OpenProperties();
|
||||
void OpenSaveFolder();
|
||||
void OpenWiki();
|
||||
void SetDefaultISO();
|
||||
void DeleteFile();
|
||||
void InstallWAD();
|
||||
void UninstallWAD();
|
||||
void ExportWiiSave();
|
||||
void CompressISO(bool decompress);
|
||||
void ChangeDisc();
|
||||
void UpdateColumnVisibility();
|
||||
|
||||
void OnHeaderViewChanged();
|
||||
void OnSectionResized(int index, int, int);
|
||||
|
||||
void MakeListView();
|
||||
void MakeGridView();
|
||||
void MakeEmptyView();
|
||||
// We only have two views, just use a bool to distinguish.
|
||||
void SetPreferredView(bool list);
|
||||
void ConsiderViewChange();
|
||||
|
||||
GameListModel* m_model;
|
||||
QSortFilterProxyModel* m_list_proxy;
|
||||
QSortFilterProxyModel* m_grid_proxy;
|
||||
|
||||
QListView* m_grid;
|
||||
QTableView* m_list;
|
||||
QLabel* m_empty;
|
||||
bool m_prefer_list;
|
||||
|
||||
protected:
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
};
|
274
Source/Core/DolphinQt/GameList/GameListModel.cpp
Normal file
274
Source/Core/DolphinQt/GameList/GameListModel.cpp
Normal file
@ -0,0 +1,274 @@
|
||||
// Copyright 2015 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/GameList/GameListModel.h"
|
||||
|
||||
#include <QPixmap>
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DiscIO/Enums.h"
|
||||
|
||||
#include "DolphinQt/QtUtils/ImageConverter.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
const QSize GAMECUBE_BANNER_SIZE(96, 32);
|
||||
|
||||
GameListModel::GameListModel(QObject* parent) : QAbstractTableModel(parent)
|
||||
{
|
||||
connect(&m_tracker, &GameTracker::GameLoaded, this, &GameListModel::AddGame);
|
||||
connect(&m_tracker, &GameTracker::GameUpdated, this, &GameListModel::UpdateGame);
|
||||
connect(&m_tracker, &GameTracker::GameRemoved, this, &GameListModel::RemoveGame);
|
||||
connect(&Settings::Instance(), &Settings::PathAdded, &m_tracker, &GameTracker::AddDirectory);
|
||||
connect(&Settings::Instance(), &Settings::PathRemoved, &m_tracker, &GameTracker::RemoveDirectory);
|
||||
connect(&Settings::Instance(), &Settings::GameListRefreshRequested, &m_tracker,
|
||||
&GameTracker::RefreshAll);
|
||||
connect(&Settings::Instance(), &Settings::TitleDBReloadRequested, this,
|
||||
[this] { m_title_database = Core::TitleDatabase(); });
|
||||
|
||||
for (const QString& dir : Settings::Instance().GetPaths())
|
||||
m_tracker.AddDirectory(dir);
|
||||
|
||||
m_tracker.Start();
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ThemeChanged, [this] {
|
||||
// Tell the view to repaint. The signal 'dataChanged' also seems like it would work here, but
|
||||
// unfortunately it won't cause a repaint until the view is focused.
|
||||
emit layoutAboutToBeChanged();
|
||||
emit layoutChanged();
|
||||
});
|
||||
}
|
||||
|
||||
QVariant GameListModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
const UICommon::GameFile& game = *m_games[index.row()];
|
||||
|
||||
switch (index.column())
|
||||
{
|
||||
case COL_PLATFORM:
|
||||
if (role == Qt::DecorationRole)
|
||||
return Resources::GetPlatform(static_cast<int>(game.GetPlatform()));
|
||||
if (role == Qt::InitialSortOrderRole)
|
||||
return static_cast<int>(game.GetPlatform());
|
||||
break;
|
||||
case COL_COUNTRY:
|
||||
if (role == Qt::DecorationRole)
|
||||
return Resources::GetCountry(static_cast<int>(game.GetCountry()));
|
||||
if (role == Qt::InitialSortOrderRole)
|
||||
return static_cast<int>(game.GetCountry());
|
||||
break;
|
||||
case COL_BANNER:
|
||||
if (role == Qt::DecorationRole)
|
||||
{
|
||||
// GameCube banners are 96x32, but Wii banners are 192x64.
|
||||
QPixmap banner = ToQPixmap(game.GetBannerImage());
|
||||
if (banner.isNull())
|
||||
banner = Resources::GetMisc(Resources::BANNER_MISSING);
|
||||
|
||||
banner.setDevicePixelRatio(
|
||||
std::max(static_cast<qreal>(banner.width()) / GAMECUBE_BANNER_SIZE.width(),
|
||||
static_cast<qreal>(banner.height()) / GAMECUBE_BANNER_SIZE.height()));
|
||||
|
||||
return banner;
|
||||
}
|
||||
break;
|
||||
case COL_TITLE:
|
||||
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
|
||||
{
|
||||
QString name = QString::fromStdString(game.GetName(m_title_database));
|
||||
const int disc_nr = game.GetDiscNumber() + 1;
|
||||
if (disc_nr > 1)
|
||||
{
|
||||
if (!name.contains(QRegExp(QStringLiteral("disc ?%1").arg(disc_nr), Qt::CaseInsensitive)))
|
||||
{
|
||||
name.append(tr(" (Disc %1)").arg(disc_nr));
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
break;
|
||||
case COL_ID:
|
||||
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
|
||||
return QString::fromStdString(game.GetGameID());
|
||||
break;
|
||||
case COL_DESCRIPTION:
|
||||
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
|
||||
return QString::fromStdString(game.GetDescription());
|
||||
break;
|
||||
case COL_MAKER:
|
||||
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
|
||||
return QString::fromStdString(game.GetMaker());
|
||||
break;
|
||||
case COL_FILE_NAME:
|
||||
if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole)
|
||||
return QString::fromStdString(game.GetFileName());
|
||||
break;
|
||||
case COL_SIZE:
|
||||
if (role == Qt::DisplayRole)
|
||||
return QString::fromStdString(UICommon::FormatSize(game.GetFileSize()));
|
||||
if (role == Qt::InitialSortOrderRole)
|
||||
return static_cast<quint64>(game.GetFileSize());
|
||||
break;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation == Qt::Vertical || role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
switch (section)
|
||||
{
|
||||
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_FILE_NAME:
|
||||
return tr("File Name");
|
||||
case COL_SIZE:
|
||||
return tr("Size");
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
int GameListModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return m_games.size();
|
||||
}
|
||||
|
||||
int GameListModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return NUM_COLS;
|
||||
}
|
||||
|
||||
bool GameListModel::ShouldDisplayGameListItem(int index) const
|
||||
{
|
||||
const UICommon::GameFile& game = *m_games[index];
|
||||
|
||||
if (!m_term.isEmpty() &&
|
||||
!QString::fromStdString(game.GetName(m_title_database)).contains(m_term, Qt::CaseInsensitive))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool show_platform = [&game] {
|
||||
switch (game.GetPlatform())
|
||||
{
|
||||
case DiscIO::Platform::GameCubeDisc:
|
||||
return SConfig::GetInstance().m_ListGC;
|
||||
case DiscIO::Platform::WiiDisc:
|
||||
return SConfig::GetInstance().m_ListWii;
|
||||
case DiscIO::Platform::WiiWAD:
|
||||
return SConfig::GetInstance().m_ListWad;
|
||||
case DiscIO::Platform::ELFOrDOL:
|
||||
return SConfig::GetInstance().m_ListElfDol;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
||||
if (!show_platform)
|
||||
return false;
|
||||
|
||||
switch (game.GetCountry())
|
||||
{
|
||||
case DiscIO::Country::Australia:
|
||||
return SConfig::GetInstance().m_ListAustralia;
|
||||
case DiscIO::Country::Europe:
|
||||
return SConfig::GetInstance().m_ListPal;
|
||||
case DiscIO::Country::France:
|
||||
return SConfig::GetInstance().m_ListFrance;
|
||||
case DiscIO::Country::Germany:
|
||||
return SConfig::GetInstance().m_ListGermany;
|
||||
case DiscIO::Country::Italy:
|
||||
return SConfig::GetInstance().m_ListItaly;
|
||||
case DiscIO::Country::Japan:
|
||||
return SConfig::GetInstance().m_ListJap;
|
||||
case DiscIO::Country::Korea:
|
||||
return SConfig::GetInstance().m_ListKorea;
|
||||
case DiscIO::Country::Netherlands:
|
||||
return SConfig::GetInstance().m_ListNetherlands;
|
||||
case DiscIO::Country::Russia:
|
||||
return SConfig::GetInstance().m_ListRussia;
|
||||
case DiscIO::Country::Spain:
|
||||
return SConfig::GetInstance().m_ListSpain;
|
||||
case DiscIO::Country::Taiwan:
|
||||
return SConfig::GetInstance().m_ListTaiwan;
|
||||
case DiscIO::Country::USA:
|
||||
return SConfig::GetInstance().m_ListUsa;
|
||||
case DiscIO::Country::World:
|
||||
return SConfig::GetInstance().m_ListWorld;
|
||||
case DiscIO::Country::Unknown:
|
||||
default:
|
||||
return SConfig::GetInstance().m_ListUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<const UICommon::GameFile> GameListModel::GetGameFile(int index) const
|
||||
{
|
||||
return m_games[index];
|
||||
}
|
||||
|
||||
void GameListModel::AddGame(const std::shared_ptr<const UICommon::GameFile>& game)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), m_games.size(), m_games.size());
|
||||
m_games.push_back(game);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void GameListModel::UpdateGame(const std::shared_ptr<const UICommon::GameFile>& game)
|
||||
{
|
||||
int index = FindGame(game->GetFilePath());
|
||||
if (index < 0)
|
||||
{
|
||||
AddGame(game);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_games[index] = game;
|
||||
emit dataChanged(createIndex(index, 0), createIndex(index + 1, columnCount(QModelIndex())));
|
||||
}
|
||||
}
|
||||
|
||||
void GameListModel::RemoveGame(const std::string& path)
|
||||
{
|
||||
int entry = FindGame(path);
|
||||
if (entry < 0)
|
||||
return;
|
||||
|
||||
beginRemoveRows(QModelIndex(), entry, entry);
|
||||
m_games.removeAt(entry);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
int GameListModel::FindGame(const std::string& path) const
|
||||
{
|
||||
for (int i = 0; i < m_games.size(); i++)
|
||||
{
|
||||
if (m_games[i]->GetFilePath() == path)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void GameListModel::SetSearchTerm(const QString& term)
|
||||
{
|
||||
m_term = term;
|
||||
}
|
70
Source/Core/DolphinQt/GameList/GameListModel.h
Normal file
70
Source/Core/DolphinQt/GameList/GameListModel.h
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2015 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QString>
|
||||
|
||||
#include "Core/TitleDatabase.h"
|
||||
|
||||
#include "DolphinQt/GameList/GameTracker.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
class GameListModel final : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GameListModel(QObject* parent = nullptr);
|
||||
|
||||
// Qt's Model/View stuff uses these overrides.
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const override;
|
||||
int rowCount(const QModelIndex& parent) const override;
|
||||
int columnCount(const QModelIndex& parent) const override;
|
||||
|
||||
std::shared_ptr<const UICommon::GameFile> GetGameFile(int index) const;
|
||||
// Path of the game at the specified index.
|
||||
QString GetPath(int index) const { return QString::fromStdString(m_games[index]->GetFilePath()); }
|
||||
// Unique identifier of the game at the specified index.
|
||||
QString GetUniqueIdentifier(int index) const
|
||||
{
|
||||
return QString::fromStdString(m_games[index]->GetUniqueIdentifier());
|
||||
}
|
||||
bool ShouldDisplayGameListItem(int index) const;
|
||||
void SetSearchTerm(const QString& term);
|
||||
|
||||
enum
|
||||
{
|
||||
COL_PLATFORM = 0,
|
||||
COL_BANNER,
|
||||
COL_TITLE,
|
||||
COL_DESCRIPTION,
|
||||
COL_MAKER,
|
||||
COL_ID,
|
||||
COL_COUNTRY,
|
||||
COL_SIZE,
|
||||
COL_FILE_NAME,
|
||||
NUM_COLS
|
||||
};
|
||||
|
||||
void AddGame(const std::shared_ptr<const UICommon::GameFile>& game);
|
||||
void UpdateGame(const std::shared_ptr<const UICommon::GameFile>& game);
|
||||
void RemoveGame(const std::string& path);
|
||||
|
||||
private:
|
||||
// Index in m_games, or -1 if it isn't found
|
||||
int FindGame(const std::string& path) const;
|
||||
|
||||
GameTracker m_tracker;
|
||||
QList<std::shared_ptr<const UICommon::GameFile>> m_games;
|
||||
Core::TitleDatabase m_title_database;
|
||||
QString m_term;
|
||||
};
|
309
Source/Core/DolphinQt/GameList/GameTracker.cpp
Normal file
309
Source/Core/DolphinQt/GameList/GameTracker.cpp
Normal file
@ -0,0 +1,309 @@
|
||||
// Copyright 2015 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/GameList/GameTracker.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DiscIO/DirectoryBlob.h"
|
||||
|
||||
#include "DolphinQt/QtUtils/QueueOnObject.h"
|
||||
#include "DolphinQt/QtUtils/RunOnObject.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
// NOTE: Qt likes to be case-sensitive here even though it shouldn't be thus this ugly regex hack
|
||||
static const QStringList game_filters{
|
||||
QStringLiteral("*.[gG][cC][mM]"), QStringLiteral("*.[iI][sS][oO]"),
|
||||
QStringLiteral("*.[tT][gG][cC]"), QStringLiteral("*.[cC][iI][sS][oO]"),
|
||||
QStringLiteral("*.[gG][cC][zZ]"), QStringLiteral("*.[wW][bB][fF][sS]"),
|
||||
QStringLiteral("*.[wW][aA][dD]"), QStringLiteral("*.[eE][lL][fF]"),
|
||||
QStringLiteral("*.[dD][oO][lL]")};
|
||||
|
||||
GameTracker::GameTracker(QObject* parent) : QFileSystemWatcher(parent)
|
||||
{
|
||||
qRegisterMetaType<std::shared_ptr<const UICommon::GameFile>>();
|
||||
qRegisterMetaType<std::string>();
|
||||
|
||||
connect(this, &QFileSystemWatcher::directoryChanged, this, &GameTracker::UpdateDirectory);
|
||||
connect(this, &QFileSystemWatcher::fileChanged, this, &GameTracker::UpdateFile);
|
||||
connect(&Settings::Instance(), &Settings::AutoRefreshToggled, this, [this] {
|
||||
const auto paths = Settings::Instance().GetPaths();
|
||||
|
||||
for (const auto& path : paths)
|
||||
{
|
||||
Settings::Instance().RemovePath(path);
|
||||
Settings::Instance().AddPath(path);
|
||||
}
|
||||
});
|
||||
|
||||
m_load_thread.Reset([this](Command command) {
|
||||
switch (command.type)
|
||||
{
|
||||
case CommandType::LoadCache:
|
||||
LoadCache();
|
||||
break;
|
||||
case CommandType::Start:
|
||||
StartInternal();
|
||||
case CommandType::AddDirectory:
|
||||
AddDirectoryInternal(command.path);
|
||||
break;
|
||||
case CommandType::RemoveDirectory:
|
||||
RemoveDirectoryInternal(command.path);
|
||||
break;
|
||||
case CommandType::UpdateDirectory:
|
||||
UpdateDirectoryInternal(command.path);
|
||||
break;
|
||||
case CommandType::UpdateFile:
|
||||
UpdateFileInternal(command.path);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
m_load_thread.EmplaceItem(Command{CommandType::LoadCache, {}});
|
||||
|
||||
// TODO: When language changes, reload m_title_database and call m_cache.UpdateAdditionalMetadata
|
||||
}
|
||||
|
||||
void GameTracker::LoadCache()
|
||||
{
|
||||
m_cache.Load();
|
||||
m_cache_loaded_event.Set();
|
||||
}
|
||||
|
||||
void GameTracker::Start()
|
||||
{
|
||||
if (m_initial_games_emitted)
|
||||
return;
|
||||
|
||||
m_initial_games_emitted = true;
|
||||
|
||||
m_load_thread.EmplaceItem(Command{CommandType::Start, {}});
|
||||
|
||||
m_cache_loaded_event.Wait();
|
||||
|
||||
m_cache.ForEach(
|
||||
[this](const std::shared_ptr<const UICommon::GameFile>& game) { emit GameLoaded(game); });
|
||||
|
||||
m_initial_games_emitted_event.Set();
|
||||
}
|
||||
|
||||
void GameTracker::StartInternal()
|
||||
{
|
||||
if (m_started)
|
||||
return;
|
||||
|
||||
m_started = true;
|
||||
|
||||
std::vector<std::string> paths;
|
||||
paths.reserve(m_tracked_files.size());
|
||||
for (const QString& path : m_tracked_files.keys())
|
||||
paths.push_back(path.toStdString());
|
||||
|
||||
const auto emit_game_loaded = [this](const std::shared_ptr<const UICommon::GameFile>& game) {
|
||||
emit GameLoaded(game);
|
||||
};
|
||||
const auto emit_game_updated = [this](const std::shared_ptr<const UICommon::GameFile>& game) {
|
||||
emit GameUpdated(game);
|
||||
};
|
||||
const auto emit_game_removed = [this](const std::string& path) { emit GameRemoved(path); };
|
||||
|
||||
m_initial_games_emitted_event.Wait();
|
||||
|
||||
bool cache_updated = m_cache.Update(paths, emit_game_loaded, emit_game_removed);
|
||||
cache_updated |= m_cache.UpdateAdditionalMetadata(emit_game_updated);
|
||||
if (cache_updated)
|
||||
m_cache.Save();
|
||||
}
|
||||
|
||||
bool GameTracker::AddPath(const QString& dir)
|
||||
{
|
||||
if (Settings::Instance().IsAutoRefreshEnabled())
|
||||
RunOnObject(this, [this, dir] { return addPath(dir); });
|
||||
|
||||
m_tracked_paths.push_back(dir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameTracker::RemovePath(const QString& dir)
|
||||
{
|
||||
if (Settings::Instance().IsAutoRefreshEnabled())
|
||||
RunOnObject(this, [this, dir] { return removePath(dir); });
|
||||
|
||||
const auto index = m_tracked_paths.indexOf(dir);
|
||||
|
||||
if (index == -1)
|
||||
return false;
|
||||
|
||||
m_tracked_paths.remove(index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameTracker::AddDirectory(const QString& dir)
|
||||
{
|
||||
m_load_thread.EmplaceItem(Command{CommandType::AddDirectory, dir});
|
||||
}
|
||||
|
||||
void GameTracker::RemoveDirectory(const QString& dir)
|
||||
{
|
||||
m_load_thread.EmplaceItem(Command{CommandType::RemoveDirectory, dir});
|
||||
}
|
||||
|
||||
void GameTracker::RefreshAll()
|
||||
{
|
||||
for (auto& file : m_tracked_files.keys())
|
||||
emit GameRemoved(file.toStdString());
|
||||
|
||||
m_tracked_files.clear();
|
||||
|
||||
for (const QString& dir : Settings::Instance().GetPaths())
|
||||
{
|
||||
m_load_thread.EmplaceItem(Command{CommandType::RemoveDirectory, dir});
|
||||
m_load_thread.EmplaceItem(Command{CommandType::AddDirectory, dir});
|
||||
}
|
||||
}
|
||||
|
||||
void GameTracker::UpdateDirectory(const QString& dir)
|
||||
{
|
||||
m_load_thread.EmplaceItem(Command{CommandType::UpdateDirectory, dir});
|
||||
}
|
||||
|
||||
void GameTracker::UpdateFile(const QString& dir)
|
||||
{
|
||||
m_load_thread.EmplaceItem(Command{CommandType::UpdateFile, dir});
|
||||
}
|
||||
|
||||
void GameTracker::AddDirectoryInternal(const QString& dir)
|
||||
{
|
||||
if (!QFileInfo(dir).exists())
|
||||
return;
|
||||
AddPath(dir);
|
||||
UpdateDirectoryInternal(dir);
|
||||
}
|
||||
|
||||
static std::unique_ptr<QDirIterator> GetIterator(const QString& dir)
|
||||
{
|
||||
return std::make_unique<QDirIterator>(dir, game_filters, QDir::NoFilter,
|
||||
SConfig::GetInstance().m_RecursiveISOFolder ?
|
||||
QDirIterator::Subdirectories :
|
||||
QDirIterator::NoIteratorFlags);
|
||||
}
|
||||
|
||||
void GameTracker::RemoveDirectoryInternal(const QString& dir)
|
||||
{
|
||||
RemovePath(dir);
|
||||
auto it = GetIterator(dir);
|
||||
while (it->hasNext())
|
||||
{
|
||||
QString path = QFileInfo(it->next()).canonicalFilePath();
|
||||
if (m_tracked_files.contains(path))
|
||||
{
|
||||
m_tracked_files[path].remove(dir);
|
||||
if (m_tracked_files[path].empty())
|
||||
{
|
||||
RemovePath(path);
|
||||
m_tracked_files.remove(path);
|
||||
if (m_started)
|
||||
emit GameRemoved(path.toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameTracker::UpdateDirectoryInternal(const QString& dir)
|
||||
{
|
||||
auto it = GetIterator(dir);
|
||||
while (it->hasNext())
|
||||
{
|
||||
QString path = QFileInfo(it->next()).canonicalFilePath();
|
||||
|
||||
if (m_tracked_files.contains(path))
|
||||
{
|
||||
auto& tracked_file = m_tracked_files[path];
|
||||
if (!tracked_file.contains(dir))
|
||||
tracked_file.insert(dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddPath(path);
|
||||
m_tracked_files[path] = QSet<QString>{dir};
|
||||
LoadGame(path);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& missing : FindMissingFiles(dir))
|
||||
{
|
||||
auto& tracked_file = m_tracked_files[missing];
|
||||
|
||||
tracked_file.remove(dir);
|
||||
if (tracked_file.empty())
|
||||
{
|
||||
m_tracked_files.remove(missing);
|
||||
if (m_started)
|
||||
GameRemoved(missing.toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameTracker::UpdateFileInternal(const QString& file)
|
||||
{
|
||||
if (QFileInfo(file).exists())
|
||||
{
|
||||
if (m_started)
|
||||
GameRemoved(file.toStdString());
|
||||
AddPath(file);
|
||||
LoadGame(file);
|
||||
}
|
||||
else if (RemovePath(file))
|
||||
{
|
||||
m_tracked_files.remove(file);
|
||||
if (m_started)
|
||||
emit GameRemoved(file.toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
QSet<QString> GameTracker::FindMissingFiles(const QString& dir)
|
||||
{
|
||||
auto it = GetIterator(dir);
|
||||
|
||||
QSet<QString> missing_files;
|
||||
|
||||
for (const auto& key : m_tracked_files.keys())
|
||||
{
|
||||
if (m_tracked_files[key].contains(dir))
|
||||
missing_files.insert(key);
|
||||
}
|
||||
|
||||
while (it->hasNext())
|
||||
{
|
||||
QString path = QFileInfo(it->next()).canonicalFilePath();
|
||||
if (m_tracked_files.contains(path))
|
||||
missing_files.remove(path);
|
||||
}
|
||||
|
||||
return missing_files;
|
||||
}
|
||||
|
||||
void GameTracker::LoadGame(const QString& path)
|
||||
{
|
||||
if (!m_started)
|
||||
return;
|
||||
|
||||
const std::string converted_path = path.toStdString();
|
||||
if (!DiscIO::ShouldHideFromGameList(converted_path))
|
||||
{
|
||||
bool cache_changed = false;
|
||||
auto game = m_cache.AddOrGet(converted_path, &cache_changed);
|
||||
if (game)
|
||||
emit GameLoaded(std::move(game));
|
||||
if (cache_changed)
|
||||
m_cache.Save();
|
||||
}
|
||||
}
|
89
Source/Core/DolphinQt/GameList/GameTracker.h
Normal file
89
Source/Core/DolphinQt/GameList/GameTracker.h
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2015 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include "Common/Event.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
#include "UICommon/GameFile.h"
|
||||
#include "UICommon/GameFileCache.h"
|
||||
|
||||
// Watches directories and loads GameFiles in a separate thread.
|
||||
// To use this, just add directories using AddDirectory, and listen for the
|
||||
// GameLoaded and GameRemoved signals.
|
||||
class GameTracker final : public QFileSystemWatcher
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GameTracker(QObject* parent = nullptr);
|
||||
|
||||
// A GameTracker won't emit any signals until this function has been called.
|
||||
// Before you call this function, make sure to call AddDirectory for all
|
||||
// directories you want to track, otherwise games will briefly disappear
|
||||
// until you call AddDirectory and the GameTracker finishes scanning the file system.
|
||||
void Start();
|
||||
|
||||
void AddDirectory(const QString& dir);
|
||||
void RemoveDirectory(const QString& dir);
|
||||
void RefreshAll();
|
||||
|
||||
signals:
|
||||
void GameLoaded(const std::shared_ptr<const UICommon::GameFile>& game);
|
||||
void GameUpdated(const std::shared_ptr<const UICommon::GameFile>& game);
|
||||
void GameRemoved(const std::string& path);
|
||||
|
||||
private:
|
||||
void LoadCache();
|
||||
void StartInternal();
|
||||
void UpdateDirectory(const QString& dir);
|
||||
void UpdateFile(const QString& path);
|
||||
void AddDirectoryInternal(const QString& dir);
|
||||
void RemoveDirectoryInternal(const QString& dir);
|
||||
void UpdateDirectoryInternal(const QString& dir);
|
||||
void UpdateFileInternal(const QString& path);
|
||||
QSet<QString> FindMissingFiles(const QString& dir);
|
||||
void LoadGame(const QString& path);
|
||||
|
||||
bool AddPath(const QString& path);
|
||||
bool RemovePath(const QString& path);
|
||||
|
||||
enum class CommandType
|
||||
{
|
||||
LoadCache,
|
||||
Start,
|
||||
AddDirectory,
|
||||
RemoveDirectory,
|
||||
UpdateDirectory,
|
||||
UpdateFile,
|
||||
};
|
||||
|
||||
struct Command
|
||||
{
|
||||
CommandType type;
|
||||
QString path;
|
||||
};
|
||||
|
||||
// game path -> directories that track it
|
||||
QMap<QString, QSet<QString>> m_tracked_files;
|
||||
QVector<QString> m_tracked_paths;
|
||||
Common::WorkQueueThread<Command> m_load_thread;
|
||||
UICommon::GameFileCache m_cache;
|
||||
Common::Event m_cache_loaded_event;
|
||||
Common::Event m_initial_games_emitted_event;
|
||||
bool m_initial_games_emitted = false;
|
||||
bool m_started = false;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<const UICommon::GameFile>)
|
||||
Q_DECLARE_METATYPE(std::string)
|
44
Source/Core/DolphinQt/GameList/GridProxyModel.cpp
Normal file
44
Source/Core/DolphinQt/GameList/GridProxyModel.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2015 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/GameList/GridProxyModel.h"
|
||||
|
||||
#include <QPixmap>
|
||||
#include <QSize>
|
||||
|
||||
#include "DolphinQt/GameList/GameListModel.h"
|
||||
|
||||
const QSize LARGE_BANNER_SIZE(144, 48);
|
||||
|
||||
GridProxyModel::GridProxyModel(QObject* parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
sort(GameListModel::COL_TITLE);
|
||||
}
|
||||
|
||||
QVariant GridProxyModel::data(const QModelIndex& i, int role) const
|
||||
{
|
||||
QModelIndex source_index = mapToSource(i);
|
||||
if (role == Qt::DisplayRole)
|
||||
{
|
||||
return sourceModel()->data(sourceModel()->index(source_index.row(), GameListModel::COL_TITLE),
|
||||
Qt::DisplayRole);
|
||||
}
|
||||
else if (role == Qt::DecorationRole)
|
||||
{
|
||||
auto pixmap = sourceModel()
|
||||
->data(sourceModel()->index(source_index.row(), GameListModel::COL_BANNER),
|
||||
Qt::DecorationRole)
|
||||
.value<QPixmap>();
|
||||
return pixmap.scaled(LARGE_BANNER_SIZE * pixmap.devicePixelRatio(), Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool GridProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
|
||||
{
|
||||
GameListModel* glm = qobject_cast<GameListModel*>(sourceModel());
|
||||
return glm->ShouldDisplayGameListItem(source_row);
|
||||
}
|
19
Source/Core/DolphinQt/GameList/GridProxyModel.h
Normal file
19
Source/Core/DolphinQt/GameList/GridProxyModel.h
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2015 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
// This subclass of QSortFilterProxyModel transforms the raw data into a
|
||||
// single-column large icon + name to be displayed in a QListView.
|
||||
class GridProxyModel final : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GridProxyModel(QObject* parent = nullptr);
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
|
||||
};
|
34
Source/Core/DolphinQt/GameList/ListProxyModel.cpp
Normal file
34
Source/Core/DolphinQt/GameList/ListProxyModel.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/GameList/ListProxyModel.h"
|
||||
#include "DolphinQt/GameList/GameListModel.h"
|
||||
|
||||
ListProxyModel::ListProxyModel(QObject* parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
setDynamicSortFilter(true);
|
||||
}
|
||||
|
||||
bool ListProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
|
||||
{
|
||||
GameListModel* glm = qobject_cast<GameListModel*>(sourceModel());
|
||||
return glm->ShouldDisplayGameListItem(source_row);
|
||||
}
|
||||
|
||||
bool ListProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
|
||||
{
|
||||
if (left.data(Qt::InitialSortOrderRole) != right.data(Qt::InitialSortOrderRole))
|
||||
return !QSortFilterProxyModel::lessThan(left, right);
|
||||
|
||||
// If two items are otherwise equal, compare them by their title
|
||||
const auto right_title =
|
||||
sourceModel()->index(right.row(), GameListModel::COL_TITLE).data().toString();
|
||||
const auto left_title =
|
||||
sourceModel()->index(left.row(), GameListModel::COL_TITLE).data().toString();
|
||||
|
||||
if (sortOrder() == Qt::AscendingOrder)
|
||||
return left_title < right_title;
|
||||
|
||||
return right_title < left_title;
|
||||
}
|
20
Source/Core/DolphinQt/GameList/ListProxyModel.h
Normal file
20
Source/Core/DolphinQt/GameList/ListProxyModel.h
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
// This subclass of QSortFilterProxyModel allows the data to be filtered by the view.
|
||||
class ListProxyModel final : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ListProxyModel(QObject* parent = nullptr);
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
|
||||
};
|
Reference in New Issue
Block a user