Qt: Add memory cards to per-game settings

This commit is contained in:
Stenzek 2023-05-29 20:42:46 +10:00 committed by refractionpcsx2
parent c0343897cd
commit f741953ee4
9 changed files with 91 additions and 50 deletions

View File

@ -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);
@ -51,7 +53,8 @@ MemoryCardSettingsWidget::MemoryCardSettingsWidget(SettingsDialog* dialog, QWidg
// 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<AvailableMcdInfo> 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<const char*>(""));
refresh();
}
@ -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;
}
@ -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<u32>(m_slots.size()); slot++)
{
const bool enabled = m_slots[slot].enable->isChecked();
const std::string slotKey = getSlotFilenameKey(slot);
const std::optional<std::string> 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<std::string> card_1_name(m_dialog->getStringValue("MemoryCards", card_1_key.c_str(), std::nullopt));
std::optional<std::string> 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<std::string> card1Name = m_dialog->getStringValue(CONFIG_SECTION, card1Key.c_str(), std::nullopt);
std::optional<std::string> 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<std::string, MemoryCardSettingsWidget::MAX_SLOTS> currentCards;
for (u32 i = 0; i < static_cast<u32>(currentCards.size()); i++)
{
const std::optional<std::string> filename = dialog->getStringValue("MemoryCards",
getSlotFilenameKey(i).c_str(), FileMcd_GetDefaultName(i).c_str());
const std::optional<std::string> 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<std::string>& name)
void MemoryCardSlotWidget::setCard(const std::optional<std::string>& name, bool inherited)
{
clear();
if (!name.has_value() || name->empty())
@ -512,4 +529,12 @@ void MemoryCardSlotWidget::setCard(const std::optional<std::string>& 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));
}
}

View File

@ -57,7 +57,7 @@ Q_SIGNALS:
void cardDropped(const QString& newCard);
public:
void setCard(const std::optional<std::string>& name);
void setCard(const std::optional<std::string>& name, bool inherited);
protected:
void dragEnterEvent(QDragEnterEvent* event) override;

View File

@ -122,14 +122,10 @@ void SettingsDialog::setupUi(const GameList::Entry* game)
tr("<strong>Audio Settings</strong><hr>These options control the audio output of the console.<br><br>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("<strong>Memory Card Settings</strong><hr>Create and configure Memory Cards here.<br><br>Mouse over an option for "
"additional information."));
}
addWidget(m_dev9_settings = new DEV9SettingsWidget(this, m_ui.settingsContainer), tr("Network & HDD"), QStringLiteral("dashboard-line"),
tr("<strong>Network & HDD Settings</strong><hr>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)

View File

@ -91,6 +91,7 @@ public:
void setIntSettingValue(const char* section, const char* key, std::optional<int> value);
void setFloatSettingValue(const char* section, const char* key, std::optional<float> value);
void setStringSettingValue(const char* section, const char* key, std::optional<const char*> value);
bool containsSettingValue(const char* section, const char* key) const;
void removeSettingValue(const char* section, const char* key);
Q_SIGNALS:

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6.53451 3H20.9993C21.5516 3 21.9993 3.44772 21.9993 4V20C21.9993 20.5523 21.5516 21 20.9993 21H6.53451C6.20015 21 5.88792 20.8329 5.70246 20.5547L0.369122 12.5547C0.145189 12.2188 0.145189 11.7812 0.369122 11.4453L5.70246 3.4453C5.88792 3.1671 6.20015 3 6.53451 3ZM7.06969 5L2.40302 12L7.06969 19H19.9993V5H7.06969ZM12.9993 10.5858L15.8277 7.75736L17.242 9.17157L14.4135 12L17.242 14.8284L15.8277 16.2426L12.9993 13.4142L10.1709 16.2426L8.75668 14.8284L11.5851 12L8.75668 9.17157L10.1709 7.75736L12.9993 10.5858Z" fill="#000000"></path></svg>

After

Width:  |  Height:  |  Size: 612 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M6.53451 3H20.9993C21.5516 3 21.9993 3.44772 21.9993 4V20C21.9993 20.5523 21.5516 21 20.9993 21H6.53451C6.20015 21 5.88792 20.8329 5.70246 20.5547L0.369122 12.5547C0.145189 12.2188 0.145189 11.7812 0.369122 11.4453L5.70246 3.4453C5.88792 3.1671 6.20015 3 6.53451 3ZM7.06969 5L2.40302 12L7.06969 19H19.9993V5H7.06969ZM12.9993 10.5858L15.8277 7.75736L17.242 9.17157L14.4135 12L17.242 14.8284L15.8277 16.2426L12.9993 13.4142L10.1709 16.2426L8.75668 14.8284L11.5851 12L8.75668 9.17157L10.1709 7.75736L12.9993 10.5858Z" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 612 B

View File

@ -13,6 +13,7 @@
<file>icons/black/svg/checkbox-multiple-blank-line.svg</file>
<file>icons/black/svg/close-line.svg</file>
<file>icons/black/svg/dashboard-line.svg</file>
<file>icons/black/svg/delete-back-2-line.svg</file>
<file>icons/black/svg/disc-line.svg</file>
<file>icons/black/svg/door-open-line.svg</file>
<file>icons/black/svg/download-2-line.svg</file>
@ -76,6 +77,7 @@
<file>icons/white/svg/checkbox-multiple-blank-line.svg</file>
<file>icons/white/svg/close-line.svg</file>
<file>icons/white/svg/dashboard-line.svg</file>
<file>icons/white/svg/delete-back-2-line.svg</file>
<file>icons/white/svg/disc-line.svg</file>
<file>icons/white/svg/door-open-line.svg</file>
<file>icons/white/svg/download-2-line.svg</file>

View File

@ -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);

View File

@ -112,6 +112,7 @@ namespace Host
void SetBaseStringListSettingValue(const char* section, const char* key, const std::vector<std::string>& 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();