diff options
Diffstat (limited to 'src/yuzu/configuration')
30 files changed, 1716 insertions, 604 deletions
| diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 83ebbd1fe..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" @@ -206,60 +207,57 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default  void Config::ReadPlayerValues() {      for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { -        Settings::values.players[p].connected = -            qt_config->value(QString("player_%1_connected").arg(p), false).toBool(); +        auto& player = Settings::values.players[p]; -        Settings::values.players[p].type = static_cast<Settings::ControllerType>( +        player.connected = qt_config->value(QString("player_%1_connected").arg(p), false).toBool(); + +        player.type = static_cast<Settings::ControllerType>(              qt_config                  ->value(QString("player_%1_type").arg(p),                          static_cast<u8>(Settings::ControllerType::DualJoycon))                  .toUInt()); -        Settings::values.players[p].body_color_left = -            qt_config -                ->value(QString("player_%1_body_color_left").arg(p), -                        Settings::JOYCON_BODY_NEON_BLUE) -                .toUInt(); -        Settings::values.players[p].body_color_right = -            qt_config -                ->value(QString("player_%1_body_color_right").arg(p), -                        Settings::JOYCON_BODY_NEON_RED) -                .toUInt(); -        Settings::values.players[p].button_color_left = -            qt_config -                ->value(QString("player_%1_button_color_left").arg(p), -                        Settings::JOYCON_BUTTONS_NEON_BLUE) -                .toUInt(); -        Settings::values.players[p].button_color_right = -            qt_config -                ->value(QString("player_%1_button_color_right").arg(p), -                        Settings::JOYCON_BUTTONS_NEON_RED) -                .toUInt(); +        player.body_color_left = qt_config +                                     ->value(QString("player_%1_body_color_left").arg(p), +                                             Settings::JOYCON_BODY_NEON_BLUE) +                                     .toUInt(); +        player.body_color_right = qt_config +                                      ->value(QString("player_%1_body_color_right").arg(p), +                                              Settings::JOYCON_BODY_NEON_RED) +                                      .toUInt(); +        player.button_color_left = qt_config +                                       ->value(QString("player_%1_button_color_left").arg(p), +                                               Settings::JOYCON_BUTTONS_NEON_BLUE) +                                       .toUInt(); +        player.button_color_right = qt_config +                                        ->value(QString("player_%1_button_color_right").arg(p), +                                                Settings::JOYCON_BUTTONS_NEON_RED) +                                        .toUInt();          for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {              std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); -            Settings::values.players[p].buttons[i] = +            player.buttons[i] =                  qt_config                      ->value(QString("player_%1_").arg(p) + Settings::NativeButton::mapping[i],                              QString::fromStdString(default_param))                      .toString()                      .toStdString(); -            if (Settings::values.players[p].buttons[i].empty()) -                Settings::values.players[p].buttons[i] = default_param; +            if (player.buttons[i].empty()) +                player.buttons[i] = default_param;          }          for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {              std::string default_param = InputCommon::GenerateAnalogParamFromKeys(                  default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],                  default_analogs[i][3], default_analogs[i][4], 0.5f); -            Settings::values.players[p].analogs[i] = +            player.analogs[i] =                  qt_config                      ->value(QString("player_%1_").arg(p) + Settings::NativeAnalog::mapping[i],                              QString::fromStdString(default_param))                      .toString()                      .toStdString(); -            if (Settings::values.players[p].analogs[i].empty()) -                Settings::values.players[p].analogs[i] = default_param; +            if (player.analogs[i].empty()) +                player.analogs[i] = default_param;          }      } @@ -342,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"); @@ -414,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"); @@ -444,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(); @@ -506,35 +538,36 @@ 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();  }  void Config::SavePlayerValues() { -    for (int p = 0; p < Settings::values.players.size(); ++p) { -        qt_config->setValue(QString("player_%1_connected").arg(p), -                            Settings::values.players[p].connected); -        qt_config->setValue(QString("player_%1_type").arg(p), -                            static_cast<u8>(Settings::values.players[p].type)); - -        qt_config->setValue(QString("player_%1_body_color_left").arg(p), -                            Settings::values.players[p].body_color_left); -        qt_config->setValue(QString("player_%1_body_color_right").arg(p), -                            Settings::values.players[p].body_color_right); +    for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { +        const auto& player = Settings::values.players[p]; + +        qt_config->setValue(QString("player_%1_connected").arg(p), player.connected); +        qt_config->setValue(QString("player_%1_type").arg(p), static_cast<u8>(player.type)); + +        qt_config->setValue(QString("player_%1_body_color_left").arg(p), player.body_color_left); +        qt_config->setValue(QString("player_%1_body_color_right").arg(p), player.body_color_right);          qt_config->setValue(QString("player_%1_button_color_left").arg(p), -                            Settings::values.players[p].button_color_left); +                            player.button_color_left);          qt_config->setValue(QString("player_%1_button_color_right").arg(p), -                            Settings::values.players[p].button_color_right); +                            player.button_color_right);          for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {              qt_config->setValue(QString("player_%1_").arg(p) +                                      QString::fromStdString(Settings::NativeButton::mapping[i]), -                                QString::fromStdString(Settings::values.players[p].buttons[i])); +                                QString::fromStdString(player.buttons[i]));          }          for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {              qt_config->setValue(QString("player_%1_").arg(p) +                                      QString::fromStdString(Settings::NativeAnalog::mapping[i]), -                                QString::fromStdString(Settings::values.players[p].analogs[i])); +                                QString::fromStdString(player.analogs[i]));          }      }  } @@ -628,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"); @@ -650,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); @@ -674,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); @@ -695,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_audio.h b/src/yuzu/configuration/configure_audio.h index 207f9dfb3..8771421c0 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -16,15 +16,14 @@ class ConfigureAudio : public QWidget {  public:      explicit ConfigureAudio(QWidget* parent = nullptr); -    ~ConfigureAudio(); +    ~ConfigureAudio() override;      void applyConfiguration();      void retranslateUi(); -public slots: +private:      void updateAudioDevices(int sink_index); -private:      void setConfiguration();      void setOutputSinkFromSinkID();      void setAudioDeviceFromDeviceID(); diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h index d167eb996..c6420b18c 100644 --- a/src/yuzu/configuration/configure_debug.h +++ b/src/yuzu/configuration/configure_debug.h @@ -16,13 +16,12 @@ class ConfigureDebug : public QWidget {  public:      explicit ConfigureDebug(QWidget* parent = nullptr); -    ~ConfigureDebug(); +    ~ConfigureDebug() override;      void applyConfiguration();  private:      void setConfiguration(); -private:      std::unique_ptr<Ui::ConfigureDebug> ui;  }; diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 3905423e9..d802443d0 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,6 +32,7 @@ void ConfigureDialog::applyConfiguration() {      ui->generalTab->applyConfiguration();      ui->gameListTab->applyConfiguration();      ui->systemTab->applyConfiguration(); +    ui->profileManagerTab->applyConfiguration();      ui->inputTab->applyConfiguration();      ui->graphicsTab->applyConfiguration();      ui->audioTab->applyConfiguration(); @@ -30,3 +40,41 @@ void ConfigureDialog::applyConfiguration() {      ui->webTab->applyConfiguration();      Settings::Apply();  } + +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 bbbdacc29..243d9fa09 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -18,13 +18,14 @@ class ConfigureDialog : public QDialog {  public:      explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry); -    ~ConfigureDialog(); +    ~ConfigureDialog() override;      void applyConfiguration();  private:      void setConfiguration(); +    void UpdateVisibleTabs(); +    void PopulateSelectionList(); -private:      std::unique_ptr<Ui::ConfigureDialog> ui;  }; diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h index bbf7e25f1..bf3f1cdfa 100644 --- a/src/yuzu/configuration/configure_gamelist.h +++ b/src/yuzu/configuration/configure_gamelist.h @@ -16,7 +16,7 @@ class ConfigureGameList : public QWidget {  public:      explicit ConfigureGameList(QWidget* parent = nullptr); -    ~ConfigureGameList(); +    ~ConfigureGameList() override;      void applyConfiguration(); 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.h b/src/yuzu/configuration/configure_general.h index 4770034cc..59738af40 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -18,7 +18,7 @@ class ConfigureGeneral : public QWidget {  public:      explicit ConfigureGeneral(QWidget* parent = nullptr); -    ~ConfigureGeneral(); +    ~ConfigureGeneral() override;      void PopulateHotkeyList(const HotkeyRegistry& registry);      void applyConfiguration(); 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_graphics.h b/src/yuzu/configuration/configure_graphics.h index 9bda26fd6..d6ffc6fde 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -16,14 +16,13 @@ class ConfigureGraphics : public QWidget {  public:      explicit ConfigureGraphics(QWidget* parent = nullptr); -    ~ConfigureGraphics(); +    ~ConfigureGraphics() override;      void applyConfiguration();  private:      void setConfiguration(); -private:      std::unique_ptr<Ui::ConfigureGraphics> ui;      QColor bg_color;  }; diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 0527d098c..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 = { @@ -88,36 +115,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)              [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });  } -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(); -    } -} +ConfigureInput::~ConfigureInput() = default;  void ConfigureInput::applyConfiguration() {      for (std::size_t i = 0; i < players_controller.size(); ++i) { diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index e8723dfcb..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,11 +20,14 @@ namespace Ui {  class ConfigureInput;  } -class ConfigureInput : public QWidget { +void OnDockedModeChanged(bool last_state, bool new_state); + +class ConfigureInput : public QDialog {      Q_OBJECT  public:      explicit ConfigureInput(QWidget* parent = nullptr); +    ~ConfigureInput() override;      /// Save all button configurations to settings file      void applyConfiguration(); @@ -32,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..ba2b32c4f 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -6,6 +6,7 @@  #include <memory>  #include <utility>  #include <QColorDialog> +#include <QGridLayout>  #include <QMenu>  #include <QMessageBox>  #include <QTimer> 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..e13d2eac8 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -0,0 +1,170 @@ +// 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 <QMessageBox> +#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..a4494446c --- /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 <QKeyEvent> +#include <QList> +#include <QWidget> + +#include "core/file_sys/vfs_types.h" + +class QTreeView; +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; + +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..94e27349d 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -15,7 +15,6 @@  #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" @@ -36,64 +35,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 +45,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 +67,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); - -    const auto& current_user = profile_manager->GetUser(Settings::values.current_user); -    ASSERT(current_user); -    const auto username = GetAccountUsername(*profile_manager, *current_user); +    ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); +    ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value()); -    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 +95,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 +143,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_web.h b/src/yuzu/configuration/configure_web.h index 7741ab95d..7752ae4a1 100644 --- a/src/yuzu/configuration/configure_web.h +++ b/src/yuzu/configuration/configure_web.h @@ -17,18 +17,17 @@ class ConfigureWeb : public QWidget {  public:      explicit ConfigureWeb(QWidget* parent = nullptr); -    ~ConfigureWeb(); +    ~ConfigureWeb() override;      void applyConfiguration();      void retranslateUi(); -public slots: +private:      void RefreshTelemetryID();      void OnLoginChanged();      void VerifyLogin();      void OnLoginVerified(); -private:      void setConfiguration();      bool user_verified = true; | 
