diff options
Diffstat (limited to 'src/yuzu/configuration')
28 files changed, 1683 insertions, 567 deletions
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index c26161169..ddf4cf552 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -4,6 +4,7 @@ #include <QSettings> #include "common/file_util.h" +#include "configure_input_simple.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" #include "input_common/main.h" @@ -339,6 +340,13 @@ void Config::ReadTouchscreenValues() { qt_config->endGroup(); } +void Config::ApplyDefaultProfileIfInputInvalid() { + if (!std::any_of(Settings::values.players.begin(), Settings::values.players.end(), + [](const Settings::PlayerInput& in) { return in.connected; })) { + ApplyInputProfileConfiguration(UISettings::values.profile_index); + } +} + void Config::ReadValues() { qt_config->beginGroup("Controls"); @@ -411,13 +419,21 @@ void Config::ReadValues() { Settings::values.language_index = qt_config->value("language_index", 1).toInt(); - const auto enabled = qt_config->value("rng_seed_enabled", false).toBool(); - if (enabled) { + const auto rng_seed_enabled = qt_config->value("rng_seed_enabled", false).toBool(); + if (rng_seed_enabled) { Settings::values.rng_seed = qt_config->value("rng_seed", 0).toULongLong(); } else { Settings::values.rng_seed = std::nullopt; } + const auto custom_rtc_enabled = qt_config->value("custom_rtc_enabled", false).toBool(); + if (custom_rtc_enabled) { + Settings::values.custom_rtc = + std::chrono::seconds(qt_config->value("custom_rtc", 0).toULongLong()); + } else { + Settings::values.custom_rtc = std::nullopt; + } + qt_config->endGroup(); qt_config->beginGroup("Miscellaneous"); @@ -441,10 +457,29 @@ void Config::ReadValues() { Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString(); qt_config->endGroup(); + const auto size = qt_config->beginReadArray("DisabledAddOns"); + for (int i = 0; i < size; ++i) { + qt_config->setArrayIndex(i); + const auto title_id = qt_config->value("title_id", 0).toULongLong(); + std::vector<std::string> out; + const auto d_size = qt_config->beginReadArray("disabled"); + for (int j = 0; j < d_size; ++j) { + qt_config->setArrayIndex(j); + out.push_back(qt_config->value("d", "").toString().toStdString()); + } + qt_config->endArray(); + Settings::values.disabled_addons.insert_or_assign(title_id, out); + } + qt_config->endArray(); + qt_config->beginGroup("UI"); UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); UISettings::values.enable_discord_presence = qt_config->value("enable_discord_presence", true).toBool(); + UISettings::values.screenshot_resolution_factor = + static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt()); + UISettings::values.select_user_on_boot = + qt_config->value("select_user_on_boot", false).toBool(); qt_config->beginGroup("UIGameList"); UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); @@ -503,6 +538,9 @@ void Config::ReadValues() { UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt(); UISettings::values.show_console = qt_config->value("showConsole", false).toBool(); + UISettings::values.profile_index = qt_config->value("profileIndex", 0).toUInt(); + + ApplyDefaultProfileIfInputInvalid(); qt_config->endGroup(); } @@ -623,6 +661,11 @@ void Config::SaveValues() { qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value()); qt_config->setValue("rng_seed", Settings::values.rng_seed.value_or(0)); + qt_config->setValue("custom_rtc_enabled", Settings::values.custom_rtc.has_value()); + qt_config->setValue("custom_rtc", + QVariant::fromValue<long long>( + Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count())); + qt_config->endGroup(); qt_config->beginGroup("Miscellaneous"); @@ -645,9 +688,27 @@ void Config::SaveValues() { qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token)); qt_config->endGroup(); + qt_config->beginWriteArray("DisabledAddOns"); + int i = 0; + for (const auto& elem : Settings::values.disabled_addons) { + qt_config->setArrayIndex(i); + qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first)); + qt_config->beginWriteArray("disabled"); + for (std::size_t j = 0; j < elem.second.size(); ++j) { + qt_config->setArrayIndex(static_cast<int>(j)); + qt_config->setValue("d", QString::fromStdString(elem.second[j])); + } + qt_config->endArray(); + ++i; + } + qt_config->endArray(); + qt_config->beginGroup("UI"); qt_config->setValue("theme", UISettings::values.theme); qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence); + qt_config->setValue("screenshot_resolution_factor", + UISettings::values.screenshot_resolution_factor); + qt_config->setValue("select_user_on_boot", UISettings::values.select_user_on_boot); qt_config->beginGroup("UIGameList"); qt_config->setValue("show_unknown", UISettings::values.show_unknown); @@ -669,6 +730,7 @@ void Config::SaveValues() { qt_config->beginGroup("Paths"); qt_config->setValue("romsPath", UISettings::values.roms_path); qt_config->setValue("symbolsPath", UISettings::values.symbols_path); + qt_config->setValue("screenshotPath", UISettings::values.screenshot_path); qt_config->setValue("gameListRootDir", UISettings::values.gamedir); qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan); qt_config->setValue("recentFiles", UISettings::values.recent_files); @@ -690,6 +752,7 @@ void Config::SaveValues() { qt_config->setValue("firstStart", UISettings::values.first_start); qt_config->setValue("calloutFlags", UISettings::values.callout_flags); qt_config->setValue("showConsole", UISettings::values.show_console); + qt_config->setValue("profileIndex", UISettings::values.profile_index); qt_config->endGroup(); } diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index a1c27bbf9..e73ad19bb 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -34,6 +34,7 @@ private: void ReadKeyboardValues(); void ReadMouseValues(); void ReadTouchscreenValues(); + void ApplyDefaultProfileIfInputInvalid(); void SaveValues(); void SavePlayerValues(); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 9b297df28..3f03f0b77 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>461</width> - <height>500</height> + <width>382</width> + <height>241</height> </rect> </property> <property name="windowTitle"> @@ -15,51 +15,76 @@ </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="ConfigureGeneral" name="generalTab"> - <attribute name="title"> - <string>General</string> - </attribute> - </widget> - <widget class="ConfigureGameList" name="gameListTab"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QListWidget" name="selectorList"> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>150</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="ConfigureGeneral" name="generalTab"> <attribute name="title"> - <string>Game List</string> + <string>General</string> </attribute> - </widget> - <widget class="ConfigureSystem" name="systemTab"> - <attribute name="title"> - <string>System</string> - </attribute> - </widget> - <widget class="ConfigureInput" name="inputTab"> - <attribute name="title"> - <string>Input</string> - </attribute> - </widget> - <widget class="ConfigureGraphics" name="graphicsTab"> - <attribute name="title"> - <string>Graphics</string> - </attribute> - </widget> - <widget class="ConfigureAudio" name="audioTab"> - <attribute name="title"> - <string>Audio</string> - </attribute> - </widget> - <widget class="ConfigureDebug" name="debugTab"> - <attribute name="title"> - <string>Debug</string> - </attribute> - </widget> - <widget class="ConfigureWeb" name="webTab"> + </widget> + <widget class="ConfigureGameList" name="gameListTab"> + <attribute name="title"> + <string>Game List</string> + </attribute> + </widget> + <widget class="ConfigureSystem" name="systemTab"> + <attribute name="title"> + <string>System</string> + </attribute> + </widget> + <widget class="ConfigureProfileManager" name="profileManagerTab"> + <attribute name="title"> + <string>Profiles</string> + </attribute> + </widget> + <widget class="ConfigureInputSimple" name="inputTab"> + <attribute name="title"> + <string>Input</string> + </attribute> + </widget> + <widget class="ConfigureGraphics" name="graphicsTab"> + <attribute name="title"> + <string>Graphics</string> + </attribute> + </widget> + <widget class="ConfigureAudio" name="audioTab"> + <attribute name="title"> + <string>Audio</string> + </attribute> + </widget> + <widget class="ConfigureDebug" name="debugTab"> + <attribute name="title"> + <string>Debug</string> + </attribute> + </widget> + <widget class="ConfigureWeb" name="webTab"> <attribute name="title"> - <string>Web</string> + <string>Web</string> </attribute> + </widget> </widget> - </widget> + </item> + </layout> </item> <item> <widget class="QDialogButtonBox" name="buttonBox"> @@ -77,12 +102,6 @@ <header>configuration/configure_general.h</header> <container>1</container> </customwidget> - <customwidget> - <class>ConfigureGameList</class> - <extends>QWidget</extends> - <header>configuration/configure_gamelist.h</header> - <container>1</container> - </customwidget> <customwidget> <class>ConfigureSystem</class> <extends>QWidget</extends> @@ -90,6 +109,12 @@ <container>1</container> </customwidget> <customwidget> + <class>ConfigureProfileManager</class> + <extends>QWidget</extends> + <header>configuration/configure_profile_manager.h</header> + <container>1</container> + </customwidget> + <customwidget> <class>ConfigureAudio</class> <extends>QWidget</extends> <header>configuration/configure_audio.h</header> @@ -102,23 +127,29 @@ <container>1</container> </customwidget> <customwidget> - <class>ConfigureInput</class> + <class>ConfigureGraphics</class> <extends>QWidget</extends> - <header>configuration/configure_input.h</header> + <header>configuration/configure_graphics.h</header> <container>1</container> </customwidget> <customwidget> - <class>ConfigureGraphics</class> + <class>ConfigureWeb</class> <extends>QWidget</extends> - <header>configuration/configure_graphics.h</header> + <header>configuration/configure_web.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureGameList</class> + <extends>QWidget</extends> + <header>configuration/configure_gamelist.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureInputSimple</class> + <extends>QWidget</extends> + <header>configuration/configure_input_simple.h</header> <container>1</container> </customwidget> - <customwidget> - <class>ConfigureWeb</class> - <extends>QWidget</extends> - <header>configuration/configure_web.h</header> - <container>1</container> - </customwidget> </customwidgets> <resources/> <connections> diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index eb1da0f9e..5d9ccc6e8 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -17,8 +17,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) ui->output_sink_combo_box->clear(); ui->output_sink_combo_box->addItem("auto"); - for (const auto& sink_detail : AudioCore::g_sink_details) { - ui->output_sink_combo_box->addItem(sink_detail.id); + for (const char* id : AudioCore::GetSinkIDs()) { + ui->output_sink_combo_box->addItem(id); } connect(ui->volume_slider, &QSlider::valueChanged, this, @@ -97,8 +97,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) { ui->audio_device_combo_box->addItem(AudioCore::auto_device_name); const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); - const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices(); - for (const auto& device : device_list) { + for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { ui->audio_device_combo_box->addItem(QString::fromStdString(device)); } } diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index aa7de7b54..550cf9dca 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -7,7 +7,6 @@ #include "common/file_util.h" #include "common/logging/backend.h" #include "common/logging/filter.h" -#include "common/logging/log.h" #include "core/core.h" #include "core/settings.h" #include "ui_configure_debug.h" diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 3905423e9..777050405 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QHash> +#include <QListWidgetItem> #include "core/settings.h" #include "ui_configure.h" #include "yuzu/configuration/config.h" @@ -13,6 +15,13 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry ui->setupUi(this); ui->generalTab->PopulateHotkeyList(registry); this->setConfiguration(); + this->PopulateSelectionList(); + connect(ui->selectorList, &QListWidget::itemSelectionChanged, this, + &ConfigureDialog::UpdateVisibleTabs); + + adjustSize(); + + ui->selectorList->setCurrentRow(0); } ConfigureDialog::~ConfigureDialog() = default; @@ -23,10 +32,50 @@ void ConfigureDialog::applyConfiguration() { ui->generalTab->applyConfiguration(); ui->gameListTab->applyConfiguration(); ui->systemTab->applyConfiguration(); + ui->profileManagerTab->applyConfiguration(); ui->inputTab->applyConfiguration(); ui->graphicsTab->applyConfiguration(); ui->audioTab->applyConfiguration(); ui->debugTab->applyConfiguration(); ui->webTab->applyConfiguration(); Settings::Apply(); + Settings::LogSettings(); +} + +void ConfigureDialog::PopulateSelectionList() { + const std::array<std::pair<QString, QStringList>, 4> items{ + {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, + {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}}, + {tr("Graphics"), {tr("Graphics")}}, + {tr("Controls"), {tr("Input")}}}}; + + for (const auto& entry : items) { + auto* const item = new QListWidgetItem(entry.first); + item->setData(Qt::UserRole, entry.second); + + ui->selectorList->addItem(item); + } +} + +void ConfigureDialog::UpdateVisibleTabs() { + const auto items = ui->selectorList->selectedItems(); + if (items.isEmpty()) + return; + + const std::map<QString, QWidget*> widgets = {{tr("General"), ui->generalTab}, + {tr("System"), ui->systemTab}, + {tr("Profiles"), ui->profileManagerTab}, + {tr("Input"), ui->inputTab}, + {tr("Graphics"), ui->graphicsTab}, + {tr("Audio"), ui->audioTab}, + {tr("Debug"), ui->debugTab}, + {tr("Web"), ui->webTab}, + {tr("Game List"), ui->gameListTab}}; + + ui->tabWidget->clear(); + + const QStringList tabs = items[0]->data(Qt::UserRole).toStringList(); + + for (const auto& tab : tabs) + ui->tabWidget->addTab(widgets.find(tab)->second, tab); } diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index f6df7b827..243d9fa09 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -24,6 +24,8 @@ public: private: void setConfiguration(); + void UpdateVisibleTabs(); + void PopulateSelectionList(); std::unique_ptr<Ui::ConfigureDialog> ui; }; diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 92a441308..4116b6cd7 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -30,6 +30,7 @@ ConfigureGeneral::~ConfigureGeneral() = default; void ConfigureGeneral::setConfiguration() { ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); + ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); ui->enable_nfc->setChecked(Settings::values.enable_nfc); @@ -42,6 +43,7 @@ void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) { void ConfigureGeneral::applyConfiguration() { UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); + UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); UISettings::values.theme = ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index bf37446c6..dff0ad5d0 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -38,6 +38,13 @@ </property> </widget> </item> + <item> + <widget class="QCheckBox" name="toggle_user_on_boot"> + <property name="text"> + <string>Prompt for user on game boot</string> + </property> + </widget> + </item> </layout> </item> </layout> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 830d26115..f39d57998 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -20,6 +20,33 @@ #include "yuzu/configuration/configure_input_player.h" #include "yuzu/configuration/configure_mouse_advanced.h" +void OnDockedModeChanged(bool last_state, bool new_state) { + if (last_state == new_state) { + return; + } + + Core::System& system{Core::System::GetInstance()}; + if (!system.IsPoweredOn()) { + return; + } + Service::SM::ServiceManager& sm = system.ServiceManager(); + + // Message queue is shared between these services, we just need to signal an operation + // change to one and it will handle both automatically + auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); + auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); + bool has_signalled = false; + + if (applet_oe != nullptr) { + applet_oe->GetMessageQueue()->OperationModeChanged(); + has_signalled = true; + } + + if (applet_ae != nullptr && !has_signalled) { + applet_ae->GetMessageQueue()->OperationModeChanged(); + } +} + namespace { template <typename Dialog, typename... Args> void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { @@ -34,7 +61,7 @@ void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { } // Anonymous namespace ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { + : QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) { ui->setupUi(this); players_controller = { @@ -90,37 +117,6 @@ ConfigureInput::ConfigureInput(QWidget* parent) ConfigureInput::~ConfigureInput() = default; -void ConfigureInput::OnDockedModeChanged(bool last_state, bool new_state) { - if (ui->use_docked_mode->isChecked() && ui->handheld_connected->isChecked()) { - ui->handheld_connected->setChecked(false); - } - - if (last_state == new_state) { - return; - } - - Core::System& system{Core::System::GetInstance()}; - if (!system.IsPoweredOn()) { - return; - } - Service::SM::ServiceManager& sm = system.ServiceManager(); - - // Message queue is shared between these services, we just need to signal an operation - // change to one and it will handle both automatically - auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); - auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); - bool has_signalled = false; - - if (applet_oe != nullptr) { - applet_oe->GetMessageQueue()->OperationModeChanged(); - has_signalled = true; - } - - if (applet_ae != nullptr && !has_signalled) { - applet_ae->GetMessageQueue()->OperationModeChanged(); - } -} - void ConfigureInput::applyConfiguration() { for (std::size_t i = 0; i < players_controller.size(); ++i) { const auto controller_type_index = players_controller[i]->currentIndex(); diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 1649e4c0b..b8e62cc2b 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -7,8 +7,8 @@ #include <array> #include <memory> +#include <QDialog> #include <QKeyEvent> -#include <QWidget> #include "ui_configure_input.h" @@ -20,7 +20,9 @@ namespace Ui { class ConfigureInput; } -class ConfigureInput : public QWidget { +void OnDockedModeChanged(bool last_state, bool new_state); + +class ConfigureInput : public QDialog { Q_OBJECT public: @@ -33,8 +35,6 @@ public: private: void updateUIEnabled(); - void OnDockedModeChanged(bool last_state, bool new_state); - /// Load configuration settings. void loadConfiguration(); /// Restore all buttons to their default values. diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index dae8277bc..0a2d9f024 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ConfigureInput</class> - <widget class="QWidget" name="ConfigureInput"> + <widget class="QDialog" name="ConfigureInput"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>473</width> - <height>685</height> + <width>384</width> + <height>576</height> </rect> </property> <property name="windowTitle"> @@ -478,6 +478,13 @@ </property> </spacer> </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> </layout> </item> </layout> @@ -485,5 +492,38 @@ </layout> </widget> <resources/> - <connections/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureInput</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>294</x> + <y>553</y> + </hint> + <hint type="destinationlabel"> + <x>191</x> + <y>287</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureInput</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>294</x> + <y>553</y> + </hint> + <hint type="destinationlabel"> + <x>191</x> + <y>287</y> + </hint> + </hints> + </connection> + </connections> </ui> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 7dadd83c1..c5a245ebe 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -6,6 +6,8 @@ #include <memory> #include <utility> #include <QColorDialog> +#include <QGridLayout> +#include <QKeyEvent> #include <QMenu> #include <QMessageBox> #include <QTimer> diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index 7a53f6715..ade8d4435 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -11,17 +11,21 @@ #include <string> #include <QDialog> -#include <QKeyEvent> #include "common/param_package.h" #include "core/settings.h" -#include "input_common/main.h" #include "ui_configure_input.h" +class QKeyEvent; class QPushButton; class QString; class QTimer; +namespace InputCommon::Polling { +class DevicePoller; +enum class DeviceType; +} // namespace InputCommon::Polling + namespace Ui { class ConfigureInputPlayer; } diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp new file mode 100644 index 000000000..07d71e9d1 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.cpp @@ -0,0 +1,137 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <tuple> + +#include "ui_configure_input_simple.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_input_simple.h" +#include "yuzu/ui_settings.h" + +namespace { + +template <typename Dialog, typename... Args> +void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) { + caller->applyConfiguration(); + Dialog dialog(caller, std::forward<Args>(args)...); + + const auto res = dialog.exec(); + if (res == QDialog::Accepted) { + dialog.applyConfiguration(); + } +} + +// OnProfileSelect functions should (when applicable): +// - Set controller types +// - Set controller enabled +// - Set docked mode +// - Set advanced controller config/enabled (i.e. debug, kbd, mouse, touch) +// +// OnProfileSelect function should NOT however: +// - Reset any button mappings +// - Open any dialogs +// - Block in any way + +constexpr std::size_t HANDHELD_INDEX = 8; + +void HandheldOnProfileSelect() { + Settings::values.players[HANDHELD_INDEX].connected = true; + Settings::values.players[HANDHELD_INDEX].type = Settings::ControllerType::DualJoycon; + + for (std::size_t player = 0; player < HANDHELD_INDEX; ++player) { + Settings::values.players[player].connected = false; + } + + Settings::values.use_docked_mode = false; + Settings::values.keyboard_enabled = false; + Settings::values.mouse_enabled = false; + Settings::values.debug_pad_enabled = false; + Settings::values.touchscreen.enabled = true; +} + +void DualJoyconsDockedOnProfileSelect() { + Settings::values.players[0].connected = true; + Settings::values.players[0].type = Settings::ControllerType::DualJoycon; + + for (std::size_t player = 1; player <= HANDHELD_INDEX; ++player) { + Settings::values.players[player].connected = false; + } + + Settings::values.use_docked_mode = true; + Settings::values.keyboard_enabled = false; + Settings::values.mouse_enabled = false; + Settings::values.debug_pad_enabled = false; + Settings::values.touchscreen.enabled = false; +} + +// Name, OnProfileSelect (called when selected in drop down), OnConfigure (called when configure +// is clicked) +using InputProfile = std::tuple<const char*, void (*)(), void (*)(ConfigureInputSimple*)>; + +constexpr std::array<InputProfile, 3> INPUT_PROFILES{{ + {QT_TR_NOOP("Single Player - Handheld - Undocked"), HandheldOnProfileSelect, + [](ConfigureInputSimple* caller) { + CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false); + }}, + {QT_TR_NOOP("Single Player - Dual Joycons - Docked"), DualJoyconsDockedOnProfileSelect, + [](ConfigureInputSimple* caller) { + CallConfigureDialog<ConfigureInputPlayer>(caller, 1, false); + }}, + {QT_TR_NOOP("Custom"), [] {}, CallConfigureDialog<ConfigureInput>}, +}}; + +} // namespace + +void ApplyInputProfileConfiguration(int profile_index) { + std::get<1>( + INPUT_PROFILES.at(std::min(profile_index, static_cast<int>(INPUT_PROFILES.size() - 1))))(); +} + +ConfigureInputSimple::ConfigureInputSimple(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputSimple>()) { + ui->setupUi(this); + + for (const auto& profile : INPUT_PROFILES) { + const QString label = tr(std::get<0>(profile)); + ui->profile_combobox->addItem(label, label); + } + + connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + &ConfigureInputSimple::OnSelectProfile); + connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure); + + this->loadConfiguration(); +} + +ConfigureInputSimple::~ConfigureInputSimple() = default; + +void ConfigureInputSimple::applyConfiguration() { + auto index = ui->profile_combobox->currentIndex(); + // Make the stored index for "Custom" very large so that if new profiles are added it + // doesn't change. + if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) + index = std::numeric_limits<int>::max(); + + UISettings::values.profile_index = index; +} + +void ConfigureInputSimple::loadConfiguration() { + const auto index = UISettings::values.profile_index; + if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) + ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1)); + else + ui->profile_combobox->setCurrentIndex(index); +} + +void ConfigureInputSimple::OnSelectProfile(int index) { + const auto old_docked = Settings::values.use_docked_mode; + ApplyInputProfileConfiguration(index); + OnDockedModeChanged(old_docked, Settings::values.use_docked_mode); +} + +void ConfigureInputSimple::OnConfigure() { + std::get<2>(INPUT_PROFILES.at(ui->profile_combobox->currentIndex()))(this); +} diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h new file mode 100644 index 000000000..5b6b69994 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.h @@ -0,0 +1,40 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include <QWidget> + +class QPushButton; +class QString; +class QTimer; + +namespace Ui { +class ConfigureInputSimple; +} + +// Used by configuration loader to apply a profile if the input is invalid. +void ApplyInputProfileConfiguration(int profile_index); + +class ConfigureInputSimple : public QWidget { + Q_OBJECT + +public: + explicit ConfigureInputSimple(QWidget* parent = nullptr); + ~ConfigureInputSimple() override; + + /// Save all button configurations to settings file + void applyConfiguration(); + +private: + /// Load configuration settings. + void loadConfiguration(); + + void OnSelectProfile(int index); + void OnConfigure(); + + std::unique_ptr<Ui::ConfigureInputSimple> ui; +}; diff --git a/src/yuzu/configuration/configure_input_simple.ui b/src/yuzu/configuration/configure_input_simple.ui new file mode 100644 index 000000000..c4889caa9 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.ui @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputSimple</class> + <widget class="QWidget" name="ConfigureInputSimple"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>473</width> + <height>685</height> + </rect> + </property> + <property name="windowTitle"> + <string>ConfigureInputSimple</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> + <property name="title"> + <string>Profile</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="2"> + <widget class="QPushButton" name="profile_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="3"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="profile_combobox"> + <property name="minimumSize"> + <size> + <width>250</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="0" column="1" colspan="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Choose a controller configuration:</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp new file mode 100644 index 000000000..022b94609 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -0,0 +1,169 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> + +#include <QHeaderView> +#include <QMenu> +#include <QStandardItemModel> +#include <QString> +#include <QTimer> +#include <QTreeView> + +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" +#include "ui_configure_per_general.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_per_general.h" +#include "yuzu/ui_settings.h" +#include "yuzu/util/util.h" + +ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) + : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) { + + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + setWindowTitle(tr("Properties")); + + layout = new QVBoxLayout; + tree_view = new QTreeView; + item_model = new QStandardItemModel(tree_view); + tree_view->setModel(item_model); + tree_view->setAlternatingRowColors(true); + tree_view->setSelectionMode(QHeaderView::SingleSelection); + tree_view->setSelectionBehavior(QHeaderView::SelectRows); + tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setSortingEnabled(true); + tree_view->setEditTriggers(QHeaderView::NoEditTriggers); + tree_view->setUniformRowHeights(true); + tree_view->setContextMenuPolicy(Qt::NoContextMenu); + + item_model->insertColumns(0, 2); + item_model->setHeaderData(0, Qt::Horizontal, tr("Patch Name")); + item_model->setHeaderData(1, Qt::Horizontal, tr("Version")); + + // We must register all custom types with the Qt Automoc system so that we are able to use it + // with signals/slots. In this case, QList falls under the umbrells of custom types. + qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(tree_view); + + ui->scrollArea->setLayout(layout); + + scene = new QGraphicsScene; + ui->icon_view->setScene(scene); + + connect(item_model, &QStandardItemModel::itemChanged, + [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); + + this->loadConfiguration(); +} + +ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; + +void ConfigurePerGameGeneral::applyConfiguration() { + std::vector<std::string> disabled_addons; + + for (const auto& item : list_items) { + const auto disabled = item.front()->checkState() == Qt::Unchecked; + if (disabled) + disabled_addons.push_back(item.front()->text().toStdString()); + } + + Settings::values.disabled_addons[title_id] = disabled_addons; +} + +void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) { + this->file = std::move(file); + this->loadConfiguration(); +} + +void ConfigurePerGameGeneral::loadConfiguration() { + if (file == nullptr) + return; + + const auto loader = Loader::GetLoader(file); + + ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str()); + + FileSys::PatchManager pm{title_id}; + const auto control = pm.GetControlMetadata(); + + if (control.first != nullptr) { + ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); + ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); + ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); + } else { + std::string title; + if (loader->ReadTitle(title) == Loader::ResultStatus::Success) + ui->display_name->setText(QString::fromStdString(title)); + + FileSys::NACP nacp; + if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) + ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); + + ui->display_version->setText(QStringLiteral("1.0.0")); + } + + if (control.second != nullptr) { + scene->clear(); + + QPixmap map; + const auto bytes = control.second->ReadAllBytes(); + map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); + + scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } else { + std::vector<u8> bytes; + if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { + scene->clear(); + + QPixmap map; + map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); + + scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + } + + FileSys::VirtualFile update_raw; + loader->ReadUpdateRaw(update_raw); + + const auto& disabled = Settings::values.disabled_addons[title_id]; + + for (const auto& patch : pm.GetPatchVersionNames(update_raw)) { + QStandardItem* first_item = new QStandardItem; + const auto name = QString::fromStdString(patch.first).replace("[D] ", ""); + first_item->setText(name); + first_item->setCheckable(true); + + const auto patch_disabled = + std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end(); + + first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked); + + list_items.push_back(QList<QStandardItem*>{ + first_item, new QStandardItem{QString::fromStdString(patch.second)}}); + item_model->appendRow(list_items.back()); + } + + tree_view->setColumnWidth(0, 5 * tree_view->width() / 16); + + ui->display_filename->setText(QString::fromStdString(file->GetName())); + + ui->display_format->setText( + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); + + const auto valueText = ReadableByteSize(file->GetSize()); + ui->display_size->setText(valueText); +} diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h new file mode 100644 index 000000000..f8a7d5326 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.h @@ -0,0 +1,50 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> + +#include <QDialog> +#include <QList> + +#include "core/file_sys/vfs_types.h" + +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; +class QTreeView; +class QVBoxLayout; + +namespace Ui { +class ConfigurePerGameGeneral; +} + +class ConfigurePerGameGeneral : public QDialog { + Q_OBJECT + +public: + explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id); + ~ConfigurePerGameGeneral() override; + + /// Save all button configurations to settings file + void applyConfiguration(); + + void loadFromFile(FileSys::VirtualFile file); + +private: + std::unique_ptr<Ui::ConfigurePerGameGeneral> ui; + FileSys::VirtualFile file; + u64 title_id; + + QVBoxLayout* layout; + QTreeView* tree_view; + QStandardItemModel* item_model; + QGraphicsScene* scene; + + std::vector<QList<QStandardItem*>> list_items; + + void loadConfiguration(); +}; diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui new file mode 100644 index 000000000..8fdd96fa4 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.ui @@ -0,0 +1,276 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGameGeneral</class> + <widget class="QDialog" name="ConfigurePerGameGeneral"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>520</height> + </rect> + </property> + <property name="windowTitle"> + <string>ConfigurePerGameGeneral</string> + </property> + <layout class="QHBoxLayout" name="HorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="VerticalLayout"> + <item> + <widget class="QGroupBox" name="GeneralGroupBox"> + <property name="title"> + <string>Info</string> + </property> + <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="6" column="1" colspan="2"> + <widget class="QLineEdit" name="display_filename"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="display_name"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Developer</string> + </property> + </widget> + </item> + <item row="5" column="1" colspan="2"> + <widget class="QLineEdit" name="display_size"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Filename</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Version</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Format</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="display_version"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="display_format"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Size</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="display_developer"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Title ID</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="display_title_id"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="2" rowspan="5"> + <widget class="QGraphicsView" name="icon_view"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>128</width> + <height>128</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>128</width> + <height>128</height> + </size> + </property> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="sizeAdjustPolicy"> + <enum>QAbstractScrollArea::AdjustToContents</enum> + </property> + <property name="interactive"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="PerformanceGroupBox"> + <property name="title"> + <string>Add-Ons</string> + </property> + <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> + <item> + <widget class="QScrollArea" name="scrollArea"> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>350</width> + <height>169</height> + </rect> + </property> + </widget> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigurePerGameGeneral</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>269</x> + <y>567</y> + </hint> + <hint type="destinationlabel"> + <x>269</x> + <y>294</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigurePerGameGeneral</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>269</x> + <y>567</y> + </hint> + <hint type="destinationlabel"> + <x>269</x> + <y>294</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp new file mode 100644 index 000000000..41663e39a --- /dev/null +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -0,0 +1,300 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <QFileDialog> +#include <QGraphicsItem> +#include <QGraphicsScene> +#include <QHeaderView> +#include <QMessageBox> +#include <QStandardItemModel> +#include <QTreeView> +#include <QVBoxLayout> +#include "common/assert.h" +#include "common/file_util.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/settings.h" +#include "ui_configure_profile_manager.h" +#include "yuzu/configuration/configure_profile_manager.h" +#include "yuzu/util/limitable_input_dialog.h" + +namespace { +// Same backup JPEG used by acc IProfile::GetImage if no jpeg found +constexpr std::array<u8, 107> backup_jpeg{ + 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, + 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, + 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, + 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, + 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, + 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, + 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, +}; + +QString GetImagePath(Service::Account::UUID uuid) { + const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; + return QString::fromStdString(path); +} + +QString GetAccountUsername(const Service::Account::ProfileManager& manager, + Service::Account::UUID uuid) { + Service::Account::ProfileBase profile; + if (!manager.GetProfileBase(uuid, profile)) { + return {}; + } + + const auto text = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); + return QString::fromStdString(text); +} + +QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { + return ConfigureProfileManager::tr("%1\n%2", + "%1 is the profile username, %2 is the formatted UUID (e.g. " + "00112233-4455-6677-8899-AABBCCDDEEFF))") + .arg(username, QString::fromStdString(uuid.FormatSwitch())); +} + +QPixmap GetIcon(Service::Account::UUID uuid) { + QPixmap icon{GetImagePath(uuid)}; + + if (!icon) { + icon.fill(Qt::black); + icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size())); + } + + return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + +QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) { + return LimitableInputDialog::GetText(parent, ConfigureProfileManager::tr("Enter Username"), + description_text, 1, + static_cast<int>(Service::Account::profile_username_size)); +} +} // Anonymous namespace + +ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent) + : QWidget(parent), ui(new Ui::ConfigureProfileManager), + profile_manager(std::make_unique<Service::Account::ProfileManager>()) { + ui->setupUi(this); + + layout = new QVBoxLayout; + tree_view = new QTreeView; + item_model = new QStandardItemModel(tree_view); + tree_view->setModel(item_model); + + tree_view->setAlternatingRowColors(true); + tree_view->setSelectionMode(QHeaderView::SingleSelection); + tree_view->setSelectionBehavior(QHeaderView::SelectRows); + tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setSortingEnabled(true); + tree_view->setEditTriggers(QHeaderView::NoEditTriggers); + tree_view->setUniformRowHeights(true); + tree_view->setIconSize({64, 64}); + tree_view->setContextMenuPolicy(Qt::NoContextMenu); + + item_model->insertColumns(0, 1); + item_model->setHeaderData(0, Qt::Horizontal, "Users"); + + // We must register all custom types with the Qt Automoc system so that we are able to use it + // with signals/slots. In this case, QList falls under the umbrells of custom types. + qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(tree_view); + + ui->scrollArea->setLayout(layout); + + connect(tree_view, &QTreeView::clicked, this, &ConfigureProfileManager::SelectUser); + + connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureProfileManager::AddUser); + connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureProfileManager::RenameUser); + connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureProfileManager::DeleteUser); + connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureProfileManager::SetUserImage); + + scene = new QGraphicsScene; + ui->current_user_icon->setScene(scene); + + this->setConfiguration(); +} + +ConfigureProfileManager::~ConfigureProfileManager() = default; + +void ConfigureProfileManager::setConfiguration() { + enabled = !Core::System::GetInstance().IsPoweredOn(); + item_model->removeRows(0, item_model->rowCount()); + list_items.clear(); + + PopulateUserList(); + UpdateCurrentUser(); +} + +void ConfigureProfileManager::PopulateUserList() { + const auto& profiles = profile_manager->GetAllUsers(); + for (const auto& user : profiles) { + Service::Account::ProfileBase profile; + if (!profile_manager->GetProfileBase(user, profile)) + continue; + + const auto username = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); + + list_items.push_back(QList<QStandardItem*>{new QStandardItem{ + GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); + } + + for (const auto& item : list_items) + item_model->appendRow(item); +} + +void ConfigureProfileManager::UpdateCurrentUser() { + ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS); + + const auto& current_user = profile_manager->GetUser(Settings::values.current_user); + ASSERT(current_user); + const auto username = GetAccountUsername(*profile_manager, *current_user); + + scene->clear(); + scene->addPixmap( + GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + ui->current_user_username->setText(username); +} + +void ConfigureProfileManager::applyConfiguration() { + if (!enabled) + return; + + Settings::Apply(); +} + +void ConfigureProfileManager::SelectUser(const QModelIndex& index) { + Settings::values.current_user = + std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1)); + + UpdateCurrentUser(); + + ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2); + ui->pm_rename->setEnabled(true); + ui->pm_set_image->setEnabled(true); +} + +void ConfigureProfileManager::AddUser() { + const auto username = + GetProfileUsernameFromUser(this, tr("Enter a username for the new user:")); + if (username.isEmpty()) { + return; + } + + const auto uuid = Service::Account::UUID::Generate(); + profile_manager->CreateNewUser(uuid, username.toStdString()); + + item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)}); +} + +void ConfigureProfileManager::RenameUser() { + const auto user = tree_view->currentIndex().row(); + const auto uuid = profile_manager->GetUser(user); + ASSERT(uuid); + + Service::Account::ProfileBase profile; + if (!profile_manager->GetProfileBase(*uuid, profile)) + return; + + const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:")); + if (new_username.isEmpty()) { + return; + } + + const auto username_std = new_username.toStdString(); + std::fill(profile.username.begin(), profile.username.end(), '\0'); + std::copy(username_std.begin(), username_std.end(), profile.username.begin()); + + profile_manager->SetProfileBase(*uuid, profile); + + item_model->setItem( + user, 0, + new QStandardItem{GetIcon(*uuid), + FormatUserEntryText(QString::fromStdString(username_std), *uuid)}); + UpdateCurrentUser(); +} + +void ConfigureProfileManager::DeleteUser() { + const auto index = tree_view->currentIndex().row(); + const auto uuid = profile_manager->GetUser(index); + ASSERT(uuid); + const auto username = GetAccountUsername(*profile_manager, *uuid); + + const auto confirm = QMessageBox::question( + this, tr("Confirm Delete"), + tr("You are about to delete user with name \"%1\". Are you sure?").arg(username)); + + if (confirm == QMessageBox::No) + return; + + if (Settings::values.current_user == tree_view->currentIndex().row()) + Settings::values.current_user = 0; + UpdateCurrentUser(); + + if (!profile_manager->RemoveUser(*uuid)) + return; + + item_model->removeRows(tree_view->currentIndex().row(), 1); + tree_view->clearSelection(); + + ui->pm_remove->setEnabled(false); + ui->pm_rename->setEnabled(false); +} + +void ConfigureProfileManager::SetUserImage() { + const auto index = tree_view->currentIndex().row(); + const auto uuid = profile_manager->GetUser(index); + ASSERT(uuid); + + const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(), + tr("JPEG Images (*.jpg *.jpeg)")); + + if (file.isEmpty()) { + return; + } + + const auto image_path = GetImagePath(*uuid); + if (QFile::exists(image_path) && !QFile::remove(image_path)) { + QMessageBox::warning( + this, tr("Error deleting image"), + tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path)); + return; + } + + const auto raw_path = QString::fromStdString( + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"); + const QFileInfo raw_info{raw_path}; + if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { + QMessageBox::warning(this, tr("Error deleting file"), + tr("Unable to delete existing file: %1.").arg(raw_path)); + return; + } + + const QString absolute_dst_path = QFileInfo{image_path}.absolutePath(); + if (!QDir{raw_path}.mkpath(absolute_dst_path)) { + QMessageBox::warning( + this, tr("Error creating user image directory"), + tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path)); + return; + } + + if (!QFile::copy(file, image_path)) { + QMessageBox::warning(this, tr("Error copying user image"), + tr("Unable to copy image from %1 to %2").arg(file, image_path)); + return; + } + + const auto username = GetAccountUsername(*profile_manager, *uuid); + item_model->setItem(index, 0, + new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)}); + UpdateCurrentUser(); +} diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h new file mode 100644 index 000000000..7fe95a2a8 --- /dev/null +++ b/src/yuzu/configuration/configure_profile_manager.h @@ -0,0 +1,57 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include <QList> +#include <QWidget> + +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; +class QTreeView; +class QVBoxLayout; + +namespace Service::Account { +class ProfileManager; +} + +namespace Ui { +class ConfigureProfileManager; +} + +class ConfigureProfileManager : public QWidget { + Q_OBJECT + +public: + explicit ConfigureProfileManager(QWidget* parent = nullptr); + ~ConfigureProfileManager() override; + + void applyConfiguration(); + void setConfiguration(); + +private: + void PopulateUserList(); + void UpdateCurrentUser(); + + void SelectUser(const QModelIndex& index); + void AddUser(); + void RenameUser(); + void DeleteUser(); + void SetUserImage(); + + QVBoxLayout* layout; + QTreeView* tree_view; + QStandardItemModel* item_model; + QGraphicsScene* scene; + + std::vector<QList<QStandardItem*>> list_items; + + std::unique_ptr<Ui::ConfigureProfileManager> ui; + bool enabled = false; + + std::unique_ptr<Service::Account::ProfileManager> profile_manager; +}; diff --git a/src/yuzu/configuration/configure_profile_manager.ui b/src/yuzu/configuration/configure_profile_manager.ui new file mode 100644 index 000000000..dedba4998 --- /dev/null +++ b/src/yuzu/configuration/configure_profile_manager.ui @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureProfileManager</class> + <widget class="QWidget" name="ConfigureProfileManager"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>366</width> + <height>483</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> + <property name="title"> + <string>Profile Manager</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="sizeConstraint"> + <enum>QLayout::SetNoConstraint</enum> + </property> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Current User</string> + </property> + </widget> + </item> + <item> + <widget class="QGraphicsView" name="current_user_icon"> + <property name="minimumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="interactive"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="current_user_username"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Username</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QScrollArea" name="scrollArea"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="widgetResizable"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QPushButton" name="pm_set_image"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Set Image</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pm_add"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pm_rename"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Rename</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pm_remove"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="label_disable_info"> + <property name="text"> + <string>Profile management is available only when game is not running.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index ab5d46492..10645a2b3 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -2,24 +2,19 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <algorithm> +#include <array> +#include <chrono> +#include <optional> + #include <QFileDialog> #include <QGraphicsItem> -#include <QGraphicsScene> -#include <QHeaderView> #include <QMessageBox> -#include <QStandardItemModel> -#include <QTreeView> -#include <QVBoxLayout> #include "common/assert.h" #include "common/file_util.h" -#include "common/string_util.h" #include "core/core.h" -#include "core/hle/service/acc/profile_manager.h" #include "core/settings.h" #include "ui_configure_system.h" #include "yuzu/configuration/configure_system.h" -#include "yuzu/util/limitable_input_dialog.h" namespace { constexpr std::array<int, 12> days_in_month = {{ @@ -36,64 +31,9 @@ constexpr std::array<int, 12> days_in_month = {{ 30, 31, }}; - -// Same backup JPEG used by acc IProfile::GetImage if no jpeg found -constexpr std::array<u8, 107> backup_jpeg{ - 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, - 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, - 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, - 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, - 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, - 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, - 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, -}; - -QString GetImagePath(Service::Account::UUID uuid) { - const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + - "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; - return QString::fromStdString(path); -} - -QString GetAccountUsername(const Service::Account::ProfileManager& manager, - Service::Account::UUID uuid) { - Service::Account::ProfileBase profile; - if (!manager.GetProfileBase(uuid, profile)) { - return {}; - } - - const auto text = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); - return QString::fromStdString(text); -} - -QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { - return ConfigureSystem::tr("%1\n%2", - "%1 is the profile username, %2 is the formatted UUID (e.g. " - "00112233-4455-6677-8899-AABBCCDDEEFF))") - .arg(username, QString::fromStdString(uuid.FormatSwitch())); -} - -QPixmap GetIcon(Service::Account::UUID uuid) { - QPixmap icon{GetImagePath(uuid)}; - - if (!icon) { - icon.fill(Qt::black); - icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size())); - } - - return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); -} - -QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) { - return LimitableInputDialog::GetText(parent, ConfigureSystem::tr("Enter Username"), - description_text, 1, - static_cast<int>(Service::Account::profile_username_size)); -} } // Anonymous namespace -ConfigureSystem::ConfigureSystem(QWidget* parent) - : QWidget(parent), ui(new Ui::ConfigureSystem), - profile_manager(std::make_unique<Service::Account::ProfileManager>()) { +ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { ui->setupUi(this); connect(ui->combo_birthmonth, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, @@ -101,50 +41,17 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, &ConfigureSystem::RefreshConsoleID); - layout = new QVBoxLayout; - tree_view = new QTreeView; - item_model = new QStandardItemModel(tree_view); - tree_view->setModel(item_model); - - tree_view->setAlternatingRowColors(true); - tree_view->setSelectionMode(QHeaderView::SingleSelection); - tree_view->setSelectionBehavior(QHeaderView::SelectRows); - tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); - tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); - tree_view->setSortingEnabled(true); - tree_view->setEditTriggers(QHeaderView::NoEditTriggers); - tree_view->setUniformRowHeights(true); - tree_view->setIconSize({64, 64}); - tree_view->setContextMenuPolicy(Qt::NoContextMenu); - - item_model->insertColumns(0, 1); - item_model->setHeaderData(0, Qt::Horizontal, "Users"); - - // We must register all custom types with the Qt Automoc system so that we are able to use it - // with signals/slots. In this case, QList falls under the umbrells of custom types. - qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); - - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - layout->addWidget(tree_view); - - ui->scrollArea->setLayout(layout); - - connect(tree_view, &QTreeView::clicked, this, &ConfigureSystem::SelectUser); - - connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser); - connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser); - connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser); - connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage); - connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { ui->rng_seed_edit->setEnabled(checked); if (!checked) ui->rng_seed_edit->setText(QStringLiteral("00000000")); }); - scene = new QGraphicsScene; - ui->current_user_icon->setScene(scene); + connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { + ui->custom_rtc_edit->setEnabled(checked); + if (!checked) + ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime()); + }); this->setConfiguration(); } @@ -156,49 +63,19 @@ void ConfigureSystem::setConfiguration() { ui->combo_language->setCurrentIndex(Settings::values.language_index); - item_model->removeRows(0, item_model->rowCount()); - list_items.clear(); - - PopulateUserList(); - UpdateCurrentUser(); - ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value()); const auto rng_seed = QString("%1").arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}).toUpper(); ui->rng_seed_edit->setText(rng_seed); -} - -void ConfigureSystem::PopulateUserList() { - const auto& profiles = profile_manager->GetAllUsers(); - for (const auto& user : profiles) { - Service::Account::ProfileBase profile; - if (!profile_manager->GetProfileBase(user, profile)) - continue; - - const auto username = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); - - list_items.push_back(QList<QStandardItem*>{new QStandardItem{ - GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); - } - - for (const auto& item : list_items) - item_model->appendRow(item); -} -void ConfigureSystem::UpdateCurrentUser() { - ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS); + ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); + ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value()); - const auto& current_user = profile_manager->GetUser(Settings::values.current_user); - ASSERT(current_user); - const auto username = GetAccountUsername(*profile_manager, *current_user); - - scene->clear(); - scene->addPixmap( - GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - ui->current_user_username->setText(username); + const auto rtc_time = Settings::values.custom_rtc.value_or( + std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); + ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); } void ConfigureSystem::ReadSystemSettings() {} @@ -214,6 +91,12 @@ void ConfigureSystem::applyConfiguration() { else Settings::values.rng_seed = std::nullopt; + if (ui->custom_rtc_checkbox->isChecked()) + Settings::values.custom_rtc = + std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()); + else + Settings::values.custom_rtc = std::nullopt; + Settings::Apply(); } @@ -256,130 +139,3 @@ void ConfigureSystem::RefreshConsoleID() { ui->label_console_id->setText( tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); } - -void ConfigureSystem::SelectUser(const QModelIndex& index) { - Settings::values.current_user = - std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1)); - - UpdateCurrentUser(); - - ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2); - ui->pm_rename->setEnabled(true); - ui->pm_set_image->setEnabled(true); -} - -void ConfigureSystem::AddUser() { - const auto username = - GetProfileUsernameFromUser(this, tr("Enter a username for the new user:")); - if (username.isEmpty()) { - return; - } - - const auto uuid = Service::Account::UUID::Generate(); - profile_manager->CreateNewUser(uuid, username.toStdString()); - - item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)}); -} - -void ConfigureSystem::RenameUser() { - const auto user = tree_view->currentIndex().row(); - const auto uuid = profile_manager->GetUser(user); - ASSERT(uuid); - - Service::Account::ProfileBase profile; - if (!profile_manager->GetProfileBase(*uuid, profile)) - return; - - const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:")); - if (new_username.isEmpty()) { - return; - } - - const auto username_std = new_username.toStdString(); - std::fill(profile.username.begin(), profile.username.end(), '\0'); - std::copy(username_std.begin(), username_std.end(), profile.username.begin()); - - profile_manager->SetProfileBase(*uuid, profile); - - item_model->setItem( - user, 0, - new QStandardItem{GetIcon(*uuid), - FormatUserEntryText(QString::fromStdString(username_std), *uuid)}); - UpdateCurrentUser(); -} - -void ConfigureSystem::DeleteUser() { - const auto index = tree_view->currentIndex().row(); - const auto uuid = profile_manager->GetUser(index); - ASSERT(uuid); - const auto username = GetAccountUsername(*profile_manager, *uuid); - - const auto confirm = QMessageBox::question( - this, tr("Confirm Delete"), - tr("You are about to delete user with name \"%1\". Are you sure?").arg(username)); - - if (confirm == QMessageBox::No) - return; - - if (Settings::values.current_user == tree_view->currentIndex().row()) - Settings::values.current_user = 0; - UpdateCurrentUser(); - - if (!profile_manager->RemoveUser(*uuid)) - return; - - item_model->removeRows(tree_view->currentIndex().row(), 1); - tree_view->clearSelection(); - - ui->pm_remove->setEnabled(false); - ui->pm_rename->setEnabled(false); -} - -void ConfigureSystem::SetUserImage() { - const auto index = tree_view->currentIndex().row(); - const auto uuid = profile_manager->GetUser(index); - ASSERT(uuid); - - const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(), - tr("JPEG Images (*.jpg *.jpeg)")); - - if (file.isEmpty()) { - return; - } - - const auto image_path = GetImagePath(*uuid); - if (QFile::exists(image_path) && !QFile::remove(image_path)) { - QMessageBox::warning( - this, tr("Error deleting image"), - tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path)); - return; - } - - const auto raw_path = QString::fromStdString( - FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"); - const QFileInfo raw_info{raw_path}; - if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) { - QMessageBox::warning(this, tr("Error deleting file"), - tr("Unable to delete existing file: %1.").arg(raw_path)); - return; - } - - const QString absolute_dst_path = QFileInfo{image_path}.absolutePath(); - if (!QDir{raw_path}.mkpath(absolute_dst_path)) { - QMessageBox::warning( - this, tr("Error creating user image directory"), - tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path)); - return; - } - - if (!QFile::copy(file, image_path)) { - QMessageBox::warning(this, tr("Error copying user image"), - tr("Unable to copy image from %1 to %2").arg(file, image_path)); - return; - } - - const auto username = GetAccountUsername(*profile_manager, *uuid); - item_model->setItem(index, 0, - new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)}); - UpdateCurrentUser(); -} diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index 07764e1f7..cf1e54de5 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -9,16 +9,6 @@ #include <QList> #include <QWidget> -class QGraphicsScene; -class QStandardItem; -class QStandardItemModel; -class QTreeView; -class QVBoxLayout; - -namespace Service::Account { -class ProfileManager; -} - namespace Ui { class ConfigureSystem; } @@ -39,21 +29,6 @@ private: void UpdateBirthdayComboBox(int birthmonth_index); void RefreshConsoleID(); - void PopulateUserList(); - void UpdateCurrentUser(); - void SelectUser(const QModelIndex& index); - void AddUser(); - void RenameUser(); - void DeleteUser(); - void SetUserImage(); - - QVBoxLayout* layout; - QTreeView* tree_view; - QStandardItemModel* item_model; - QGraphicsScene* scene; - - std::vector<QList<QStandardItem*>> list_items; - std::unique_ptr<Ui::ConfigureSystem> ui; bool enabled = false; @@ -61,6 +36,4 @@ private: int birthday = 0; int language_index = 0; int sound_index = 0; - - std::unique_ptr<Service::Account::ProfileManager> profile_manager; }; diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index a91580893..073327298 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -22,6 +22,13 @@ <string>System Settings</string> </property> <layout class="QGridLayout" name="gridLayout"> + <item row="2" column="0"> + <widget class="QLabel" name="label_sound"> + <property name="text"> + <string>Sound output mode</string> + </property> + </widget> + </item> <item row="1" column="1"> <widget class="QComboBox" name="combo_language"> <property name="toolTip"> @@ -114,27 +121,6 @@ </item> </widget> </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_console_id"> - <property name="text"> - <string>Console ID:</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_sound"> - <property name="text"> - <string>Sound output mode</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_birthday"> - <property name="text"> - <string>Birthday</string> - </property> - </widget> - </item> <item row="0" column="1"> <layout class="QHBoxLayout" name="horizontalLayout_birthday2"> <item> @@ -206,6 +192,20 @@ </item> </layout> </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_console_id"> + <property name="text"> + <string>Console ID:</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_birthday"> + <property name="text"> + <string>Birthday</string> + </property> + </widget> + </item> <item row="3" column="1"> <widget class="QPushButton" name="button_regenerate_console_id"> <property name="sizePolicy"> @@ -241,21 +241,21 @@ </item> </widget> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_language"> + <item row="5" column="0"> + <widget class="QCheckBox" name="rng_seed_checkbox"> <property name="text"> - <string>Language</string> + <string>RNG Seed</string> </property> </widget> </item> - <item row="4" column="0"> - <widget class="QCheckBox" name="rng_seed_checkbox"> + <item row="1" column="0"> + <widget class="QLabel" name="label_language"> <property name="text"> - <string>RNG Seed</string> + <string>Language</string> </property> </widget> </item> - <item row="4" column="1"> + <item row="5" column="1"> <widget class="QLineEdit" name="rng_seed_edit"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> @@ -276,147 +276,44 @@ </property> </widget> </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="gridGroupBox"> - <property name="title"> - <string>Profile Manager</string> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <property name="sizeConstraint"> - <enum>QLayout::SetNoConstraint</enum> - </property> - <item row="0" column="0"> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QLabel" name="label"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Current User</string> - </property> - </widget> - </item> - <item> - <widget class="QGraphicsView" name="current_user_icon"> - <property name="minimumSize"> - <size> - <width>48</width> - <height>48</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>48</width> - <height>48</height> - </size> - </property> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="interactive"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="current_user_username"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Username</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QScrollArea" name="scrollArea"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <item row="4" column="0"> + <widget class="QCheckBox" name="custom_rtc_checkbox"> + <property name="text"> + <string>Custom RTC</string> </property> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> + </widget> + </item> + <item row="4" column="1"> + <widget class="QDateTimeEdit" name="custom_rtc_edit"> + <property name="minimumDate"> + <date> + <year>1970</year> + <month>1</month> + <day>1</day> + </date> </property> - <property name="widgetResizable"> - <bool>false</bool> + <property name="displayFormat"> + <string>d MMM yyyy h:mm:ss AP</string> </property> </widget> </item> - <item row="2" column="0"> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QPushButton" name="pm_set_image"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Set Image</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="pm_add"> - <property name="text"> - <string>Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="pm_rename"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Rename</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="pm_remove"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Remove</string> - </property> - </widget> - </item> - </layout> - </item> </layout> </widget> </item> <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> <widget class="QLabel" name="label_disable_info"> <property name="text"> <string>System settings are available only when game is not running.</string> diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.h b/src/yuzu/configuration/configure_touchscreen_advanced.h index 41cd255fb..3d0772c87 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.h +++ b/src/yuzu/configuration/configure_touchscreen_advanced.h @@ -6,8 +6,6 @@ #include <memory> #include <QDialog> -#include <QWidget> -#include "yuzu/configuration/config.h" namespace Ui { class ConfigureTouchscreenAdvanced; diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index 3c2ccb76f..18566d028 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -89,12 +89,11 @@ void ConfigureWeb::OnLoginChanged() { void ConfigureWeb::VerifyLogin() { ui->button_verify_login->setDisabled(true); - ui->button_verify_login->setText(tr("Verifying")); - verify_watcher.setFuture( - QtConcurrent::run([this, username = ui->edit_username->text().toStdString(), - token = ui->edit_token->text().toStdString()]() { - return Core::VerifyLogin(username, token); - })); + ui->button_verify_login->setText(tr("Verifying...")); + verify_watcher.setFuture(QtConcurrent::run([username = ui->edit_username->text().toStdString(), + token = ui->edit_token->text().toStdString()] { + return Core::VerifyLogin(username, token); + })); } void ConfigureWeb::OnLoginVerified() { |