diff --git a/pcsx2-qt/Settings/MemoryCardSettingsWidget.cpp b/pcsx2-qt/Settings/MemoryCardSettingsWidget.cpp index 2771436294..03b631e22c 100644 --- a/pcsx2-qt/Settings/MemoryCardSettingsWidget.cpp +++ b/pcsx2-qt/Settings/MemoryCardSettingsWidget.cpp @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2022 PCSX2 Dev Team + * Copyright (C) 2002-2023 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- @@ -24,16 +24,18 @@ #include "common/StringUtil.h" -#include "MemoryCardSettingsWidget.h" #include "CreateMemoryCardDialog.h" -#include "QtHost.h" #include "MemoryCardConvertDialog.h" +#include "MemoryCardSettingsWidget.h" +#include "QtHost.h" #include "QtUtils.h" #include "SettingWidgetBinder.h" #include "SettingsDialog.h" #include "pcsx2/MemoryCardFile.h" +static constexpr const char* CONFIG_SECTION = "MemoryCards"; + static std::string getSlotFilenameKey(u32 slot) { return StringUtil::StdStringFromFormat("Slot%u_Filename", slot + 1); @@ -50,8 +52,9 @@ MemoryCardSettingsWidget::MemoryCardSettingsWidget(SettingsDialog* dialog, QWidg // this is a bit lame, but resizeEvent() isn't good enough to autosize our columns, // since the group box hasn't been resized at that point. m_ui.cardGroupBox->installEventFilter(this); - - SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.directory, m_ui.browse, m_ui.open, m_ui.reset, "Folders", "MemoryCards", Path::Combine(EmuFolders::DataRoot, "memcards")); + + SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.directory, m_ui.browse, m_ui.open, m_ui.reset, "Folders", + "MemoryCards", Path::Combine(EmuFolders::DataRoot, "memcards")); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.autoEject, "EmuCore", "McdEnableEjection", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.automaticManagement, "EmuCore", "McdFolderAutoManage", true); @@ -59,8 +62,10 @@ MemoryCardSettingsWidget::MemoryCardSettingsWidget(SettingsDialog* dialog, QWidg connect(m_ui.directory, &QLineEdit::textChanged, this, &MemoryCardSettingsWidget::refresh); m_ui.cardList->setContextMenuPolicy(Qt::CustomContextMenu); - connect(m_ui.cardList, &MemoryCardListWidget::itemSelectionChanged, this, &MemoryCardSettingsWidget::updateCardActions); - connect(m_ui.cardList, &MemoryCardListWidget::customContextMenuRequested, this, &MemoryCardSettingsWidget::listContextMenuRequested); + connect( + m_ui.cardList, &MemoryCardListWidget::itemSelectionChanged, this, &MemoryCardSettingsWidget::updateCardActions); + connect(m_ui.cardList, &MemoryCardListWidget::customContextMenuRequested, this, + &MemoryCardSettingsWidget::listContextMenuRequested); connect(m_ui.refreshCard, &QPushButton::clicked, this, &MemoryCardSettingsWidget::refresh); connect(m_ui.createCard, &QPushButton::clicked, this, &MemoryCardSettingsWidget::createCard); @@ -74,8 +79,10 @@ MemoryCardSettingsWidget::MemoryCardSettingsWidget(SettingsDialog* dialog, QWidg dialog->registerWidgetHelp(m_ui.autoEject, tr("Auto-eject Memory Cards when loading save states"), tr("Checked"), tr("Avoids broken Memory Card saves. May not work with some games such as Guitar Hero.")); - dialog->registerWidgetHelp(m_ui.automaticManagement, tr("Automatically manage saves based on running game"), tr("Checked"), - tr("(Folder type only / Card size: Auto) Loads only the relevant booted game saves, ignoring others. Avoids running out of space for saves.")); + dialog->registerWidgetHelp(m_ui.automaticManagement, tr("Automatically manage saves based on running game"), + tr("Checked"), + tr("(Folder type only / Card size: Auto) Loads only the relevant booted game saves, ignoring others. Avoids " + "running out of space for saves.")); } MemoryCardSettingsWidget::~MemoryCardSettingsWidget() = default; @@ -110,21 +117,25 @@ void MemoryCardSettingsWidget::setupAdditionalUi() void MemoryCardSettingsWidget::createSlotWidgets(SlotGroup* port, u32 slot) { + const bool perGame = m_dialog->isPerGameSettings(); + port->root = new QWidget(m_ui.portGroupBox); SettingsInterface* sif = m_dialog->getSettingsInterface(); port->enable = new QCheckBox(tr("Port %1").arg(slot + 1), port->root); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, port->enable, "MemoryCards", StringUtil::StdStringFromFormat("Slot%u_Enable", slot + 1), true); + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, port->enable, CONFIG_SECTION, StringUtil::StdStringFromFormat("Slot%u_Enable", slot + 1), true); connect(port->enable, &QCheckBox::stateChanged, this, &MemoryCardSettingsWidget::refresh); port->eject = new QToolButton(port->root); - port->eject->setIcon(QIcon::fromTheme("eject-line")); + port->eject->setIcon(QIcon::fromTheme(perGame ? QStringLiteral("delete-back-2-line") : QStringLiteral("eject-line"))); port->eject->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); - port->eject->setToolTip(tr("Eject Memory Card")); + port->eject->setToolTip(perGame ? tr("Reset") : tr("Eject Memory Card")); connect(port->eject, &QToolButton::clicked, this, [this, slot]() { ejectSlot(slot); }); port->slot = new MemoryCardSlotWidget(port->root); - connect(port->slot, &MemoryCardSlotWidget::cardDropped, this, [this, slot](const QString& card) { tryInsertCard(slot, card); }); + connect(port->slot, &MemoryCardSlotWidget::cardDropped, this, + [this, slot](const QString& card) { tryInsertCard(slot, card); }); QHBoxLayout* bottom_layout = new QHBoxLayout(); bottom_layout->setContentsMargins(0, 0, 0, 0); @@ -148,25 +159,28 @@ void MemoryCardSettingsWidget::tryInsertCard(u32 slot, const QString& newCard) { // handle where the card is dragged in from explorer or something const int lastSlashPos = std::max(newCard.lastIndexOf('/'), newCard.lastIndexOf('\\')); - const std::string newCardStr((lastSlashPos >= 0) ? newCard.mid(0, lastSlashPos).toStdString() : newCard.toStdString()); + const std::string newCardStr( + (lastSlashPos >= 0) ? newCard.mid(0, lastSlashPos).toStdString() : newCard.toStdString()); if (newCardStr.empty()) return; // make sure it's a card in the directory const std::vector mcds(FileMcd_GetAvailableCards(true)); - if (std::none_of(mcds.begin(), mcds.end(), [&newCardStr](const AvailableMcdInfo& mcd) { return mcd.name == newCardStr; })) + if (std::none_of( + mcds.begin(), mcds.end(), [&newCardStr](const AvailableMcdInfo& mcd) { return mcd.name == newCardStr; })) { QMessageBox::critical(this, tr("Error"), tr("This Memory Card is unknown.")); return; } - m_dialog->setStringSettingValue("MemoryCards", getSlotFilenameKey(slot).c_str(), newCardStr.c_str()); + m_dialog->setStringSettingValue(CONFIG_SECTION, getSlotFilenameKey(slot).c_str(), newCardStr.c_str()); refresh(); } void MemoryCardSettingsWidget::ejectSlot(u32 slot) { - m_dialog->setStringSettingValue("MemoryCards", getSlotFilenameKey(slot).c_str(), m_dialog->isPerGameSettings() ? nullptr : ""); + m_dialog->setStringSettingValue(CONFIG_SECTION, getSlotFilenameKey(slot).c_str(), + m_dialog->isPerGameSettings() ? std::nullopt : std::optional("")); refresh(); } @@ -192,7 +206,7 @@ void MemoryCardSettingsWidget::updateCardActions() { QString selectedCard = getSelectedCard(); const bool hasSelection = !selectedCard.isEmpty(); - + std::optional cardInfo = FileMcd_GetCardInfo(selectedCard.toStdString()); bool isPS1 = (cardInfo.has_value() ? cardInfo.value().file_type == MemoryCardFileType::PS1 : false); @@ -241,15 +255,15 @@ void MemoryCardSettingsWidget::renameCard() if (selectedCard.isEmpty()) return; - const QString newName(QInputDialog::getText(QtUtils::GetRootWidget(this), - tr("Rename Memory Card"), tr("New Card Name"), QLineEdit::Normal, selectedCard)); + const QString newName(QInputDialog::getText( + QtUtils::GetRootWidget(this), tr("Rename Memory Card"), tr("New Card Name"), QLineEdit::Normal, selectedCard)); if (newName.isEmpty() || newName == selectedCard) return; if (!newName.endsWith(QStringLiteral(".ps2")) || newName.length() <= 4) { - QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Rename Memory Card"), - tr("New name is invalid, it must end with .ps2")); + QMessageBox::critical( + QtUtils::GetRootWidget(this), tr("Rename Memory Card"), tr("New name is invalid, it must end with .ps2")); return; } @@ -274,12 +288,12 @@ void MemoryCardSettingsWidget::renameCard() void MemoryCardSettingsWidget::convertCard() { const QString selectedCard(getSelectedCard()); - + if (selectedCard.isEmpty()) return; MemoryCardConvertDialog dialog(QtUtils::GetRootWidget(this), selectedCard); - + if (dialog.IsSetup() && dialog.exec() == QDialog::Accepted) refresh(); } @@ -293,8 +307,8 @@ void MemoryCardSettingsWidget::listContextMenuRequested(const QPoint& pos) { for (u32 slot = 0; slot < MAX_SLOTS; slot++) { - connect(menu.addAction(tr("Use for Port %1").arg(slot + 1)), &QAction::triggered, - this, [this, &selectedCard, slot]() { tryInsertCard(slot, selectedCard); }); + connect(menu.addAction(tr("Use for Port %1").arg(slot + 1)), &QAction::triggered, this, + [this, &selectedCard, slot]() { tryInsertCard(slot, selectedCard); }); } menu.addSeparator(); @@ -312,14 +326,17 @@ void MemoryCardSettingsWidget::listContextMenuRequested(const QPoint& pos) void MemoryCardSettingsWidget::refresh() { + const bool perGame = m_dialog->isPerGameSettings(); + for (u32 slot = 0; slot < static_cast(m_slots.size()); slot++) { const bool enabled = m_slots[slot].enable->isChecked(); + const std::string slotKey = getSlotFilenameKey(slot); const std::optional name( - m_dialog->getStringValue("MemoryCards", getSlotFilenameKey(slot).c_str(), - FileMcd_GetDefaultName(slot).c_str())); + m_dialog->getEffectiveStringValue(CONFIG_SECTION, slotKey.c_str(), FileMcd_GetDefaultName(slot).c_str())); + const bool inherited = perGame ? !m_dialog->containsSettingValue(CONFIG_SECTION, slotKey.c_str()) : false; - m_slots[slot].slot->setCard(name); + m_slots[slot].slot->setCard(name, inherited); m_slots[slot].slot->setEnabled(enabled); m_slots[slot].eject->setEnabled(enabled); } @@ -330,19 +347,19 @@ void MemoryCardSettingsWidget::refresh() void MemoryCardSettingsWidget::swapCards() { - const std::string card_1_key(getSlotFilenameKey(0)); - const std::string card_2_key(getSlotFilenameKey(1)); - std::optional card_1_name(m_dialog->getStringValue("MemoryCards", card_1_key.c_str(), std::nullopt)); - std::optional card_2_name(m_dialog->getStringValue("MemoryCards", card_2_key.c_str(), std::nullopt)); - if (!card_1_name.has_value() || card_1_name->empty() || - !card_2_name.has_value() || card_2_name->empty()) + const std::string card1Key = getSlotFilenameKey(0); + const std::string card2Key = getSlotFilenameKey(1); + std::optional card1Name = m_dialog->getStringValue(CONFIG_SECTION, card1Key.c_str(), std::nullopt); + std::optional card2Name = m_dialog->getStringValue(CONFIG_SECTION, card2Key.c_str(), std::nullopt); + if (!card1Name.has_value() || card1Name->empty() || !card2Name.has_value() || card2Name->empty()) { - QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"), tr("Both ports must have a card selected to swap.")); + QMessageBox::critical( + QtUtils::GetRootWidget(this), tr("Error"), tr("Both ports must have a card selected to swap.")); return; } - m_dialog->setStringSettingValue("MemoryCards", card_1_key.c_str(), card_2_name->c_str()); - m_dialog->setStringSettingValue("MemoryCards", card_2_key.c_str(), card_1_name->c_str()); + m_dialog->setStringSettingValue(CONFIG_SECTION, card1Key.c_str(), card2Name->c_str()); + m_dialog->setStringSettingValue(CONFIG_SECTION, card2Key.c_str(), card1Name->c_str()); refresh(); } @@ -437,8 +454,8 @@ void MemoryCardListWidget::refresh(SettingsDialog* dialog) std::array currentCards; for (u32 i = 0; i < static_cast(currentCards.size()); i++) { - const std::optional filename = dialog->getStringValue("MemoryCards", - getSlotFilenameKey(i).c_str(), FileMcd_GetDefaultName(i).c_str()); + const std::optional filename = dialog->getEffectiveStringValue( + CONFIG_SECTION, getSlotFilenameKey(i).c_str(), FileMcd_GetDefaultName(i).c_str()); if (filename.has_value()) currentCards[i] = std::move(filename.value()); } @@ -492,7 +509,7 @@ void MemoryCardSlotWidget::dropEvent(QDropEvent* event) emit cardDropped(text); } -void MemoryCardSlotWidget::setCard(const std::optional& name) +void MemoryCardSlotWidget::setCard(const std::optional& name, bool inherited) { clear(); if (!name.has_value() || name->empty()) @@ -512,4 +529,12 @@ void MemoryCardSlotWidget::setCard(const std::optional& name) //: Ignore Crowdin's warning for [Missing], the text should be translated. item->setText(tr("%1 [Missing]").arg(QString::fromStdString(name.value()))); } + + if (inherited) + { + QFont font = item->font(); + font.setItalic(true); + item->setFont(font); + item->setForeground(palette().brush(QPalette::Disabled, QPalette::Text)); + } } diff --git a/pcsx2-qt/Settings/MemoryCardSettingsWidget.h b/pcsx2-qt/Settings/MemoryCardSettingsWidget.h index af68408705..8e2fe78485 100644 --- a/pcsx2-qt/Settings/MemoryCardSettingsWidget.h +++ b/pcsx2-qt/Settings/MemoryCardSettingsWidget.h @@ -57,7 +57,7 @@ Q_SIGNALS: void cardDropped(const QString& newCard); public: - void setCard(const std::optional& name); + void setCard(const std::optional& name, bool inherited); protected: void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/pcsx2-qt/Settings/SettingsDialog.cpp b/pcsx2-qt/Settings/SettingsDialog.cpp index 60e7176bee..cd692163b6 100644 --- a/pcsx2-qt/Settings/SettingsDialog.cpp +++ b/pcsx2-qt/Settings/SettingsDialog.cpp @@ -122,14 +122,10 @@ void SettingsDialog::setupUi(const GameList::Entry* game) tr("Audio Settings
These options control the audio output of the console.

Mouse over an option for " "additional information.")); - // for now, Memory Cards aren't settable per-game - if (!isPerGameSettings()) - { - addWidget(m_memory_card_settings = new MemoryCardSettingsWidget(this, m_ui.settingsContainer), tr("Memory Cards"), - QStringLiteral("sd-card-line"), - tr("Memory Card Settings
Create and configure Memory Cards here.

Mouse over an option for " - "additional information.")); - } + addWidget(m_memory_card_settings = new MemoryCardSettingsWidget(this, m_ui.settingsContainer), tr("Memory Cards"), + QStringLiteral("sd-card-line"), + tr("Memory Card Settings
Create and configure Memory Cards here.

Mouse over an option for " + "additional information.")); addWidget(m_dev9_settings = new DEV9SettingsWidget(this, m_ui.settingsContainer), tr("Network & HDD"), QStringLiteral("dashboard-line"), tr("Network & HDD Settings
These options control the network connectivity and internal HDD storage of the " @@ -482,6 +478,14 @@ void SettingsDialog::setStringSettingValue(const char* section, const char* key, } } +bool SettingsDialog::containsSettingValue(const char* section, const char* key) const +{ + if (m_sif) + return m_sif->ContainsValue(section, key); + else + return Host::ContainsBaseSettingValue(section, key); +} + void SettingsDialog::removeSettingValue(const char* section, const char* key) { if (m_sif) diff --git a/pcsx2-qt/Settings/SettingsDialog.h b/pcsx2-qt/Settings/SettingsDialog.h index 2c87b45d43..d1a0876f44 100644 --- a/pcsx2-qt/Settings/SettingsDialog.h +++ b/pcsx2-qt/Settings/SettingsDialog.h @@ -91,6 +91,7 @@ public: void setIntSettingValue(const char* section, const char* key, std::optional value); void setFloatSettingValue(const char* section, const char* key, std::optional value); void setStringSettingValue(const char* section, const char* key, std::optional value); + bool containsSettingValue(const char* section, const char* key) const; void removeSettingValue(const char* section, const char* key); Q_SIGNALS: diff --git a/pcsx2-qt/resources/icons/black/svg/delete-back-2-line.svg b/pcsx2-qt/resources/icons/black/svg/delete-back-2-line.svg new file mode 100644 index 0000000000..0909894136 --- /dev/null +++ b/pcsx2-qt/resources/icons/black/svg/delete-back-2-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/white/svg/delete-back-2-line.svg b/pcsx2-qt/resources/icons/white/svg/delete-back-2-line.svg new file mode 100644 index 0000000000..dde6707589 --- /dev/null +++ b/pcsx2-qt/resources/icons/white/svg/delete-back-2-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/resources/resources.qrc b/pcsx2-qt/resources/resources.qrc index 13e3c089d2..cc681ca58b 100644 --- a/pcsx2-qt/resources/resources.qrc +++ b/pcsx2-qt/resources/resources.qrc @@ -13,6 +13,7 @@ icons/black/svg/checkbox-multiple-blank-line.svg icons/black/svg/close-line.svg icons/black/svg/dashboard-line.svg + icons/black/svg/delete-back-2-line.svg icons/black/svg/disc-line.svg icons/black/svg/door-open-line.svg icons/black/svg/download-2-line.svg @@ -76,6 +77,7 @@ icons/white/svg/checkbox-multiple-blank-line.svg icons/white/svg/close-line.svg icons/white/svg/dashboard-line.svg + icons/white/svg/delete-back-2-line.svg icons/white/svg/disc-line.svg icons/white/svg/door-open-line.svg icons/white/svg/download-2-line.svg diff --git a/pcsx2/Host.cpp b/pcsx2/Host.cpp index 035250cf08..2cb3a2f1a7 100644 --- a/pcsx2/Host.cpp +++ b/pcsx2/Host.cpp @@ -167,6 +167,12 @@ bool Host::RemoveBaseValueFromStringList(const char* section, const char* key, c ->RemoveFromStringList(section, key, value); } +bool Host::ContainsBaseSettingValue(const char* section, const char* key) +{ + std::unique_lock lock(s_settings_mutex); + return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->ContainsValue(section, key); +} + void Host::RemoveBaseSettingValue(const char* section, const char* key) { std::unique_lock lock(s_settings_mutex); diff --git a/pcsx2/Host.h b/pcsx2/Host.h index 4ac75487cf..070bc0ffa8 100644 --- a/pcsx2/Host.h +++ b/pcsx2/Host.h @@ -112,6 +112,7 @@ namespace Host void SetBaseStringListSettingValue(const char* section, const char* key, const std::vector& values); bool AddBaseValueToStringList(const char* section, const char* key, const char* value); bool RemoveBaseValueFromStringList(const char* section, const char* key, const char* value); + bool ContainsBaseSettingValue(const char* section, const char* key); void RemoveBaseSettingValue(const char* section, const char* key); void CommitBaseSettingChanges();