Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
dolphin/Source/Core/DolphinQt/GameList/GameList.cpp
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
958 lines (785 sloc)
30.8 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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 <QInputDialog> | |
| #include <QKeyEvent> | |
| #include <QLabel> | |
| #include <QListView> | |
| #include <QMap> | |
| #include <QMenu> | |
| #include <QMessageBox> | |
| #include <QProgressDialog> | |
| #include <QShortcut> | |
| #include <QSortFilterProxyModel> | |
| #include <QTableView> | |
| #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/GameListModel.h" | |
| #include "DolphinQt/GameList/GridProxyModel.h" | |
| #include "DolphinQt/GameList/ListProxyModel.h" | |
| #include "DolphinQt/QtUtils/DoubleClickEventFilter.h" | |
| #include "DolphinQt/Resources.h" | |
| #include "DolphinQt/Settings.h" | |
| #include "DolphinQt/WiiUpdate.h" | |
| #include "UICommon/GameFile.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(); | |
| if (Settings::GetQSettings().contains(QStringLiteral("gridview/scale"))) | |
| m_model->SetScale(Settings::GetQSettings().value(QStringLiteral("gridview/scale")).toFloat()); | |
| 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(); | |
| auto* zoom_in = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Plus), this); | |
| auto* zoom_out = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Minus), this); | |
| connect(zoom_in, &QShortcut::activated, this, &GameList::ZoomIn); | |
| connect(zoom_out, &QShortcut::activated, this, &GameList::ZoomOut); | |
| connect(&Settings::Instance(), &Settings::MetadataRefreshCompleted, this, | |
| [this] { m_grid_proxy->invalidate(); }); | |
| } | |
| 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); | |
| hor_header->setSectionResizeMode(GameListModel::COL_TAGS, 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()); | |
| Settings::GetQSettings().setValue(QStringLiteral("gridview/scale"), m_model->GetScale()); | |
| } | |
| 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); | |
| m_list->setColumnHidden(GameListModel::COL_TAGS, !SConfig::GetInstance().m_showTagsColumn); | |
| } | |
| 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) | |
| menu->addAction(tr("Compress selected ISOs..."), this, [this] { CompressISO(false); }); | |
| if (decompress) | |
| menu->addAction(tr("Decompress selected ISOs..."), this, [this] { CompressISO(true); }); | |
| if (compress || decompress) | |
| menu->addSeparator(); | |
| if (wii_saves) | |
| { | |
| menu->addAction(tr("Export Wii saves (Experimental)"), this, &GameList::ExportWiiSave); | |
| menu->addSeparator(); | |
| } | |
| menu->addAction(tr("Delete selected files..."), this, &GameList::DeleteFile); | |
| } | |
| else | |
| { | |
| const auto game = GetSelectedGame(); | |
| DiscIO::Platform platform = game->GetPlatform(); | |
| if (platform != DiscIO::Platform::ELFOrDOL) | |
| { | |
| menu->addAction(tr("&Properties"), this, &GameList::OpenProperties); | |
| menu->addAction(tr("&Wiki"), this, &GameList::OpenWiki); | |
| menu->addSeparator(); | |
| } | |
| if (platform == DiscIO::Platform::GameCubeDisc || platform == DiscIO::Platform::WiiDisc) | |
| { | |
| menu->addAction(tr("Set as &default ISO"), this, &GameList::SetDefaultISO); | |
| const auto blob_type = game->GetBlobType(); | |
| if (blob_type == DiscIO::BlobType::GCZ) | |
| menu->addAction(tr("Decompress ISO..."), this, [this] { CompressISO(true); }); | |
| else if (blob_type == DiscIO::BlobType::PLAIN) | |
| menu->addAction(tr("Compress ISO..."), this, [this] { CompressISO(false); }); | |
| QAction* change_disc = menu->addAction(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 = menu->addAction(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) | |
| { | |
| menu->addAction(tr("Open Wii &save folder"), this, &GameList::OpenSaveFolder); | |
| menu->addAction(tr("Export Wii save (Experimental)"), this, &GameList::ExportWiiSave); | |
| menu->addSeparator(); | |
| } | |
| menu->addAction(tr("Open &containing folder"), this, &GameList::OpenContainingFolder); | |
| menu->addAction(tr("Delete File..."), this, &GameList::DeleteFile); | |
| menu->addSeparator(); | |
| auto* model = Settings::Instance().GetGameListModel(); | |
| auto* tags_menu = menu->addMenu(tr("Tags")); | |
| auto path = game->GetFilePath(); | |
| auto game_tags = model->GetGameTags(path); | |
| for (const auto& tag : model->GetAllTags()) | |
| { | |
| auto* tag_action = tags_menu->addAction(tag); | |
| tag_action->setCheckable(true); | |
| tag_action->setChecked(game_tags.contains(tag)); | |
| connect(tag_action, &QAction::toggled, this, [this, path, tag, model](bool checked) { | |
| if (!checked) | |
| model->RemoveGameTag(path, tag); | |
| else | |
| model->AddGameTag(path, tag); | |
| }); | |
| } | |
| menu->addAction(tr("New Tag..."), this, &GameList::NewTag); | |
| menu->addAction(tr("Remove Tag..."), this, &GameList::DeleteTag); | |
| menu->addSeparator(); | |
| 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)")); | |
| if (dst_path.isEmpty()) | |
| return; | |
| } | |
| 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::SetViewColumn(int col, bool view) | |
| { | |
| m_list->setColumnHidden(col, !view); | |
| } | |
| 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}, | |
| {tr("Tags"), GameListModel::COL_TAGS}}; | |
| 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::NewTag() | |
| { | |
| auto tag = QInputDialog::getText(this, tr("New tag"), tr("Name for a new tag:")); | |
| if (tag.isEmpty()) | |
| return; | |
| Settings::Instance().GetGameListModel()->NewTag(tag); | |
| } | |
| void GameList::DeleteTag() | |
| { | |
| auto tag = QInputDialog::getText(this, tr("Remove tag"), tr("Name of the tag to remove:")); | |
| if (tag.isEmpty()) | |
| return; | |
| Settings::Instance().GetGameListModel()->DeleteTag(tag); | |
| } | |
| void GameList::SetSearchTerm(const QString& term) | |
| { | |
| m_model->SetSearchTerm(term); | |
| m_list_proxy->invalidate(); | |
| m_grid_proxy->invalidate(); | |
| UpdateColumnVisibility(); | |
| } | |
| void GameList::ZoomIn() | |
| { | |
| m_model->SetScale(m_model->GetScale() + 0.1); | |
| m_list_proxy->invalidate(); | |
| m_grid_proxy->invalidate(); | |
| UpdateFont(); | |
| } | |
| void GameList::ZoomOut() | |
| { | |
| if (m_model->GetScale() <= 0.1) | |
| return; | |
| m_model->SetScale(m_model->GetScale() - 0.1); | |
| m_list_proxy->invalidate(); | |
| m_grid_proxy->invalidate(); | |
| UpdateFont(); | |
| } | |
| void GameList::UpdateFont() | |
| { | |
| QFont f; | |
| f.setPointSizeF(m_model->GetScale() * f.pointSize()); | |
| m_grid->setFont(f); | |
| } |