diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index a7879fa1b..5355515c1 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -18,6 +18,9 @@ add_executable(duckstation-qt gamelistsettingswidget.ui gamelistwidget.cpp gamelistwidget.h + gamepropertiesdialog.cpp + gamepropertiesdialog.h + gamepropertiesdialog.ui generalsettingswidget.cpp generalsettingswidget.h generalsettingswidget.ui diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 268a65d82..36fcc5231 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -46,6 +46,7 @@ + @@ -73,6 +74,7 @@ + @@ -128,6 +130,9 @@ Document + + Document + @@ -140,6 +145,7 @@ + diff --git a/src/duckstation-qt/gamepropertiesdialog.cpp b/src/duckstation-qt/gamepropertiesdialog.cpp new file mode 100644 index 000000000..cd33243a2 --- /dev/null +++ b/src/duckstation-qt/gamepropertiesdialog.cpp @@ -0,0 +1,231 @@ +#include "gamepropertiesdialog.h" +#include "common/cd_image.h" +#include "core/game_list.h" +#include "core/settings.h" +#include "qthostinterface.h" +#include "qtutils.h" +#include "scmversion/scmversion.h" +#include + +GamePropertiesDialog::GamePropertiesDialog(QtHostInterface* host_interface, QWidget* parent /* = nullptr */) + : QDialog(parent), m_host_interface(host_interface) +{ + m_ui.setupUi(this); + setupAdditionalUi(); + connectUi(); +} + +GamePropertiesDialog::~GamePropertiesDialog() = default; + +void GamePropertiesDialog::clear() +{ + m_ui.imagePath->clear(); + m_ui.gameCode->clear(); + m_ui.title->clear(); + m_ui.region->setCurrentIndex(0); + + { + QSignalBlocker blocker(m_ui.compatibility); + m_ui.compatibility->setCurrentIndex(0); + } + { + QSignalBlocker blocker(m_ui.upscalingIssues); + m_ui.upscalingIssues->clear(); + } + + { + QSignalBlocker blocker(m_ui.comments); + m_ui.comments->clear(); + } + + m_ui.tracks->clearContents(); +} + +void GamePropertiesDialog::populate(const GameListEntry* ge) +{ + const QString title_qstring(QString::fromStdString(ge->title)); + + setWindowTitle(tr("Game Properties - %1").arg(title_qstring)); + m_ui.imagePath->setText(QString::fromStdString(ge->path)); + m_ui.title->setText(title_qstring); + m_ui.gameCode->setText(QString::fromStdString(ge->code)); + m_ui.region->setCurrentIndex(static_cast(ge->region)); + + if (ge->code.empty()) + { + // can't fill in info without a code + m_ui.compatibility->setDisabled(true); + m_ui.upscalingIssues->setDisabled(true); + m_ui.versionTested->setDisabled(true); + } + else + { + populateCompatibilityInfo(ge->code); + } + + populateTracksInfo(ge->path.c_str()); +} + +void GamePropertiesDialog::populateCompatibilityInfo(const std::string& game_code) +{ + const GameListCompatibilityEntry* entry = m_host_interface->getGameList()->GetCompatibilityEntryForCode(game_code); + + { + QSignalBlocker blocker(m_ui.compatibility); + m_ui.compatibility->setCurrentIndex(entry ? static_cast(entry->compatibility_rating) : 0); + } + + { + QSignalBlocker blocker(m_ui.upscalingIssues); + m_ui.upscalingIssues->setText(entry ? QString::fromStdString(entry->upscaling_issues) : QString()); + } + + { + QSignalBlocker blocker(m_ui.comments); + m_ui.comments->setText(entry ? QString::fromStdString(entry->comments) : QString()); + } +} + +void GamePropertiesDialog::setupAdditionalUi() +{ + for (u8 i = 0; i < static_cast(DiscRegion::Count); i++) + m_ui.region->addItem(tr(Settings::GetDiscRegionDisplayName(static_cast(i)))); + + for (int i = 0; i < static_cast(GameListCompatibilityRating::Count); i++) + { + m_ui.compatibility->addItem( + tr(GameList::GetGameListCompatibilityRatingString(static_cast(i)))); + } + + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +} + +void GamePropertiesDialog::showForEntry(QtHostInterface* host_interface, const GameListEntry* ge) +{ + GamePropertiesDialog* gpd = new GamePropertiesDialog(host_interface); + gpd->populate(ge); + gpd->show(); + gpd->onResize(); +} + +static QString MSFTotString(const CDImage::Position& position) +{ + return QStringLiteral("%1:%2:%3 (LBA %4)") + .arg(static_cast(position.minute), 2, 10, static_cast('0')) + .arg(static_cast(position.second), 2, 10, static_cast('0')) + .arg(static_cast(position.frame), 2, 10, static_cast('0')) + .arg(static_cast(position.ToLBA())); +} + +void GamePropertiesDialog::populateTracksInfo(const char* image_path) +{ + static constexpr std::array track_mode_strings = { + {"Audio", "Mode 1", "Mode 1/Raw", "Mode 2", "Mode 2/Form 1", "Mode 2/Form 2", "Mode 2/Mix", "Mode 2/Raw"}}; + + m_ui.tracks->clearContents(); + + std::unique_ptr image = CDImage::Open(image_path); + if (!image) + return; + + const u32 num_tracks = image->GetTrackCount(); + for (u32 track = 1; track <= num_tracks; track++) + { + const CDImage::Position position = image->GetTrackStartMSFPosition(static_cast(track)); + const CDImage::Position length = image->GetTrackMSFLength(static_cast(track)); + const CDImage::TrackMode mode = image->GetTrackMode(static_cast(track)); + const int row = static_cast(track - 1u); + m_ui.tracks->insertRow(row); + m_ui.tracks->setItem(row, 0, new QTableWidgetItem(tr("%1").arg(track))); + m_ui.tracks->setItem(row, 1, new QTableWidgetItem(tr(track_mode_strings[static_cast(mode)]))); + m_ui.tracks->setItem(row, 2, new QTableWidgetItem(MSFTotString(position))); + m_ui.tracks->setItem(row, 3, new QTableWidgetItem(MSFTotString(length))); + m_ui.tracks->setItem(row, 4, new QTableWidgetItem(tr(""))); + } +} + +void GamePropertiesDialog::closeEvent(QCloseEvent* ev) +{ + deleteLater(); +} + +void GamePropertiesDialog::resizeEvent(QResizeEvent* ev) +{ + QDialog::resizeEvent(ev); + onResize(); +} + +void GamePropertiesDialog::onResize() +{ + QtUtils::ResizeColumnsForTableView(m_ui.tracks, {20, 85, 125, 125, -1}); +} + +void GamePropertiesDialog::connectUi() +{ + connect(m_ui.compatibility, static_cast(&QComboBox::currentIndexChanged), this, + &GamePropertiesDialog::saveCompatibilityInfo); + connect(m_ui.comments, &QLineEdit::textChanged, this, &GamePropertiesDialog::setCompatibilityInfoChanged); + connect(m_ui.comments, &QLineEdit::editingFinished, this, &GamePropertiesDialog::saveCompatibilityInfoIfChanged); + connect(m_ui.upscalingIssues, &QLineEdit::textChanged, this, &GamePropertiesDialog::setCompatibilityInfoChanged); + connect(m_ui.upscalingIssues, &QLineEdit::editingFinished, this, + &GamePropertiesDialog::saveCompatibilityInfoIfChanged); + connect(m_ui.setToCurrent, &QPushButton::clicked, this, &GamePropertiesDialog::onSetVersionTestedToCurrentClicked); + connect(m_ui.computeHashes, &QPushButton::clicked, this, &GamePropertiesDialog::onComputeHashClicked); + connect(m_ui.verifyDump, &QPushButton::clicked, this, &GamePropertiesDialog::onVerifyDumpClicked); + connect(m_ui.exportCompatibilityInfo, &QPushButton::clicked, this, + &GamePropertiesDialog::onExportCompatibilityInfoClicked); + connect(m_ui.close, &QPushButton::clicked, this, &QDialog::close); +} + +void GamePropertiesDialog::saveCompatibilityInfo() +{ + GameListCompatibilityEntry new_entry; + new_entry.code = m_ui.gameCode->text().toStdString(); + new_entry.title = m_ui.title->text().toStdString(); + new_entry.version_tested = m_ui.versionTested->text().toStdString(); + new_entry.upscaling_issues = m_ui.upscalingIssues->text().toStdString(); + new_entry.comments = m_ui.comments->text().toStdString(); + new_entry.compatibility_rating = static_cast(m_ui.compatibility->currentIndex()); + new_entry.region = static_cast(m_ui.region->currentIndex()); + + if (new_entry.code.empty()) + return; + + m_host_interface->getGameList()->UpdateCompatibilityEntry(std::move(new_entry), true); + emit m_host_interface->gameListRefreshed(); + m_compatibility_info_changed = false; +} + +void GamePropertiesDialog::saveCompatibilityInfoIfChanged() +{ + if (!m_compatibility_info_changed) + return; + + saveCompatibilityInfo(); +} + +void GamePropertiesDialog::setCompatibilityInfoChanged() +{ + m_compatibility_info_changed = true; +} + +void GamePropertiesDialog::onSetVersionTestedToCurrentClicked() +{ + m_ui.versionTested->setText(QString::fromUtf8(g_scm_tag_str)); + saveCompatibilityInfo(); +} + +void GamePropertiesDialog::onComputeHashClicked() +{ + QMessageBox::critical(this, tr("Not yet implemented"), tr("Not yet implemented")); +} + +void GamePropertiesDialog::onVerifyDumpClicked() +{ + QMessageBox::critical(this, tr("Not yet implemented"), tr("Not yet implemented")); +} + +void GamePropertiesDialog::onExportCompatibilityInfoClicked() +{ + QMessageBox::critical(this, tr("Not yet implemented"), tr("Not yet implemented")); +} diff --git a/src/duckstation-qt/gamepropertiesdialog.h b/src/duckstation-qt/gamepropertiesdialog.h new file mode 100644 index 000000000..51325b8b3 --- /dev/null +++ b/src/duckstation-qt/gamepropertiesdialog.h @@ -0,0 +1,50 @@ +#pragma once +#include "ui_gamepropertiesdialog.h" +#include +#include + +struct GameListEntry; + +class QtHostInterface; + +class GamePropertiesDialog final : public QDialog +{ + Q_OBJECT + +public: + GamePropertiesDialog(QtHostInterface* host_interface, QWidget* parent = nullptr); + ~GamePropertiesDialog(); + + static void showForEntry(QtHostInterface* host_interface, const GameListEntry* ge); + +public Q_SLOTS: + void clear(); + void populate(const GameListEntry* ge); + +protected: + void closeEvent(QCloseEvent* ev); + void resizeEvent(QResizeEvent* ev); + +private Q_SLOTS: + void saveCompatibilityInfo(); + void saveCompatibilityInfoIfChanged(); + void setCompatibilityInfoChanged(); + + void onSetVersionTestedToCurrentClicked(); + void onComputeHashClicked(); + void onVerifyDumpClicked(); + void onExportCompatibilityInfoClicked(); + +private: + void setupAdditionalUi(); + void connectUi(); + void populateCompatibilityInfo(const std::string& game_code); + void populateTracksInfo(const char* image_path); + void onResize(); + + Ui::GamePropertiesDialog m_ui; + + QtHostInterface* m_host_interface; + + bool m_compatibility_info_changed = false; +}; diff --git a/src/duckstation-qt/gamepropertiesdialog.ui b/src/duckstation-qt/gamepropertiesdialog.ui new file mode 100644 index 000000000..ce0322847 --- /dev/null +++ b/src/duckstation-qt/gamepropertiesdialog.ui @@ -0,0 +1,225 @@ + + + GamePropertiesDialog + + + + 0 + 0 + 722 + 466 + + + + Dialog + + + + :/icons/duck.png:/icons/duck.png + + + + + + Image Path: + + + + + + + true + + + + + + + Game Code: + + + + + + + true + + + + + + + Title: + + + + + + + true + + + + + + + Region: + + + + + + + false + + + + + + + Compatibility: + + + + + + + + + + Upscaling Issues: + + + + + + + + + + Comments: + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Compute Hashes + + + + + + + Verify Dump + + + + + + + Export Compatibility Info + + + + + + + Close + + + true + + + + + + + + + Tracks: + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + + # + + + + + Mode + + + + + Start + + + + + Length + + + + + Hash + + + + + + + + + + + Version Tested: + + + + + + + + + + + + Set to Current + + + + + + + + + + diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 90ec57bf9..65913bf3a 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -5,6 +5,7 @@ #include "core/system.h" #include "gamelistsettingswidget.h" #include "gamelistwidget.h" +#include "gamepropertiesdialog.h" #include "qtdisplaywidget.h" #include "qthostdisplay.h" #include "qthostinterface.h" @@ -347,7 +348,8 @@ void MainWindow::onGameListContextMenuRequested(const QPoint& point, const GameL // Hopefully this pointer doesn't disappear... it shouldn't. if (entry) { - connect(menu.addAction(tr("Properties...")), &QAction::triggered, [this]() { reportError(tr("TODO")); }); + connect(menu.addAction(tr("Properties...")), &QAction::triggered, + [this, entry]() { GamePropertiesDialog::showForEntry(m_host_interface, entry); }); connect(menu.addAction(tr("Open Containing Directory...")), &QAction::triggered, [this, entry]() { const QFileInfo fi(QString::fromStdString(entry->path)); diff --git a/src/duckstation-qt/resources/icons.qrc b/src/duckstation-qt/resources/icons.qrc index 93629c6bd..356d5cc3b 100644 --- a/src/duckstation-qt/resources/icons.qrc +++ b/src/duckstation-qt/resources/icons.qrc @@ -6,6 +6,12 @@ icons/flag-jp.svg icons/flag-us.png icons/flag-us.svg + icons/star-0.png + icons/star-1.png + icons/star-2.png + icons/star-3.png + icons/star-4.png + icons/star-5.png icons/applications-internet.png icons/system-search.png icons/list-add.png diff --git a/src/duckstation-qt/resources/icons/star-0.png b/src/duckstation-qt/resources/icons/star-0.png new file mode 100644 index 000000000..e5b56db70 Binary files /dev/null and b/src/duckstation-qt/resources/icons/star-0.png differ diff --git a/src/duckstation-qt/resources/icons/star-1.png b/src/duckstation-qt/resources/icons/star-1.png new file mode 100644 index 000000000..ae91a29ac Binary files /dev/null and b/src/duckstation-qt/resources/icons/star-1.png differ diff --git a/src/duckstation-qt/resources/icons/star-2.png b/src/duckstation-qt/resources/icons/star-2.png new file mode 100644 index 000000000..f7bee9b1d Binary files /dev/null and b/src/duckstation-qt/resources/icons/star-2.png differ diff --git a/src/duckstation-qt/resources/icons/star-3.png b/src/duckstation-qt/resources/icons/star-3.png new file mode 100644 index 000000000..330aefbac Binary files /dev/null and b/src/duckstation-qt/resources/icons/star-3.png differ diff --git a/src/duckstation-qt/resources/icons/star-4.png b/src/duckstation-qt/resources/icons/star-4.png new file mode 100644 index 000000000..4e9f58dfa Binary files /dev/null and b/src/duckstation-qt/resources/icons/star-4.png differ diff --git a/src/duckstation-qt/resources/icons/star-5.png b/src/duckstation-qt/resources/icons/star-5.png new file mode 100644 index 000000000..aa5707ea7 Binary files /dev/null and b/src/duckstation-qt/resources/icons/star-5.png differ