diff options
Diffstat (limited to 'src/yuzu')
78 files changed, 2800 insertions, 2591 deletions
| diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 4cab599b4..3dc0e47d0 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -7,6 +7,8 @@ add_executable(yuzu      Info.plist      about_dialog.cpp      about_dialog.h +    applets/error.cpp +    applets/error.h      applets/profile_select.cpp      applets/profile_select.h      applets/software_keyboard.cpp @@ -31,6 +33,8 @@ add_executable(yuzu      configuration/configure_general.h      configuration/configure_graphics.cpp      configuration/configure_graphics.h +    configuration/configure_hotkeys.cpp +    configuration/configure_hotkeys.h      configuration/configure_input.cpp      configuration/configure_input.h      configuration/configure_input_player.cpp @@ -54,8 +58,6 @@ add_executable(yuzu      debugger/graphics/graphics_breakpoints.cpp      debugger/graphics/graphics_breakpoints.h      debugger/graphics/graphics_breakpoints_p.h -    debugger/graphics/graphics_surface.cpp -    debugger/graphics/graphics_surface.h      debugger/console.cpp      debugger/console.h      debugger/profiler.cpp @@ -78,8 +80,8 @@ add_executable(yuzu      ui_settings.h      util/limitable_input_dialog.cpp      util/limitable_input_dialog.h -    util/spinbox.cpp -    util/spinbox.h +    util/sequence_dialog/sequence_dialog.cpp +    util/sequence_dialog/sequence_dialog.h      util/util.cpp      util/util.h      compatdb.cpp @@ -95,6 +97,7 @@ set(UIS      configuration/configure_gamelist.ui      configuration/configure_general.ui      configuration/configure_graphics.ui +    configuration/configure_hotkeys.ui      configuration/configure_input.ui      configuration/configure_input_player.ui      configuration/configure_input_simple.ui @@ -105,7 +108,6 @@ set(UIS      configuration/configure_touchscreen_advanced.ui      configuration/configure_web.ui      compatdb.ui -    hotkeys.ui      loading_screen.ui      main.ui  ) @@ -149,6 +151,25 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)  target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)  target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) +target_compile_definitions(yuzu PRIVATE +    # Use QStringBuilder for string concatenation to reduce +    # the overall number of temporary strings created. +    -DQT_USE_QSTRINGBUILDER + +    # Disable implicit conversions from/to C strings +    -DQT_NO_CAST_FROM_ASCII +    -DQT_NO_CAST_TO_ASCII + +    # Disable implicit type narrowing in signal/slot connect() calls. +    -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT + +    # Disable unsafe overloads of QProcess' start() function. +    -DQT_NO_PROCESS_COMBINED_ARGUMENT_START + +    # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. +    -DQT_NO_URL_CAST_FROM_STRING +) +  if (YUZU_ENABLE_COMPATIBILITY_REPORTING)      target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_COMPATIBILITY_REPORTING)  endif() diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp index 3efa65a38..d39b3f07a 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -9,10 +9,10 @@  AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) {      ui->setupUi(this); -    ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200)); -    ui->labelBuildInfo->setText( -        ui->labelBuildInfo->text().arg(Common::g_build_fullname, Common::g_scm_branch, -                                       Common::g_scm_desc, QString(Common::g_build_date).left(10))); +    ui->labelLogo->setPixmap(QIcon::fromTheme(QStringLiteral("yuzu")).pixmap(200)); +    ui->labelBuildInfo->setText(ui->labelBuildInfo->text().arg( +        QString::fromUtf8(Common::g_build_fullname), QString::fromUtf8(Common::g_scm_branch), +        QString::fromUtf8(Common::g_scm_desc), QString::fromUtf8(Common::g_build_date).left(10)));  }  AboutDialog::~AboutDialog() = default; diff --git a/src/yuzu/applets/error.cpp b/src/yuzu/applets/error.cpp new file mode 100644 index 000000000..08ed57355 --- /dev/null +++ b/src/yuzu/applets/error.cpp @@ -0,0 +1,61 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QDateTime> +#include "core/hle/lock.h" +#include "yuzu/applets/error.h" +#include "yuzu/main.h" + +QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) { +    connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent, +            &GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection); +    connect(&parent, &GMainWindow::ErrorDisplayFinished, this, +            &QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection); +} + +QtErrorDisplay::~QtErrorDisplay() = default; + +void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) const { +    this->callback = std::move(finished); +    emit MainWindowDisplayError( +        tr("An error has occured.\nPlease try again or contact the developer of the " +           "software.\n\nError Code: %1-%2 (0x%3)") +            .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) +            .arg(error.description, 4, 10, QChar::fromLatin1('0')) +            .arg(error.raw, 8, 16, QChar::fromLatin1('0'))); +} + +void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, +                                            std::function<void()> finished) const { +    this->callback = std::move(finished); + +    const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); +    emit MainWindowDisplayError( +        tr("An error occured on %1 at %2.\nPlease try again or contact the " +           "developer of the software.\n\nError Code: %3-%4 (0x%5)") +            .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) +            .arg(date_time.toString(QStringLiteral("h:mm:ss A"))) +            .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) +            .arg(error.description, 4, 10, QChar::fromLatin1('0')) +            .arg(error.raw, 8, 16, QChar::fromLatin1('0'))); +} + +void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text, +                                         std::string fullscreen_text, +                                         std::function<void()> finished) const { +    this->callback = std::move(finished); +    emit MainWindowDisplayError( +        tr("An error has occured.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5") +            .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) +            .arg(error.description, 4, 10, QChar::fromLatin1('0')) +            .arg(error.raw, 8, 16, QChar::fromLatin1('0')) +            .arg(QString::fromStdString(dialog_text)) +            .arg(QString::fromStdString(fullscreen_text))); +} + +void QtErrorDisplay::MainWindowFinishedError() { +    // Acquire the HLE mutex +    std::lock_guard lock{HLE::g_hle_lock}; +    callback(); +} diff --git a/src/yuzu/applets/error.h b/src/yuzu/applets/error.h new file mode 100644 index 000000000..b0932d895 --- /dev/null +++ b/src/yuzu/applets/error.h @@ -0,0 +1,33 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QObject> + +#include "core/frontend/applets/error.h" + +class GMainWindow; + +class QtErrorDisplay final : public QObject, public Core::Frontend::ErrorApplet { +    Q_OBJECT + +public: +    explicit QtErrorDisplay(GMainWindow& parent); +    ~QtErrorDisplay() override; + +    void ShowError(ResultCode error, std::function<void()> finished) const override; +    void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, +                                std::function<void()> finished) const override; +    void ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text, +                             std::function<void()> finished) const override; + +signals: +    void MainWindowDisplayError(QString error) const; + +private: +    void MainWindowFinishedError(); + +    mutable std::function<void()> callback; +}; diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp index 5c1b65a2c..6aff38735 100644 --- a/src/yuzu/applets/profile_select.cpp +++ b/src/yuzu/applets/profile_select.cpp @@ -4,6 +4,7 @@  #include <mutex>  #include <QDialogButtonBox> +#include <QHeaderView>  #include <QLabel>  #include <QLineEdit>  #include <QScrollArea> @@ -11,40 +12,31 @@  #include <QVBoxLayout>  #include "common/file_util.h"  #include "common/string_util.h" +#include "core/constants.h"  #include "core/hle/lock.h"  #include "yuzu/applets/profile_select.h"  #include "yuzu/main.h" -// 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 FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { +QString FormatUserEntryText(const QString& username, Common::UUID uuid) {      return QtProfileSelectionDialog::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()));  } -QString GetImagePath(Service::Account::UUID uuid) { +QString GetImagePath(Common::UUID uuid) {      const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +                        "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";      return QString::fromStdString(path);  } -QPixmap GetIcon(Service::Account::UUID uuid) { +QPixmap GetIcon(Common::UUID uuid) {      QPixmap icon{GetImagePath(uuid)};      if (!icon) {          icon.fill(Qt::black); -        icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size())); +        icon.loadFromData(Core::Constants::ACCOUNT_BACKUP_JPEG.data(), +                          static_cast<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));      }      return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); @@ -58,10 +50,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)      scroll_area = new QScrollArea; -    buttons = new QDialogButtonBox; -    buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); -    buttons->addButton(tr("OK"), QDialogButtonBox::AcceptRole); - +    buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);      connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept);      connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject); @@ -86,10 +75,10 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)      tree_view->setContextMenuPolicy(Qt::NoContextMenu);      item_model->insertColumns(0, 1); -    item_model->setHeaderData(0, Qt::Horizontal, "Users"); +    item_model->setHeaderData(0, Qt::Horizontal, tr("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. +    // with signals/slots. In this case, QList falls under the umbrella of custom types.      qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");      layout->setContentsMargins(0, 0, 0, 0); @@ -124,21 +113,15 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)  QtProfileSelectionDialog::~QtProfileSelectionDialog() = default;  void QtProfileSelectionDialog::accept() { -    ok = true;      QDialog::accept();  }  void QtProfileSelectionDialog::reject() { -    ok = false;      user_index = 0;      QDialog::reject();  } -bool QtProfileSelectionDialog::GetStatus() const { -    return ok; -} - -u32 QtProfileSelectionDialog::GetIndex() const { +int QtProfileSelectionDialog::GetIndex() const {      return user_index;  } @@ -156,13 +139,13 @@ QtProfileSelector::QtProfileSelector(GMainWindow& parent) {  QtProfileSelector::~QtProfileSelector() = default;  void QtProfileSelector::SelectProfile( -    std::function<void(std::optional<Service::Account::UUID>)> callback) const { +    std::function<void(std::optional<Common::UUID>)> callback) const {      this->callback = std::move(callback);      emit MainWindowSelectProfile();  } -void QtProfileSelector::MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid) { +void QtProfileSelector::MainWindowFinishedSelection(std::optional<Common::UUID> uuid) {      // Acquire the HLE mutex -    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); +    std::lock_guard lock{HLE::g_hle_lock};      callback(uuid);  } diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h index 868573324..cee886a77 100644 --- a/src/yuzu/applets/profile_select.h +++ b/src/yuzu/applets/profile_select.h @@ -7,7 +7,9 @@  #include <vector>  #include <QDialog>  #include <QList> +#include <QTreeView>  #include "core/frontend/applets/profile_select.h" +#include "core/hle/service/acc/profile_manager.h"  class GMainWindow;  class QDialogButtonBox; @@ -16,7 +18,6 @@ class QLabel;  class QScrollArea;  class QStandardItem;  class QStandardItemModel; -class QTreeView;  class QVBoxLayout;  class QtProfileSelectionDialog final : public QDialog { @@ -29,15 +30,13 @@ public:      void accept() override;      void reject() override; -    bool GetStatus() const; -    u32 GetIndex() const; +    int GetIndex() const;  private: -    bool ok = false; -    u32 user_index = 0; -      void SelectUser(const QModelIndex& index); +    int user_index = 0; +      QVBoxLayout* layout;      QTreeView* tree_view;      QStandardItemModel* item_model; @@ -60,14 +59,13 @@ public:      explicit QtProfileSelector(GMainWindow& parent);      ~QtProfileSelector() override; -    void SelectProfile( -        std::function<void(std::optional<Service::Account::UUID>)> callback) const override; +    void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const override;  signals:      void MainWindowSelectProfile() const;  private: -    void MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid); +    void MainWindowFinishedSelection(std::optional<Common::UUID> uuid); -    mutable std::function<void(std::optional<Service::Account::UUID>)> callback; +    mutable std::function<void(std::optional<Common::UUID>)> callback;  }; diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp index 8a26fdff1..af36f07c6 100644 --- a/src/yuzu/applets/software_keyboard.cpp +++ b/src/yuzu/applets/software_keyboard.cpp @@ -18,23 +18,30 @@ QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator(      : parameters(std::move(parameters)) {}  QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const { -    if (input.size() > parameters.max_length) +    if (input.size() > static_cast<s64>(parameters.max_length)) {          return Invalid; -    if (parameters.disable_space && input.contains(' ')) +    } +    if (parameters.disable_space && input.contains(QLatin1Char{' '})) {          return Invalid; -    if (parameters.disable_address && input.contains('@')) +    } +    if (parameters.disable_address && input.contains(QLatin1Char{'@'})) {          return Invalid; -    if (parameters.disable_percent && input.contains('%')) +    } +    if (parameters.disable_percent && input.contains(QLatin1Char{'%'})) {          return Invalid; -    if (parameters.disable_slash && (input.contains('/') || input.contains('\\'))) +    } +    if (parameters.disable_slash && +        (input.contains(QLatin1Char{'/'}) || input.contains(QLatin1Char{'\\'}))) {          return Invalid; +    }      if (parameters.disable_number &&          std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) {          return Invalid;      } -    if (parameters.disable_download_code && -        std::any_of(input.begin(), input.end(), [](QChar c) { return c == 'O' || c == 'I'; })) { +    if (parameters.disable_download_code && std::any_of(input.begin(), input.end(), [](QChar c) { +            return c == QLatin1Char{'O'} || c == QLatin1Char{'I'}; +        })) {          return Invalid;      } @@ -75,13 +82,13 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(          length_label->setText(QStringLiteral("%1/%2").arg(text.size()).arg(parameters.max_length));      }); -    buttons = new QDialogButtonBox; -    buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); -    buttons->addButton(parameters.submit_text.empty() -                           ? tr("OK") -                           : QString::fromStdU16String(parameters.submit_text), -                       QDialogButtonBox::AcceptRole); - +    buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); +    if (parameters.submit_text.empty()) { +        buttons->addButton(QDialogButtonBox::Ok); +    } else { +        buttons->addButton(QString::fromStdU16String(parameters.submit_text), +                           QDialogButtonBox::AcceptRole); +    }      connect(buttons, &QDialogButtonBox::accepted, this, &QtSoftwareKeyboardDialog::accept);      connect(buttons, &QDialogButtonBox::rejected, this, &QtSoftwareKeyboardDialog::reject);      layout->addWidget(header_label); @@ -97,13 +104,11 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(  QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default;  void QtSoftwareKeyboardDialog::accept() { -    ok = true;      text = line_edit->text().toStdU16String();      QDialog::accept();  }  void QtSoftwareKeyboardDialog::reject() { -    ok = false;      text.clear();      QDialog::reject();  } @@ -112,10 +117,6 @@ std::u16string QtSoftwareKeyboardDialog::GetText() const {      return text;  } -bool QtSoftwareKeyboardDialog::GetStatus() const { -    return ok; -} -  QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {      connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window,              &GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection); @@ -141,12 +142,12 @@ void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message,  void QtSoftwareKeyboard::MainWindowFinishedText(std::optional<std::u16string> text) {      // Acquire the HLE mutex -    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); -    text_output(text); +    std::lock_guard lock{HLE::g_hle_lock}; +    text_output(std::move(text));  }  void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() {      // Acquire the HLE mutex -    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); +    std::lock_guard lock{HLE::g_hle_lock};      finished_check();  } diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h index c63720ba4..44bcece75 100644 --- a/src/yuzu/applets/software_keyboard.h +++ b/src/yuzu/applets/software_keyboard.h @@ -6,7 +6,6 @@  #include <QDialog>  #include <QValidator> -#include "common/assert.h"  #include "core/frontend/applets/software_keyboard.h"  class GMainWindow; @@ -37,10 +36,8 @@ public:      void reject() override;      std::u16string GetText() const; -    bool GetStatus() const;  private: -    bool ok = false;      std::u16string text;      QDialogButtonBox* buttons; diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp index 6a9138d53..ac80b2fa2 100644 --- a/src/yuzu/applets/web_browser.cpp +++ b/src/yuzu/applets/web_browser.cpp @@ -56,6 +56,8 @@ constexpr char NX_SHIM_INJECT_SCRIPT[] = R"(      window.nx.endApplet = function() {          applet_done = true;      }; + +    window.onkeypress = function(e) { if (e.keyCode === 13) { applet_done = true; } };  )";  QString GetNXShimInjectionScript() { @@ -102,12 +104,12 @@ void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_r  void QtWebBrowser::MainWindowUnpackRomFS() {      // Acquire the HLE mutex -    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); +    std::lock_guard lock{HLE::g_hle_lock};      unpack_romfs_callback();  }  void QtWebBrowser::MainWindowFinishedBrowsing() {      // Acquire the HLE mutex -    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); +    std::lock_guard lock{HLE::g_hle_lock};      finished_callback();  } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 3b070bfbb..07a720494 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -1,6 +1,13 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +  #include <QApplication>  #include <QHBoxLayout>  #include <QKeyEvent> +#include <QOffscreenSurface> +#include <QOpenGLWindow> +#include <QPainter>  #include <QScreen>  #include <QWindow>  #include <fmt/format.h> @@ -19,13 +26,13 @@  EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} +EmuThread::~EmuThread() = default; +  void EmuThread::run() {      render_window->MakeCurrent();      MicroProfileOnThreadCreate("EmuThread"); -    stop_run = false; -      emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);      Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( @@ -40,7 +47,7 @@ void EmuThread::run() {          render_window->DoneCurrent();      } -    // holds whether the cpu was running during the last iteration, +    // Holds whether the cpu was running during the last iteration,      // so that the DebugModeLeft signal can be emitted before the      // next execution step      bool was_active = false; @@ -69,7 +76,7 @@ void EmuThread::run() {              was_active = false;          } else { -            std::unique_lock<std::mutex> lock(running_mutex); +            std::unique_lock lock{running_mutex};              running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; });          }      } @@ -84,13 +91,36 @@ void EmuThread::run() {      render_window->moveContext();  } +class GGLContext : public Core::Frontend::GraphicsContext { +public: +    explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} { +        context.setFormat(shared_context->format()); +        context.setShareContext(shared_context); +        context.create(); +    } + +    void MakeCurrent() override { +        context.makeCurrent(shared_context->surface()); +    } + +    void DoneCurrent() override { +        context.doneCurrent(); +    } + +    void SwapBuffers() override {} + +private: +    QOpenGLContext* shared_context; +    QOpenGLContext context; +}; +  // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL  // context.  // The corresponding functionality is handled in EmuThread instead -class GGLWidgetInternal : public QGLWidget { +class GGLWidgetInternal : public QOpenGLWindow {  public: -    GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent) -        : QGLWidget(fmt, parent), parent(parent) {} +    GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context) +        : QOpenGLWindow(shared_context), parent(parent) {}      void paintEvent(QPaintEvent* ev) override {          if (do_painting) { @@ -103,9 +133,51 @@ public:          parent->OnFramebufferSizeChanged();      } +    void keyPressEvent(QKeyEvent* event) override { +        InputCommon::GetKeyboard()->PressKey(event->key()); +    } + +    void keyReleaseEvent(QKeyEvent* event) override { +        InputCommon::GetKeyboard()->ReleaseKey(event->key()); +    } + +    void mousePressEvent(QMouseEvent* event) override { +        if (event->source() == Qt::MouseEventSynthesizedBySystem) +            return; // touch input is handled in TouchBeginEvent + +        const auto pos{event->pos()}; +        if (event->button() == Qt::LeftButton) { +            const auto [x, y] = parent->ScaleTouch(pos); +            parent->TouchPressed(x, y); +        } else if (event->button() == Qt::RightButton) { +            InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); +        } +    } + +    void mouseMoveEvent(QMouseEvent* event) override { +        if (event->source() == Qt::MouseEventSynthesizedBySystem) +            return; // touch input is handled in TouchUpdateEvent + +        const auto pos{event->pos()}; +        const auto [x, y] = parent->ScaleTouch(pos); +        parent->TouchMoved(x, y); +        InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); +    } + +    void mouseReleaseEvent(QMouseEvent* event) override { +        if (event->source() == Qt::MouseEventSynthesizedBySystem) +            return; // touch input is handled in TouchEndEvent + +        if (event->button() == Qt::LeftButton) +            parent->TouchReleased(); +        else if (event->button() == Qt::RightButton) +            InputCommon::GetMotionEmu()->EndTilt(); +    } +      void DisablePainting() {          do_painting = false;      } +      void EnablePainting() {          do_painting = true;      } @@ -115,17 +187,16 @@ private:      bool do_painting;  }; -GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) -    : QWidget(parent), child(nullptr), emu_thread(emu_thread) { - +GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) +    : QWidget(parent), emu_thread(emu_thread) {      setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") -                       .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); +                       .arg(QString::fromUtf8(Common::g_build_name), +                            QString::fromUtf8(Common::g_scm_branch), +                            QString::fromUtf8(Common::g_scm_desc)));      setAttribute(Qt::WA_AcceptTouchEvents);      InputCommon::Init(); -    InputCommon::StartJoystickEventHandler(); -    connect(this, &GRenderWindow::FirstFrameDisplayed, static_cast<GMainWindow*>(parent), -            &GMainWindow::OnLoadComplete); +    connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);  }  GRenderWindow::~GRenderWindow() { @@ -140,19 +211,19 @@ void GRenderWindow::moveContext() {      auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)                        ? emu_thread                        : qApp->thread(); -    child->context()->moveToThread(thread); +    context->moveToThread(thread);  }  void GRenderWindow::SwapBuffers() { -    // In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`, +    // In our multi-threaded QWidget use case we shouldn't need to call `makeCurrent`,      // since we never call `doneCurrent` in this thread.      // However:      // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called      // since the last time `swapBuffers` was executed; -    // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks. -    child->makeCurrent(); +    // - On macOS, if `makeCurrent` isn't called explicitly, resizing the buffer breaks. +    context->makeCurrent(child); -    child->swapBuffers(); +    context->swapBuffers(child);      if (!first_frame) {          emit FirstFrameDisplayed();          first_frame = true; @@ -160,11 +231,11 @@ void GRenderWindow::SwapBuffers() {  }  void GRenderWindow::MakeCurrent() { -    child->makeCurrent(); +    context->makeCurrent(child);  }  void GRenderWindow::DoneCurrent() { -    child->doneCurrent(); +    context->doneCurrent();  }  void GRenderWindow::PollEvents() {} @@ -177,14 +248,26 @@ void GRenderWindow::PollEvents() {}  void GRenderWindow::OnFramebufferSizeChanged() {      // Screen changes potentially incur a change in screen DPI, hence we should update the      // framebuffer size -    qreal pixelRatio = windowPixelRatio(); -    unsigned width = child->QPaintDevice::width() * pixelRatio; -    unsigned height = child->QPaintDevice::height() * pixelRatio; +    const qreal pixel_ratio = GetWindowPixelRatio(); +    const u32 width = child->QPaintDevice::width() * pixel_ratio; +    const u32 height = child->QPaintDevice::height() * pixel_ratio;      UpdateCurrentFramebufferLayout(width, height);  } +void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) { +    if (child) { +        child->keyPressEvent(event); +    } +} + +void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) { +    if (child) { +        child->keyReleaseEvent(event); +    } +} +  void GRenderWindow::BackupGeometry() { -    geometry = ((QGLWidget*)this)->saveGeometry(); +    geometry = QWidget::saveGeometry();  }  void GRenderWindow::RestoreGeometry() { @@ -201,21 +284,22 @@ void GRenderWindow::restoreGeometry(const QByteArray& geometry) {  QByteArray GRenderWindow::saveGeometry() {      // If we are a top-level widget, store the current geometry      // otherwise, store the last backup -    if (parent() == nullptr) -        return ((QGLWidget*)this)->saveGeometry(); -    else -        return geometry; +    if (parent() == nullptr) { +        return QWidget::saveGeometry(); +    } + +    return geometry;  } -qreal GRenderWindow::windowPixelRatio() const { +qreal GRenderWindow::GetWindowPixelRatio() const {      // windowHandle() might not be accessible until the window is displayed to screen.      return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;  } -std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const { -    const qreal pixel_ratio = windowPixelRatio(); -    return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), -            static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; +std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { +    const qreal pixel_ratio = GetWindowPixelRatio(); +    return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), +            static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};  }  void GRenderWindow::closeEvent(QCloseEvent* event) { @@ -223,47 +307,6 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {      QWidget::closeEvent(event);  } -void GRenderWindow::keyPressEvent(QKeyEvent* event) { -    InputCommon::GetKeyboard()->PressKey(event->key()); -} - -void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { -    InputCommon::GetKeyboard()->ReleaseKey(event->key()); -} - -void GRenderWindow::mousePressEvent(QMouseEvent* event) { -    if (event->source() == Qt::MouseEventSynthesizedBySystem) -        return; // touch input is handled in TouchBeginEvent - -    auto pos = event->pos(); -    if (event->button() == Qt::LeftButton) { -        const auto [x, y] = ScaleTouch(pos); -        this->TouchPressed(x, y); -    } else if (event->button() == Qt::RightButton) { -        InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); -    } -} - -void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { -    if (event->source() == Qt::MouseEventSynthesizedBySystem) -        return; // touch input is handled in TouchUpdateEvent - -    auto pos = event->pos(); -    const auto [x, y] = ScaleTouch(pos); -    this->TouchMoved(x, y); -    InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); -} - -void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { -    if (event->source() == Qt::MouseEventSynthesizedBySystem) -        return; // touch input is handled in TouchEndEvent - -    if (event->button() == Qt::LeftButton) -        this->TouchReleased(); -    else if (event->button() == Qt::RightButton) -        InputCommon::GetMotionEmu()->EndTilt(); -} -  void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {      // TouchBegin always has exactly one touch point, so take the .first()      const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); @@ -312,39 +355,65 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) {      InputCommon::GetKeyboard()->ReleaseAllKeys();  } -void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) { +void GRenderWindow::OnClientAreaResized(u32 width, u32 height) {      NotifyClientAreaSizeChanged(std::make_pair(width, height));  } +std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { +    return std::make_unique<GGLContext>(context.get()); +} +  void GRenderWindow::InitRenderTarget() { -    if (child) { -        delete child; -    } +    shared_context.reset(); +    context.reset(); -    if (layout()) { -        delete layout(); -    } +    delete child; +    child = nullptr; + +    delete container; +    container = nullptr; + +    delete layout();      first_frame = false;      // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,      // WA_DontShowOnScreen, WA_DeleteOnClose -    QGLFormat fmt; +    QSurfaceFormat fmt;      fmt.setVersion(4, 3); -    fmt.setProfile(QGLFormat::CoreProfile); -    fmt.setSwapInterval(false); +    fmt.setProfile(QSurfaceFormat::CompatibilityProfile); +    fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); +    // TODO: expose a setting for buffer value (ie default/single/double/triple) +    fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); +    shared_context = std::make_unique<QOpenGLContext>(); +    shared_context->setFormat(fmt); +    shared_context->create(); +    context = std::make_unique<QOpenGLContext>(); +    context->setShareContext(shared_context.get()); +    context->setFormat(fmt); +    context->create(); +    fmt.setSwapInterval(0); + +    child = new GGLWidgetInternal(this, shared_context.get()); +    container = QWidget::createWindowContainer(child, this); -    // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X -    fmt.setOption(QGL::NoDeprecatedFunctions); - -    child = new GGLWidgetInternal(fmt, this);      QBoxLayout* layout = new QHBoxLayout(this); - -    resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); -    layout->addWidget(child); +    layout->addWidget(container);      layout->setMargin(0);      setLayout(layout); +    // Reset minimum size to avoid unwanted resizes when this function is called for a second time. +    setMinimumSize(1, 1); + +    // Show causes the window to actually be created and the OpenGL context as well, but we don't +    // want the widget to be shown yet, so immediately hide it. +    show(); +    hide(); + +    resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); +    child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); +    container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); +      OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);      OnFramebufferSizeChanged(); @@ -353,24 +422,29 @@ void GRenderWindow::InitRenderTarget() {      BackupGeometry();  } -void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) { +void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {      auto& renderer = Core::System::GetInstance().Renderer(); -    if (!res_scale) +    if (res_scale == 0) {          res_scale = VideoCore::GetResolutionScaleFactor(renderer); +    }      const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};      screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); -    renderer.RequestScreenshot(screenshot_image.bits(), -                               [=] { -                                   screenshot_image.mirrored(false, true).save(screenshot_path); -                                   LOG_INFO(Frontend, "The screenshot is saved."); -                               }, -                               layout); +    renderer.RequestScreenshot( +        screenshot_image.bits(), +        [=] { +            const std::string std_screenshot_path = screenshot_path.toStdString(); +            if (screenshot_image.mirrored(false, true).save(screenshot_path)) { +                LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path); +            } else { +                LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path); +            } +        }, +        layout);  } -void GRenderWindow::OnMinimalClientAreaChangeRequest( -    const std::pair<unsigned, unsigned>& minimal_size) { +void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {      setMinimumSize(minimal_size.first, minimal_size.second);  } diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 7226e690e..2fc64895f 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -7,10 +7,9 @@  #include <atomic>  #include <condition_variable>  #include <mutex> -#include <QGLWidget>  #include <QImage>  #include <QThread> -#include "common/thread.h" +#include <QWidget>  #include "core/core.h"  #include "core/frontend/emu_window.h" @@ -21,16 +20,19 @@ class QTouchEvent;  class GGLWidgetInternal;  class GMainWindow;  class GRenderWindow; +class QSurface; +class QOpenGLContext;  namespace VideoCore {  enum class LoadCallbackStage;  } -class EmuThread : public QThread { +class EmuThread final : public QThread {      Q_OBJECT  public:      explicit EmuThread(GRenderWindow* render_window); +    ~EmuThread() override;      /**       * Start emulation (on new thread) @@ -53,7 +55,7 @@ public:       * @note This function is thread-safe       */      void SetRunning(bool running) { -        std::unique_lock<std::mutex> lock(running_mutex); +        std::unique_lock lock{running_mutex};          this->running = running;          lock.unlock();          running_cv.notify_all(); @@ -113,7 +115,7 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {      Q_OBJECT  public: -    GRenderWindow(QWidget* parent, EmuThread* emu_thread); +    GRenderWindow(GMainWindow* parent, EmuThread* emu_thread);      ~GRenderWindow() override;      // EmuWindow implementation @@ -121,32 +123,28 @@ public:      void MakeCurrent() override;      void DoneCurrent() override;      void PollEvents() override; +    std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; + +    void ForwardKeyPressEvent(QKeyEvent* event); +    void ForwardKeyReleaseEvent(QKeyEvent* event);      void BackupGeometry();      void RestoreGeometry();      void restoreGeometry(const QByteArray& geometry); // overridden      QByteArray saveGeometry();                        // overridden -    qreal windowPixelRatio() const; +    qreal GetWindowPixelRatio() const; +    std::pair<u32, u32> ScaleTouch(QPointF pos) const;      void closeEvent(QCloseEvent* event) override; - -    void keyPressEvent(QKeyEvent* event) override; -    void keyReleaseEvent(QKeyEvent* event) override; - -    void mousePressEvent(QMouseEvent* event) override; -    void mouseMoveEvent(QMouseEvent* event) override; -    void mouseReleaseEvent(QMouseEvent* event) override; -      bool event(QEvent* event) override; -      void focusOutEvent(QFocusEvent* event) override; -    void OnClientAreaResized(unsigned width, unsigned height); +    void OnClientAreaResized(u32 width, u32 height);      void InitRenderTarget(); -    void CaptureScreenshot(u16 res_scale, const QString& screenshot_path); +    void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);  public slots:      void moveContext(); // overridden @@ -161,19 +159,23 @@ signals:      void FirstFrameDisplayed();  private: -    std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;      void TouchBeginEvent(const QTouchEvent* event);      void TouchUpdateEvent(const QTouchEvent* event);      void TouchEndEvent(); -    void OnMinimalClientAreaChangeRequest( -        const std::pair<unsigned, unsigned>& minimal_size) override; +    void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; -    GGLWidgetInternal* child; +    QWidget* container = nullptr; +    GGLWidgetInternal* child = nullptr;      QByteArray geometry;      EmuThread* emu_thread; +    // Context that backs the GGLWidgetInternal (and will be used by core to render) +    std::unique_ptr<QOpenGLContext> context; +    // Context that will be shared between all newly created contexts. This should never be made +    // current +    std::unique_ptr<QOpenGLContext> shared_context;      /// Temporary storage of the screenshot taken      QImage screenshot_image; diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp index c8b0a5ec0..5477f050c 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp @@ -58,7 +58,7 @@ void CompatDB::Submit() {          button(NextButton)->setEnabled(false);          button(NextButton)->setText(tr("Submitting")); -        button(QWizard::CancelButton)->setVisible(false); +        button(CancelButton)->setVisible(false);          testcase_watcher.setFuture(QtConcurrent::run(              [] { return Core::System::GetInstance().TelemetrySession().SubmitTestcase(); })); @@ -74,12 +74,12 @@ void CompatDB::OnTestcaseSubmitted() {                                tr("An error occured while sending the Testcase"));          button(NextButton)->setEnabled(true);          button(NextButton)->setText(tr("Next")); -        button(QWizard::CancelButton)->setVisible(true); +        button(CancelButton)->setVisible(true);      } else {          next();          // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a          // workaround -        button(QWizard::CancelButton)->setVisible(false); +        button(CancelButton)->setVisible(false);      }  } diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 4650f96a3..10e5c5c38 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -2,6 +2,8 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <array> +#include <QKeySequence>  #include <QSettings>  #include "common/file_util.h"  #include "configure_input_simple.h" @@ -17,7 +19,6 @@ Config::Config() {      FileUtil::CreateFullPath(qt_config_loc);      qt_config =          std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); -      Reload();  } @@ -205,59 +206,92 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default      Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight,  }; +// This shouldn't have anything except static initializers (no functions). So +// QKeySequence(...).toString() is NOT ALLOWED HERE. +// This must be in alphabetical order according to action name as it must have the same order as +// UISetting::values.shortcuts, which is alphabetically ordered. +// clang-format off +const std::array<UISettings::Shortcut, 15> default_hotkeys{{ +    {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}}, +    {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, +    {QStringLiteral("Decrease Speed Limit"),     QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}}, +    {QStringLiteral("Exit yuzu"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}}, +    {QStringLiteral("Exit Fullscreen"),          QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}}, +    {QStringLiteral("Fullscreen"),               QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}}, +    {QStringLiteral("Increase Speed Limit"),     QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}}, +    {QStringLiteral("Load Amiibo"),              QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}}, +    {QStringLiteral("Load File"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}}, +    {QStringLiteral("Restart Emulation"),        QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, +    {QStringLiteral("Stop Emulation"),           QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, +    {QStringLiteral("Toggle Filter Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, +    {QStringLiteral("Toggle Speed Limit"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}}, +    {QStringLiteral("Toggle Status Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, +    {QStringLiteral("Change Docked Mode"),       QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}}, +}}; +// clang-format on +  void Config::ReadPlayerValues() {      for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {          auto& player = Settings::values.players[p]; -        player.connected = ReadSetting(QString("player_%1_connected").arg(p), false).toBool(); +        player.connected = +            ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool();          player.type = static_cast<Settings::ControllerType>(              qt_config -                ->value(QString("player_%1_type").arg(p), +                ->value(QStringLiteral("player_%1_type").arg(p),                          static_cast<u8>(Settings::ControllerType::DualJoycon))                  .toUInt());          player.body_color_left = qt_config -                                     ->value(QString("player_%1_body_color_left").arg(p), +                                     ->value(QStringLiteral("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), +                                      ->value(QStringLiteral("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), +                                       ->value(QStringLiteral("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(); +        player.button_color_right = +            qt_config +                ->value(QStringLiteral("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]); -            player.buttons[i] = -                qt_config -                    ->value(QString("player_%1_").arg(p) + Settings::NativeButton::mapping[i], -                            QString::fromStdString(default_param)) -                    .toString() -                    .toStdString(); -            if (player.buttons[i].empty()) -                player.buttons[i] = default_param; +            const std::string default_param = +                InputCommon::GenerateKeyboardParam(default_buttons[i]); +            auto& player_buttons = player.buttons[i]; + +            player_buttons = qt_config +                                 ->value(QStringLiteral("player_%1_").arg(p) + +                                             QString::fromUtf8(Settings::NativeButton::mapping[i]), +                                         QString::fromStdString(default_param)) +                                 .toString() +                                 .toStdString(); +            if (player_buttons.empty()) { +                player_buttons = default_param; +            }          }          for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { -            std::string default_param = InputCommon::GenerateAnalogParamFromKeys( +            const 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); -            player.analogs[i] = -                qt_config -                    ->value(QString("player_%1_").arg(p) + Settings::NativeAnalog::mapping[i], -                            QString::fromStdString(default_param)) -                    .toString() -                    .toStdString(); -            if (player.analogs[i].empty()) -                player.analogs[i] = default_param; +            auto& player_analogs = player.analogs[i]; + +            player_analogs = qt_config +                                 ->value(QStringLiteral("player_%1_").arg(p) + +                                             QString::fromUtf8(Settings::NativeAnalog::mapping[i]), +                                         QString::fromStdString(default_param)) +                                 .toString() +                                 .toStdString(); +            if (player_analogs.empty()) { +                player_analogs = default_param; +            }          }      } @@ -269,36 +303,45 @@ void Config::ReadPlayerValues() {  }  void Config::ReadDebugValues() { -    Settings::values.debug_pad_enabled = ReadSetting("debug_pad_enabled", false).toBool(); +    Settings::values.debug_pad_enabled = +        ReadSetting(QStringLiteral("debug_pad_enabled"), false).toBool(); +      for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { -        std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); -        Settings::values.debug_pad_buttons[i] = -            qt_config -                ->value(QString("debug_pad_") + Settings::NativeButton::mapping[i], -                        QString::fromStdString(default_param)) -                .toString() -                .toStdString(); -        if (Settings::values.debug_pad_buttons[i].empty()) -            Settings::values.debug_pad_buttons[i] = default_param; +        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); +        auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i]; + +        debug_pad_buttons = qt_config +                                ->value(QStringLiteral("debug_pad_") + +                                            QString::fromUtf8(Settings::NativeButton::mapping[i]), +                                        QString::fromStdString(default_param)) +                                .toString() +                                .toStdString(); +        if (debug_pad_buttons.empty()) { +            debug_pad_buttons = default_param; +        }      }      for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { -        std::string default_param = InputCommon::GenerateAnalogParamFromKeys( +        const 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.debug_pad_analogs[i] = -            qt_config -                ->value(QString("debug_pad_") + Settings::NativeAnalog::mapping[i], -                        QString::fromStdString(default_param)) -                .toString() -                .toStdString(); -        if (Settings::values.debug_pad_analogs[i].empty()) -            Settings::values.debug_pad_analogs[i] = default_param; +        auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i]; + +        debug_pad_analogs = qt_config +                                ->value(QStringLiteral("debug_pad_") + +                                            QString::fromUtf8(Settings::NativeAnalog::mapping[i]), +                                        QString::fromStdString(default_param)) +                                .toString() +                                .toStdString(); +        if (debug_pad_analogs.empty()) { +            debug_pad_analogs = default_param; +        }      }  }  void Config::ReadKeyboardValues() { -    Settings::values.keyboard_enabled = ReadSetting("keyboard_enabled", false).toBool(); +    Settings::values.keyboard_enabled = +        ReadSetting(QStringLiteral("keyboard_enabled"), false).toBool();      std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(),                     Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); @@ -311,31 +354,41 @@ void Config::ReadKeyboardValues() {  }  void Config::ReadMouseValues() { -    Settings::values.mouse_enabled = ReadSetting("mouse_enabled", false).toBool(); +    Settings::values.mouse_enabled = ReadSetting(QStringLiteral("mouse_enabled"), false).toBool();      for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { -        std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); -        Settings::values.mouse_buttons[i] = -            qt_config -                ->value(QString("mouse_") + Settings::NativeMouseButton::mapping[i], -                        QString::fromStdString(default_param)) -                .toString() -                .toStdString(); -        if (Settings::values.mouse_buttons[i].empty()) -            Settings::values.mouse_buttons[i] = default_param; +        const std::string default_param = +            InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); +        auto& mouse_buttons = Settings::values.mouse_buttons[i]; + +        mouse_buttons = qt_config +                            ->value(QStringLiteral("mouse_") + +                                        QString::fromUtf8(Settings::NativeMouseButton::mapping[i]), +                                    QString::fromStdString(default_param)) +                            .toString() +                            .toStdString(); +        if (mouse_buttons.empty()) { +            mouse_buttons = default_param; +        }      }  }  void Config::ReadTouchscreenValues() { -    Settings::values.touchscreen.enabled = ReadSetting("touchscreen_enabled", true).toBool(); +    Settings::values.touchscreen.enabled = +        ReadSetting(QStringLiteral("touchscreen_enabled"), true).toBool();      Settings::values.touchscreen.device = -        ReadSetting("touchscreen_device", "engine:emu_window").toString().toStdString(); +        ReadSetting(QStringLiteral("touchscreen_device"), QStringLiteral("engine:emu_window")) +            .toString() +            .toStdString(); -    Settings::values.touchscreen.finger = ReadSetting("touchscreen_finger", 0).toUInt(); -    Settings::values.touchscreen.rotation_angle = ReadSetting("touchscreen_angle", 0).toUInt(); -    Settings::values.touchscreen.diameter_x = ReadSetting("touchscreen_diameter_x", 15).toUInt(); -    Settings::values.touchscreen.diameter_y = ReadSetting("touchscreen_diameter_y", 15).toUInt(); -    qt_config->endGroup(); +    Settings::values.touchscreen.finger = +        ReadSetting(QStringLiteral("touchscreen_finger"), 0).toUInt(); +    Settings::values.touchscreen.rotation_angle = +        ReadSetting(QStringLiteral("touchscreen_angle"), 0).toUInt(); +    Settings::values.touchscreen.diameter_x = +        ReadSetting(QStringLiteral("touchscreen_diameter_x"), 15).toUInt(); +    Settings::values.touchscreen.diameter_y = +        ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();  }  void Config::ApplyDefaultProfileIfInputInvalid() { @@ -345,8 +398,25 @@ void Config::ApplyDefaultProfileIfInputInvalid() {      }  } -void Config::ReadValues() { -    qt_config->beginGroup("Controls"); +void Config::ReadAudioValues() { +    qt_config->beginGroup(QStringLiteral("Audio")); + +    Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto")) +                                   .toString() +                                   .toStdString(); +    Settings::values.enable_audio_stretching = +        ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool(); +    Settings::values.audio_device_id = +        ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto")) +            .toString() +            .toStdString(); +    Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat(); + +    qt_config->endGroup(); +} + +void Config::ReadControlValues() { +    qt_config->beginGroup(QStringLiteral("Controls"));      ReadPlayerValues();      ReadDebugValues(); @@ -355,223 +425,308 @@ void Config::ReadValues() {      ReadTouchscreenValues();      Settings::values.motion_device = -        ReadSetting("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01") +        ReadSetting(QStringLiteral("motion_device"), +                    QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))              .toString()              .toStdString(); -    qt_config->beginGroup("Core"); -    Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool(); -    Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();      qt_config->endGroup(); +} -    qt_config->beginGroup("Renderer"); -    Settings::values.resolution_factor = ReadSetting("resolution_factor", 1.0).toFloat(); -    Settings::values.use_frame_limit = ReadSetting("use_frame_limit", true).toBool(); -    Settings::values.frame_limit = ReadSetting("frame_limit", 100).toInt(); -    Settings::values.use_disk_shader_cache = ReadSetting("use_disk_shader_cache", true).toBool(); -    Settings::values.use_accurate_gpu_emulation = -        ReadSetting("use_accurate_gpu_emulation", false).toBool(); -    Settings::values.use_asynchronous_gpu_emulation = -        ReadSetting("use_asynchronous_gpu_emulation", false).toBool(); +void Config::ReadCoreValues() { +    qt_config->beginGroup(QStringLiteral("Core")); -    Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat(); -    Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat(); -    Settings::values.bg_blue = ReadSetting("bg_blue", 0.0).toFloat(); -    qt_config->endGroup(); +    Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool(); +    Settings::values.use_multi_core = ReadSetting(QStringLiteral("use_multi_core"), false).toBool(); -    qt_config->beginGroup("Audio"); -    Settings::values.sink_id = ReadSetting("output_engine", "auto").toString().toStdString(); -    Settings::values.enable_audio_stretching = -        ReadSetting("enable_audio_stretching", true).toBool(); -    Settings::values.audio_device_id = -        ReadSetting("output_device", "auto").toString().toStdString(); -    Settings::values.volume = ReadSetting("volume", 1).toFloat();      qt_config->endGroup(); +} + +void Config::ReadDataStorageValues() { +    qt_config->beginGroup(QStringLiteral("Data Storage")); -    qt_config->beginGroup("Data Storage"); -    Settings::values.use_virtual_sd = ReadSetting("use_virtual_sd", true).toBool(); +    Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();      FileUtil::GetUserPath(          FileUtil::UserPath::NANDDir,          qt_config -            ->value("nand_directory", +            ->value(QStringLiteral("nand_directory"),                      QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)))              .toString()              .toStdString());      FileUtil::GetUserPath(          FileUtil::UserPath::SDMCDir,          qt_config -            ->value("sdmc_directory", +            ->value(QStringLiteral("sdmc_directory"),                      QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)))              .toString()              .toStdString()); +      qt_config->endGroup(); +} + +void Config::ReadDebuggingValues() { +    qt_config->beginGroup(QStringLiteral("Debugging")); + +    Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool(); +    Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt(); +    Settings::values.program_args = +        ReadSetting(QStringLiteral("program_args"), QStringLiteral("")).toString().toStdString(); +    Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool(); +    Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool(); -    qt_config->beginGroup("Core"); -    Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool(); -    Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();      qt_config->endGroup(); +} -    qt_config->beginGroup("System"); -    Settings::values.use_docked_mode = ReadSetting("use_docked_mode", false).toBool(); -    Settings::values.enable_nfc = ReadSetting("enable_nfc", true).toBool(); +void Config::ReadDisabledAddOnValues() { +    const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns")); -    Settings::values.current_user = -        std::clamp<int>(ReadSetting("current_user", 0).toInt(), 0, Service::Account::MAX_USERS - 1); +    for (int i = 0; i < size; ++i) { +        qt_config->setArrayIndex(i); +        const auto title_id = ReadSetting(QStringLiteral("title_id"), 0).toULongLong(); +        std::vector<std::string> out; +        const auto d_size = qt_config->beginReadArray(QStringLiteral("disabled")); +        for (int j = 0; j < d_size; ++j) { +            qt_config->setArrayIndex(j); +            out.push_back( +                ReadSetting(QStringLiteral("d"), QStringLiteral("")).toString().toStdString()); +        } +        qt_config->endArray(); +        Settings::values.disabled_addons.insert_or_assign(title_id, out); +    } + +    qt_config->endArray(); +} -    Settings::values.language_index = ReadSetting("language_index", 1).toInt(); +void Config::ReadMiscellaneousValues() { +    qt_config->beginGroup(QStringLiteral("Miscellaneous")); -    const auto rng_seed_enabled = ReadSetting("rng_seed_enabled", false).toBool(); +    Settings::values.log_filter = +        ReadSetting(QStringLiteral("log_filter"), QStringLiteral("*:Info")) +            .toString() +            .toStdString(); +    Settings::values.use_dev_keys = ReadSetting(QStringLiteral("use_dev_keys"), false).toBool(); + +    qt_config->endGroup(); +} + +void Config::ReadPathValues() { +    qt_config->beginGroup(QStringLiteral("Paths")); + +    UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString(); +    UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); +    UISettings::values.game_directory_path = +        ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); +    UISettings::values.game_directory_deepscan = +        ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool(); +    UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); + +    qt_config->endGroup(); +} + +void Config::ReadRendererValues() { +    qt_config->beginGroup(QStringLiteral("Renderer")); + +    Settings::values.resolution_factor = +        ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat(); +    Settings::values.use_frame_limit = +        ReadSetting(QStringLiteral("use_frame_limit"), true).toBool(); +    Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt(); +    Settings::values.use_disk_shader_cache = +        ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool(); +    Settings::values.use_accurate_gpu_emulation = +        ReadSetting(QStringLiteral("use_accurate_gpu_emulation"), false).toBool(); +    Settings::values.use_asynchronous_gpu_emulation = +        ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool(); +    Settings::values.force_30fps_mode = +        ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool(); + +    Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat(); +    Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat(); +    Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat(); + +    qt_config->endGroup(); +} + +void Config::ReadShortcutValues() { +    qt_config->beginGroup(QStringLiteral("Shortcuts")); + +    for (const auto& [name, group, shortcut] : default_hotkeys) { +        const auto& [keyseq, context] = shortcut; +        qt_config->beginGroup(group); +        qt_config->beginGroup(name); +        UISettings::values.shortcuts.push_back( +            {name, +             group, +             {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), +              ReadSetting(QStringLiteral("Context"), context).toInt()}}); +        qt_config->endGroup(); +        qt_config->endGroup(); +    } + +    qt_config->endGroup(); +} + +void Config::ReadSystemValues() { +    qt_config->beginGroup(QStringLiteral("System")); + +    Settings::values.use_docked_mode = +        ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); + +    Settings::values.current_user = std::clamp<int>( +        ReadSetting(QStringLiteral("current_user"), 0).toInt(), 0, Service::Account::MAX_USERS - 1); + +    Settings::values.language_index = ReadSetting(QStringLiteral("language_index"), 1).toInt(); + +    const auto rng_seed_enabled = ReadSetting(QStringLiteral("rng_seed_enabled"), false).toBool();      if (rng_seed_enabled) { -        Settings::values.rng_seed = ReadSetting("rng_seed", 0).toULongLong(); +        Settings::values.rng_seed = ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong();      } else {          Settings::values.rng_seed = std::nullopt;      } -    const auto custom_rtc_enabled = ReadSetting("custom_rtc_enabled", false).toBool(); +    const auto custom_rtc_enabled = +        ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool();      if (custom_rtc_enabled) {          Settings::values.custom_rtc = -            std::chrono::seconds(ReadSetting("custom_rtc", 0).toULongLong()); +            std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong());      } else {          Settings::values.custom_rtc = std::nullopt;      }      qt_config->endGroup(); +} -    qt_config->beginGroup("Miscellaneous"); -    Settings::values.log_filter = ReadSetting("log_filter", "*:Info").toString().toStdString(); -    Settings::values.use_dev_keys = ReadSetting("use_dev_keys", false).toBool(); -    qt_config->endGroup(); +void Config::ReadUIValues() { +    qt_config->beginGroup(QStringLiteral("UI")); -    qt_config->beginGroup("Debugging"); -    Settings::values.use_gdbstub = ReadSetting("use_gdbstub", false).toBool(); -    Settings::values.gdbstub_port = ReadSetting("gdbstub_port", 24689).toInt(); -    Settings::values.program_args = ReadSetting("program_args", "").toString().toStdString(); -    Settings::values.dump_exefs = ReadSetting("dump_exefs", false).toBool(); -    Settings::values.dump_nso = ReadSetting("dump_nso", false).toBool(); -    qt_config->endGroup(); +    UISettings::values.theme = +        ReadSetting(QStringLiteral("theme"), QString::fromUtf8(UISettings::themes[0].second)) +            .toString(); +    UISettings::values.enable_discord_presence = +        ReadSetting(QStringLiteral("enable_discord_presence"), true).toBool(); +    UISettings::values.screenshot_resolution_factor = +        static_cast<u16>(ReadSetting(QStringLiteral("screenshot_resolution_factor"), 0).toUInt()); +    UISettings::values.select_user_on_boot = +        ReadSetting(QStringLiteral("select_user_on_boot"), false).toBool(); + +    ReadUIGamelistValues(); +    ReadUILayoutValues(); +    ReadPathValues(); +    ReadShortcutValues(); + +    UISettings::values.single_window_mode = +        ReadSetting(QStringLiteral("singleWindowMode"), true).toBool(); +    UISettings::values.fullscreen = ReadSetting(QStringLiteral("fullscreen"), false).toBool(); +    UISettings::values.display_titlebar = +        ReadSetting(QStringLiteral("displayTitleBars"), true).toBool(); +    UISettings::values.show_filter_bar = +        ReadSetting(QStringLiteral("showFilterBar"), true).toBool(); +    UISettings::values.show_status_bar = +        ReadSetting(QStringLiteral("showStatusBar"), true).toBool(); +    UISettings::values.confirm_before_closing = +        ReadSetting(QStringLiteral("confirmClose"), true).toBool(); +    UISettings::values.first_start = ReadSetting(QStringLiteral("firstStart"), true).toBool(); +    UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt(); +    UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool(); +    UISettings::values.profile_index = ReadSetting(QStringLiteral("profileIndex"), 0).toUInt(); + +    ApplyDefaultProfileIfInputInvalid(); -    qt_config->beginGroup("WebService"); -    Settings::values.enable_telemetry = ReadSetting("enable_telemetry", true).toBool(); -    Settings::values.web_api_url = -        ReadSetting("web_api_url", "https://api.yuzu-emu.org").toString().toStdString(); -    Settings::values.yuzu_username = ReadSetting("yuzu_username").toString().toStdString(); -    Settings::values.yuzu_token = ReadSetting("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 = ReadSetting("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(ReadSetting("d", "").toString().toStdString()); -        } -        qt_config->endArray(); -        Settings::values.disabled_addons.insert_or_assign(title_id, out); -    } -    qt_config->endArray(); +void Config::ReadUIGamelistValues() { +    qt_config->beginGroup(QStringLiteral("UIGameList")); + +    UISettings::values.show_unknown = ReadSetting(QStringLiteral("show_unknown"), true).toBool(); +    UISettings::values.show_add_ons = ReadSetting(QStringLiteral("show_add_ons"), true).toBool(); +    UISettings::values.icon_size = ReadSetting(QStringLiteral("icon_size"), 64).toUInt(); +    UISettings::values.row_1_text_id = ReadSetting(QStringLiteral("row_1_text_id"), 3).toUInt(); +    UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt(); +    UISettings::values.cache_game_list = +        ReadSetting(QStringLiteral("cache_game_list"), true).toBool(); -    qt_config->beginGroup("UI"); -    UISettings::values.theme = ReadSetting("theme", UISettings::themes[0].second).toString(); -    UISettings::values.enable_discord_presence = -        ReadSetting("enable_discord_presence", true).toBool(); -    UISettings::values.screenshot_resolution_factor = -        static_cast<u16>(ReadSetting("screenshot_resolution_factor", 0).toUInt()); -    UISettings::values.select_user_on_boot = ReadSetting("select_user_on_boot", false).toBool(); - -    qt_config->beginGroup("UIGameList"); -    UISettings::values.show_unknown = ReadSetting("show_unknown", true).toBool(); -    UISettings::values.show_add_ons = ReadSetting("show_add_ons", true).toBool(); -    UISettings::values.icon_size = ReadSetting("icon_size", 64).toUInt(); -    UISettings::values.row_1_text_id = ReadSetting("row_1_text_id", 3).toUInt(); -    UISettings::values.row_2_text_id = ReadSetting("row_2_text_id", 2).toUInt();      qt_config->endGroup(); +} + +void Config::ReadUILayoutValues() { +    qt_config->beginGroup(QStringLiteral("UILayout")); -    qt_config->beginGroup("UILayout"); -    UISettings::values.geometry = ReadSetting("geometry").toByteArray(); -    UISettings::values.state = ReadSetting("state").toByteArray(); -    UISettings::values.renderwindow_geometry = ReadSetting("geometryRenderWindow").toByteArray(); -    UISettings::values.gamelist_header_state = ReadSetting("gameListHeaderState").toByteArray(); +    UISettings::values.geometry = ReadSetting(QStringLiteral("geometry")).toByteArray(); +    UISettings::values.state = ReadSetting(QStringLiteral("state")).toByteArray(); +    UISettings::values.renderwindow_geometry = +        ReadSetting(QStringLiteral("geometryRenderWindow")).toByteArray(); +    UISettings::values.gamelist_header_state = +        ReadSetting(QStringLiteral("gameListHeaderState")).toByteArray();      UISettings::values.microprofile_geometry = -        ReadSetting("microProfileDialogGeometry").toByteArray(); +        ReadSetting(QStringLiteral("microProfileDialogGeometry")).toByteArray();      UISettings::values.microprofile_visible = -        ReadSetting("microProfileDialogVisible", false).toBool(); -    qt_config->endGroup(); +        ReadSetting(QStringLiteral("microProfileDialogVisible"), false).toBool(); -    qt_config->beginGroup("Paths"); -    UISettings::values.roms_path = ReadSetting("romsPath").toString(); -    UISettings::values.symbols_path = ReadSetting("symbolsPath").toString(); -    UISettings::values.gamedir = ReadSetting("gameListRootDir", ".").toString(); -    UISettings::values.gamedir_deepscan = ReadSetting("gameListDeepScan", false).toBool(); -    UISettings::values.recent_files = ReadSetting("recentFiles").toStringList();      qt_config->endGroup(); +} -    qt_config->beginGroup("Shortcuts"); -    QStringList groups = qt_config->childGroups(); -    for (auto group : groups) { -        qt_config->beginGroup(group); +void Config::ReadWebServiceValues() { +    qt_config->beginGroup(QStringLiteral("WebService")); -        QStringList hotkeys = qt_config->childGroups(); -        for (auto hotkey : hotkeys) { -            qt_config->beginGroup(hotkey); -            UISettings::values.shortcuts.emplace_back(UISettings::Shortcut( -                group + "/" + hotkey, -                UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(), -                                               ReadSetting("Context").toInt()))); -            qt_config->endGroup(); -        } +    Settings::values.enable_telemetry = +        ReadSetting(QStringLiteral("enable_telemetry"), true).toBool(); +    Settings::values.web_api_url = +        ReadSetting(QStringLiteral("web_api_url"), QStringLiteral("https://api.yuzu-emu.org")) +            .toString() +            .toStdString(); +    Settings::values.yuzu_username = +        ReadSetting(QStringLiteral("yuzu_username")).toString().toStdString(); +    Settings::values.yuzu_token = +        ReadSetting(QStringLiteral("yuzu_token")).toString().toStdString(); -        qt_config->endGroup(); -    }      qt_config->endGroup(); +} -    UISettings::values.single_window_mode = ReadSetting("singleWindowMode", true).toBool(); -    UISettings::values.fullscreen = ReadSetting("fullscreen", false).toBool(); -    UISettings::values.display_titlebar = ReadSetting("displayTitleBars", true).toBool(); -    UISettings::values.show_filter_bar = ReadSetting("showFilterBar", true).toBool(); -    UISettings::values.show_status_bar = ReadSetting("showStatusBar", true).toBool(); -    UISettings::values.confirm_before_closing = ReadSetting("confirmClose", true).toBool(); -    UISettings::values.first_start = ReadSetting("firstStart", true).toBool(); -    UISettings::values.callout_flags = ReadSetting("calloutFlags", 0).toUInt(); -    UISettings::values.show_console = ReadSetting("showConsole", false).toBool(); -    UISettings::values.profile_index = ReadSetting("profileIndex", 0).toUInt(); - -    ApplyDefaultProfileIfInputInvalid(); - -    qt_config->endGroup(); +void Config::ReadValues() { +    ReadControlValues(); +    ReadCoreValues(); +    ReadRendererValues(); +    ReadAudioValues(); +    ReadDataStorageValues(); +    ReadSystemValues(); +    ReadMiscellaneousValues(); +    ReadDebugValues(); +    ReadWebServiceValues(); +    ReadDisabledAddOnValues(); +    ReadUIValues();  }  void Config::SavePlayerValues() {      for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {          const auto& player = Settings::values.players[p]; -        WriteSetting(QString("player_%1_connected").arg(p), player.connected, false); -        WriteSetting(QString("player_%1_type").arg(p), static_cast<u8>(player.type), +        WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false); +        WriteSetting(QStringLiteral("player_%1_type").arg(p), static_cast<u8>(player.type),                       static_cast<u8>(Settings::ControllerType::DualJoycon)); -        WriteSetting(QString("player_%1_body_color_left").arg(p), player.body_color_left, +        WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left,                       Settings::JOYCON_BODY_NEON_BLUE); -        WriteSetting(QString("player_%1_body_color_right").arg(p), player.body_color_right, +        WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right,                       Settings::JOYCON_BODY_NEON_RED); -        WriteSetting(QString("player_%1_button_color_left").arg(p), player.button_color_left, +        WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left,                       Settings::JOYCON_BUTTONS_NEON_BLUE); -        WriteSetting(QString("player_%1_button_color_right").arg(p), player.button_color_right, -                     Settings::JOYCON_BUTTONS_NEON_RED); +        WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p), +                     player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED);          for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { -            std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); -            WriteSetting(QString("player_%1_").arg(p) + +            const std::string default_param = +                InputCommon::GenerateKeyboardParam(default_buttons[i]); +            WriteSetting(QStringLiteral("player_%1_").arg(p) +                               QString::fromStdString(Settings::NativeButton::mapping[i]),                           QString::fromStdString(player.buttons[i]),                           QString::fromStdString(default_param));          }          for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { -            std::string default_param = InputCommon::GenerateAnalogParamFromKeys( +            const 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); -            WriteSetting(QString("player_%1_").arg(p) + +            WriteSetting(QStringLiteral("player_%1_").arg(p) +                               QString::fromStdString(Settings::NativeAnalog::mapping[i]),                           QString::fromStdString(player.analogs[i]),                           QString::fromStdString(default_param)); @@ -580,19 +735,19 @@ void Config::SavePlayerValues() {  }  void Config::SaveDebugValues() { -    WriteSetting("debug_pad_enabled", Settings::values.debug_pad_enabled, false); +    WriteSetting(QStringLiteral("debug_pad_enabled"), Settings::values.debug_pad_enabled, false);      for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { -        std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); -        WriteSetting(QString("debug_pad_") + +        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); +        WriteSetting(QStringLiteral("debug_pad_") +                           QString::fromStdString(Settings::NativeButton::mapping[i]),                       QString::fromStdString(Settings::values.debug_pad_buttons[i]),                       QString::fromStdString(default_param));      }      for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { -        std::string default_param = InputCommon::GenerateAnalogParamFromKeys( +        const 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); -        WriteSetting(QString("debug_pad_") + +        WriteSetting(QStringLiteral("debug_pad_") +                           QString::fromStdString(Settings::NativeAnalog::mapping[i]),                       QString::fromStdString(Settings::values.debug_pad_analogs[i]),                       QString::fromStdString(default_param)); @@ -600,11 +755,12 @@ void Config::SaveDebugValues() {  }  void Config::SaveMouseValues() { -    WriteSetting("mouse_enabled", Settings::values.mouse_enabled, false); +    WriteSetting(QStringLiteral("mouse_enabled"), Settings::values.mouse_enabled, false);      for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { -        std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); -        WriteSetting(QString("mouse_") + +        const std::string default_param = +            InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); +        WriteSetting(QStringLiteral("mouse_") +                           QString::fromStdString(Settings::NativeMouseButton::mapping[i]),                       QString::fromStdString(Settings::values.mouse_buttons[i]),                       QString::fromStdString(default_param)); @@ -612,170 +768,276 @@ void Config::SaveMouseValues() {  }  void Config::SaveTouchscreenValues() { -    WriteSetting("touchscreen_enabled", Settings::values.touchscreen.enabled, true); -    WriteSetting("touchscreen_device", QString::fromStdString(Settings::values.touchscreen.device), -                 "engine:emu_window"); +    const auto& touchscreen = Settings::values.touchscreen; -    WriteSetting("touchscreen_finger", Settings::values.touchscreen.finger, 0); -    WriteSetting("touchscreen_angle", Settings::values.touchscreen.rotation_angle, 0); -    WriteSetting("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x, 15); -    WriteSetting("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y, 15); +    WriteSetting(QStringLiteral("touchscreen_enabled"), touchscreen.enabled, true); +    WriteSetting(QStringLiteral("touchscreen_device"), QString::fromStdString(touchscreen.device), +                 QStringLiteral("engine:emu_window")); + +    WriteSetting(QStringLiteral("touchscreen_finger"), touchscreen.finger, 0); +    WriteSetting(QStringLiteral("touchscreen_angle"), touchscreen.rotation_angle, 0); +    WriteSetting(QStringLiteral("touchscreen_diameter_x"), touchscreen.diameter_x, 15); +    WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);  }  void Config::SaveValues() { -    qt_config->beginGroup("Controls"); +    SaveControlValues(); +    SaveCoreValues(); +    SaveRendererValues(); +    SaveAudioValues(); +    SaveDataStorageValues(); +    SaveSystemValues(); +    SaveMiscellaneousValues(); +    SaveDebuggingValues(); +    SaveWebServiceValues(); +    SaveDisabledAddOnValues(); +    SaveUIValues(); +} + +void Config::SaveAudioValues() { +    qt_config->beginGroup(QStringLiteral("Audio")); + +    WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id), +                 QStringLiteral("auto")); +    WriteSetting(QStringLiteral("enable_audio_stretching"), +                 Settings::values.enable_audio_stretching, true); +    WriteSetting(QStringLiteral("output_device"), +                 QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto")); +    WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f); + +    qt_config->endGroup(); +} + +void Config::SaveControlValues() { +    qt_config->beginGroup(QStringLiteral("Controls"));      SavePlayerValues();      SaveDebugValues();      SaveMouseValues();      SaveTouchscreenValues(); -    WriteSetting("motion_device", QString::fromStdString(Settings::values.motion_device), -                 "engine:motion_emu,update_period:100,sensitivity:0.01"); -    WriteSetting("keyboard_enabled", Settings::values.keyboard_enabled, false); +    WriteSetting(QStringLiteral("motion_device"), +                 QString::fromStdString(Settings::values.motion_device), +                 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); +    WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);      qt_config->endGroup(); +} -    qt_config->beginGroup("Core"); -    WriteSetting("use_cpu_jit", Settings::values.use_cpu_jit, true); -    WriteSetting("use_multi_core", Settings::values.use_multi_core, false); -    qt_config->endGroup(); +void Config::SaveCoreValues() { +    qt_config->beginGroup(QStringLiteral("Core")); -    qt_config->beginGroup("Renderer"); -    WriteSetting("resolution_factor", (double)Settings::values.resolution_factor, 1.0); -    WriteSetting("use_frame_limit", Settings::values.use_frame_limit, true); -    WriteSetting("frame_limit", Settings::values.frame_limit, 100); -    WriteSetting("use_disk_shader_cache", Settings::values.use_disk_shader_cache, true); -    WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false); -    WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation, -                 false); +    WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true); +    WriteSetting(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false); -    // Cast to double because Qt's written float values are not human-readable -    WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0); -    WriteSetting("bg_green", (double)Settings::values.bg_green, 0.0); -    WriteSetting("bg_blue", (double)Settings::values.bg_blue, 0.0);      qt_config->endGroup(); +} -    qt_config->beginGroup("Audio"); -    WriteSetting("output_engine", QString::fromStdString(Settings::values.sink_id), "auto"); -    WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true); -    WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto"); -    WriteSetting("volume", Settings::values.volume, 1.0f); -    qt_config->endGroup(); +void Config::SaveDataStorageValues() { +    qt_config->beginGroup(QStringLiteral("Data Storage")); -    qt_config->beginGroup("Data Storage"); -    WriteSetting("use_virtual_sd", Settings::values.use_virtual_sd, true); -    WriteSetting("nand_directory", +    WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true); +    WriteSetting(QStringLiteral("nand_directory"),                   QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),                   QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); -    WriteSetting("sdmc_directory", +    WriteSetting(QStringLiteral("sdmc_directory"),                   QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),                   QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); -    qt_config->endGroup(); - -    qt_config->beginGroup("System"); -    WriteSetting("use_docked_mode", Settings::values.use_docked_mode, false); -    WriteSetting("enable_nfc", Settings::values.enable_nfc, true); -    WriteSetting("current_user", Settings::values.current_user, 0); -    WriteSetting("language_index", Settings::values.language_index, 1); - -    WriteSetting("rng_seed_enabled", Settings::values.rng_seed.has_value(), false); -    WriteSetting("rng_seed", Settings::values.rng_seed.value_or(0), 0); - -    WriteSetting("custom_rtc_enabled", Settings::values.custom_rtc.has_value(), false); -    WriteSetting("custom_rtc", -                 QVariant::fromValue<long long>( -                     Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()), -                 0);      qt_config->endGroup(); +} -    qt_config->beginGroup("Miscellaneous"); -    WriteSetting("log_filter", QString::fromStdString(Settings::values.log_filter), "*:Info"); -    WriteSetting("use_dev_keys", Settings::values.use_dev_keys, false); -    qt_config->endGroup(); +void Config::SaveDebuggingValues() { +    qt_config->beginGroup(QStringLiteral("Debugging")); -    qt_config->beginGroup("Debugging"); -    WriteSetting("use_gdbstub", Settings::values.use_gdbstub, false); -    WriteSetting("gdbstub_port", Settings::values.gdbstub_port, 24689); -    WriteSetting("program_args", QString::fromStdString(Settings::values.program_args), ""); -    WriteSetting("dump_exefs", Settings::values.dump_exefs, false); -    WriteSetting("dump_nso", Settings::values.dump_nso, false); -    qt_config->endGroup(); +    WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false); +    WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689); +    WriteSetting(QStringLiteral("program_args"), +                 QString::fromStdString(Settings::values.program_args), QStringLiteral("")); +    WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false); +    WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false); -    qt_config->beginGroup("WebService"); -    WriteSetting("enable_telemetry", Settings::values.enable_telemetry, true); -    WriteSetting("web_api_url", QString::fromStdString(Settings::values.web_api_url), -                 "https://api.yuzu-emu.org"); -    WriteSetting("yuzu_username", QString::fromStdString(Settings::values.yuzu_username)); -    WriteSetting("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));      qt_config->endGroup(); +} + +void Config::SaveDisabledAddOnValues() { +    qt_config->beginWriteArray(QStringLiteral("DisabledAddOns")); -    qt_config->beginWriteArray("DisabledAddOns");      int i = 0;      for (const auto& elem : Settings::values.disabled_addons) {          qt_config->setArrayIndex(i); -        WriteSetting("title_id", QVariant::fromValue<u64>(elem.first), 0); -        qt_config->beginWriteArray("disabled"); +        WriteSetting(QStringLiteral("title_id"), QVariant::fromValue<u64>(elem.first), 0); +        qt_config->beginWriteArray(QStringLiteral("disabled"));          for (std::size_t j = 0; j < elem.second.size(); ++j) {              qt_config->setArrayIndex(static_cast<int>(j)); -            WriteSetting("d", QString::fromStdString(elem.second[j]), ""); +            WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]), +                         QStringLiteral(""));          }          qt_config->endArray();          ++i;      } +      qt_config->endArray(); +} + +void Config::SaveMiscellaneousValues() { +    qt_config->beginGroup(QStringLiteral("Miscellaneous")); + +    WriteSetting(QStringLiteral("log_filter"), QString::fromStdString(Settings::values.log_filter), +                 QStringLiteral("*:Info")); +    WriteSetting(QStringLiteral("use_dev_keys"), Settings::values.use_dev_keys, false); -    qt_config->beginGroup("UI"); -    WriteSetting("theme", UISettings::values.theme, UISettings::themes[0].second); -    WriteSetting("enable_discord_presence", UISettings::values.enable_discord_presence, true); -    WriteSetting("screenshot_resolution_factor", UISettings::values.screenshot_resolution_factor, -                 0); -    WriteSetting("select_user_on_boot", UISettings::values.select_user_on_boot, false); - -    qt_config->beginGroup("UIGameList"); -    WriteSetting("show_unknown", UISettings::values.show_unknown, true); -    WriteSetting("show_add_ons", UISettings::values.show_add_ons, true); -    WriteSetting("icon_size", UISettings::values.icon_size, 64); -    WriteSetting("row_1_text_id", UISettings::values.row_1_text_id, 3); -    WriteSetting("row_2_text_id", UISettings::values.row_2_text_id, 2);      qt_config->endGroup(); +} + +void Config::SavePathValues() { +    qt_config->beginGroup(QStringLiteral("Paths")); + +    WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); +    WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); +    WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); +    WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path, +                 QStringLiteral(".")); +    WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan, +                 false); +    WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); -    qt_config->beginGroup("UILayout"); -    WriteSetting("geometry", UISettings::values.geometry); -    WriteSetting("state", UISettings::values.state); -    WriteSetting("geometryRenderWindow", UISettings::values.renderwindow_geometry); -    WriteSetting("gameListHeaderState", UISettings::values.gamelist_header_state); -    WriteSetting("microProfileDialogGeometry", UISettings::values.microprofile_geometry); -    WriteSetting("microProfileDialogVisible", UISettings::values.microprofile_visible, false);      qt_config->endGroup(); +} + +void Config::SaveRendererValues() { +    qt_config->beginGroup(QStringLiteral("Renderer")); + +    WriteSetting(QStringLiteral("resolution_factor"), +                 static_cast<double>(Settings::values.resolution_factor), 1.0); +    WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); +    WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); +    WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache, +                 true); +    WriteSetting(QStringLiteral("use_accurate_gpu_emulation"), +                 Settings::values.use_accurate_gpu_emulation, false); +    WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"), +                 Settings::values.use_asynchronous_gpu_emulation, false); +    WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false); + +    // Cast to double because Qt's written float values are not human-readable +    WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0); +    WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0); +    WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0); -    qt_config->beginGroup("Paths"); -    WriteSetting("romsPath", UISettings::values.roms_path); -    WriteSetting("symbolsPath", UISettings::values.symbols_path); -    WriteSetting("screenshotPath", UISettings::values.screenshot_path); -    WriteSetting("gameListRootDir", UISettings::values.gamedir, "."); -    WriteSetting("gameListDeepScan", UISettings::values.gamedir_deepscan, false); -    WriteSetting("recentFiles", UISettings::values.recent_files);      qt_config->endGroup(); +} + +void Config::SaveShortcutValues() { +    qt_config->beginGroup(QStringLiteral("Shortcuts")); -    qt_config->beginGroup("Shortcuts"); -    for (auto shortcut : UISettings::values.shortcuts) { -        WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first); -        WriteSetting(shortcut.first + "/Context", shortcut.second.second); +    // Lengths of UISettings::values.shortcuts & default_hotkeys are same. +    // However, their ordering must also be the same. +    for (std::size_t i = 0; i < default_hotkeys.size(); i++) { +        const auto& [name, group, shortcut] = UISettings::values.shortcuts[i]; +        const auto& default_hotkey = default_hotkeys[i].shortcut; + +        qt_config->beginGroup(group); +        qt_config->beginGroup(name); +        WriteSetting(QStringLiteral("KeySeq"), shortcut.first, default_hotkey.first); +        WriteSetting(QStringLiteral("Context"), shortcut.second, default_hotkey.second); +        qt_config->endGroup(); +        qt_config->endGroup();      } +      qt_config->endGroup(); +} + +void Config::SaveSystemValues() { +    qt_config->beginGroup(QStringLiteral("System")); + +    WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); +    WriteSetting(QStringLiteral("current_user"), Settings::values.current_user, 0); +    WriteSetting(QStringLiteral("language_index"), Settings::values.language_index, 1); + +    WriteSetting(QStringLiteral("rng_seed_enabled"), Settings::values.rng_seed.has_value(), false); +    WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.value_or(0), 0); + +    WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(), +                 false); +    WriteSetting(QStringLiteral("custom_rtc"), +                 QVariant::fromValue<long long>( +                     Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()), +                 0); + +    qt_config->endGroup(); +} + +void Config::SaveUIValues() { +    qt_config->beginGroup(QStringLiteral("UI")); + +    WriteSetting(QStringLiteral("theme"), UISettings::values.theme, +                 QString::fromUtf8(UISettings::themes[0].second)); +    WriteSetting(QStringLiteral("enable_discord_presence"), +                 UISettings::values.enable_discord_presence, true); +    WriteSetting(QStringLiteral("screenshot_resolution_factor"), +                 UISettings::values.screenshot_resolution_factor, 0); +    WriteSetting(QStringLiteral("select_user_on_boot"), UISettings::values.select_user_on_boot, +                 false); + +    SaveUIGamelistValues(); +    SaveUILayoutValues(); +    SavePathValues(); +    SaveShortcutValues(); + +    WriteSetting(QStringLiteral("singleWindowMode"), UISettings::values.single_window_mode, true); +    WriteSetting(QStringLiteral("fullscreen"), UISettings::values.fullscreen, false); +    WriteSetting(QStringLiteral("displayTitleBars"), UISettings::values.display_titlebar, true); +    WriteSetting(QStringLiteral("showFilterBar"), UISettings::values.show_filter_bar, true); +    WriteSetting(QStringLiteral("showStatusBar"), UISettings::values.show_status_bar, true); +    WriteSetting(QStringLiteral("confirmClose"), UISettings::values.confirm_before_closing, true); +    WriteSetting(QStringLiteral("firstStart"), UISettings::values.first_start, true); +    WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0); +    WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false); +    WriteSetting(QStringLiteral("profileIndex"), UISettings::values.profile_index, 0); + +    qt_config->endGroup(); +} + +void Config::SaveUIGamelistValues() { +    qt_config->beginGroup(QStringLiteral("UIGameList")); + +    WriteSetting(QStringLiteral("show_unknown"), UISettings::values.show_unknown, true); +    WriteSetting(QStringLiteral("show_add_ons"), UISettings::values.show_add_ons, true); +    WriteSetting(QStringLiteral("icon_size"), UISettings::values.icon_size, 64); +    WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3); +    WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2); +    WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true); + +    qt_config->endGroup(); +} + +void Config::SaveUILayoutValues() { +    qt_config->beginGroup(QStringLiteral("UILayout")); + +    WriteSetting(QStringLiteral("geometry"), UISettings::values.geometry); +    WriteSetting(QStringLiteral("state"), UISettings::values.state); +    WriteSetting(QStringLiteral("geometryRenderWindow"), UISettings::values.renderwindow_geometry); +    WriteSetting(QStringLiteral("gameListHeaderState"), UISettings::values.gamelist_header_state); +    WriteSetting(QStringLiteral("microProfileDialogGeometry"), +                 UISettings::values.microprofile_geometry); +    WriteSetting(QStringLiteral("microProfileDialogVisible"), +                 UISettings::values.microprofile_visible, false); + +    qt_config->endGroup(); +} + +void Config::SaveWebServiceValues() { +    qt_config->beginGroup(QStringLiteral("WebService")); + +    WriteSetting(QStringLiteral("enable_telemetry"), Settings::values.enable_telemetry, true); +    WriteSetting(QStringLiteral("web_api_url"), +                 QString::fromStdString(Settings::values.web_api_url), +                 QStringLiteral("https://api.yuzu-emu.org")); +    WriteSetting(QStringLiteral("yuzu_username"), +                 QString::fromStdString(Settings::values.yuzu_username)); +    WriteSetting(QStringLiteral("yuzu_token"), QString::fromStdString(Settings::values.yuzu_token)); -    WriteSetting("singleWindowMode", UISettings::values.single_window_mode, true); -    WriteSetting("fullscreen", UISettings::values.fullscreen, false); -    WriteSetting("displayTitleBars", UISettings::values.display_titlebar, true); -    WriteSetting("showFilterBar", UISettings::values.show_filter_bar, true); -    WriteSetting("showStatusBar", UISettings::values.show_status_bar, true); -    WriteSetting("confirmClose", UISettings::values.confirm_before_closing, true); -    WriteSetting("firstStart", UISettings::values.first_start, true); -    WriteSetting("calloutFlags", UISettings::values.callout_flags, 0); -    WriteSetting("showConsole", UISettings::values.show_console, false); -    WriteSetting("profileIndex", UISettings::values.profile_index, 0);      qt_config->endGroup();  } @@ -785,7 +1047,7 @@ QVariant Config::ReadSetting(const QString& name) const {  QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const {      QVariant result; -    if (qt_config->value(name + "/default", false).toBool()) { +    if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {          result = default_value;      } else {          result = qt_config->value(name, default_value); @@ -799,7 +1061,7 @@ void Config::WriteSetting(const QString& name, const QVariant& value) {  void Config::WriteSetting(const QString& name, const QVariant& value,                            const QVariant& default_value) { -    qt_config->setValue(name + "/default", value == default_value); +    qt_config->setValue(name + QStringLiteral("/default"), value == default_value);      qt_config->setValue(name, value);  } diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index f4185db18..6b523ecdd 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -36,12 +36,46 @@ private:      void ReadTouchscreenValues();      void ApplyDefaultProfileIfInputInvalid(); +    // Read functions bases off the respective config section names. +    void ReadAudioValues(); +    void ReadControlValues(); +    void ReadCoreValues(); +    void ReadDataStorageValues(); +    void ReadDebuggingValues(); +    void ReadDisabledAddOnValues(); +    void ReadMiscellaneousValues(); +    void ReadPathValues(); +    void ReadRendererValues(); +    void ReadShortcutValues(); +    void ReadSystemValues(); +    void ReadUIValues(); +    void ReadUIGamelistValues(); +    void ReadUILayoutValues(); +    void ReadWebServiceValues(); +      void SaveValues();      void SavePlayerValues();      void SaveDebugValues();      void SaveMouseValues();      void SaveTouchscreenValues(); +    // Save functions based off the respective config section names. +    void SaveAudioValues(); +    void SaveControlValues(); +    void SaveCoreValues(); +    void SaveDataStorageValues(); +    void SaveDebuggingValues(); +    void SaveDisabledAddOnValues(); +    void SaveMiscellaneousValues(); +    void SavePathValues(); +    void SaveRendererValues(); +    void SaveShortcutValues(); +    void SaveSystemValues(); +    void SaveUIValues(); +    void SaveUIGamelistValues(); +    void SaveUILayoutValues(); +    void SaveWebServiceValues(); +      QVariant ReadSetting(const QString& name) const;      QVariant ReadSetting(const QString& name, const QVariant& default_value) const;      void WriteSetting(const QString& name, const QVariant& value); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 3f03f0b77..267717bc9 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -7,9 +7,15 @@      <x>0</x>      <y>0</y>      <width>382</width> -    <height>241</height> +    <height>650</height>     </rect>    </property> +  <property name="minimumSize"> +   <size> +    <width>0</width> +    <height>650</height> +   </size> +  </property>    <property name="windowTitle">     <string>yuzu Configuration</string>    </property> @@ -62,6 +68,11 @@           <string>Input</string>          </attribute>         </widget> +       <widget class="ConfigureHotkeys" name="hotkeysTab"> +        <attribute name="title"> +         <string>Hotkeys</string> +        </attribute> +       </widget>         <widget class="ConfigureGraphics" name="graphicsTab">          <attribute name="title">           <string>Graphics</string> @@ -150,6 +161,12 @@     <header>configuration/configure_input_simple.h</header>     <container>1</container>    </customwidget> +  <customwidget> +   <class>ConfigureHotkeys</class> +   <extends>QWidget</extends> +   <header>configuration/configure_hotkeys.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 5d9ccc6e8..f370c690f 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -4,6 +4,8 @@  #include <memory> +#include <QSignalBlocker> +  #include "audio_core/sink.h"  #include "audio_core/sink_details.h"  #include "core/core.h" @@ -15,42 +17,39 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)      : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()) {      ui->setupUi(this); -    ui->output_sink_combo_box->clear(); -    ui->output_sink_combo_box->addItem("auto"); -    for (const char* id : AudioCore::GetSinkIDs()) { -        ui->output_sink_combo_box->addItem(id); -    } +    InitializeAudioOutputSinkComboBox();      connect(ui->volume_slider, &QSlider::valueChanged, this, -            &ConfigureAudio::setVolumeIndicatorText); +            &ConfigureAudio::SetVolumeIndicatorText); +    connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, +            &ConfigureAudio::UpdateAudioDevices); -    this->setConfiguration(); -    connect(ui->output_sink_combo_box, -            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, -            &ConfigureAudio::updateAudioDevices); +    SetConfiguration(); -    ui->output_sink_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); -    ui->audio_device_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); +    const bool is_powered_on = Core::System::GetInstance().IsPoweredOn(); +    ui->output_sink_combo_box->setEnabled(!is_powered_on); +    ui->audio_device_combo_box->setEnabled(!is_powered_on);  }  ConfigureAudio::~ConfigureAudio() = default; -void ConfigureAudio::setConfiguration() { -    setOutputSinkFromSinkID(); +void ConfigureAudio::SetConfiguration() { +    SetOutputSinkFromSinkID();      // The device list cannot be pre-populated (nor listed) until the output sink is known. -    updateAudioDevices(ui->output_sink_combo_box->currentIndex()); +    UpdateAudioDevices(ui->output_sink_combo_box->currentIndex()); -    setAudioDeviceFromDeviceID(); +    SetAudioDeviceFromDeviceID();      ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);      ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum()); -    setVolumeIndicatorText(ui->volume_slider->sliderPosition()); +    SetVolumeIndicatorText(ui->volume_slider->sliderPosition());  } -void ConfigureAudio::setOutputSinkFromSinkID() { -    int new_sink_index = 0; +void ConfigureAudio::SetOutputSinkFromSinkID() { +    [[maybe_unused]] const QSignalBlocker blocker(ui->output_sink_combo_box); +    int new_sink_index = 0;      const QString sink_id = QString::fromStdString(Settings::values.sink_id);      for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {          if (ui->output_sink_combo_box->itemText(index) == sink_id) { @@ -62,7 +61,7 @@ void ConfigureAudio::setOutputSinkFromSinkID() {      ui->output_sink_combo_box->setCurrentIndex(new_sink_index);  } -void ConfigureAudio::setAudioDeviceFromDeviceID() { +void ConfigureAudio::SetAudioDeviceFromDeviceID() {      int new_device_index = -1;      const QString device_id = QString::fromStdString(Settings::values.audio_device_id); @@ -76,11 +75,11 @@ void ConfigureAudio::setAudioDeviceFromDeviceID() {      ui->audio_device_combo_box->setCurrentIndex(new_device_index);  } -void ConfigureAudio::setVolumeIndicatorText(int percentage) { +void ConfigureAudio::SetVolumeIndicatorText(int percentage) {      ui->volume_indicator->setText(tr("%1%", "Volume percentage (e.g. 50%)").arg(percentage));  } -void ConfigureAudio::applyConfiguration() { +void ConfigureAudio::ApplyConfiguration() {      Settings::values.sink_id =          ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())              .toStdString(); @@ -92,9 +91,17 @@ void ConfigureAudio::applyConfiguration() {          static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum();  } -void ConfigureAudio::updateAudioDevices(int sink_index) { +void ConfigureAudio::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureAudio::UpdateAudioDevices(int sink_index) {      ui->audio_device_combo_box->clear(); -    ui->audio_device_combo_box->addItem(AudioCore::auto_device_name); +    ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));      const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();      for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { @@ -102,6 +109,16 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {      }  } -void ConfigureAudio::retranslateUi() { +void ConfigureAudio::InitializeAudioOutputSinkComboBox() { +    ui->output_sink_combo_box->clear(); +    ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); + +    for (const char* id : AudioCore::GetSinkIDs()) { +        ui->output_sink_combo_box->addItem(QString::fromUtf8(id)); +    } +} + +void ConfigureAudio::RetranslateUI() {      ui->retranslateUi(this); +    SetVolumeIndicatorText(ui->volume_slider->sliderPosition());  } diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h index 8771421c0..ea83bd72d 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -18,16 +18,21 @@ public:      explicit ConfigureAudio(QWidget* parent = nullptr);      ~ConfigureAudio() override; -    void applyConfiguration(); -    void retranslateUi(); +    void ApplyConfiguration();  private: -    void updateAudioDevices(int sink_index); +    void changeEvent(QEvent* event) override; -    void setConfiguration(); -    void setOutputSinkFromSinkID(); -    void setAudioDeviceFromDeviceID(); -    void setVolumeIndicatorText(int percentage); +    void InitializeAudioOutputSinkComboBox(); + +    void RetranslateUI(); + +    void UpdateAudioDevices(int sink_index); + +    void SetConfiguration(); +    void SetOutputSinkFromSinkID(); +    void SetAudioDeviceFromDeviceID(); +    void SetVolumeIndicatorText(int percentage);      std::unique_ptr<Ui::ConfigureAudio> ui;  }; diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index a29a0e265..a098b9acc 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -20,7 +20,7 @@        <item>         <layout class="QHBoxLayout">          <item> -         <widget class="QLabel" name="label"> +         <widget class="QLabel" name="label_1">            <property name="text">             <string>Output Engine:</string>            </property> @@ -44,7 +44,7 @@        <item>         <layout class="QHBoxLayout">          <item> -         <widget class="QLabel" name="label"> +         <widget class="QLabel" name="label_2">            <property name="text">             <string>Audio Device:</string>            </property> @@ -61,7 +61,7 @@           <number>0</number>          </property>          <item> -         <widget class="QLabel" name="label"> +         <widget class="QLabel" name="label_3">            <property name="text">             <string>Volume:</string>            </property> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 550cf9dca..efc2bedfd 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -16,7 +16,8 @@  ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) {      ui->setupUi(this); -    this->setConfiguration(); +    SetConfiguration(); +      connect(ui->open_log_button, &QPushButton::pressed, []() {          QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));          QDesktopServices::openUrl(QUrl::fromLocalFile(path)); @@ -25,7 +26,7 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co  ConfigureDebug::~ConfigureDebug() = default; -void ConfigureDebug::setConfiguration() { +void ConfigureDebug::SetConfiguration() {      ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub);      ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub);      ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); @@ -37,7 +38,7 @@ void ConfigureDebug::setConfiguration() {      ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso);  } -void ConfigureDebug::applyConfiguration() { +void ConfigureDebug::ApplyConfiguration() {      Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked();      Settings::values.gdbstub_port = ui->gdbport_spinbox->value();      UISettings::values.show_console = ui->toggle_console->isChecked(); @@ -50,3 +51,15 @@ void ConfigureDebug::applyConfiguration() {      filter.ParseFilterString(Settings::values.log_filter);      Log::SetGlobalFilter(filter);  } + +void ConfigureDebug::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureDebug::RetranslateUI() { +    ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h index c6420b18c..f4805a1d8 100644 --- a/src/yuzu/configuration/configure_debug.h +++ b/src/yuzu/configuration/configure_debug.h @@ -18,10 +18,13 @@ public:      explicit ConfigureDebug(QWidget* parent = nullptr);      ~ConfigureDebug() override; -    void applyConfiguration(); +    void ApplyConfiguration();  private: -    void setConfiguration(); +    void changeEvent(QEvent* event) override; + +    void RetranslateUI(); +    void SetConfiguration();      std::unique_ptr<Ui::ConfigureDebug> ui;  }; diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 758a92335..5ca9ce0e6 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -45,7 +45,7 @@             </spacer>            </item>            <item> -           <widget class="QLabel" name="label_2"> +           <widget class="QLabel" name="label_1">              <property name="text">               <string>Port:</string>              </property> @@ -70,11 +70,11 @@       <property name="title">        <string>Logging</string>       </property> -     <layout class="QVBoxLayout" name="verticalLayout"> +     <layout class="QVBoxLayout" name="verticalLayout_4">        <item> -       <layout class="QHBoxLayout" name="horizontalLayout"> +       <layout class="QHBoxLayout" name="horizontalLayout_2">          <item> -         <widget class="QLabel" name="label"> +         <widget class="QLabel" name="label_2">            <property name="text">             <string>Global Log Filter</string>            </property> @@ -86,7 +86,7 @@         </layout>        </item>        <item> -       <layout class="QHBoxLayout" name="horizontalLayout_2"> +       <layout class="QHBoxLayout" name="horizontalLayout_3">          <item>           <widget class="QCheckBox" name="toggle_console">            <property name="text"> @@ -111,11 +111,11 @@       <property name="title">        <string>Homebrew</string>       </property> -     <layout class="QVBoxLayout" name="verticalLayout"> +     <layout class="QVBoxLayout" name="verticalLayout_5">        <item> -       <layout class="QHBoxLayout" name="horizontalLayout"> +       <layout class="QHBoxLayout" name="horizontalLayout_4">          <item> -         <widget class="QLabel" name="label"> +         <widget class="QLabel" name="label_3">            <property name="text">             <string>Arguments String</string>            </property> @@ -134,7 +134,7 @@       <property name="title">        <string>Dump</string>       </property> -     <layout class="QVBoxLayout" name="verticalLayout_4"> +     <layout class="QVBoxLayout" name="verticalLayout_6">        <item>         <widget class="QCheckBox" name="dump_decompressed_nso">          <property name="whatsThis"> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 777050405..e636964e3 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -4,51 +4,81 @@  #include <QHash>  #include <QListWidgetItem> +#include <QSignalBlocker>  #include "core/settings.h"  #include "ui_configure.h"  #include "yuzu/configuration/config.h"  #include "yuzu/configuration/configure_dialog.h" +#include "yuzu/configuration/configure_input_player.h"  #include "yuzu/hotkeys.h" -ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry) -    : QDialog(parent), ui(new Ui::ConfigureDialog) { +ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry) +    : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) {      ui->setupUi(this); -    ui->generalTab->PopulateHotkeyList(registry); -    this->setConfiguration(); -    this->PopulateSelectionList(); +    ui->hotkeysTab->Populate(registry); +    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + +    SetConfiguration(); +    PopulateSelectionList(); +      connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,              &ConfigureDialog::UpdateVisibleTabs);      adjustSize(); -      ui->selectorList->setCurrentRow(0);  }  ConfigureDialog::~ConfigureDialog() = default; -void ConfigureDialog::setConfiguration() {} - -void ConfigureDialog::applyConfiguration() { -    ui->generalTab->applyConfiguration(); -    ui->gameListTab->applyConfiguration(); -    ui->systemTab->applyConfiguration(); -    ui->profileManagerTab->applyConfiguration(); -    ui->inputTab->applyConfiguration(); -    ui->graphicsTab->applyConfiguration(); -    ui->audioTab->applyConfiguration(); -    ui->debugTab->applyConfiguration(); -    ui->webTab->applyConfiguration(); +void ConfigureDialog::SetConfiguration() {} + +void ConfigureDialog::ApplyConfiguration() { +    ui->generalTab->ApplyConfiguration(); +    ui->gameListTab->ApplyConfiguration(); +    ui->systemTab->ApplyConfiguration(); +    ui->profileManagerTab->ApplyConfiguration(); +    ui->inputTab->ApplyConfiguration(); +    ui->hotkeysTab->ApplyConfiguration(registry); +    ui->graphicsTab->ApplyConfiguration(); +    ui->audioTab->ApplyConfiguration(); +    ui->debugTab->ApplyConfiguration(); +    ui->webTab->ApplyConfiguration();      Settings::Apply();      Settings::LogSettings();  } +void ConfigureDialog::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QDialog::changeEvent(event); +} + +void ConfigureDialog::RetranslateUI() { +    const int old_row = ui->selectorList->currentRow(); +    const int old_index = ui->tabWidget->currentIndex(); + +    ui->retranslateUi(this); + +    PopulateSelectionList(); +    ui->selectorList->setCurrentRow(old_row); + +    UpdateVisibleTabs(); +    ui->tabWidget->setCurrentIndex(old_index); +} +  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")}}}}; +         {tr("Controls"), {tr("Input"), tr("Hotkeys")}}}, +    }; +    [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList); + +    ui->selectorList->clear();      for (const auto& entry : items) {          auto* const item = new QListWidgetItem(entry.first);          item->setData(Qt::UserRole, entry.second); @@ -59,23 +89,28 @@ void ConfigureDialog::PopulateSelectionList() {  void ConfigureDialog::UpdateVisibleTabs() {      const auto items = ui->selectorList->selectedItems(); -    if (items.isEmpty()) +    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}}; +    const std::map<QString, QWidget*> widgets = { +        {tr("General"), ui->generalTab}, +        {tr("System"), ui->systemTab}, +        {tr("Profiles"), ui->profileManagerTab}, +        {tr("Input"), ui->inputTab}, +        {tr("Hotkeys"), ui->hotkeysTab}, +        {tr("Graphics"), ui->graphicsTab}, +        {tr("Audio"), ui->audioTab}, +        {tr("Debug"), ui->debugTab}, +        {tr("Web"), ui->webTab}, +        {tr("Game List"), ui->gameListTab}, +    }; -    ui->tabWidget->clear(); +    [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); +    ui->tabWidget->clear();      const QStringList tabs = items[0]->data(Qt::UserRole).toStringList(); - -    for (const auto& tab : tabs) +    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 243d9fa09..2d3bfc2da 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -17,15 +17,20 @@ class ConfigureDialog : public QDialog {      Q_OBJECT  public: -    explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry); +    explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);      ~ConfigureDialog() override; -    void applyConfiguration(); +    void ApplyConfiguration();  private: -    void setConfiguration(); +    void changeEvent(QEvent* event) override; + +    void RetranslateUI(); + +    void SetConfiguration();      void UpdateVisibleTabs();      void PopulateSelectionList();      std::unique_ptr<Ui::ConfigureDialog> ui; +    HotkeyRegistry& registry;  }; diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp index ae8cac243..d1724ba89 100644 --- a/src/yuzu/configuration/configure_gamelist.cpp +++ b/src/yuzu/configuration/configure_gamelist.cpp @@ -12,20 +12,20 @@  #include "yuzu/ui_settings.h"  namespace { -constexpr std::array<std::pair<u32, const char*>, 5> default_icon_sizes{{ +constexpr std::array default_icon_sizes{      std::make_pair(0, QT_TR_NOOP("None")),      std::make_pair(32, QT_TR_NOOP("Small (32x32)")),      std::make_pair(64, QT_TR_NOOP("Standard (64x64)")),      std::make_pair(128, QT_TR_NOOP("Large (128x128)")),      std::make_pair(256, QT_TR_NOOP("Full Size (256x256)")), -}}; +}; -constexpr std::array<const char*, 4> row_text_names{{ +constexpr std::array row_text_names{      QT_TR_NOOP("Filename"),      QT_TR_NOOP("Filetype"),      QT_TR_NOOP("Title ID"),      QT_TR_NOOP("Title Name"), -}}; +};  } // Anonymous namespace  ConfigureGameList::ConfigureGameList(QWidget* parent) @@ -35,7 +35,7 @@ ConfigureGameList::ConfigureGameList(QWidget* parent)      InitializeIconSizeComboBox();      InitializeRowComboBoxes(); -    this->setConfiguration(); +    SetConfiguration();      // Force game list reload if any of the relevant settings are changed.      connect(ui->show_unknown, &QCheckBox::stateChanged, this, @@ -50,7 +50,7 @@ ConfigureGameList::ConfigureGameList(QWidget* parent)  ConfigureGameList::~ConfigureGameList() = default; -void ConfigureGameList::applyConfiguration() { +void ConfigureGameList::ApplyConfiguration() {      UISettings::values.show_unknown = ui->show_unknown->isChecked();      UISettings::values.show_add_ons = ui->show_add_ons->isChecked();      UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); @@ -63,7 +63,7 @@ void ConfigureGameList::RequestGameListUpdate() {      UISettings::values.is_game_list_reload_pending.exchange(true);  } -void ConfigureGameList::setConfiguration() { +void ConfigureGameList::SetConfiguration() {      ui->show_unknown->setChecked(UISettings::values.show_unknown);      ui->show_add_ons->setChecked(UISettings::values.show_add_ons);      ui->icon_size_combobox->setCurrentIndex( @@ -77,7 +77,6 @@ void ConfigureGameList::setConfiguration() {  void ConfigureGameList::changeEvent(QEvent* event) {      if (event->type() == QEvent::LanguageChange) {          RetranslateUI(); -        return;      }      QWidget::changeEvent(event); @@ -100,13 +99,15 @@ void ConfigureGameList::RetranslateUI() {  void ConfigureGameList::InitializeIconSizeComboBox() {      for (const auto& size : default_icon_sizes) { -        ui->icon_size_combobox->addItem(size.second, size.first); +        ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first);      }  }  void ConfigureGameList::InitializeRowComboBoxes() {      for (std::size_t i = 0; i < row_text_names.size(); ++i) { -        ui->row_1_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i)); -        ui->row_2_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i)); +        const QString row_text_name = QString::fromUtf8(row_text_names[i]); + +        ui->row_1_text_combobox->addItem(row_text_name, QVariant::fromValue(i)); +        ui->row_2_text_combobox->addItem(row_text_name, QVariant::fromValue(i));      }  } diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h index bf3f1cdfa..e11822919 100644 --- a/src/yuzu/configuration/configure_gamelist.h +++ b/src/yuzu/configuration/configure_gamelist.h @@ -18,12 +18,12 @@ public:      explicit ConfigureGameList(QWidget* parent = nullptr);      ~ConfigureGameList() override; -    void applyConfiguration(); +    void ApplyConfiguration();  private:      void RequestGameListUpdate(); -    void setConfiguration(); +    void SetConfiguration();      void changeEvent(QEvent*) override;      void RetranslateUI(); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 4116b6cd7..06d368dfc 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -14,10 +14,11 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)      ui->setupUi(this);      for (const auto& theme : UISettings::themes) { -        ui->theme_combobox->addItem(theme.first, theme.second); +        ui->theme_combobox->addItem(QString::fromUtf8(theme.first), +                                    QString::fromUtf8(theme.second));      } -    this->setConfiguration(); +    SetConfiguration();      connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this,              [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); @@ -27,26 +28,32 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)  ConfigureGeneral::~ConfigureGeneral() = default; -void ConfigureGeneral::setConfiguration() { -    ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); +void ConfigureGeneral::SetConfiguration() { +    ui->toggle_deepscan->setChecked(UISettings::values.game_directory_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);  } -void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) { -    ui->widget->Populate(registry); -} - -void ConfigureGeneral::applyConfiguration() { -    UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); +void ConfigureGeneral::ApplyConfiguration() { +    UISettings::values.game_directory_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();      Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); -    Settings::values.enable_nfc = ui->enable_nfc->isChecked(); +} + +void ConfigureGeneral::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureGeneral::RetranslateUI() { +    ui->retranslateUi(this);  } diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index 59738af40..ef05ce065 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -20,11 +20,13 @@ public:      explicit ConfigureGeneral(QWidget* parent = nullptr);      ~ConfigureGeneral() override; -    void PopulateHotkeyList(const HotkeyRegistry& registry); -    void applyConfiguration(); +    void ApplyConfiguration();  private: -    void setConfiguration(); +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    void SetConfiguration();      std::unique_ptr<Ui::ConfigureGeneral> ui;  }; diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index dff0ad5d0..1a5721fe7 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -71,26 +71,6 @@        </widget>       </item>       <item> -      <widget class="QGroupBox" name="EmulationGroupBox"> -       <property name="title"> -        <string>Emulation</string> -       </property> -       <layout class="QHBoxLayout" name="EmulationHorizontalLayout"> -        <item> -         <layout class="QVBoxLayout" name="EmulationVerticalLayout"> -          <item> -           <widget class="QCheckBox" name="enable_nfc"> -            <property name="text"> -             <string>Enable NFC</string> -            </property> -           </widget> -          </item> -         </layout> -        </item> -       </layout> -      </widget> -     </item> -     <item>        <widget class="QGroupBox" name="theme_group_box">         <property name="title">          <string>Theme</string> @@ -118,22 +98,6 @@        </widget>       </item>       <item> -      <widget class="QGroupBox" name="HotKeysGroupBox"> -       <property name="title"> -        <string>Hotkeys</string> -       </property> -       <layout class="QHBoxLayout" name="HotKeysHorizontalLayout"> -        <item> -         <layout class="QVBoxLayout" name="HotKeysVerticalLayout"> -          <item> -           <widget class="GHotkeysDialog" name="widget" native="true"/> -          </item> -         </layout> -        </item> -       </layout> -      </widget> -     </item> -     <item>        <spacer name="verticalSpacer">         <property name="orientation">          <enum>Qt::Vertical</enum> @@ -150,14 +114,6 @@     </item>    </layout>   </widget> - <customwidgets> -  <customwidget> -   <class>GHotkeysDialog</class> -   <extends>QWidget</extends> -   <header>hotkeys.h</header> -   <container>1</container> -  </customwidget> - </customwidgets>   <resources/>   <connections/>  </ui> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index dd1d67488..2b17b250c 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -51,37 +51,42 @@ Resolution FromResolutionFactor(float factor) {  ConfigureGraphics::ConfigureGraphics(QWidget* parent)      : QWidget(parent), ui(new Ui::ConfigureGraphics) { -      ui->setupUi(this); -    this->setConfiguration(); -    ui->frame_limit->setEnabled(Settings::values.use_frame_limit); -    connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit, -            &QSpinBox::setEnabled); +    SetConfiguration(); + +    connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);      connect(ui->bg_button, &QPushButton::clicked, this, [this] {          const QColor new_bg_color = QColorDialog::getColor(bg_color); -        if (!new_bg_color.isValid()) +        if (!new_bg_color.isValid()) {              return; +        }          UpdateBackgroundColorButton(new_bg_color);      });  }  ConfigureGraphics::~ConfigureGraphics() = default; -void ConfigureGraphics::setConfiguration() { +void ConfigureGraphics::SetConfiguration() { +    const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); +      ui->resolution_factor_combobox->setCurrentIndex(          static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));      ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); +    ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());      ui->frame_limit->setValue(Settings::values.frame_limit); +    ui->use_disk_shader_cache->setEnabled(runtime_lock);      ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);      ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); -    ui->use_asynchronous_gpu_emulation->setEnabled(!Core::System::GetInstance().IsPoweredOn()); +    ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);      ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); +    ui->force_30fps_mode->setEnabled(runtime_lock); +    ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);      UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,                                                   Settings::values.bg_blue));  } -void ConfigureGraphics::applyConfiguration() { +void ConfigureGraphics::ApplyConfiguration() {      Settings::values.resolution_factor =          ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));      Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); @@ -90,11 +95,24 @@ void ConfigureGraphics::applyConfiguration() {      Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();      Settings::values.use_asynchronous_gpu_emulation =          ui->use_asynchronous_gpu_emulation->isChecked(); +    Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked();      Settings::values.bg_red = static_cast<float>(bg_color.redF());      Settings::values.bg_green = static_cast<float>(bg_color.greenF());      Settings::values.bg_blue = static_cast<float>(bg_color.blueF());  } +void ConfigureGraphics::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureGraphics::RetranslateUI() { +    ui->retranslateUi(this); +} +  void ConfigureGraphics::UpdateBackgroundColorButton(QColor color) {      bg_color = color; diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index f2799822d..fae28d98e 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -18,10 +18,13 @@ public:      explicit ConfigureGraphics(QWidget* parent = nullptr);      ~ConfigureGraphics() override; -    void applyConfiguration(); +    void ApplyConfiguration();  private: -    void setConfiguration(); +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    void SetConfiguration();      void UpdateBackgroundColorButton(QColor color); diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index c6767e0ca..15ab18ecd 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -71,6 +71,13 @@           </widget>          </item>          <item> +         <widget class="QCheckBox" name="force_30fps_mode"> +          <property name="text"> +           <string>Force 30 FPS mode</string> +          </property> +         </widget> +        </item> +        <item>           <layout class="QHBoxLayout" name="horizontalLayout">            <item>             <widget class="QLabel" name="label"> diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp new file mode 100644 index 000000000..3ea0b8d67 --- /dev/null +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -0,0 +1,129 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QMessageBox> +#include <QStandardItemModel> +#include "core/settings.h" +#include "ui_configure_hotkeys.h" +#include "yuzu/configuration/configure_hotkeys.h" +#include "yuzu/hotkeys.h" +#include "yuzu/util/sequence_dialog/sequence_dialog.h" + +ConfigureHotkeys::ConfigureHotkeys(QWidget* parent) +    : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) { +    ui->setupUi(this); +    setFocusPolicy(Qt::ClickFocus); + +    model = new QStandardItemModel(this); +    model->setColumnCount(3); + +    connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure); +    ui->hotkey_list->setModel(model); + +    // TODO(Kloen): Make context configurable as well (hiding the column for now) +    ui->hotkey_list->hideColumn(2); + +    ui->hotkey_list->setColumnWidth(0, 200); +    ui->hotkey_list->resizeColumnToContents(1); + +    RetranslateUI(); +} + +ConfigureHotkeys::~ConfigureHotkeys() = default; + +void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) { +    for (const auto& group : registry.hotkey_groups) { +        auto* parent_item = new QStandardItem(group.first); +        parent_item->setEditable(false); +        for (const auto& hotkey : group.second) { +            auto* action = new QStandardItem(hotkey.first); +            auto* keyseq = +                new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText)); +            action->setEditable(false); +            keyseq->setEditable(false); +            parent_item->appendRow({action, keyseq}); +        } +        model->appendRow(parent_item); +    } + +    ui->hotkey_list->expandAll(); +} + +void ConfigureHotkeys::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureHotkeys::RetranslateUI() { +    ui->retranslateUi(this); + +    model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")}); +} + +void ConfigureHotkeys::Configure(QModelIndex index) { +    if (!index.parent().isValid()) { +        return; +    } + +    index = index.sibling(index.row(), 1); +    auto* const model = ui->hotkey_list->model(); +    const auto previous_key = model->data(index); + +    SequenceDialog hotkey_dialog{this}; + +    const int return_code = hotkey_dialog.exec(); +    const auto key_sequence = hotkey_dialog.GetSequence(); +    if (return_code == QDialog::Rejected || key_sequence.isEmpty()) { +        return; +    } + +    if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) { +        QMessageBox::warning(this, tr("Conflicting Key Sequence"), +                             tr("The entered key sequence is already assigned to another hotkey.")); +    } else { +        model->setData(index, key_sequence.toString(QKeySequence::NativeText)); +    } +} + +bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const { +    for (int r = 0; r < model->rowCount(); r++) { +        const QStandardItem* const parent = model->item(r, 0); + +        for (int r2 = 0; r2 < parent->rowCount(); r2++) { +            const QStandardItem* const key_seq_item = parent->child(r2, 1); +            const auto key_seq_str = key_seq_item->text(); +            const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText); + +            if (key_sequence == key_seq) { +                return true; +            } +        } +    } + +    return false; +} + +void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { +    for (int key_id = 0; key_id < model->rowCount(); key_id++) { +        const QStandardItem* parent = model->item(key_id, 0); +        for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) { +            const QStandardItem* action = parent->child(key_column_id, 0); +            const QStandardItem* keyseq = parent->child(key_column_id, 1); +            for (auto& [group, sub_actions] : registry.hotkey_groups) { +                if (group != parent->text()) +                    continue; +                for (auto& [action_name, hotkey] : sub_actions) { +                    if (action_name != action->text()) +                        continue; +                    hotkey.keyseq = QKeySequence(keyseq->text()); +                } +            } +        } +    } + +    registry.SaveHotkeys(); +} diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h new file mode 100644 index 000000000..8f8c6173b --- /dev/null +++ b/src/yuzu/configuration/configure_hotkeys.h @@ -0,0 +1,43 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWidget> + +namespace Ui { +class ConfigureHotkeys; +} + +class HotkeyRegistry; +class QStandardItemModel; + +class ConfigureHotkeys : public QWidget { +    Q_OBJECT + +public: +    explicit ConfigureHotkeys(QWidget* parent = nullptr); +    ~ConfigureHotkeys() override; + +    void ApplyConfiguration(HotkeyRegistry& registry); + +    /** +     * Populates the hotkey list widget using data from the provided registry. +     * Called everytime the Configure dialog is opened. +     * @param registry The HotkeyRegistry whose data is used to populate the list. +     */ +    void Populate(const HotkeyRegistry& registry); + +private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    void Configure(QModelIndex index); +    bool IsUsedKey(QKeySequence key_sequence) const; + +    std::unique_ptr<Ui::ConfigureHotkeys> ui; + +    QStandardItemModel* model; +}; diff --git a/src/yuzu/configuration/configure_hotkeys.ui b/src/yuzu/configuration/configure_hotkeys.ui new file mode 100644 index 000000000..0d0b70f38 --- /dev/null +++ b/src/yuzu/configuration/configure_hotkeys.ui @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureHotkeys</class> + <widget class="QWidget" name="ConfigureHotkeys"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>363</width> +    <height>388</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Hotkey Settings</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout"> +   <item> +    <layout class="QVBoxLayout" name="verticalLayout_2"> +     <item> +      <widget class="QLabel" name="label_2"> +       <property name="text"> +        <string>Double-click on a binding to change it.</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QTreeView" name="hotkey_list"> +       <property name="editTriggers"> +        <set>QAbstractItemView::NoEditTriggers</set> +       </property> +       <property name="sortingEnabled"> +        <bool>false</bool> +       </property> +      </widget> +     </item> +    </layout> +   </item> +  </layout> + </widget> + <resources/> + <connections/> +</ui>
\ No newline at end of file diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index f39d57998..4dd775aab 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -5,6 +5,7 @@  #include <algorithm>  #include <memory> +#include <QSignalBlocker>  #include <QTimer>  #include "configuration/configure_touchscreen_advanced.h" @@ -50,12 +51,12 @@ void OnDockedModeChanged(bool last_state, bool new_state) {  namespace {  template <typename Dialog, typename... Args>  void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { -    parent.applyConfiguration(); +    parent.ApplyConfiguration();      Dialog dialog(&parent, std::forward<Args>(args)...);      const auto res = dialog.exec();      if (res == QDialog::Accepted) { -        dialog.applyConfiguration(); +        dialog.ApplyConfiguration();      }  }  } // Anonymous namespace @@ -74,28 +75,25 @@ ConfigureInput::ConfigureInput(QWidget* parent)          ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure,      }; -    for (auto* controller_box : players_controller) { -        controller_box->addItems({"None", "Pro Controller", "Dual Joycons", "Single Right Joycon", -                                  "Single Left Joycon"}); -    } - -    this->loadConfiguration(); -    updateUIEnabled(); +    RetranslateUI(); +    LoadConfiguration(); +    UpdateUIEnabled();      connect(ui->restore_defaults_button, &QPushButton::pressed, this, -            &ConfigureInput::restoreDefaults); +            &ConfigureInput::RestoreDefaults); -    for (auto* enabled : players_controller) +    for (auto* enabled : players_controller) {          connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this, -                &ConfigureInput::updateUIEnabled); -    connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); +                &ConfigureInput::UpdateUIEnabled); +    } +    connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);      connect(ui->handheld_connected, &QCheckBox::stateChanged, this, -            &ConfigureInput::updateUIEnabled); -    connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); -    connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); -    connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); +            &ConfigureInput::UpdateUIEnabled); +    connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled); +    connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled); +    connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::UpdateUIEnabled);      connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, -            &ConfigureInput::updateUIEnabled); +            &ConfigureInput::UpdateUIEnabled);      for (std::size_t i = 0; i < players_configure.size(); ++i) {          connect(players_configure[i], &QPushButton::pressed, this, @@ -117,7 +115,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)  ConfigureInput::~ConfigureInput() = default; -void ConfigureInput::applyConfiguration() { +void ConfigureInput::ApplyConfiguration() {      for (std::size_t i = 0; i < players_controller.size(); ++i) {          const auto controller_type_index = players_controller[i]->currentIndex(); @@ -143,14 +141,41 @@ void ConfigureInput::applyConfiguration() {      Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();  } -void ConfigureInput::updateUIEnabled() { +void ConfigureInput::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QDialog::changeEvent(event); +} + +void ConfigureInput::RetranslateUI() { +    ui->retranslateUi(this); +    RetranslateControllerComboBoxes(); +} + +void ConfigureInput::RetranslateControllerComboBoxes() { +    for (auto* controller_box : players_controller) { +        [[maybe_unused]] const QSignalBlocker blocker(controller_box); + +        controller_box->clear(); +        controller_box->addItems({tr("None"), tr("Pro Controller"), tr("Dual Joycons"), +                                  tr("Single Right Joycon"), tr("Single Left Joycon")}); +    } + +    LoadPlayerControllerIndices(); +} + +void ConfigureInput::UpdateUIEnabled() {      bool hit_disabled = false;      for (auto* player : players_controller) {          player->setDisabled(hit_disabled); -        if (hit_disabled) +        if (hit_disabled) {              player->setCurrentIndex(0); -        if (!hit_disabled && player->currentIndex() == 0) +        } +        if (!hit_disabled && player->currentIndex() == 0) {              hit_disabled = true; +        }      }      for (std::size_t i = 0; i < players_controller.size(); ++i) { @@ -165,18 +190,14 @@ void ConfigureInput::updateUIEnabled() {      ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());  } -void ConfigureInput::loadConfiguration() { +void ConfigureInput::LoadConfiguration() {      std::stable_partition(          Settings::values.players.begin(),          Settings::values.players.begin() +              Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD),          [](const auto& player) { return player.connected; }); -    for (std::size_t i = 0; i < players_controller.size(); ++i) { -        const auto connected = Settings::values.players[i].connected; -        players_controller[i]->setCurrentIndex( -            connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0); -    } +    LoadPlayerControllerIndices();      ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);      ui->handheld_connected->setChecked( @@ -188,10 +209,18 @@ void ConfigureInput::loadConfiguration() {      ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);      ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); -    updateUIEnabled(); +    UpdateUIEnabled(); +} + +void ConfigureInput::LoadPlayerControllerIndices() { +    for (std::size_t i = 0; i < players_controller.size(); ++i) { +        const auto connected = Settings::values.players[i].connected; +        players_controller[i]->setCurrentIndex( +            connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0); +    }  } -void ConfigureInput::restoreDefaults() { +void ConfigureInput::RestoreDefaults() {      players_controller[0]->setCurrentIndex(2);      for (std::size_t i = 1; i < players_controller.size(); ++i) { @@ -204,5 +233,5 @@ void ConfigureInput::restoreDefaults() {      ui->keyboard_enabled->setCheckState(Qt::Unchecked);      ui->debug_enabled->setCheckState(Qt::Unchecked);      ui->touchscreen_enabled->setCheckState(Qt::Checked); -    updateUIEnabled(); +    UpdateUIEnabled();  } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index b8e62cc2b..2f70cb3ca 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -30,15 +30,21 @@ public:      ~ConfigureInput() override;      /// Save all button configurations to settings file -    void applyConfiguration(); +    void ApplyConfiguration();  private: -    void updateUIEnabled(); +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); +    void RetranslateControllerComboBoxes(); + +    void UpdateUIEnabled();      /// Load configuration settings. -    void loadConfiguration(); +    void LoadConfiguration(); +    void LoadPlayerControllerIndices(); +      /// Restore all buttons to their default values. -    void restoreDefaults(); +    void RestoreDefaults();      std::unique_ptr<Ui::ConfigureInput> ui; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index f924e3494..efffd8487 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -17,7 +17,7 @@     <item>      <layout class="QVBoxLayout" name="verticalLayout">       <item> -      <widget class="QGroupBox" name="gridGroupBox"> +      <widget class="QGroupBox" name="gridGroupBox_1">         <property name="title">          <string>Players</string>         </property> @@ -260,7 +260,7 @@        </widget>       </item>       <item> -      <widget class="QGroupBox" name="gridGroupBox"> +      <widget class="QGroupBox" name="gridGroupBox_2">         <property name="title">          <string>Handheld</string>         </property> @@ -332,7 +332,7 @@        </widget>       </item>       <item> -      <widget class="QGroupBox" name="gridGroupBox"> +      <widget class="QGroupBox" name="gridGroupBox_3">         <property name="title">          <string>Other</string>         </property> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index c5a245ebe..916baccc1 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -45,7 +45,7 @@ static QString GetKeyName(int key_code) {      case Qt::Key_Alt:          return QObject::tr("Alt");      case Qt::Key_Meta: -        return ""; +        return {};      default:          return QKeySequence(key_code).toString();      } @@ -65,46 +65,70 @@ static void SetAnalogButton(const Common::ParamPackage& input_param,  static QString ButtonToText(const Common::ParamPackage& param) {      if (!param.Has("engine")) {          return QObject::tr("[not set]"); -    } else if (param.Get("engine", "") == "keyboard") { +    } + +    if (param.Get("engine", "") == "keyboard") {          return GetKeyName(param.Get("code", 0)); -    } else if (param.Get("engine", "") == "sdl") { +    } + +    if (param.Get("engine", "") == "sdl") {          if (param.Has("hat")) { -            return QString(QObject::tr("Hat %1 %2")) -                .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); +            const QString hat_str = QString::fromStdString(param.Get("hat", "")); +            const QString direction_str = QString::fromStdString(param.Get("direction", "")); + +            return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);          } +          if (param.Has("axis")) { -            return QString(QObject::tr("Axis %1%2")) -                .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); +            const QString axis_str = QString::fromStdString(param.Get("axis", "")); +            const QString direction_str = QString::fromStdString(param.Get("direction", "")); + +            return QObject::tr("Axis %1%2").arg(axis_str, direction_str);          } +          if (param.Has("button")) { -            return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); +            const QString button_str = QString::fromStdString(param.Get("button", "")); + +            return QObject::tr("Button %1").arg(button_str);          } -        return QString(); -    } else { -        return QObject::tr("[unknown]"); + +        return {};      } -}; + +    return QObject::tr("[unknown]"); +}  static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {      if (!param.Has("engine")) {          return QObject::tr("[not set]"); -    } else if (param.Get("engine", "") == "analog_from_button") { +    } + +    if (param.Get("engine", "") == "analog_from_button") {          return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); -    } else if (param.Get("engine", "") == "sdl") { +    } + +    if (param.Get("engine", "") == "sdl") {          if (dir == "modifier") { -            return QString(QObject::tr("[unused]")); +            return QObject::tr("[unused]");          }          if (dir == "left" || dir == "right") { -            return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str()); -        } else if (dir == "up" || dir == "down") { -            return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str()); +            const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + +            return QObject::tr("Axis %1").arg(axis_x_str);          } -        return QString(); -    } else { -        return QObject::tr("[unknown]"); + +        if (dir == "up" || dir == "down") { +            const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + +            return QObject::tr("Axis %1").arg(axis_y_str); +        } + +        return {};      } -}; + +    return QObject::tr("[unknown]"); +}  ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug)      : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), @@ -214,47 +238,51 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i      analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};      for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { -        if (!button_map[button_id]) +        auto* const button = button_map[button_id]; +        if (button == nullptr) {              continue; -        button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); -        connect(button_map[button_id], &QPushButton::released, [=]() { -            handleClick( +        } + +        button->setContextMenuPolicy(Qt::CustomContextMenu); +        connect(button, &QPushButton::released, [=] { +            HandleClick(                  button_map[button_id],                  [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },                  InputCommon::Polling::DeviceType::Button);          }); -        connect(button_map[button_id], &QPushButton::customContextMenuRequested, -                [=](const QPoint& menu_location) { -                    QMenu context_menu; -                    context_menu.addAction(tr("Clear"), [&] { -                        buttons_param[button_id].Clear(); -                        button_map[button_id]->setText(tr("[not set]")); -                    }); -                    context_menu.addAction(tr("Restore Default"), [&] { -                        buttons_param[button_id] = Common::ParamPackage{ -                            InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; -                        button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); -                    }); -                    context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); -                }); +        connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { +            QMenu context_menu; +            context_menu.addAction(tr("Clear"), [&] { +                buttons_param[button_id].Clear(); +                button_map[button_id]->setText(tr("[not set]")); +            }); +            context_menu.addAction(tr("Restore Default"), [&] { +                buttons_param[button_id] = Common::ParamPackage{ +                    InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +                button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); +            }); +            context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); +        });      }      for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {          for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { -            if (!analog_map_buttons[analog_id][sub_button_id]) +            auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; +            if (analog_button == nullptr) {                  continue; -            analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( -                Qt::CustomContextMenu); -            connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() { -                handleClick(analog_map_buttons[analog_id][sub_button_id], +            } + +            analog_button->setContextMenuPolicy(Qt::CustomContextMenu); +            connect(analog_button, &QPushButton::released, [=]() { +                HandleClick(analog_map_buttons[analog_id][sub_button_id],                              [=](const Common::ParamPackage& params) {                                  SetAnalogButton(params, analogs_param[analog_id],                                                  analog_sub_buttons[sub_button_id]);                              },                              InputCommon::Polling::DeviceType::Button);              }); -            connect(analog_map_buttons[analog_id][sub_button_id], -                    &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { +            connect(analog_button, &QPushButton::customContextMenuRequested, +                    [=](const QPoint& menu_location) {                          QMenu context_menu;                          context_menu.addAction(tr("Clear"), [&] {                              analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); @@ -272,11 +300,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i                              menu_location));                      });          } -        connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { +        connect(analog_map_stick[analog_id], &QPushButton::released, [=] {              QMessageBox::information(this, tr("Information"),                                       tr("After pressing OK, first move your joystick horizontally, "                                          "and then vertically.")); -            handleClick( +            HandleClick(                  analog_map_stick[analog_id],                  [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },                  InputCommon::Polling::DeviceType::Analog); @@ -284,17 +312,17 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i      }      connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); -    connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); +    connect(ui->buttonRestoreDefaults, &QPushButton::released, [this] { RestoreDefaults(); });      timeout_timer->setSingleShot(true); -    connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); +    connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); -    connect(poll_timer.get(), &QTimer::timeout, [this]() { +    connect(poll_timer.get(), &QTimer::timeout, [this] {          Common::ParamPackage params;          for (auto& poller : device_pollers) {              params = poller->GetNextInput();              if (params.Has("engine")) { -                setPollingResult(params, false); +                SetPollingResult(params, false);                  return;              }          } @@ -312,8 +340,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i                  [this, i] { OnControllerButtonClick(static_cast<int>(i)); });      } -    this->loadConfiguration(); -    this->resize(0, 0); +    LoadConfiguration(); +    resize(0, 0);      // TODO(wwylele): enable this when we actually emulate it      ui->buttonHome->setEnabled(false); @@ -321,7 +349,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i  ConfigureInputPlayer::~ConfigureInputPlayer() = default; -void ConfigureInputPlayer::applyConfiguration() { +void ConfigureInputPlayer::ApplyConfiguration() {      auto& buttons =          debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons;      auto& analogs = @@ -345,16 +373,29 @@ void ConfigureInputPlayer::applyConfiguration() {      Settings::values.players[player_index].button_color_right = colors[3];  } +void ConfigureInputPlayer::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QDialog::changeEvent(event); +} + +void ConfigureInputPlayer::RetranslateUI() { +    ui->retranslateUi(this); +    UpdateButtonLabels(); +} +  void ConfigureInputPlayer::OnControllerButtonClick(int i) {      const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]);      if (!new_bg_color.isValid())          return;      controller_colors[i] = new_bg_color;      controller_color_buttons[i]->setStyleSheet( -        QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); +        QStringLiteral("QPushButton { background-color: %1 }").arg(controller_colors[i].name()));  } -void ConfigureInputPlayer::loadConfiguration() { +void ConfigureInputPlayer::LoadConfiguration() {      if (debug) {          std::transform(Settings::values.debug_pad_buttons.begin(),                         Settings::values.debug_pad_buttons.end(), buttons_param.begin(), @@ -371,7 +412,7 @@ void ConfigureInputPlayer::loadConfiguration() {                         [](const std::string& str) { return Common::ParamPackage(str); });      } -    updateButtonLabels(); +    UpdateButtonLabels();      if (debug)          return; @@ -388,11 +429,12 @@ void ConfigureInputPlayer::loadConfiguration() {      for (std::size_t i = 0; i < colors.size(); ++i) {          controller_color_buttons[i]->setStyleSheet( -            QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); +            QStringLiteral("QPushButton { background-color: %1 }") +                .arg(controller_colors[i].name()));      }  } -void ConfigureInputPlayer::restoreDefaults() { +void ConfigureInputPlayer::RestoreDefaults() {      for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {          buttons_param[button_id] = Common::ParamPackage{              InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; @@ -405,42 +447,54 @@ void ConfigureInputPlayer::restoreDefaults() {              SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);          }      } -    updateButtonLabels(); +    UpdateButtonLabels();  }  void ConfigureInputPlayer::ClearAll() {      for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { -        if (button_map[button_id] && button_map[button_id]->isEnabled()) -            buttons_param[button_id].Clear(); +        const auto* const button = button_map[button_id]; +        if (button == nullptr || !button->isEnabled()) { +            continue; +        } + +        buttons_param[button_id].Clear();      } +      for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {          for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { -            if (analog_map_buttons[analog_id][sub_button_id] && -                analog_map_buttons[analog_id][sub_button_id]->isEnabled()) -                analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); +            const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; +            if (analog_button == nullptr || !analog_button->isEnabled()) { +                continue; +            } + +            analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);          }      } -    updateButtonLabels(); +    UpdateButtonLabels();  } -void ConfigureInputPlayer::updateButtonLabels() { +void ConfigureInputPlayer::UpdateButtonLabels() {      for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {          button_map[button]->setText(ButtonToText(buttons_param[button]));      }      for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {          for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { -            if (analog_map_buttons[analog_id][sub_button_id]) { -                analog_map_buttons[analog_id][sub_button_id]->setText( -                    AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); +            auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; + +            if (analog_button == nullptr) { +                continue;              } + +            analog_button->setText( +                AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));          }          analog_map_stick[analog_id]->setText(tr("Set Analog Stick"));      }  } -void ConfigureInputPlayer::handleClick( +void ConfigureInputPlayer::HandleClick(      QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,      InputCommon::Polling::DeviceType type) {      button->setText(tr("[press key]")); @@ -468,7 +522,7 @@ void ConfigureInputPlayer::handleClick(      poll_timer->start(200);     // Check for new inputs every 200ms  } -void ConfigureInputPlayer::setPollingResult(const Common::ParamPackage& params, bool abort) { +void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {      releaseKeyboard();      releaseMouse();      timeout_timer->stop(); @@ -481,22 +535,23 @@ void ConfigureInputPlayer::setPollingResult(const Common::ParamPackage& params,          (*input_setter)(params);      } -    updateButtonLabels(); +    UpdateButtonLabels();      input_setter = std::nullopt;  }  void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { -    if (!input_setter || !event) +    if (!input_setter || !event) {          return; +    }      if (event->key() != Qt::Key_Escape) {          if (want_keyboard_keys) { -            setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, +            SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},                               false);          } else {              // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling              return;          }      } -    setPollingResult({}, true); +    SetPollingResult({}, true);  } diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index ade8d4435..c66027651 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -38,28 +38,31 @@ public:      ~ConfigureInputPlayer() override;      /// Save all button configurations to settings file -    void applyConfiguration(); +    void ApplyConfiguration();  private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); +      void OnControllerButtonClick(int i);      /// Load configuration settings. -    void loadConfiguration(); +    void LoadConfiguration();      /// Restore all buttons to their default values. -    void restoreDefaults(); +    void RestoreDefaults();      /// Clear all input configuration      void ClearAll();      /// Update UI to reflect current configuration. -    void updateButtonLabels(); +    void UpdateButtonLabels();      /// Called when the button was pressed. -    void handleClick(QPushButton* button, +    void HandleClick(QPushButton* button,                       std::function<void(const Common::ParamPackage&)> new_input_setter,                       InputCommon::Polling::DeviceType type);      /// Finish polling and configure input using the input_setter -    void setPollingResult(const Common::ParamPackage& params, bool abort); +    void SetPollingResult(const Common::ParamPackage& params, bool abort);      /// Handle key press events.      void keyPressEvent(QKeyEvent* event) override; diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp index 07d71e9d1..864803ea3 100644 --- a/src/yuzu/configuration/configure_input_simple.cpp +++ b/src/yuzu/configuration/configure_input_simple.cpp @@ -15,12 +15,12 @@ namespace {  template <typename Dialog, typename... Args>  void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) { -    caller->applyConfiguration(); +    caller->ApplyConfiguration();      Dialog dialog(caller, std::forward<Args>(args)...);      const auto res = dialog.exec();      if (res == QDialog::Accepted) { -        dialog.applyConfiguration(); +        dialog.ApplyConfiguration();      }  } @@ -103,27 +103,41 @@ ConfigureInputSimple::ConfigureInputSimple(QWidget* parent)              &ConfigureInputSimple::OnSelectProfile);      connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure); -    this->loadConfiguration(); +    LoadConfiguration();  }  ConfigureInputSimple::~ConfigureInputSimple() = default; -void ConfigureInputSimple::applyConfiguration() { +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)) +    if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) {          index = std::numeric_limits<int>::max(); +    }      UISettings::values.profile_index = index;  } -void ConfigureInputSimple::loadConfiguration() { +void ConfigureInputSimple::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureInputSimple::RetranslateUI() { +    ui->retranslateUi(this); +} + +void ConfigureInputSimple::LoadConfiguration() {      const auto index = UISettings::values.profile_index; -    if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) +    if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) {          ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1)); -    else +    } else {          ui->profile_combobox->setCurrentIndex(index); +    }  }  void ConfigureInputSimple::OnSelectProfile(int index) { diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h index 5b6b69994..bb5050224 100644 --- a/src/yuzu/configuration/configure_input_simple.h +++ b/src/yuzu/configuration/configure_input_simple.h @@ -27,11 +27,14 @@ public:      ~ConfigureInputSimple() override;      /// Save all button configurations to settings file -    void applyConfiguration(); +    void ApplyConfiguration();  private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); +      /// Load configuration settings. -    void loadConfiguration(); +    void LoadConfiguration();      void OnSelectProfile(int index);      void OnConfigure(); diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp index ef857035e..b7305e653 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.cpp +++ b/src/yuzu/configuration/configure_mouse_advanced.cpp @@ -25,7 +25,7 @@ static QString GetKeyName(int key_code) {      case Qt::Key_Alt:          return QObject::tr("Alt");      case Qt::Key_Meta: -        return ""; +        return {};      default:          return QKeySequence(key_code).toString();      } @@ -34,24 +34,36 @@ static QString GetKeyName(int key_code) {  static QString ButtonToText(const Common::ParamPackage& param) {      if (!param.Has("engine")) {          return QObject::tr("[not set]"); -    } else if (param.Get("engine", "") == "keyboard") { +    } + +    if (param.Get("engine", "") == "keyboard") {          return GetKeyName(param.Get("code", 0)); -    } else if (param.Get("engine", "") == "sdl") { +    } + +    if (param.Get("engine", "") == "sdl") {          if (param.Has("hat")) { -            return QString(QObject::tr("Hat %1 %2")) -                .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); +            const QString hat_str = QString::fromStdString(param.Get("hat", "")); +            const QString direction_str = QString::fromStdString(param.Get("direction", "")); + +            return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);          } +          if (param.Has("axis")) { -            return QString(QObject::tr("Axis %1%2")) -                .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); +            const QString axis_str = QString::fromStdString(param.Get("axis", "")); +            const QString direction_str = QString::fromStdString(param.Get("direction", "")); + +            return QObject::tr("Axis %1%2").arg(axis_str, direction_str);          } +          if (param.Has("button")) { -            return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); +            const QString button_str = QString::fromStdString(param.Get("button", "")); + +            return QObject::tr("Button %1").arg(button_str);          } -        return QString(); -    } else { -        return QObject::tr("[unknown]"); +        return {};      } + +    return QObject::tr("[unknown]");  }  ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) @@ -65,93 +77,108 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent)      };      for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) { -        if (!button_map[button_id]) +        auto* const button = button_map[button_id]; +        if (button == nullptr) {              continue; -        button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); -        connect(button_map[button_id], &QPushButton::released, [=]() { -            handleClick( +        } + +        button->setContextMenuPolicy(Qt::CustomContextMenu); +        connect(button, &QPushButton::released, [=] { +            HandleClick(                  button_map[button_id],                  [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },                  InputCommon::Polling::DeviceType::Button);          }); -        connect(button_map[button_id], &QPushButton::customContextMenuRequested, -                [=](const QPoint& menu_location) { -                    QMenu context_menu; -                    context_menu.addAction(tr("Clear"), [&] { -                        buttons_param[button_id].Clear(); -                        button_map[button_id]->setText(tr("[not set]")); -                    }); -                    context_menu.addAction(tr("Restore Default"), [&] { -                        buttons_param[button_id] = -                            Common::ParamPackage{InputCommon::GenerateKeyboardParam( -                                Config::default_mouse_buttons[button_id])}; -                        button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); -                    }); -                    context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); -                }); +        connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { +            QMenu context_menu; +            context_menu.addAction(tr("Clear"), [&] { +                buttons_param[button_id].Clear(); +                button_map[button_id]->setText(tr("[not set]")); +            }); +            context_menu.addAction(tr("Restore Default"), [&] { +                buttons_param[button_id] = Common::ParamPackage{ +                    InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])}; +                button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); +            }); +            context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); +        });      }      connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); -    connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); +    connect(ui->buttonRestoreDefaults, &QPushButton::released, [this] { RestoreDefaults(); });      timeout_timer->setSingleShot(true); -    connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); +    connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); -    connect(poll_timer.get(), &QTimer::timeout, [this]() { +    connect(poll_timer.get(), &QTimer::timeout, [this] {          Common::ParamPackage params;          for (auto& poller : device_pollers) {              params = poller->GetNextInput();              if (params.Has("engine")) { -                setPollingResult(params, false); +                SetPollingResult(params, false);                  return;              }          }      }); -    loadConfiguration(); +    LoadConfiguration();      resize(0, 0);  }  ConfigureMouseAdvanced::~ConfigureMouseAdvanced() = default; -void ConfigureMouseAdvanced::applyConfiguration() { +void ConfigureMouseAdvanced::ApplyConfiguration() {      std::transform(buttons_param.begin(), buttons_param.end(),                     Settings::values.mouse_buttons.begin(),                     [](const Common::ParamPackage& param) { return param.Serialize(); });  } -void ConfigureMouseAdvanced::loadConfiguration() { +void ConfigureMouseAdvanced::LoadConfiguration() {      std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(),                     buttons_param.begin(),                     [](const std::string& str) { return Common::ParamPackage(str); }); -    updateButtonLabels(); +    UpdateButtonLabels(); +} + +void ConfigureMouseAdvanced::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QDialog::changeEvent(event); +} + +void ConfigureMouseAdvanced::RetranslateUI() { +    ui->retranslateUi(this);  } -void ConfigureMouseAdvanced::restoreDefaults() { +void ConfigureMouseAdvanced::RestoreDefaults() {      for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {          buttons_param[button_id] = Common::ParamPackage{              InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])};      } -    updateButtonLabels(); +    UpdateButtonLabels();  }  void ConfigureMouseAdvanced::ClearAll() {      for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { -        if (button_map[i] && button_map[i]->isEnabled()) +        const auto* const button = button_map[i]; +        if (button != nullptr && button->isEnabled()) {              buttons_param[i].Clear(); +        }      } -    updateButtonLabels(); +    UpdateButtonLabels();  } -void ConfigureMouseAdvanced::updateButtonLabels() { +void ConfigureMouseAdvanced::UpdateButtonLabels() {      for (int button = 0; button < Settings::NativeMouseButton::NumMouseButtons; button++) {          button_map[button]->setText(ButtonToText(buttons_param[button]));      }  } -void ConfigureMouseAdvanced::handleClick( +void ConfigureMouseAdvanced::HandleClick(      QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,      InputCommon::Polling::DeviceType type) {      button->setText(tr("[press key]")); @@ -179,7 +206,7 @@ void ConfigureMouseAdvanced::handleClick(      poll_timer->start(200);     // Check for new inputs every 200ms  } -void ConfigureMouseAdvanced::setPollingResult(const Common::ParamPackage& params, bool abort) { +void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) {      releaseKeyboard();      releaseMouse();      timeout_timer->stop(); @@ -192,22 +219,23 @@ void ConfigureMouseAdvanced::setPollingResult(const Common::ParamPackage& params          (*input_setter)(params);      } -    updateButtonLabels(); +    UpdateButtonLabels();      input_setter = std::nullopt;  }  void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) { -    if (!input_setter || !event) +    if (!input_setter || !event) {          return; +    }      if (event->key() != Qt::Key_Escape) {          if (want_keyboard_keys) { -            setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, +            SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},                               false);          } else {              // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling              return;          }      } -    setPollingResult({}, true); +    SetPollingResult({}, true);  } diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h index e04da4bf2..342b82412 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.h +++ b/src/yuzu/configuration/configure_mouse_advanced.h @@ -25,26 +25,29 @@ public:      explicit ConfigureMouseAdvanced(QWidget* parent);      ~ConfigureMouseAdvanced() override; -    void applyConfiguration(); +    void ApplyConfiguration();  private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); +      /// Load configuration settings. -    void loadConfiguration(); +    void LoadConfiguration();      /// Restore all buttons to their default values. -    void restoreDefaults(); +    void RestoreDefaults();      /// Clear all input configuration      void ClearAll();      /// Update UI to reflect current configuration. -    void updateButtonLabels(); +    void UpdateButtonLabels();      /// Called when the button was pressed. -    void handleClick(QPushButton* button, +    void HandleClick(QPushButton* button,                       std::function<void(const Common::ParamPackage&)> new_input_setter,                       InputCommon::Polling::DeviceType type);      /// Finish polling and configure input using the input_setter -    void setPollingResult(const Common::ParamPackage& params, bool abort); +    void SetPollingResult(const Common::ParamPackage& params, bool abort);      /// Handle key press events.      void keyPressEvent(QKeyEvent* event) override; diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp index 022b94609..90336e235 100644 --- a/src/yuzu/configuration/configure_per_general.cpp +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -13,6 +13,8 @@  #include <QTimer>  #include <QTreeView> +#include "common/common_paths.h" +#include "common/file_util.h"  #include "core/file_sys/control_metadata.h"  #include "core/file_sys/patch_manager.h"  #include "core/file_sys/xts_archive.h" @@ -65,12 +67,12 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)      connect(item_model, &QStandardItemModel::itemChanged,              [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); -    this->loadConfiguration(); +    LoadConfiguration();  }  ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; -void ConfigurePerGameGeneral::applyConfiguration() { +void ConfigurePerGameGeneral::ApplyConfiguration() {      std::vector<std::string> disabled_addons;      for (const auto& item : list_items) { @@ -79,24 +81,44 @@ void ConfigurePerGameGeneral::applyConfiguration() {              disabled_addons.push_back(item.front()->text().toStdString());      } +    auto current = Settings::values.disabled_addons[title_id]; +    std::sort(disabled_addons.begin(), disabled_addons.end()); +    std::sort(current.begin(), current.end()); +    if (disabled_addons != current) { +        FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + +                         "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id)); +    } +      Settings::values.disabled_addons[title_id] = disabled_addons;  } -void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) { +void ConfigurePerGameGeneral::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QDialog::changeEvent(event); +} + +void ConfigurePerGameGeneral::RetranslateUI() { +    ui->retranslateUi(this); +} + +void ConfigurePerGameGeneral::LoadFromFile(FileSys::VirtualFile file) {      this->file = std::move(file); -    this->loadConfiguration(); +    LoadConfiguration();  } -void ConfigurePerGameGeneral::loadConfiguration() { -    if (file == nullptr) +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()); +    ui->display_title_id->setText(QString::fromStdString(fmt::format("{:016X}", title_id)));      FileSys::PatchManager pm{title_id};      const auto control = pm.GetControlMetadata(); +    const auto loader = Loader::GetLoader(file);      if (control.first != nullptr) {          ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); @@ -142,8 +164,10 @@ void ConfigurePerGameGeneral::loadConfiguration() {      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] ", ""); +        const auto name = +            QString::fromStdString(patch.first).replace(QStringLiteral("[D] "), QString{}); + +        auto* const first_item = new QStandardItem;          first_item->setText(name);          first_item->setCheckable(true); diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h index f8a7d5326..a3b2cdeff 100644 --- a/src/yuzu/configuration/configure_per_general.h +++ b/src/yuzu/configuration/configure_per_general.h @@ -30,11 +30,16 @@ public:      ~ConfigurePerGameGeneral() override;      /// Save all button configurations to settings file -    void applyConfiguration(); +    void ApplyConfiguration(); -    void loadFromFile(FileSys::VirtualFile file); +    void LoadFromFile(FileSys::VirtualFile file);  private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    void LoadConfiguration(); +      std::unique_ptr<Ui::ConfigurePerGameGeneral> ui;      FileSys::VirtualFile file;      u64 title_id; @@ -45,6 +50,4 @@ private:      QGraphicsScene* scene;      std::vector<QList<QStandardItem*>> list_items; - -    void loadConfiguration();  }; diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index 41663e39a..c90f4cdd8 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -33,14 +33,13 @@ constexpr std::array<u8, 107> backup_jpeg{      0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,  }; -QString GetImagePath(Service::Account::UUID uuid) { +QString GetImagePath(Common::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) { +QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) {      Service::Account::ProfileBase profile;      if (!manager.GetProfileBase(uuid, profile)) {          return {}; @@ -51,14 +50,14 @@ QString GetAccountUsername(const Service::Account::ProfileManager& manager,      return QString::fromStdString(text);  } -QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { +QString FormatUserEntryText(const QString& username, Common::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 GetIcon(Common::UUID uuid) {      QPixmap icon{GetImagePath(uuid)};      if (!icon) { @@ -81,11 +80,10 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)        profile_manager(std::make_unique<Service::Account::ProfileManager>()) {      ui->setupUi(this); -    layout = new QVBoxLayout;      tree_view = new QTreeView;      item_model = new QStandardItemModel(tree_view); +    item_model->insertColumns(0, 1);      tree_view->setModel(item_model); -      tree_view->setAlternatingRowColors(true);      tree_view->setSelectionMode(QHeaderView::SingleSelection);      tree_view->setSelectionBehavior(QHeaderView::SelectRows); @@ -97,13 +95,11 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)      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 = new QVBoxLayout;      layout->setContentsMargins(0, 0, 0, 0);      layout->setSpacing(0);      layout->addWidget(tree_view); @@ -120,12 +116,26 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)      scene = new QGraphicsScene;      ui->current_user_icon->setScene(scene); -    this->setConfiguration(); +    SetConfiguration(); +    RetranslateUI();  }  ConfigureProfileManager::~ConfigureProfileManager() = default; -void ConfigureProfileManager::setConfiguration() { +void ConfigureProfileManager::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureProfileManager::RetranslateUI() { +    ui->retranslateUi(this); +    item_model->setHeaderData(0, Qt::Horizontal, tr("Users")); +} + +void ConfigureProfileManager::SetConfiguration() {      enabled = !Core::System::GetInstance().IsPoweredOn();      item_model->removeRows(0, item_model->rowCount());      list_items.clear(); @@ -165,9 +175,10 @@ void ConfigureProfileManager::UpdateCurrentUser() {      ui->current_user_username->setText(username);  } -void ConfigureProfileManager::applyConfiguration() { -    if (!enabled) +void ConfigureProfileManager::ApplyConfiguration() { +    if (!enabled) {          return; +    }      Settings::Apply();  } @@ -190,7 +201,7 @@ void ConfigureProfileManager::AddUser() {          return;      } -    const auto uuid = Service::Account::UUID::Generate(); +    const auto uuid = Common::UUID::Generate();      profile_manager->CreateNewUser(uuid, username.toStdString());      item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)}); diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h index 7fe95a2a8..0a9bca2a6 100644 --- a/src/yuzu/configuration/configure_profile_manager.h +++ b/src/yuzu/configuration/configure_profile_manager.h @@ -30,10 +30,14 @@ public:      explicit ConfigureProfileManager(QWidget* parent = nullptr);      ~ConfigureProfileManager() override; -    void applyConfiguration(); -    void setConfiguration(); +    void ApplyConfiguration();  private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    void SetConfiguration(); +      void PopulateUserList();      void UpdateCurrentUser(); diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 10645a2b3..e1b52f8d9 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -16,49 +16,43 @@  #include "ui_configure_system.h"  #include "yuzu/configuration/configure_system.h" -namespace { -constexpr std::array<int, 12> days_in_month = {{ -    31, -    29, -    31, -    30, -    31, -    30, -    31, -    31, -    30, -    31, -    30, -    31, -}}; -} // Anonymous namespace -  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, -            &ConfigureSystem::UpdateBirthdayComboBox);      connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,              &ConfigureSystem::RefreshConsoleID);      connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {          ui->rng_seed_edit->setEnabled(checked); -        if (!checked) +        if (!checked) {              ui->rng_seed_edit->setText(QStringLiteral("00000000")); +        }      });      connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) {          ui->custom_rtc_edit->setEnabled(checked); -        if (!checked) +        if (!checked) {              ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime()); +        }      }); -    this->setConfiguration(); +    SetConfiguration();  }  ConfigureSystem::~ConfigureSystem() = default; -void ConfigureSystem::setConfiguration() { +void ConfigureSystem::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureSystem::RetranslateUI() { +    ui->retranslateUi(this); +} + +void ConfigureSystem::SetConfiguration() {      enabled = !Core::System::GetInstance().IsPoweredOn();      ui->combo_language->setCurrentIndex(Settings::values.language_index); @@ -66,8 +60,9 @@ void ConfigureSystem::setConfiguration() {      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(); +    const auto rng_seed = QStringLiteral("%1") +                              .arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}) +                              .toUpper();      ui->rng_seed_edit->setText(rng_seed);      ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); @@ -80,49 +75,27 @@ void ConfigureSystem::setConfiguration() {  void ConfigureSystem::ReadSystemSettings() {} -void ConfigureSystem::applyConfiguration() { -    if (!enabled) +void ConfigureSystem::ApplyConfiguration() { +    if (!enabled) {          return; +    }      Settings::values.language_index = ui->combo_language->currentIndex(); -    if (ui->rng_seed_checkbox->isChecked()) +    if (ui->rng_seed_checkbox->isChecked()) {          Settings::values.rng_seed = ui->rng_seed_edit->text().toULongLong(nullptr, 16); -    else +    } else {          Settings::values.rng_seed = std::nullopt; +    } -    if (ui->custom_rtc_checkbox->isChecked()) +    if (ui->custom_rtc_checkbox->isChecked()) {          Settings::values.custom_rtc =              std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()); -    else +    } else {          Settings::values.custom_rtc = std::nullopt; - -    Settings::Apply(); -} - -void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) { -    if (birthmonth_index < 0 || birthmonth_index >= 12) -        return; - -    // store current day selection -    int birthday_index = ui->combo_birthday->currentIndex(); - -    // get number of days in the new selected month -    int days = days_in_month[birthmonth_index]; - -    // if the selected day is out of range, -    // reset it to 1st -    if (birthday_index < 0 || birthday_index >= days) -        birthday_index = 0; - -    // update the day combo box -    ui->combo_birthday->clear(); -    for (int i = 1; i <= days; ++i) { -        ui->combo_birthday->addItem(QString::number(i));      } -    // restore the day selection -    ui->combo_birthday->setCurrentIndex(birthday_index); +    Settings::Apply();  }  void ConfigureSystem::RefreshConsoleID() { @@ -133,8 +106,10 @@ void ConfigureSystem::RefreshConsoleID() {                                "if you use an outdated config savegame. Continue?");      reply = QMessageBox::critical(this, tr("Warning"), warning_text,                                    QMessageBox::No | QMessageBox::Yes); -    if (reply == QMessageBox::No) +    if (reply == QMessageBox::No) {          return; +    } +      u64 console_id{};      ui->label_console_id->setText(          tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index cf1e54de5..1eab3781d 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -20,20 +20,21 @@ public:      explicit ConfigureSystem(QWidget* parent = nullptr);      ~ConfigureSystem() override; -    void applyConfiguration(); -    void setConfiguration(); +    void ApplyConfiguration();  private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); + +    void SetConfiguration(); +      void ReadSystemSettings(); -    void UpdateBirthdayComboBox(int birthmonth_index);      void RefreshConsoleID();      std::unique_ptr<Ui::ConfigureSystem> ui;      bool enabled = false; -    int birthmonth = 0; -    int birthday = 0;      int language_index = 0;      int sound_index = 0;  }; diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 073327298..65745a2f8 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -22,14 +22,21 @@          <string>System Settings</string>         </property>         <layout class="QGridLayout" name="gridLayout"> -        <item row="2" column="0"> +        <item row="1" column="0">           <widget class="QLabel" name="label_sound">            <property name="text">             <string>Sound output mode</string>            </property>           </widget>          </item> -        <item row="1" column="1"> +        <item row="2" column="0"> +         <widget class="QLabel" name="label_console_id"> +          <property name="text"> +           <string>Console ID:</string> +          </property> +         </widget> +        </item> +        <item row="0" column="1">           <widget class="QComboBox" name="combo_language">            <property name="toolTip">             <string>Note: this can be overridden when region setting is auto-select</string> @@ -121,108 +128,14 @@            </item>           </widget>          </item> -        <item row="0" column="1"> -         <layout class="QHBoxLayout" name="horizontalLayout_birthday2"> -          <item> -           <widget class="QComboBox" name="combo_birthmonth"> -            <item> -             <property name="text"> -              <string>January</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>February</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>March</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>April</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>May</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>June</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>July</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>August</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>September</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>October</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>November</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>December</string> -             </property> -            </item> -           </widget> -          </item> -          <item> -           <widget class="QComboBox" name="combo_birthday"/> -          </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"> -           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> -            <horstretch>0</horstretch> -            <verstretch>0</verstretch> -           </sizepolicy> -          </property> -          <property name="layoutDirection"> -           <enum>Qt::RightToLeft</enum> -          </property> +        <item row="4" column="0"> +         <widget class="QCheckBox" name="rng_seed_checkbox">            <property name="text"> -           <string>Regenerate</string> +           <string>RNG Seed</string>            </property>           </widget>          </item> -        <item row="2" column="1"> +        <item row="1" column="1">           <widget class="QComboBox" name="combo_sound">            <item>             <property name="text"> @@ -241,49 +154,37 @@            </item>           </widget>          </item> -        <item row="5" column="0"> -         <widget class="QCheckBox" name="rng_seed_checkbox"> -          <property name="text"> -           <string>RNG Seed</string> -          </property> -         </widget> -        </item> -        <item row="1" column="0"> +        <item row="0" column="0">           <widget class="QLabel" name="label_language">            <property name="text">             <string>Language</string>            </property>           </widget>          </item> -        <item row="5" column="1"> -         <widget class="QLineEdit" name="rng_seed_edit"> +        <item row="2" column="1"> +         <widget class="QPushButton" name="button_regenerate_console_id">            <property name="sizePolicy"> -           <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +           <sizepolicy hsizetype="Fixed" vsizetype="Fixed">              <horstretch>0</horstretch>              <verstretch>0</verstretch>             </sizepolicy>            </property> -          <property name="font"> -           <font> -            <family>Lucida Console</family> -           </font> -          </property> -          <property name="inputMask"> -           <string notr="true">HHHHHHHH</string> +          <property name="layoutDirection"> +           <enum>Qt::RightToLeft</enum>            </property> -          <property name="maxLength"> -           <number>8</number> +          <property name="text"> +           <string>Regenerate</string>            </property>           </widget>          </item> -        <item row="4" column="0"> +        <item row="3" column="0">           <widget class="QCheckBox" name="custom_rtc_checkbox">            <property name="text">             <string>Custom RTC</string>            </property>           </widget>          </item> -        <item row="4" column="1"> +        <item row="3" column="1">           <widget class="QDateTimeEdit" name="custom_rtc_edit">            <property name="minimumDate">             <date> @@ -297,6 +198,27 @@            </property>           </widget>          </item> +        <item row="4" column="1"> +         <widget class="QLineEdit" name="rng_seed_edit"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="font"> +           <font> +            <family>Lucida Console</family> +           </font> +          </property> +          <property name="inputMask"> +           <string notr="true">HHHHHHHH</string> +          </property> +          <property name="maxLength"> +           <number>8</number> +          </property> +         </widget> +        </item>         </layout>        </widget>       </item> diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp index 9c1561e9d..8ced28c75 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp +++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp @@ -12,29 +12,41 @@ ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent)      ui->setupUi(this);      connect(ui->restore_defaults_button, &QPushButton::pressed, this, -            &ConfigureTouchscreenAdvanced::restoreDefaults); +            &ConfigureTouchscreenAdvanced::RestoreDefaults); -    loadConfiguration(); +    LoadConfiguration();      resize(0, 0);  }  ConfigureTouchscreenAdvanced::~ConfigureTouchscreenAdvanced() = default; -void ConfigureTouchscreenAdvanced::applyConfiguration() { +void ConfigureTouchscreenAdvanced::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QDialog::changeEvent(event); +} + +void ConfigureTouchscreenAdvanced::RetranslateUI() { +    ui->retranslateUi(this); +} + +void ConfigureTouchscreenAdvanced::ApplyConfiguration() {      Settings::values.touchscreen.finger = ui->finger_box->value();      Settings::values.touchscreen.diameter_x = ui->diameter_x_box->value();      Settings::values.touchscreen.diameter_y = ui->diameter_y_box->value();      Settings::values.touchscreen.rotation_angle = ui->angle_box->value();  } -void ConfigureTouchscreenAdvanced::loadConfiguration() { +void ConfigureTouchscreenAdvanced::LoadConfiguration() {      ui->finger_box->setValue(Settings::values.touchscreen.finger);      ui->diameter_x_box->setValue(Settings::values.touchscreen.diameter_x);      ui->diameter_y_box->setValue(Settings::values.touchscreen.diameter_y);      ui->angle_box->setValue(Settings::values.touchscreen.rotation_angle);  } -void ConfigureTouchscreenAdvanced::restoreDefaults() { +void ConfigureTouchscreenAdvanced::RestoreDefaults() {      ui->finger_box->setValue(0);      ui->diameter_x_box->setValue(15);      ui->diameter_y_box->setValue(15); diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.h b/src/yuzu/configuration/configure_touchscreen_advanced.h index 3d0772c87..72061492c 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.h +++ b/src/yuzu/configuration/configure_touchscreen_advanced.h @@ -18,13 +18,16 @@ public:      explicit ConfigureTouchscreenAdvanced(QWidget* parent);      ~ConfigureTouchscreenAdvanced() override; -    void applyConfiguration(); +    void ApplyConfiguration();  private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); +      /// Load configuration settings. -    void loadConfiguration(); +    void LoadConfiguration();      /// Restore all buttons to their default values. -    void restoreDefaults(); +    void RestoreDefaults();      std::unique_ptr<Ui::ConfigureTouchscreenAdvanced> ui;  }; diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index 18566d028..5a70ef168 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -22,41 +22,61 @@ ConfigureWeb::ConfigureWeb(QWidget* parent)  #ifndef USE_DISCORD_PRESENCE      ui->discord_group->setVisible(false);  #endif -    this->setConfiguration(); + +    SetConfiguration(); +    RetranslateUI();  }  ConfigureWeb::~ConfigureWeb() = default; -void ConfigureWeb::setConfiguration() { -    ui->web_credentials_disclaimer->setWordWrap(true); -    ui->telemetry_learn_more->setOpenExternalLinks(true); +void ConfigureWeb::changeEvent(QEvent* event) { +    if (event->type() == QEvent::LanguageChange) { +        RetranslateUI(); +    } + +    QWidget::changeEvent(event); +} + +void ConfigureWeb::RetranslateUI() { +    ui->retranslateUi(this); +      ui->telemetry_learn_more->setText(          tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'><span style=\"text-decoration: "             "underline; color:#039be5;\">Learn more</span></a>")); -    ui->web_signup_link->setOpenExternalLinks(true);      ui->web_signup_link->setText(          tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; "             "color:#039be5;\">Sign up</span></a>")); -    ui->web_token_info_link->setOpenExternalLinks(true); +      ui->web_token_info_link->setText(          tr("<a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style=\"text-decoration: "             "underline; color:#039be5;\">What is my token?</span></a>")); +    ui->label_telemetry_id->setText( +        tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper())); +} + +void ConfigureWeb::SetConfiguration() { +    ui->web_credentials_disclaimer->setWordWrap(true); + +    ui->telemetry_learn_more->setOpenExternalLinks(true); +    ui->web_signup_link->setOpenExternalLinks(true); +    ui->web_token_info_link->setOpenExternalLinks(true); +      ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);      ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username));      ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token)); +      // Connect after setting the values, to avoid calling OnLoginChanged now      connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);      connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); -    ui->label_telemetry_id->setText( -        tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper())); +      user_verified = true;      ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence);  } -void ConfigureWeb::applyConfiguration() { +void ConfigureWeb::ApplyConfiguration() {      Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();      UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();      if (user_verified) { @@ -78,12 +98,16 @@ void ConfigureWeb::RefreshTelemetryID() {  void ConfigureWeb::OnLoginChanged() {      if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {          user_verified = true; -        ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); -        ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); + +        const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); +        ui->label_username_verified->setPixmap(pixmap); +        ui->label_token_verified->setPixmap(pixmap);      } else {          user_verified = false; -        ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); -        ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); + +        const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); +        ui->label_username_verified->setPixmap(pixmap); +        ui->label_token_verified->setPixmap(pixmap);      }  } @@ -101,18 +125,18 @@ void ConfigureWeb::OnLoginVerified() {      ui->button_verify_login->setText(tr("Verify"));      if (verify_watcher.result()) {          user_verified = true; -        ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); -        ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); + +        const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); +        ui->label_username_verified->setPixmap(pixmap); +        ui->label_token_verified->setPixmap(pixmap);      } else { -        ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); -        ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); +        const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); +        ui->label_username_verified->setPixmap(pixmap); +        ui->label_token_verified->setPixmap(pixmap); +          QMessageBox::critical(              this, tr("Verification failed"),              tr("Verification failed. Check that you have entered your username and token "                 "correctly, and that your internet connection is working."));      }  } - -void ConfigureWeb::retranslateUi() { -    ui->retranslateUi(this); -} diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h index 7752ae4a1..9054711ea 100644 --- a/src/yuzu/configuration/configure_web.h +++ b/src/yuzu/configuration/configure_web.h @@ -19,16 +19,18 @@ public:      explicit ConfigureWeb(QWidget* parent = nullptr);      ~ConfigureWeb() override; -    void applyConfiguration(); -    void retranslateUi(); +    void ApplyConfiguration();  private: +    void changeEvent(QEvent* event) override; +    void RetranslateUI(); +      void RefreshTelemetryID();      void OnLoginChanged();      void VerifyLogin();      void OnLoginVerified(); -    void setConfiguration(); +    void SetConfiguration();      bool user_verified = true;      QFutureWatcher<bool> verify_watcher; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp index 67ed0ba6d..1c80082a4 100644 --- a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp @@ -135,7 +135,7 @@ GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(      std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)      : QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(                                                            debug_context) { -    setObjectName("TegraBreakPointsWidget"); +    setObjectName(QStringLiteral("TegraBreakPointsWidget"));      status_text = new QLabel(tr("Emulation running"));      resume_button = new QPushButton(tr("Resume")); diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp deleted file mode 100644 index 71683da8e..000000000 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QBoxLayout> -#include <QComboBox> -#include <QDebug> -#include <QFileDialog> -#include <QLabel> -#include <QMouseEvent> -#include <QPushButton> -#include <QScrollArea> -#include <QSpinBox> -#include "common/vector_math.h" -#include "core/core.h" -#include "core/memory.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/gpu.h" -#include "video_core/textures/decoders.h" -#include "video_core/textures/texture.h" -#include "yuzu/debugger/graphics/graphics_surface.h" -#include "yuzu/util/spinbox.h" - -static Tegra::Texture::TextureFormat ConvertToTextureFormat( -    Tegra::RenderTargetFormat render_target_format) { -    switch (render_target_format) { -    case Tegra::RenderTargetFormat::RGBA8_UNORM: -        return Tegra::Texture::TextureFormat::A8R8G8B8; -    case Tegra::RenderTargetFormat::RGB10_A2_UNORM: -        return Tegra::Texture::TextureFormat::A2B10G10R10; -    default: -        UNIMPLEMENTED_MSG("Unimplemented RT format"); -        return Tegra::Texture::TextureFormat::A8R8G8B8; -    } -} - -SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) -    : QLabel(parent), surface_widget(surface_widget_) {} - -SurfacePicture::~SurfacePicture() = default; - -void SurfacePicture::mousePressEvent(QMouseEvent* event) { -    // Only do something while the left mouse button is held down -    if (!(event->buttons() & Qt::LeftButton)) -        return; - -    if (pixmap() == nullptr) -        return; - -    if (surface_widget) -        surface_widget->Pick(event->x() * pixmap()->width() / width(), -                             event->y() * pixmap()->height() / height()); -} - -void SurfacePicture::mouseMoveEvent(QMouseEvent* event) { -    // We also want to handle the event if the user moves the mouse while holding down the LMB -    mousePressEvent(event); -} - -GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, -                                             QWidget* parent) -    : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent), -      surface_source(Source::RenderTarget0) { -    setObjectName("MaxwellSurface"); - -    surface_source_list = new QComboBox; -    surface_source_list->addItem(tr("Render Target 0")); -    surface_source_list->addItem(tr("Render Target 1")); -    surface_source_list->addItem(tr("Render Target 2")); -    surface_source_list->addItem(tr("Render Target 3")); -    surface_source_list->addItem(tr("Render Target 4")); -    surface_source_list->addItem(tr("Render Target 5")); -    surface_source_list->addItem(tr("Render Target 6")); -    surface_source_list->addItem(tr("Render Target 7")); -    surface_source_list->addItem(tr("Z Buffer")); -    surface_source_list->addItem(tr("Custom")); -    surface_source_list->setCurrentIndex(static_cast<int>(surface_source)); - -    surface_address_control = new CSpinBox; -    surface_address_control->SetBase(16); -    surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF); -    surface_address_control->SetPrefix("0x"); - -    unsigned max_dimension = 16384; // TODO: Find actual maximum - -    surface_width_control = new QSpinBox; -    surface_width_control->setRange(0, max_dimension); - -    surface_height_control = new QSpinBox; -    surface_height_control->setRange(0, max_dimension); - -    surface_picker_x_control = new QSpinBox; -    surface_picker_x_control->setRange(0, max_dimension - 1); - -    surface_picker_y_control = new QSpinBox; -    surface_picker_y_control->setRange(0, max_dimension - 1); - -    surface_format_control = new QComboBox; - -    // Color formats sorted by Maxwell texture format index -    surface_format_control->addItem(tr("None")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("A8R8G8B8")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("Unknown")); -    surface_format_control->addItem(tr("DXT1")); -    surface_format_control->addItem(tr("DXT23")); -    surface_format_control->addItem(tr("DXT45")); -    surface_format_control->addItem(tr("DXN1")); -    surface_format_control->addItem(tr("DXN2")); - -    surface_info_label = new QLabel(); -    surface_info_label->setWordWrap(true); - -    surface_picture_label = new SurfacePicture(0, this); -    surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); -    surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); -    surface_picture_label->setScaledContents(false); - -    auto scroll_area = new QScrollArea(); -    scroll_area->setBackgroundRole(QPalette::Dark); -    scroll_area->setWidgetResizable(false); -    scroll_area->setWidget(surface_picture_label); - -    save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); - -    // Connections -    connect(this, &GraphicsSurfaceWidget::Update, this, &GraphicsSurfaceWidget::OnUpdate); -    connect(surface_source_list, -            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, -            &GraphicsSurfaceWidget::OnSurfaceSourceChanged); -    connect(surface_address_control, &CSpinBox::ValueChanged, this, -            &GraphicsSurfaceWidget::OnSurfaceAddressChanged); -    connect(surface_width_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), -            this, &GraphicsSurfaceWidget::OnSurfaceWidthChanged); -    connect(surface_height_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), -            this, &GraphicsSurfaceWidget::OnSurfaceHeightChanged); -    connect(surface_format_control, -            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, -            &GraphicsSurfaceWidget::OnSurfaceFormatChanged); -    connect(surface_picker_x_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), -            this, &GraphicsSurfaceWidget::OnSurfacePickerXChanged); -    connect(surface_picker_y_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), -            this, &GraphicsSurfaceWidget::OnSurfacePickerYChanged); -    connect(save_surface, &QPushButton::clicked, this, &GraphicsSurfaceWidget::SaveSurface); - -    auto main_widget = new QWidget; -    auto main_layout = new QVBoxLayout; -    { -        auto sub_layout = new QHBoxLayout; -        sub_layout->addWidget(new QLabel(tr("Source:"))); -        sub_layout->addWidget(surface_source_list); -        main_layout->addLayout(sub_layout); -    } -    { -        auto sub_layout = new QHBoxLayout; -        sub_layout->addWidget(new QLabel(tr("GPU Address:"))); -        sub_layout->addWidget(surface_address_control); -        main_layout->addLayout(sub_layout); -    } -    { -        auto sub_layout = new QHBoxLayout; -        sub_layout->addWidget(new QLabel(tr("Width:"))); -        sub_layout->addWidget(surface_width_control); -        main_layout->addLayout(sub_layout); -    } -    { -        auto sub_layout = new QHBoxLayout; -        sub_layout->addWidget(new QLabel(tr("Height:"))); -        sub_layout->addWidget(surface_height_control); -        main_layout->addLayout(sub_layout); -    } -    { -        auto sub_layout = new QHBoxLayout; -        sub_layout->addWidget(new QLabel(tr("Format:"))); -        sub_layout->addWidget(surface_format_control); -        main_layout->addLayout(sub_layout); -    } -    main_layout->addWidget(scroll_area); - -    auto info_layout = new QHBoxLayout; -    { -        auto xy_layout = new QVBoxLayout; -        { -            { -                auto sub_layout = new QHBoxLayout; -                sub_layout->addWidget(new QLabel(tr("X:"))); -                sub_layout->addWidget(surface_picker_x_control); -                xy_layout->addLayout(sub_layout); -            } -            { -                auto sub_layout = new QHBoxLayout; -                sub_layout->addWidget(new QLabel(tr("Y:"))); -                sub_layout->addWidget(surface_picker_y_control); -                xy_layout->addLayout(sub_layout); -            } -        } -        info_layout->addLayout(xy_layout); -        surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); -        info_layout->addWidget(surface_info_label); -    } -    main_layout->addLayout(info_layout); - -    main_layout->addWidget(save_surface); -    main_widget->setLayout(main_layout); -    setWidget(main_widget); - -    // Load current data - TODO: Make sure this works when emulation is not running -    if (debug_context && debug_context->at_breakpoint) { -        emit Update(); -        widget()->setEnabled(debug_context->at_breakpoint); -    } else { -        widget()->setEnabled(false); -    } -} - -void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { -    emit Update(); -    widget()->setEnabled(true); -} - -void GraphicsSurfaceWidget::OnResumed() { -    widget()->setEnabled(false); -} - -void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) { -    surface_source = static_cast<Source>(new_value); -    emit Update(); -} - -void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) { -    if (surface_address != new_value) { -        surface_address = static_cast<Tegra::GPUVAddr>(new_value); - -        surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); -        emit Update(); -    } -} - -void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) { -    if (surface_width != static_cast<unsigned>(new_value)) { -        surface_width = static_cast<unsigned>(new_value); - -        surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); -        emit Update(); -    } -} - -void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) { -    if (surface_height != static_cast<unsigned>(new_value)) { -        surface_height = static_cast<unsigned>(new_value); - -        surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); -        emit Update(); -    } -} - -void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) { -    if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) { -        surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value); - -        surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); -        emit Update(); -    } -} - -void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) { -    if (surface_picker_x != new_value) { -        surface_picker_x = new_value; -        Pick(surface_picker_x, surface_picker_y); -    } -} - -void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) { -    if (surface_picker_y != new_value) { -        surface_picker_y = new_value; -        Pick(surface_picker_x, surface_picker_y); -    } -} - -void GraphicsSurfaceWidget::Pick(int x, int y) { -    surface_picker_x_control->setValue(x); -    surface_picker_y_control->setValue(y); - -    if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 || -        y >= static_cast<int>(surface_height)) { -        surface_info_label->setText(tr("Pixel out of bounds")); -        surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); -        return; -    } - -    surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>")); -    surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); -} - -void GraphicsSurfaceWidget::OnUpdate() { -    auto& gpu = Core::System::GetInstance().GPU(); - -    QPixmap pixmap; - -    switch (surface_source) { -    case Source::RenderTarget0: -    case Source::RenderTarget1: -    case Source::RenderTarget2: -    case Source::RenderTarget3: -    case Source::RenderTarget4: -    case Source::RenderTarget5: -    case Source::RenderTarget6: -    case Source::RenderTarget7: { -        // TODO: Store a reference to the registers in the debug context instead of accessing them -        // directly... - -        const auto& registers = gpu.Maxwell3D().regs; -        const auto& rt = registers.rt[static_cast<std::size_t>(surface_source) - -                                      static_cast<std::size_t>(Source::RenderTarget0)]; - -        surface_address = rt.Address(); -        surface_width = rt.width; -        surface_height = rt.height; -        if (rt.format != Tegra::RenderTargetFormat::NONE) { -            surface_format = ConvertToTextureFormat(rt.format); -        } - -        break; -    } - -    case Source::Custom: { -        // Keep user-specified values -        break; -    } - -    default: -        qDebug() << "Unknown surface source " << static_cast<int>(surface_source); -        break; -    } - -    surface_address_control->SetValue(surface_address); -    surface_width_control->setValue(surface_width); -    surface_height_control->setValue(surface_height); -    surface_format_control->setCurrentIndex(static_cast<int>(surface_format)); - -    if (surface_address == 0) { -        surface_picture_label->hide(); -        surface_info_label->setText(tr("(invalid surface address)")); -        surface_info_label->setAlignment(Qt::AlignCenter); -        surface_picker_x_control->setEnabled(false); -        surface_picker_y_control->setEnabled(false); -        save_surface->setEnabled(false); -        return; -    } - -    // TODO: Implement a good way to visualize alpha components! - -    QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); -    std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address); - -    // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles. -    // Needs to be fixed if we plan to use this feature more, otherwise we may remove it. -    auto unswizzled_data = Tegra::Texture::UnswizzleTexture( -        *address, 1, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width, -        surface_height, 1U); - -    auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, -                                                      surface_width, surface_height); - -    surface_picture_label->show(); - -    for (unsigned int y = 0; y < surface_height; ++y) { -        for (unsigned int x = 0; x < surface_width; ++x) { -            Common::Vec4<u8> color; -            color[0] = texture_data[x + y * surface_width + 0]; -            color[1] = texture_data[x + y * surface_width + 1]; -            color[2] = texture_data[x + y * surface_width + 2]; -            color[3] = texture_data[x + y * surface_width + 3]; -            decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); -        } -    } - -    pixmap = QPixmap::fromImage(decoded_image); -    surface_picture_label->setPixmap(pixmap); -    surface_picture_label->resize(pixmap.size()); - -    // Update the info with pixel data -    surface_picker_x_control->setEnabled(true); -    surface_picker_y_control->setEnabled(true); -    Pick(surface_picker_x, surface_picker_y); - -    // Enable saving the converted pixmap to file -    save_surface->setEnabled(true); -} - -void GraphicsSurfaceWidget::SaveSurface() { -    QString png_filter = tr("Portable Network Graphic (*.png)"); -    QString bin_filter = tr("Binary data (*.bin)"); - -    QString selectedFilter; -    QString filename = QFileDialog::getSaveFileName( -        this, tr("Save Surface"), -        QString("texture-0x%1.png").arg(QString::number(surface_address, 16)), -        QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter); - -    if (filename.isEmpty()) { -        // If the user canceled the dialog, don't save anything. -        return; -    } - -    if (selectedFilter == png_filter) { -        const QPixmap* pixmap = surface_picture_label->pixmap(); -        ASSERT_MSG(pixmap != nullptr, "No pixmap set"); - -        QFile file(filename); -        file.open(QIODevice::WriteOnly); -        if (pixmap) -            pixmap->save(&file, "PNG"); -    } else if (selectedFilter == bin_filter) { -        auto& gpu = Core::System::GetInstance().GPU(); -        std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address); - -        const u8* buffer = Memory::GetPointer(*address); -        ASSERT_MSG(buffer != nullptr, "Memory not accessible"); - -        QFile file(filename); -        file.open(QIODevice::WriteOnly); -        int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format); -        QByteArray data(reinterpret_cast<const char*>(buffer), size); -        file.write(data); -    } else { -        UNREACHABLE_MSG("Unhandled filter selected"); -    } -} diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h deleted file mode 100644 index 323e39d94..000000000 --- a/src/yuzu/debugger/graphics/graphics_surface.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <QLabel> -#include <QPushButton> -#include "video_core/memory_manager.h" -#include "video_core/textures/texture.h" -#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" - -class QComboBox; -class QSpinBox; -class CSpinBox; - -class GraphicsSurfaceWidget; - -class SurfacePicture : public QLabel { -    Q_OBJECT - -public: -    explicit SurfacePicture(QWidget* parent = nullptr, -                            GraphicsSurfaceWidget* surface_widget = nullptr); -    ~SurfacePicture() override; - -protected slots: -    void mouseMoveEvent(QMouseEvent* event) override; -    void mousePressEvent(QMouseEvent* event) override; - -private: -    GraphicsSurfaceWidget* surface_widget; -}; - -class GraphicsSurfaceWidget : public BreakPointObserverDock { -    Q_OBJECT - -    using Event = Tegra::DebugContext::Event; - -    enum class Source { -        RenderTarget0 = 0, -        RenderTarget1 = 1, -        RenderTarget2 = 2, -        RenderTarget3 = 3, -        RenderTarget4 = 4, -        RenderTarget5 = 5, -        RenderTarget6 = 6, -        RenderTarget7 = 7, -        ZBuffer = 8, -        Custom = 9, -    }; - -public: -    explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, -                                   QWidget* parent = nullptr); -    void Pick(int x, int y); - -public slots: -    void OnSurfaceSourceChanged(int new_value); -    void OnSurfaceAddressChanged(qint64 new_value); -    void OnSurfaceWidthChanged(int new_value); -    void OnSurfaceHeightChanged(int new_value); -    void OnSurfaceFormatChanged(int new_value); -    void OnSurfacePickerXChanged(int new_value); -    void OnSurfacePickerYChanged(int new_value); -    void OnUpdate(); - -signals: -    void Update(); - -private: -    void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override; -    void OnResumed() override; - -    void SaveSurface(); - -    QComboBox* surface_source_list; -    CSpinBox* surface_address_control; -    QSpinBox* surface_width_control; -    QSpinBox* surface_height_control; -    QComboBox* surface_format_control; - -    SurfacePicture* surface_picture_label; -    QSpinBox* surface_picker_x_control; -    QSpinBox* surface_picker_y_control; -    QLabel* surface_info_label; -    QPushButton* save_surface; - -    Source surface_source; -    Tegra::GPUVAddr surface_address; -    unsigned surface_width; -    unsigned surface_height; -    Tegra::Texture::TextureFormat surface_format; -    int surface_picker_x = 0; -    int surface_picker_y = 0; -}; diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index 8b30e0a85..f594ef076 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -7,6 +7,7 @@  #include <QMouseEvent>  #include <QPainter>  #include <QString> +#include <QTimer>  #include "common/common_types.h"  #include "common/microprofile.h"  #include "yuzu/debugger/profiler.h" @@ -46,7 +47,7 @@ private:  #endif  MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { -    setObjectName("MicroProfile"); +    setObjectName(QStringLiteral("MicroProfile"));      setWindowTitle(tr("MicroProfile"));      resize(1000, 600);      // Remove the "?" button from the titlebar and enable the maximize button @@ -190,7 +191,7 @@ void MicroProfileDrawText(int x, int y, u32 hex_color, const char* text, u32 tex      for (u32 i = 0; i < text_length; ++i) {          // Position the text baseline 1 pixel above the bottom of the text cell, this gives nice          // vertical alignment of text for a wide range of tested fonts. -        mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QChar(text[i])); +        mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QString{QLatin1Char{text[i]}});          x += MICROPROFILE_TEXT_WIDTH + 1;      }  } diff --git a/src/yuzu/debugger/profiler.h b/src/yuzu/debugger/profiler.h index eae1e9e3c..8e69fdb06 100644 --- a/src/yuzu/debugger/profiler.h +++ b/src/yuzu/debugger/profiler.h @@ -4,10 +4,11 @@  #pragma once -#include <QAbstractItemModel> -#include <QDockWidget> -#include <QTimer> -#include "common/microprofile.h" +#include <QWidget> + +class QAction; +class QHideEvent; +class QShowEvent;  class MicroProfileDialog : public QWidget {      Q_OBJECT diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 06ad74ffe..cd8180f8b 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -91,19 +91,19 @@ WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address, const Kernel::HandleTa  WaitTreeMutexInfo::~WaitTreeMutexInfo() = default;  QString WaitTreeMutexInfo::GetText() const { -    return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char('0')); +    return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char{'0'});  }  std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const { -    std::vector<std::unique_ptr<WaitTreeItem>> list; - -    bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0; +    const bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0; +    std::vector<std::unique_ptr<WaitTreeItem>> list;      list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters)));      list.push_back(std::make_unique<WaitTreeText>( -        tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char('0')))); -    if (owner != nullptr) +        tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char{'0'}))); +    if (owner != nullptr) {          list.push_back(std::make_unique<WaitTreeThread>(*owner)); +    }      return list;  } @@ -121,11 +121,14 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons      u64 base_pointer = thread.GetContext().cpu_registers[BaseRegister];      while (base_pointer != 0) { -        u64 lr = Memory::Read64(base_pointer + sizeof(u64)); -        if (lr == 0) +        const u64 lr = Memory::Read64(base_pointer + sizeof(u64)); +        if (lr == 0) {              break; -        list.push_back( -            std::make_unique<WaitTreeText>(tr("0x%1").arg(lr - sizeof(u32), 16, 16, QChar('0')))); +        } + +        list.push_back(std::make_unique<WaitTreeText>( +            tr("0x%1").arg(lr - sizeof(u32), 16, 16, QLatin1Char{'0'}))); +          base_pointer = Memory::Read64(base_pointer);      } @@ -174,10 +177,10 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeWaitObject::GetChildren() con  QString WaitTreeWaitObject::GetResetTypeQString(Kernel::ResetType reset_type) {      switch (reset_type) { -    case Kernel::ResetType::OneShot: -        return tr("one shot"); -    case Kernel::ResetType::Sticky: -        return tr("sticky"); +    case Kernel::ResetType::Automatic: +        return tr("automatic reset"); +    case Kernel::ResetType::Manual: +        return tr("manual reset");      }      UNREACHABLE();      return {}; @@ -227,13 +230,15 @@ QString WaitTreeThread::GetText() const {      case Kernel::ThreadStatus::WaitIPC:          status = tr("waiting for IPC reply");          break; -    case Kernel::ThreadStatus::WaitSynchAll: -    case Kernel::ThreadStatus::WaitSynchAny: +    case Kernel::ThreadStatus::WaitSynch:          status = tr("waiting for objects");          break;      case Kernel::ThreadStatus::WaitMutex:          status = tr("waiting for mutex");          break; +    case Kernel::ThreadStatus::WaitCondVar: +        status = tr("waiting for condition variable"); +        break;      case Kernel::ThreadStatus::WaitArb:          status = tr("waiting for address arbiter");          break; @@ -247,9 +252,9 @@ QString WaitTreeThread::GetText() const {      const auto& context = thread.GetContext();      const QString pc_info = tr(" PC = 0x%1 LR = 0x%2") -                                .arg(context.pc, 8, 16, QLatin1Char('0')) -                                .arg(context.cpu_registers[30], 8, 16, QLatin1Char('0')); -    return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") "; +                                .arg(context.pc, 8, 16, QLatin1Char{'0'}) +                                .arg(context.cpu_registers[30], 8, 16, QLatin1Char{'0'}); +    return QStringLiteral("%1%2 (%3) ").arg(WaitTreeWaitObject::GetText(), pc_info, status);  }  QColor WaitTreeThread::GetColor() const { @@ -266,9 +271,9 @@ QColor WaitTreeThread::GetColor() const {          return QColor(Qt::GlobalColor::darkRed);      case Kernel::ThreadStatus::WaitSleep:          return QColor(Qt::GlobalColor::darkYellow); -    case Kernel::ThreadStatus::WaitSynchAll: -    case Kernel::ThreadStatus::WaitSynchAny: +    case Kernel::ThreadStatus::WaitSynch:      case Kernel::ThreadStatus::WaitMutex: +    case Kernel::ThreadStatus::WaitCondVar:      case Kernel::ThreadStatus::WaitArb:          return QColor(Qt::GlobalColor::red);      case Kernel::ThreadStatus::Dormant: @@ -321,10 +326,9 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {          list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex")));      } -    if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAny || -        thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAll) { +    if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynch) {          list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetWaitObjects(), -                                                            thread.IsSleepingOnWaitAll())); +                                                            thread.IsSleepingOnWait()));      }      list.push_back(std::make_unique<WaitTreeCallstack>(thread)); @@ -423,7 +427,7 @@ void WaitTreeModel::InitItems() {  }  WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("Wait Tree"), parent) { -    setObjectName("WaitTreeWidget"); +    setObjectName(QStringLiteral("WaitTreeWidget"));      view = new QTreeView(this);      view->setHeaderHidden(true);      setWidget(view); diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index c0e3c5fa9..83d675773 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -14,10 +14,10 @@  #include <QMenu>  #include <QThreadPool>  #include <fmt/format.h> -#include "common/common_paths.h"  #include "common/common_types.h"  #include "common/logging/log.h"  #include "core/file_sys/patch_manager.h" +#include "core/file_sys/registered_cache.h"  #include "yuzu/compatibility_list.h"  #include "yuzu/game_list.h"  #include "yuzu/game_list_p.h" @@ -47,7 +47,7 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve                  return QObject::eventFilter(obj, event);              } else {                  gamelist->search_field->edit_filter->clear(); -                edit_filter_text = ""; +                edit_filter_text.clear();              }              break;          } @@ -70,9 +70,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve              }              if (resultCount == 1) {                  // To avoid loading error dialog loops while confirming them using enter -                // Also users usually want to run a diffrent game after closing one -                gamelist->search_field->edit_filter->setText(""); -                edit_filter_text = ""; +                // Also users usually want to run a different game after closing one +                gamelist->search_field->edit_filter->clear(); +                edit_filter_text.clear();                  emit gamelist->GameChosen(file_path);              } else {                  return QObject::eventFilter(obj, event); @@ -92,7 +92,7 @@ void GameListSearchField::setFilterResult(int visible, int total) {  }  void GameListSearchField::clear() { -    edit_filter->setText(""); +    edit_filter->clear();  }  void GameListSearchField::setFocus() { @@ -102,25 +102,26 @@ void GameListSearchField::setFocus() {  }  GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { -    KeyReleaseEater* keyReleaseEater = new KeyReleaseEater(parent); +    auto* const key_release_eater = new KeyReleaseEater(parent);      layout_filter = new QHBoxLayout;      layout_filter->setMargin(8);      label_filter = new QLabel;      label_filter->setText(tr("Filter:"));      edit_filter = new QLineEdit; -    edit_filter->setText(""); +    edit_filter->clear();      edit_filter->setPlaceholderText(tr("Enter pattern to filter")); -    edit_filter->installEventFilter(keyReleaseEater); +    edit_filter->installEventFilter(key_release_eater);      edit_filter->setClearButtonEnabled(true);      connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged);      label_filter_result = new QLabel;      button_filter_close = new QToolButton(this); -    button_filter_close->setText("X"); +    button_filter_close->setText(QStringLiteral("X"));      button_filter_close->setCursor(Qt::ArrowCursor); -    button_filter_close->setStyleSheet("QToolButton{ border: none; padding: 0px; color: " -                                       "#000000; font-weight: bold; background: #F0F0F0; }" -                                       "QToolButton:hover{ border: none; padding: 0px; color: " -                                       "#EEEEEE; font-weight: bold; background: #E81123}"); +    button_filter_close->setStyleSheet( +        QStringLiteral("QToolButton{ border: none; padding: 0px; color: " +                       "#000000; font-weight: bold; background: #F0F0F0; }" +                       "QToolButton:hover{ border: none; padding: 0px; color: " +                       "#EEEEEE; font-weight: bold; background: #E81123}"));      connect(button_filter_close, &QToolButton::clicked, parent, &GameList::onFilterCloseClicked);      layout_filter->setSpacing(10);      layout_filter->addWidget(label_filter); @@ -140,36 +141,34 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {   */  static bool ContainsAllWords(const QString& haystack, const QString& userinput) {      const QStringList userinput_split = -        userinput.split(' ', QString::SplitBehavior::SkipEmptyParts); +        userinput.split(QLatin1Char{' '}, QString::SplitBehavior::SkipEmptyParts);      return std::all_of(userinput_split.begin(), userinput_split.end(),                         [&haystack](const QString& s) { return haystack.contains(s); });  }  // Event in order to filter the gamelist after editing the searchfield -void GameList::onTextChanged(const QString& newText) { -    int rowCount = tree_view->model()->rowCount(); -    QString edit_filter_text = newText.toLower(); - -    QModelIndex root_index = item_model->invisibleRootItem()->index(); +void GameList::onTextChanged(const QString& new_text) { +    const int row_count = tree_view->model()->rowCount(); +    const QString edit_filter_text = new_text.toLower(); +    const QModelIndex root_index = item_model->invisibleRootItem()->index();      // If the searchfield is empty every item is visible      // Otherwise the filter gets applied      if (edit_filter_text.isEmpty()) { -        for (int i = 0; i < rowCount; ++i) { +        for (int i = 0; i < row_count; ++i) {              tree_view->setRowHidden(i, root_index, false);          } -        search_field->setFilterResult(rowCount, rowCount); +        search_field->setFilterResult(row_count, row_count);      } else {          int result_count = 0; -        for (int i = 0; i < rowCount; ++i) { +        for (int i = 0; i < row_count; ++i) {              const QStandardItem* child_file = item_model->item(i, 0);              const QString file_path =                  child_file->data(GameListItemPath::FullPathRole).toString().toLower(); -            QString file_name = file_path.mid(file_path.lastIndexOf('/') + 1);              const QString file_title =                  child_file->data(GameListItemPath::TitleRole).toString().toLower(); -            const QString file_programmid = +            const QString file_program_id =                  child_file->data(GameListItemPath::ProgramIdRole).toString().toLower();              // Only items which filename in combination with its title contains all words @@ -177,14 +176,16 @@ void GameList::onTextChanged(const QString& newText) {              // The search is case insensitive because of toLower()              // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent              // multiple conversions of edit_filter_text for each game in the gamelist -            if (ContainsAllWords(file_name.append(' ').append(file_title), edit_filter_text) || -                (file_programmid.count() == 16 && edit_filter_text.contains(file_programmid))) { +            const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + +                                      QLatin1Char{' '} + file_title; +            if (ContainsAllWords(file_name, edit_filter_text) || +                (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) {                  tree_view->setRowHidden(i, root_index, false);                  ++result_count;              } else {                  tree_view->setRowHidden(i, root_index, true);              } -            search_field->setFilterResult(result_count, rowCount); +            search_field->setFilterResult(result_count, row_count);          }      }  } @@ -193,8 +194,9 @@ void GameList::onFilterCloseClicked() {      main_window->filterBarSetChecked(false);  } -GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent) -    : QWidget{parent}, vfs(std::move(vfs)) { +GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvider* provider, +                   GMainWindow* parent) +    : QWidget{parent}, vfs(std::move(vfs)), provider(provider) {      watcher = new QFileSystemWatcher(this);      connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); @@ -214,7 +216,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)      tree_view->setEditTriggers(QHeaderView::NoEditTriggers);      tree_view->setUniformRowHeights(true);      tree_view->setContextMenuPolicy(Qt::CustomContextMenu); -    tree_view->setStyleSheet("QTreeView{ border: none; }"); +    tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));      item_model->insertColumns(0, UISettings::values.show_add_ons ? COLUMN_COUNT : COLUMN_COUNT - 1);      item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); @@ -280,9 +282,9 @@ void GameList::ValidateEntry(const QModelIndex& item) {      const QFileInfo file_info{file_path};      if (file_info.isDir()) {          const QDir dir{file_path}; -        const QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); +        const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files);          if (matching_main.size() == 1) { -            emit GameChosen(dir.path() + DIR_SEP + matching_main[0]); +            emit GameChosen(dir.path() + QDir::separator() + matching_main[0]);          }          return;      } @@ -329,6 +331,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {      QMenu context_menu;      QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));      QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); +    QAction* open_transferable_shader_cache = +        context_menu.addAction(tr("Open Transferable Shader Cache"));      context_menu.addSeparator();      QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));      QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); @@ -344,6 +348,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {              [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });      connect(open_lfs_location, &QAction::triggered,              [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); }); +    connect(open_transferable_shader_cache, &QAction::triggered, +            [&]() { emit OpenTransferableShaderCacheRequested(program_id); });      connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); });      connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });      connect(navigate_to_gamedb_entry, &QAction::triggered, @@ -354,7 +360,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {  }  void GameList::LoadCompatibilityList() { -    QFile compat_list{":compatibility_list/compatibility_list.json"}; +    QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")};      if (!compat_list.open(QFile::ReadOnly | QFile::Text)) {          LOG_ERROR(Frontend, "Unable to open game compatibility list"); @@ -372,25 +378,27 @@ void GameList::LoadCompatibilityList() {          return;      } -    const QString string_content = content; -    QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8()); -    QJsonArray arr = json.array(); +    const QJsonDocument json = QJsonDocument::fromJson(content); +    const QJsonArray arr = json.array(); -    for (const QJsonValueRef value : arr) { -        QJsonObject game = value.toObject(); +    for (const QJsonValue value : arr) { +        const QJsonObject game = value.toObject(); +        const QString compatibility_key = QStringLiteral("compatibility"); -        if (game.contains("compatibility") && game["compatibility"].isDouble()) { -            int compatibility = game["compatibility"].toInt(); -            QString directory = game["directory"].toString(); -            QJsonArray ids = game["releases"].toArray(); +        if (!game.contains(compatibility_key) || !game[compatibility_key].isDouble()) { +            continue; +        } -            for (const QJsonValueRef id_ref : ids) { -                QJsonObject id_object = id_ref.toObject(); -                QString id = id_object["id"].toString(); -                compatibility_list.emplace( -                    id.toUpper().toStdString(), -                    std::make_pair(QString::number(compatibility), directory)); -            } +        const int compatibility = game[compatibility_key].toInt(); +        const QString directory = game[QStringLiteral("directory")].toString(); +        const QJsonArray ids = game[QStringLiteral("releases")].toArray(); + +        for (const QJsonValue id_ref : ids) { +            const QJsonObject id_object = id_ref.toObject(); +            const QString id = id_object[QStringLiteral("id")].toString(); + +            compatibility_list.emplace(id.toUpper().toStdString(), +                                       std::make_pair(QString::number(compatibility), directory));          }      }  } @@ -428,7 +436,8 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {      emit ShouldCancelWorker(); -    GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan, compatibility_list); +    GameListWorker* worker = +        new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list);      connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);      connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, @@ -457,12 +466,16 @@ void GameList::LoadInterfaceLayout() {      item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());  } -const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"}; +const QStringList GameList::supported_file_extensions = { +    QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), +    QStringLiteral("xci"), QStringLiteral("nsp"), +};  void GameList::RefreshGameDirectory() { -    if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { +    if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) {          LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");          search_field->clear(); -        PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +        PopulateAsync(UISettings::values.game_directory_path, +                      UISettings::values.game_directory_deepscan);      }  } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index b317eb2fc..f8f8bd6c5 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -26,8 +26,9 @@ class GameListSearchField;  class GMainWindow;  namespace FileSys { +class ManualContentProvider;  class VfsFilesystem; -} +} // namespace FileSys  enum class GameListOpenTarget {      SaveData, @@ -47,7 +48,8 @@ public:          COLUMN_COUNT, // Number of columns      }; -    explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, GMainWindow* parent = nullptr); +    explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, +                      FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);      ~GameList() override;      void clearFilter(); @@ -66,6 +68,7 @@ signals:      void GameChosen(QString game_path);      void ShouldCancelWorker();      void OpenFolderRequested(u64 program_id, GameListOpenTarget target); +    void OpenTransferableShaderCacheRequested(u64 program_id);      void DumpRomFSRequested(u64 program_id, const std::string& game_path);      void CopyTIDRequested(u64 program_id);      void NavigateToGamedbEntryRequested(u64 program_id, @@ -73,7 +76,7 @@ signals:      void OpenPerGameGeneralRequested(const std::string& file);  private slots: -    void onTextChanged(const QString& newText); +    void onTextChanged(const QString& new_text);      void onFilterCloseClicked();  private: @@ -85,6 +88,7 @@ private:      void RefreshGameDirectory();      std::shared_ptr<FileSys::VfsFilesystem> vfs; +    FileSys::ManualContentProvider* provider;      GameListSearchField* search_field;      GMainWindow* main_window = nullptr;      QVBoxLayout* layout = nullptr; diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 3db0e90da..0b458ef48 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -4,11 +4,9 @@  #pragma once -#include <algorithm>  #include <array>  #include <map>  #include <string> -#include <unordered_map>  #include <utility>  #include <QCoreApplication> @@ -25,8 +23,8 @@  #include "yuzu/util/util.h"  /** - * Gets the default icon (for games without valid SMDH) - * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) + * Gets the default icon (for games without valid title metadata) + * @param size The desired width and height of the default icon.   * @return QPixmap default icon   */  static QPixmap GetDefaultIcon(u32 size) { @@ -46,7 +44,7 @@ public:   * A specialization of GameListItem for path values.   * This class ensures that for every full path value it holds, a correct string representation   * of just the filename (with no extension) will be displayed to the user. - * If this class receives valid SMDH data, it will also display game icons and titles. + * If this class receives valid title metadata, it will also display game icons and titles.   */  class GameListItemPath : public GameListItem {  public: @@ -95,7 +93,7 @@ public:              if (row2.isEmpty())                  return row1; -            return row1 + "\n    " + row2; +            return QString(row1 + QStringLiteral("\n    ") + row2);          }          return GameListItem::data(role); @@ -115,13 +113,14 @@ public:          };          // clang-format off          static const std::map<QString, CompatStatus> status_data = { -        {"0",  {"#5c93ed", QT_TR_NOOP("Perfect"),    QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}}, -        {"1",  {"#47d35c", QT_TR_NOOP("Great"),      QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}}, -        {"2",  {"#94b242", QT_TR_NOOP("Okay"),       QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}}, -        {"3",  {"#f2d624", QT_TR_NOOP("Bad"),        QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}}, -        {"4",  {"#FF0000", QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}}, -        {"5",  {"#828282", QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}}, -        {"99", {"#000000", QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}}; +            {QStringLiteral("0"),  {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"),    QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}}, +            {QStringLiteral("1"),  {QStringLiteral("#47d35c"), QT_TR_NOOP("Great"),      QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}}, +            {QStringLiteral("2"),  {QStringLiteral("#94b242"), QT_TR_NOOP("Okay"),       QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}}, +            {QStringLiteral("3"),  {QStringLiteral("#f2d624"), QT_TR_NOOP("Bad"),        QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}}, +            {QStringLiteral("4"),  {QStringLiteral("#FF0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}}, +            {QStringLiteral("5"),  {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}}, +            {QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}, +        };          // clang-format on          auto iterator = status_data.find(compatibility); diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index b37710f59..4f30e9147 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -8,16 +8,21 @@  #include <vector>  #include <QDir> +#include <QFile>  #include <QFileInfo> +#include <QSettings>  #include "common/common_paths.h"  #include "common/file_util.h" +#include "core/core.h" +#include "core/file_sys/card_image.h"  #include "core/file_sys/content_archive.h"  #include "core/file_sys/control_metadata.h"  #include "core/file_sys/mode.h"  #include "core/file_sys/nca_metadata.h"  #include "core/file_sys/patch_manager.h"  #include "core/file_sys/registered_cache.h" +#include "core/file_sys/submission_package.h"  #include "core/hle/service/filesystem/filesystem.h"  #include "core/loader/loader.h"  #include "yuzu/compatibility_list.h" @@ -27,13 +32,108 @@  #include "yuzu/ui_settings.h"  namespace { + +QString GetGameListCachedObject(const std::string& filename, const std::string& ext, +                                const std::function<QString()>& generator) { +    if (!UISettings::values.cache_game_list || filename == "0000000000000000") { +        return generator(); +    } + +    const auto path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + +                      DIR_SEP + filename + '.' + ext; + +    FileUtil::CreateFullPath(path); + +    if (!FileUtil::Exists(path)) { +        const auto str = generator(); + +        QFile file{QString::fromStdString(path)}; +        if (file.open(QFile::WriteOnly)) { +            file.write(str.toUtf8()); +        } + +        return str; +    } + +    QFile file{QString::fromStdString(path)}; +    if (file.open(QFile::ReadOnly)) { +        return QString::fromUtf8(file.readAll()); +    } + +    return generator(); +} + +std::pair<std::vector<u8>, std::string> GetGameListCachedObject( +    const std::string& filename, const std::string& ext, +    const std::function<std::pair<std::vector<u8>, std::string>()>& generator) { +    if (!UISettings::values.cache_game_list || filename == "0000000000000000") { +        return generator(); +    } + +    const auto path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + +                       DIR_SEP + filename + ".jpeg"; +    const auto path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" + +                       DIR_SEP + filename + ".appname.txt"; + +    FileUtil::CreateFullPath(path1); + +    if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) { +        const auto [icon, nacp] = generator(); + +        QFile file1{QString::fromStdString(path1)}; +        if (!file1.open(QFile::WriteOnly)) { +            LOG_ERROR(Frontend, "Failed to open cache file."); +            return generator(); +        } + +        if (!file1.resize(icon.size())) { +            LOG_ERROR(Frontend, "Failed to resize cache file to necessary size."); +            return generator(); +        } + +        if (file1.write(reinterpret_cast<const char*>(icon.data()), icon.size()) != icon.size()) { +            LOG_ERROR(Frontend, "Failed to write data to cache file."); +            return generator(); +        } + +        QFile file2{QString::fromStdString(path2)}; +        if (file2.open(QFile::WriteOnly)) { +            file2.write(nacp.data(), nacp.size()); +        } + +        return std::make_pair(icon, nacp); +    } + +    QFile file1(QString::fromStdString(path1)); +    QFile file2(QString::fromStdString(path2)); + +    if (!file1.open(QFile::ReadOnly)) { +        LOG_ERROR(Frontend, "Failed to open cache file for reading."); +        return generator(); +    } + +    if (!file2.open(QFile::ReadOnly)) { +        LOG_ERROR(Frontend, "Failed to open cache file for reading."); +        return generator(); +    } + +    std::vector<u8> vec(file1.size()); +    if (file1.read(reinterpret_cast<char*>(vec.data()), vec.size()) != +        static_cast<s64>(vec.size())) { +        return generator(); +    } + +    const auto data = file2.readAll(); +    return std::make_pair(vec, data.toStdString()); +} +  void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca,                                 std::vector<u8>& icon, std::string& name) { -    auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca); -    if (icon_file != nullptr) -        icon = icon_file->ReadAllBytes(); -    if (nacp != nullptr) -        name = nacp->GetApplicationName(); +    std::tie(icon, name) = GetGameListCachedObject( +        fmt::format("{:016X}", patch_manager.GetTitleID()), {}, [&patch_manager, &nca] { +            const auto [nacp, icon_f] = patch_manager.ParseControlNCA(nca); +            return std::make_pair(icon_f->ReadAllBytes(), nacp->GetApplicationName()); +        });  }  bool HasSupportedFileExtension(const std::string& file_name) { @@ -42,7 +142,7 @@ bool HasSupportedFileExtension(const std::string& file_name) {  }  bool IsExtractedNCAMain(const std::string& file_name) { -    return QFileInfo(QString::fromStdString(file_name)).fileName() == "main"; +    return QFileInfo(QString::fromStdString(file_name)).fileName() == QStringLiteral("main");  }  QString FormatGameName(const std::string& physical_name) { @@ -94,7 +194,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri      const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);      // The game list uses this as compatibility number for untested games -    QString compatibility{"99"}; +    QString compatibility{QStringLiteral("99")};      if (it != compatibility_list.end()) {          compatibility = it->second.first;      } @@ -111,28 +211,36 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri      };      if (UISettings::values.show_add_ons) { -        list.insert( -            2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()))); +        const auto patch_versions = GetGameListCachedObject( +            fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { +                return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); +            }); +        list.insert(2, new GameListItem(patch_versions));      }      return list;  }  } // Anonymous namespace -GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan, -                               const CompatibilityList& compatibility_list) -    : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan), +GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, +                               FileSys::ManualContentProvider* provider, QString dir_path, +                               bool deep_scan, const CompatibilityList& compatibility_list) +    : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan),        compatibility_list(compatibility_list) {}  GameListWorker::~GameListWorker() = default; -void GameListWorker::AddInstalledTitlesToGameList() { -    const auto cache = Service::FileSystem::GetUnionContents(); -    const auto installed_games = cache.ListEntriesFilter(FileSys::TitleType::Application, -                                                         FileSys::ContentRecordType::Program); +void GameListWorker::AddTitlesToGameList() { +    const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>( +        Core::System::GetInstance().GetContentProvider()); +    const auto installed_games = cache.ListEntriesFilterOrigin( +        std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program); -    for (const auto& game : installed_games) { -        const auto file = cache.GetEntryUnparsed(game); +    for (const auto& [slot, game] : installed_games) { +        if (slot == FileSys::ContentProviderUnionSlot::FrontendManual) +            continue; + +        const auto file = cache.GetEntryUnparsed(game.title_id, game.type);          std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);          if (!loader)              continue; @@ -150,45 +258,13 @@ void GameListWorker::AddInstalledTitlesToGameList() {          emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,                                            compatibility_list, patch));      } - -    const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application, -                                                      FileSys::ContentRecordType::Control); - -    for (const auto& entry : control_data) { -        auto nca = cache.GetEntry(entry); -        if (nca != nullptr) { -            nca_control_map.insert_or_assign(entry.title_id, std::move(nca)); -        } -    } -} - -void GameListWorker::FillControlMap(const std::string& dir_path) { -    const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory, -                                             const std::string& virtual_name) -> bool { -        if (stop_processing) { -            // Breaks the callback loop -            return false; -        } - -        const std::string physical_name = directory + DIR_SEP + virtual_name; -        const QFileInfo file_info(QString::fromStdString(physical_name)); -        if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) { -            auto nca = -                std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read)); -            if (nca->GetType() == FileSys::NCAContentType::Control) { -                const u64 title_id = nca->GetTitleId(); -                nca_control_map.insert_or_assign(title_id, std::move(nca)); -            } -        } -        return true; -    }; - -    FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);  } -void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { -    const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory, -                                            const std::string& virtual_name) -> bool { +void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, +                                    unsigned int recursion) { +    const auto callback = [this, target, recursion](u64* num_entries_out, +                                                    const std::string& directory, +                                                    const std::string& virtual_name) -> bool {          if (stop_processing) {              // Breaks the callback loop.              return false; @@ -198,7 +274,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign          const bool is_dir = FileUtil::IsDirectory(physical_name);          if (!is_dir &&              (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { -            auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); +            const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read); +            auto loader = Loader::GetLoader(file);              if (!loader) {                  return true;              } @@ -209,31 +286,42 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign                  return true;              } -            std::vector<u8> icon; -            const auto res1 = loader->ReadIcon(icon); -              u64 program_id = 0;              const auto res2 = loader->ReadProgramId(program_id); -            std::string name = " "; -            const auto res3 = loader->ReadTitle(name); +            if (target == ScanTarget::FillManualContentProvider) { +                if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { +                    provider->AddEntry(FileSys::TitleType::Application, +                                       FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), +                                       program_id, file); +                } else if (res2 == Loader::ResultStatus::Success && +                           (file_type == Loader::FileType::XCI || +                            file_type == Loader::FileType::NSP)) { +                    const auto nsp = file_type == Loader::FileType::NSP +                                         ? std::make_shared<FileSys::NSP>(file) +                                         : FileSys::XCI{file}.GetSecurePartitionNSP(); +                    for (const auto& title : nsp->GetNCAs()) { +                        for (const auto& entry : title.second) { +                            provider->AddEntry(entry.first.first, entry.first.second, title.first, +                                               entry.second->GetBaseFile()); +                        } +                    } +                } +            } else { +                std::vector<u8> icon; +                const auto res1 = loader->ReadIcon(icon); -            const FileSys::PatchManager patch{program_id}; +                std::string name = " "; +                const auto res3 = loader->ReadTitle(name); -            if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success && -                res2 == Loader::ResultStatus::Success) { -                // Use from metadata pool. -                if (nca_control_map.find(program_id) != nca_control_map.end()) { -                    const auto& nca = nca_control_map[program_id]; -                    GetMetadataFromControlNCA(patch, *nca, icon, name); -                } -            } +                const FileSys::PatchManager patch{program_id}; -            emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, -                                              compatibility_list, patch)); +                emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, +                                                  compatibility_list, patch)); +            }          } else if (is_dir && recursion > 0) {              watch_list.append(QString::fromStdString(physical_name)); -            AddFstEntriesToGameList(physical_name, recursion - 1); +            ScanFileSystem(target, physical_name, recursion - 1);          }          return true; @@ -245,10 +333,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign  void GameListWorker::run() {      stop_processing = false;      watch_list.append(dir_path); -    FillControlMap(dir_path.toStdString()); -    AddInstalledTitlesToGameList(); -    AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); -    nca_control_map.clear(); +    provider->ClearAllEntries(); +    ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(), +                   deep_scan ? 256 : 0); +    AddTitlesToGameList(); +    ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0);      emit Finished(watch_list);  } diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 0e42d0bde..7c3074af9 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -33,7 +33,8 @@ class GameListWorker : public QObject, public QRunnable {      Q_OBJECT  public: -    GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan, +    GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, +                   FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan,                     const CompatibilityList& compatibility_list);      ~GameListWorker() override; @@ -58,12 +59,17 @@ signals:      void Finished(QStringList watch_list);  private: -    void AddInstalledTitlesToGameList(); -    void FillControlMap(const std::string& dir_path); -    void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); +    void AddTitlesToGameList(); + +    enum class ScanTarget { +        FillManualContentProvider, +        PopulateGameList, +    }; + +    void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0);      std::shared_ptr<FileSys::VfsFilesystem> vfs; -    std::map<u64, std::unique_ptr<FileSys::NCA>> nca_control_map; +    FileSys::ManualContentProvider* provider;      QStringList watch_list;      QString dir_path;      bool deep_scan; diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp index dce399774..4582e7f21 100644 --- a/src/yuzu/hotkeys.cpp +++ b/src/yuzu/hotkeys.cpp @@ -2,7 +2,6 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. -#include <map>  #include <QKeySequence>  #include <QShortcut>  #include <QTreeWidgetItem> @@ -13,47 +12,32 @@  HotkeyRegistry::HotkeyRegistry() = default;  HotkeyRegistry::~HotkeyRegistry() = default; -void HotkeyRegistry::LoadHotkeys() { -    // Make sure NOT to use a reference here because it would become invalid once we call -    // beginGroup() -    for (auto shortcut : UISettings::values.shortcuts) { -        const QStringList cat = shortcut.first.split('/'); -        Q_ASSERT(cat.size() >= 2); - -        // RegisterHotkey assigns default keybindings, so use old values as default parameters -        Hotkey& hk = hotkey_groups[cat[0]][cat[1]]; -        if (!shortcut.second.first.isEmpty()) { -            hk.keyseq = QKeySequence::fromString(shortcut.second.first); -            hk.context = static_cast<Qt::ShortcutContext>(shortcut.second.second); -        } -        if (hk.shortcut) -            hk.shortcut->setKey(hk.keyseq); -    } -} -  void HotkeyRegistry::SaveHotkeys() {      UISettings::values.shortcuts.clear();      for (const auto& group : hotkey_groups) {          for (const auto& hotkey : group.second) { -            UISettings::values.shortcuts.emplace_back( -                UISettings::Shortcut(group.first + '/' + hotkey.first, -                                     UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), -                                                                    hotkey.second.context))); +            UISettings::values.shortcuts.push_back( +                {hotkey.first, group.first, +                 UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), +                                                hotkey.second.context)});          }      }  } -void HotkeyRegistry::RegisterHotkey(const QString& group, const QString& action, -                                    const QKeySequence& default_keyseq, -                                    Qt::ShortcutContext default_context) { -    auto& hotkey_group = hotkey_groups[group]; -    if (hotkey_group.find(action) != hotkey_group.end()) { -        return; +void HotkeyRegistry::LoadHotkeys() { +    // Make sure NOT to use a reference here because it would become invalid once we call +    // beginGroup() +    for (auto shortcut : UISettings::values.shortcuts) { +        Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name]; +        if (!shortcut.shortcut.first.isEmpty()) { +            hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText); +            hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second); +        } +        if (hk.shortcut) { +            hk.shortcut->disconnect(); +            hk.shortcut->setKey(hk.keyseq); +        }      } - -    auto& hotkey_action = hotkey_groups[group][action]; -    hotkey_action.keyseq = default_keyseq; -    hotkey_action.context = default_context;  }  QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) { @@ -65,24 +49,11 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action      return hk.shortcut;  } -GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) { -    ui.setupUi(this); +QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) { +    return hotkey_groups[group][action].keyseq;  } -void GHotkeysDialog::Populate(const HotkeyRegistry& registry) { -    for (const auto& group : registry.hotkey_groups) { -        QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first)); -        for (const auto& hotkey : group.second) { -            QStringList columns; -            columns << hotkey.first << hotkey.second.keyseq.toString(); -            QTreeWidgetItem* item = new QTreeWidgetItem(columns); -            toplevel_item->addChild(item); -        } -        ui.treeWidget->addTopLevelItem(toplevel_item); -    } -    // TODO: Make context configurable as well (hiding the column for now) -    ui.treeWidget->setColumnCount(2); - -    ui.treeWidget->resizeColumnToContents(0); -    ui.treeWidget->resizeColumnToContents(1); +Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group, +                                                       const QString& action) { +    return hotkey_groups[group][action].context;  } diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h index f38e6c002..248fadaf3 100644 --- a/src/yuzu/hotkeys.h +++ b/src/yuzu/hotkeys.h @@ -5,7 +5,6 @@  #pragma once  #include <map> -#include "ui_hotkeys.h"  class QDialog;  class QKeySequence; @@ -14,7 +13,7 @@ class QShortcut;  class HotkeyRegistry final {  public: -    friend class GHotkeysDialog; +    friend class ConfigureHotkeys;      explicit HotkeyRegistry();      ~HotkeyRegistry(); @@ -49,19 +48,22 @@ public:      QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);      /** -     * Register a hotkey. +     * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.       * -     * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger") -     * @param action Name of the action (e.g. "Start Emulation", "Load Image") -     * @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the -     *                       settings file before -     * @param default_context Default context to assign if the hotkey wasn't present in the settings -     *                        file before -     * @warning Both the group and action strings will be displayed in the hotkey settings dialog +     * @param group  General group this hotkey belongs to (e.g. "Main Window", "Debugger"). +     * @param action Name of the action (e.g. "Start Emulation", "Load Image"). +     */ +    QKeySequence GetKeySequence(const QString& group, const QString& action); + +    /** +     * Returns a Qt::ShortcutContext object who can be connected to other +     * QAction::setShortcutContext. +     * +     * @param group  General group this shortcut context belongs to (e.g. "Main Window", +     * "Debugger"). +     * @param action Name of the action (e.g. "Start Emulation", "Load Image").       */ -    void RegisterHotkey(const QString& group, const QString& action, -                        const QKeySequence& default_keyseq = {}, -                        Qt::ShortcutContext default_context = Qt::WindowShortcut); +    Qt::ShortcutContext GetShortcutContext(const QString& group, const QString& action);  private:      struct Hotkey { @@ -75,15 +77,3 @@ private:      HotkeyGroupMap hotkey_groups;  }; - -class GHotkeysDialog : public QWidget { -    Q_OBJECT - -public: -    explicit GHotkeysDialog(QWidget* parent = nullptr); - -    void Populate(const HotkeyRegistry& registry); - -private: -    Ui::hotkeys ui; -}; diff --git a/src/yuzu/hotkeys.ui b/src/yuzu/hotkeys.ui deleted file mode 100644 index 050fe064e..000000000 --- a/src/yuzu/hotkeys.ui +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>hotkeys</class> - <widget class="QWidget" name="hotkeys"> -  <property name="geometry"> -   <rect> -    <x>0</x> -    <y>0</y> -    <width>363</width> -    <height>388</height> -   </rect> -  </property> -  <property name="windowTitle"> -   <string>Hotkey Settings</string> -  </property> -  <layout class="QVBoxLayout" name="verticalLayout"> -   <item> -    <widget class="QTreeWidget" name="treeWidget"> -     <property name="selectionBehavior"> -      <enum>QAbstractItemView::SelectItems</enum> -     </property> -     <property name="headerHidden"> -      <bool>false</bool> -     </property> -     <column> -      <property name="text"> -       <string>Action</string> -      </property> -     </column> -     <column> -      <property name="text"> -       <string>Hotkey</string> -      </property> -     </column> -     <column> -      <property name="text"> -       <string>Context</string> -      </property> -     </column> -    </widget> -   </item> -  </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp index 86f6d0165..4f2bfab48 100644 --- a/src/yuzu/loading_screen.cpp +++ b/src/yuzu/loading_screen.cpp @@ -30,11 +30,11 @@  #include <QMovie>  #endif -constexpr const char PROGRESSBAR_STYLE_PREPARE[] = R"( +constexpr char PROGRESSBAR_STYLE_PREPARE[] = R"(  QProgressBar {}  QProgressBar::chunk {})"; -constexpr const char PROGRESSBAR_STYLE_DECOMPILE[] = R"( +constexpr char PROGRESSBAR_STYLE_DECOMPILE[] = R"(  QProgressBar {    background-color: black;    border: 2px solid white; @@ -46,7 +46,7 @@ QProgressBar::chunk {    width: 1px;  })"; -constexpr const char PROGRESSBAR_STYLE_BUILD[] = R"( +constexpr char PROGRESSBAR_STYLE_BUILD[] = R"(  QProgressBar {    background-color: black;    border: 2px solid white; @@ -58,7 +58,7 @@ QProgressBar::chunk {    width: 1px;  })"; -constexpr const char PROGRESSBAR_STYLE_COMPLETE[] = R"( +constexpr char PROGRESSBAR_STYLE_COMPLETE[] = R"(  QProgressBar {    background-color: #0ab9e6;    border: 2px solid white; @@ -149,10 +149,10 @@ void LoadingScreen::OnLoadComplete() {  void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value,                                     std::size_t total) {      using namespace std::chrono; -    auto now = high_resolution_clock::now(); +    const auto now = high_resolution_clock::now();      // reset the timer if the stage changes      if (stage != previous_stage) { -        ui->progress_bar->setStyleSheet(progressbar_style[stage]); +        ui->progress_bar->setStyleSheet(QString::fromUtf8(progressbar_style[stage]));          // Hide the progress bar during the prepare stage          if (stage == VideoCore::LoadCallbackStage::Prepare) {              ui->progress_bar->hide(); @@ -178,21 +178,26 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size              slow_shader_first_value = value;          }          // only calculate an estimate time after a second has passed since stage change -        auto diff = duration_cast<milliseconds>(now - slow_shader_start); +        const auto diff = duration_cast<milliseconds>(now - slow_shader_start);          if (diff > seconds{1}) { -            auto eta_mseconds = +            const auto eta_mseconds =                  static_cast<long>(static_cast<double>(total - slow_shader_first_value) /                                    (value - slow_shader_first_value) * diff.count());              estimate =                  tr("Estimated Time %1")                      .arg(QTime(0, 0, 0, 0)                               .addMSecs(std::max<long>(eta_mseconds - diff.count() + 1000, 1000)) -                             .toString("mm:ss")); +                             .toString(QStringLiteral("mm:ss")));          }      }      // update labels and progress bar -    ui->stage->setText(stage_translations[stage].arg(value).arg(total)); +    if (stage == VideoCore::LoadCallbackStage::Decompile || +        stage == VideoCore::LoadCallbackStage::Build) { +        ui->stage->setText(stage_translations[stage].arg(value).arg(total)); +    } else { +        ui->stage->setText(stage_translations[stage]); +    }      ui->value->setText(estimate);      ui->progress_bar->setValue(static_cast<int>(value));      previous_time = now; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 0f5a14841..66a7080c9 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -8,12 +8,15 @@  #include <thread>  // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "applets/error.h"  #include "applets/profile_select.h"  #include "applets/software_keyboard.h"  #include "applets/web_browser.h" +#include "configuration/configure_input.h"  #include "configuration/configure_per_general.h"  #include "core/file_sys/vfs.h"  #include "core/file_sys/vfs_real.h" +#include "core/frontend/applets/general_frontend.h"  #include "core/frontend/scope_acquire_window_context.h"  #include "core/hle/service/acc/profile_manager.h"  #include "core/hle/service/am/applets/applets.h" @@ -36,14 +39,20 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual  #include <glad/glad.h>  #define QT_NO_OPENGL +#include <QClipboard> +#include <QDesktopServices>  #include <QDesktopWidget>  #include <QDialogButtonBox>  #include <QFile>  #include <QFileDialog> +#include <QInputDialog>  #include <QMessageBox> +#include <QProgressBar> +#include <QProgressDialog> +#include <QShortcut> +#include <QStatusBar>  #include <QtConcurrent/QtConcurrent> -#include <QtGui> -#include <QtWidgets> +  #include <fmt/format.h>  #include "common/common_paths.h"  #include "common/detached_tasks.h" @@ -54,11 +63,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual  #include "common/microprofile.h"  #include "common/scm_rev.h"  #include "common/scope_exit.h" -#include "common/string_util.h"  #include "common/telemetry.h"  #include "core/core.h"  #include "core/crypto/key_manager.h" -#include "core/file_sys/bis_factory.h"  #include "core/file_sys/card_image.h"  #include "core/file_sys/content_archive.h"  #include "core/file_sys/control_metadata.h" @@ -70,7 +77,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual  #include "core/frontend/applets/software_keyboard.h"  #include "core/hle/kernel/process.h"  #include "core/hle/service/filesystem/filesystem.h" -#include "core/hle/service/filesystem/fsp_ldr.h"  #include "core/hle/service/nfp/nfp.h"  #include "core/hle/service/sm/sm.h"  #include "core/loader/loader.h" @@ -86,7 +92,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual  #include "yuzu/configuration/configure_dialog.h"  #include "yuzu/debugger/console.h"  #include "yuzu/debugger/graphics/graphics_breakpoints.h" -#include "yuzu/debugger/graphics/graphics_surface.h"  #include "yuzu/debugger/profiler.h"  #include "yuzu/debugger/wait_tree.h"  #include "yuzu/discord.h" @@ -167,7 +172,8 @@ static void InitializeLogging() {  GMainWindow::GMainWindow()      : config(new Config()), emu_thread(nullptr), -      vfs(std::make_shared<FileSys::RealVfsFilesystem>()) { +      vfs(std::make_shared<FileSys::RealVfsFilesystem>()), +      provider(std::make_unique<FileSys::ManualContentProvider>()) {      InitializeLogging();      debug_context = Tegra::DebugContext::Construct(); @@ -192,20 +198,25 @@ GMainWindow::GMainWindow()      ConnectMenuEvents();      ConnectWidgetEvents(); +      LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,               Common::g_scm_desc); +    UpdateWindowTitle(); -    setWindowTitle(QString("yuzu %1| %2-%3") -                       .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));      show(); +    Core::System::GetInstance().SetContentProvider( +        std::make_unique<FileSys::ContentProviderUnion>()); +    Core::System::GetInstance().RegisterContentProvider( +        FileSys::ContentProviderUnionSlot::FrontendManual, provider.get()); +    Service::FileSystem::CreateFactories(*vfs); +      // Gen keys if necessary      OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); -    // Necessary to load titles from nand in gamelist. -    Service::FileSystem::CreateFactories(*vfs);      game_list->LoadCompatibilityList(); -    game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +    game_list->PopulateAsync(UISettings::values.game_directory_path, +                             UISettings::values.game_directory_deepscan);      // Show one-time "callout" messages to the user      ShowTelemetryCallout(); @@ -227,15 +238,13 @@ void GMainWindow::ProfileSelectorSelectProfile() {      dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |                            Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);      dialog.setWindowModality(Qt::WindowModal); -    dialog.exec(); - -    if (!dialog.GetStatus()) { +    if (dialog.exec() == QDialog::Rejected) {          emit ProfileSelectorFinishedSelection(std::nullopt);          return;      }      Service::Account::ProfileManager manager; -    const auto uuid = manager.GetUser(dialog.GetIndex()); +    const auto uuid = manager.GetUser(static_cast<std::size_t>(dialog.GetIndex()));      if (!uuid.has_value()) {          emit ProfileSelectorFinishedSelection(std::nullopt);          return; @@ -250,9 +259,8 @@ void GMainWindow::SoftwareKeyboardGetText(      dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |                            Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);      dialog.setWindowModality(Qt::WindowModal); -    dialog.exec(); -    if (!dialog.GetStatus()) { +    if (dialog.exec() == QDialog::Rejected) {          emit SoftwareKeyboardFinishedText(std::nullopt);          return;      } @@ -270,7 +278,7 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message  void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {      NXInputWebEngineView web_browser_view(this); -    // Scope to contain the QProgressDialog for initalization +    // Scope to contain the QProgressDialog for initialization      {          QProgressDialog progress(this);          progress.setMinimumDuration(200); @@ -290,7 +298,7 @@ void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view          QWebEngineScript nx_shim;          nx_shim.setSourceCode(GetNXShimInjectionScript());          nx_shim.setWorldId(QWebEngineScript::MainWorld); -        nx_shim.setName("nx_inject.js"); +        nx_shim.setName(QStringLiteral("nx_inject.js"));          nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation);          nx_shim.setRunsOnSubFrames(true);          web_browser_view.page()->profile()->scripts()->insert(nx_shim); @@ -336,9 +344,14 @@ void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view      const auto fire_js_keypress = [&web_browser_view](u32 key_code) {          web_browser_view.page()->runJavaScript(              QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));") -                .arg(QString::fromStdString(std::to_string(key_code)))); +                .arg(key_code));      }; +    QMessageBox::information( +        this, tr("Exit"), +        tr("To exit the web application, use the game provided controls to select exit, select the " +           "'Exit Web Applet' option in the menu bar, or press the 'Enter' key.")); +      bool running_exit_check = false;      while (!finished) {          QApplication::processEvents(); @@ -410,7 +423,7 @@ void GMainWindow::InitializeWidgets() {      render_window = new GRenderWindow(this, emu_thread.get());      render_window->hide(); -    game_list = new GameList(vfs, this); +    game_list = new GameList(vfs, provider.get(), this);      ui.horizontalLayout->addWidget(game_list);      loading_screen = new LoadingScreen(this); @@ -452,7 +465,7 @@ void GMainWindow::InitializeWidgets() {          statusBar()->addPermanentWidget(label, 0);      }      statusBar()->setVisible(true); -    setStyleSheet("QStatusBar::item{border: none;}"); +    setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}"));  }  void GMainWindow::InitializeDebugWidgets() { @@ -469,11 +482,6 @@ void GMainWindow::InitializeDebugWidgets() {      graphicsBreakpointsWidget->hide();      debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); -    graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this); -    addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget); -    graphicsSurfaceWidget->hide(); -    debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction()); -      waitTreeWidget = new WaitTreeWidget(this);      addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);      waitTreeWidget->hide(); @@ -505,58 +513,69 @@ void GMainWindow::InitializeRecentFileMenuActions() {  }  void GMainWindow::InitializeHotkeys() { -    hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open); -    hotkey_registry.RegisterHotkey("Main Window", "Start Emulation"); -    hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4)); -    hotkey_registry.RegisterHotkey("Main Window", "Restart", QKeySequence(Qt::Key_F5)); -    hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen); -    hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape), -                                   Qt::ApplicationShortcut); -    hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"), -                                   Qt::ApplicationShortcut); -    hotkey_registry.RegisterHotkey("Main Window", "Increase Speed Limit", QKeySequence("+"), -                                   Qt::ApplicationShortcut); -    hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"), -                                   Qt::ApplicationShortcut); -    hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2), -                                   Qt::ApplicationShortcut); -    hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot", -                                   QKeySequence(QKeySequence::Print)); -      hotkey_registry.LoadHotkeys(); -    connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, -            this, &GMainWindow::OnMenuLoadFile); -    connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this), -            &QShortcut::activated, this, &GMainWindow::OnStartGame); -    connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated, -            this, [&] { -                if (emulation_running) { -                    if (emu_thread->IsRunning()) { -                        OnPauseGame(); -                    } else { -                        OnStartGame(); -                    } +    const QString main_window = QStringLiteral("Main Window"); +    const QString load_file = QStringLiteral("Load File"); +    const QString exit_yuzu = QStringLiteral("Exit yuzu"); +    const QString stop_emulation = QStringLiteral("Stop Emulation"); +    const QString toggle_filter_bar = QStringLiteral("Toggle Filter Bar"); +    const QString toggle_status_bar = QStringLiteral("Toggle Status Bar"); +    const QString fullscreen = QStringLiteral("Fullscreen"); + +    ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence(main_window, load_file)); +    ui.action_Load_File->setShortcutContext( +        hotkey_registry.GetShortcutContext(main_window, load_file)); + +    ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence(main_window, exit_yuzu)); +    ui.action_Exit->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, exit_yuzu)); + +    ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence(main_window, stop_emulation)); +    ui.action_Stop->setShortcutContext( +        hotkey_registry.GetShortcutContext(main_window, stop_emulation)); + +    ui.action_Show_Filter_Bar->setShortcut( +        hotkey_registry.GetKeySequence(main_window, toggle_filter_bar)); +    ui.action_Show_Filter_Bar->setShortcutContext( +        hotkey_registry.GetShortcutContext(main_window, toggle_filter_bar)); + +    ui.action_Show_Status_Bar->setShortcut( +        hotkey_registry.GetKeySequence(main_window, toggle_status_bar)); +    ui.action_Show_Status_Bar->setShortcutContext( +        hotkey_registry.GetShortcutContext(main_window, toggle_status_bar)); + +    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load File"), this), +            &QShortcut::activated, this, &GMainWindow::OnMenuLoadFile); +    connect( +        hotkey_registry.GetHotkey(main_window, QStringLiteral("Continue/Pause Emulation"), this), +        &QShortcut::activated, this, [&] { +            if (emulation_running) { +                if (emu_thread->IsRunning()) { +                    OnPauseGame(); +                } else { +                    OnStartGame();                  } -            }); -    connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this, -            [this] { -                if (!Core::System::GetInstance().IsPoweredOn()) +            } +        }); +    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Restart Emulation"), this), +            &QShortcut::activated, this, [this] { +                if (!Core::System::GetInstance().IsPoweredOn()) {                      return; -                BootGame(QString(game_path)); +                } +                BootGame(game_path);              }); -    connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window), +    connect(hotkey_registry.GetHotkey(main_window, fullscreen, render_window),              &QShortcut::activated, ui.action_Fullscreen, &QAction::trigger); -    connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window), +    connect(hotkey_registry.GetHotkey(main_window, fullscreen, render_window),              &QShortcut::activatedAmbiguously, ui.action_Fullscreen, &QAction::trigger); -    connect(hotkey_registry.GetHotkey("Main Window", "Exit Fullscreen", this), +    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Exit Fullscreen"), this),              &QShortcut::activated, this, [&] {                  if (emulation_running) {                      ui.action_Fullscreen->setChecked(false);                      ToggleFullscreen();                  }              }); -    connect(hotkey_registry.GetHotkey("Main Window", "Toggle Speed Limit", this), +    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Speed Limit"), this),              &QShortcut::activated, this, [&] {                  Settings::values.use_frame_limit = !Settings::values.use_frame_limit;                  UpdateStatusBar(); @@ -565,32 +584,38 @@ void GMainWindow::InitializeHotkeys() {      // MSVC occurs and we make it a requirement (see:      // https://developercommunity.visualstudio.com/content/problem/93922/constexprs-are-trying-to-be-captured-in-lambda-fun.html)      static constexpr u16 SPEED_LIMIT_STEP = 5; -    connect(hotkey_registry.GetHotkey("Main Window", "Increase Speed Limit", this), +    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Increase Speed Limit"), this),              &QShortcut::activated, this, [&] {                  if (Settings::values.frame_limit < 9999 - SPEED_LIMIT_STEP) {                      Settings::values.frame_limit += SPEED_LIMIT_STEP;                      UpdateStatusBar();                  }              }); -    connect(hotkey_registry.GetHotkey("Main Window", "Decrease Speed Limit", this), +    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Decrease Speed Limit"), this),              &QShortcut::activated, this, [&] {                  if (Settings::values.frame_limit > SPEED_LIMIT_STEP) {                      Settings::values.frame_limit -= SPEED_LIMIT_STEP;                      UpdateStatusBar();                  }              }); -    connect(hotkey_registry.GetHotkey("Main Window", "Load Amiibo", this), &QShortcut::activated, -            this, [&] { +    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load Amiibo"), this), +            &QShortcut::activated, this, [&] {                  if (ui.action_Load_Amiibo->isEnabled()) {                      OnLoadAmiibo();                  }              }); -    connect(hotkey_registry.GetHotkey("Main Window", "Capture Screenshot", this), +    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this),              &QShortcut::activated, this, [&] {                  if (emu_thread->IsRunning()) {                      OnCaptureScreenshot();                  }              }); +    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Change Docked Mode"), this), +            &QShortcut::activated, this, [&] { +                Settings::values.use_docked_mode = !Settings::values.use_docked_mode; +                OnDockedModeChanged(!Settings::values.use_docked_mode, +                                    Settings::values.use_docked_mode); +            });  }  void GMainWindow::SetDefaultUIGeometry() { @@ -635,6 +660,8 @@ void GMainWindow::RestoreUIState() {  void GMainWindow::ConnectWidgetEvents() {      connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);      connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); +    connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, +            &GMainWindow::OnTransferableShaderCacheOpenFile);      connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);      connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);      connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, @@ -679,13 +706,14 @@ void GMainWindow::ConnectMenuEvents() {              &GMainWindow::ToggleWindowMode);      connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,              &GMainWindow::OnDisplayTitleBars); -    ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F"));      connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);      connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);      // Fullscreen      ui.action_Fullscreen->setShortcut( -        hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key()); +        hotkey_registry +            .GetHotkey(QStringLiteral("Main Window"), QStringLiteral("Fullscreen"), this) +            ->key());      connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);      // Movie @@ -722,25 +750,33 @@ void GMainWindow::OnDisplayTitleBars(bool show) {  QStringList GMainWindow::GetUnsupportedGLExtensions() {      QStringList unsupported_ext; -    if (!GLAD_GL_ARB_direct_state_access) -        unsupported_ext.append("ARB_direct_state_access"); -    if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) -        unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev"); -    if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) -        unsupported_ext.append("ARB_texture_mirror_clamp_to_edge"); -    if (!GLAD_GL_ARB_multi_bind) -        unsupported_ext.append("ARB_multi_bind"); +    if (!GLAD_GL_ARB_direct_state_access) { +        unsupported_ext.append(QStringLiteral("ARB_direct_state_access")); +    } +    if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) { +        unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev")); +    } +    if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) { +        unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge")); +    } +    if (!GLAD_GL_ARB_multi_bind) { +        unsupported_ext.append(QStringLiteral("ARB_multi_bind")); +    }      // Extensions required to support some texture formats. -    if (!GLAD_GL_EXT_texture_compression_s3tc) -        unsupported_ext.append("EXT_texture_compression_s3tc"); -    if (!GLAD_GL_ARB_texture_compression_rgtc) -        unsupported_ext.append("ARB_texture_compression_rgtc"); -    if (!GLAD_GL_ARB_depth_buffer_float) -        unsupported_ext.append("ARB_depth_buffer_float"); - -    for (const QString& ext : unsupported_ext) +    if (!GLAD_GL_EXT_texture_compression_s3tc) { +        unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc")); +    } +    if (!GLAD_GL_ARB_texture_compression_rgtc) { +        unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc")); +    } +    if (!GLAD_GL_ARB_depth_buffer_float) { +        unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float")); +    } + +    for (const QString& ext : unsupported_ext) {          LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString()); +    }      return unsupported_ext;  } @@ -762,13 +798,13 @@ bool GMainWindow::LoadROM(const QString& filename) {          }      } -    QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions(); +    const QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();      if (!unsupported_gl_extensions.empty()) {          QMessageBox::critical(this, tr("Error while initializing OpenGL Core!"),                                tr("Your GPU may not support one or more required OpenGL"                                   "extensions. Please ensure you have the latest graphics "                                   "driver.<br><br>Unsupported extensions:<br>") + -                                  unsupported_gl_extensions.join("<br>")); +                                  unsupported_gl_extensions.join(QStringLiteral("<br>")));          return false;      } @@ -777,9 +813,13 @@ bool GMainWindow::LoadROM(const QString& filename) {      system.SetGPUDebugContext(debug_context); -    system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this)); -    system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this)); -    system.SetWebBrowser(std::make_unique<QtWebBrowser>(*this)); +    system.SetAppletFrontendSet({ +        std::make_unique<QtErrorDisplay>(*this), +        nullptr, +        std::make_unique<QtProfileSelector>(*this), +        std::make_unique<QtSoftwareKeyboard>(*this), +        std::make_unique<QtWebBrowser>(*this), +    });      const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; @@ -807,11 +847,6 @@ bool GMainWindow::LoadROM(const QString& filename) {              QMessageBox::critical(this, tr("Error while loading ROM!"),                                    tr("The ROM format is not supported."));              break; -        case Core::System::ResultStatus::ErrorSystemMode: -            LOG_CRITICAL(Frontend, "Failed to load ROM!"); -            QMessageBox::critical(this, tr("Error while loading ROM!"), -                                  tr("Could not determine the system mode.")); -            break;          case Core::System::ResultStatus::ErrorVideoCore:              QMessageBox::critical(                  this, tr("An error occurred initializing the video core."), @@ -858,11 +893,12 @@ void GMainWindow::SelectAndSetCurrentUser() {      dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |                            Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);      dialog.setWindowModality(Qt::WindowModal); -    dialog.exec(); -    if (dialog.GetStatus()) { -        Settings::values.current_user = static_cast<s32>(dialog.GetIndex()); +    if (dialog.exec() == QDialog::Rejected) { +        return;      } + +    Settings::values.current_user = dialog.GetIndex();  }  void GMainWindow::BootGame(const QString& filename) { @@ -912,9 +948,7 @@ void GMainWindow::BootGame(const QString& filename) {              title_name = FileUtil::GetFilename(filename.toStdString());      } -    setWindowTitle(QString("yuzu %1| %4 | %2-%3") -                       .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, -                            QString::fromStdString(title_name))); +    UpdateWindowTitle(QString::fromStdString(title_name));      loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());      loading_screen->show(); @@ -955,8 +989,8 @@ void GMainWindow::ShutdownGame() {      loading_screen->Clear();      game_list->show();      game_list->setFilterFocus(); -    setWindowTitle(QString("yuzu %1| %2-%3") -                       .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc)); + +    UpdateWindowTitle();      // Disable status bar updates      status_bar_update_timer.stop(); @@ -985,7 +1019,7 @@ void GMainWindow::UpdateRecentFiles() {          std::min(UISettings::values.recent_files.size(), max_recent_files_item);      for (int i = 0; i < num_recent_files; i++) { -        const QString text = QString("&%1. %2").arg(i + 1).arg( +        const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg(              QFileInfo(UISettings::values.recent_files[i]).fileName());          actions_recent_files[i]->setText(text);          actions_recent_files[i]->setData(UISettings::values.recent_files[i]); @@ -1007,21 +1041,20 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {  void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target) {      std::string path; -    std::string open_target; +    QString open_target;      switch (target) {      case GameListOpenTarget::SaveData: { -        open_target = "Save Data"; +        open_target = tr("Save Data");          const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);          ASSERT(program_id != 0); -        const auto select_profile = [this]() -> s32 { +        const auto select_profile = [this] {              QtProfileSelectionDialog dialog(this);              dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |                                    Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);              dialog.setWindowModality(Qt::WindowModal); -            dialog.exec(); -            if (!dialog.GetStatus()) { +            if (dialog.exec() == QDialog::Rejected) {                  return -1;              } @@ -1029,11 +1062,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target          };          const auto index = select_profile(); -        if (index == -1) +        if (index == -1) {              return; +        }          Service::Account::ProfileManager manager; -        const auto user_id = manager.GetUser(index); +        const auto user_id = manager.GetUser(static_cast<std::size_t>(index));          ASSERT(user_id);          path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,                                                                  FileSys::SaveDataType::SaveData, @@ -1047,7 +1081,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target          break;      }      case GameListOpenTarget::ModData: { -        open_target = "Mod Data"; +        open_target = tr("Mod Data");          const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir);          path = fmt::format("{}{:016X}", load_dir, program_id);          break; @@ -1057,18 +1091,50 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target      }      const QString qpath = QString::fromStdString(path); -      const QDir dir(qpath);      if (!dir.exists()) { -        QMessageBox::warning(this, -                             tr("Error Opening %1 Folder").arg(QString::fromStdString(open_target)), +        QMessageBox::warning(this, tr("Error Opening %1 Folder").arg(open_target),                               tr("Folder does not exist!"));          return;      } -    LOG_INFO(Frontend, "Opening {} path for program_id={:016x}", open_target, program_id); +    LOG_INFO(Frontend, "Opening {} path for program_id={:016x}", open_target.toStdString(), +             program_id);      QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));  } +void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { +    ASSERT(program_id != 0); + +    const QString shader_dir = +        QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)); +    const QString tranferable_shader_cache_folder_path = +        shader_dir + QStringLiteral("opengl") + QDir::separator() + QStringLiteral("transferable"); +    const QString transferable_shader_cache_file_path = +        tranferable_shader_cache_folder_path + QDir::separator() + +        QString::fromStdString(fmt::format("{:016X}.bin", program_id)); + +    if (!QFile::exists(transferable_shader_cache_file_path)) { +        QMessageBox::warning(this, tr("Error Opening Transferable Shader Cache"), +                             tr("A shader cache for this title does not exist.")); +        return; +    } + +    // Windows supports opening a folder with selecting a specified file in explorer. On every other +    // OS we just open the transferable shader cache folder without preselecting the transferable +    // shader cache file for the selected game. +#if defined(Q_OS_WIN) +    const QString explorer = QStringLiteral("explorer"); +    QStringList param; +    if (!QFileInfo(transferable_shader_cache_file_path).isDir()) { +        param << QStringLiteral("/select,"); +    } +    param << QDir::toNativeSeparators(transferable_shader_cache_file_path); +    QProcess::startDetached(explorer, param); +#else +    QDesktopServices::openUrl(QUrl::fromLocalFile(tranferable_shader_cache_folder_path)); +#endif +} +  static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) {      std::size_t out = 0; @@ -1128,7 +1194,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa          return;      } -    const auto installed = Service::FileSystem::GetUnionContents(); +    const auto& installed = Core::System::GetInstance().GetContentProvider();      const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);      if (!romfs_title_id) { @@ -1161,20 +1227,21 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa          return;      } -    bool ok; +    bool ok = false; +    const QStringList selections{tr("Full"), tr("Skeleton")};      const auto res = QInputDialog::getItem(          this, tr("Select RomFS Dump Mode"),          tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the "             "files into the new directory while <br>skeleton will only create the directory "             "structure."), -        {"Full", "Skeleton"}, 0, false, &ok); +        selections, 0, false, &ok);      if (!ok) {          failed();          vfs->DeleteDirectory(path);          return;      } -    const auto full = res == "Full"; +    const auto full = res == selections.constFirst();      const auto entry_size = CalculateRomFSEntrySize(extracted, full);      QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, @@ -1204,10 +1271,11 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,      const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);      QString directory; -    if (it != compatibility_list.end()) +    if (it != compatibility_list.end()) {          directory = it->second.second; +    } -    QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory)); +    QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));  }  void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { @@ -1221,15 +1289,15 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {      }      ConfigurePerGameGeneral dialog(this, title_id); -    dialog.loadFromFile(v_file); +    dialog.LoadFromFile(v_file);      auto result = dialog.exec();      if (result == QDialog::Accepted) { -        dialog.applyConfiguration(); +        dialog.ApplyConfiguration();          const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);          if (reload) { -            game_list->PopulateAsync(UISettings::values.gamedir, -                                     UISettings::values.gamedir_deepscan); +            game_list->PopulateAsync(UISettings::values.game_directory_path, +                                     UISettings::values.game_directory_deepscan);          }          config->Save(); @@ -1238,7 +1306,9 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {  void GMainWindow::OnMenuLoadFile() {      const QString extensions = -        QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main"); +        QStringLiteral("*.") +            .append(GameList::supported_file_extensions.join(QStringLiteral(" *."))) +            .append(QStringLiteral(" main"));      const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)",                                     "%1 is an identifier for the Switch executable file extensions.")                                      .arg(extensions); @@ -1262,9 +1332,9 @@ void GMainWindow::OnMenuLoadFolder() {      }      const QDir dir{dir_path}; -    const QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); +    const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files);      if (matching_main.size() == 1) { -        BootGame(dir.path() + DIR_SEP + matching_main[0]); +        BootGame(dir.path() + QDir::separator() + matching_main[0]);      } else {          QMessageBox::warning(this, tr("Invalid Directory Selected"),                               tr("The directory you have selected does not contain a 'main' file.")); @@ -1317,7 +1387,10 @@ void GMainWindow::OnMenuInstallToNAND() {      const auto success = [this]() {          QMessageBox::information(this, tr("Successfully Installed"),                                   tr("The file was successfully installed.")); -        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +        game_list->PopulateAsync(UISettings::values.game_directory_path, +                                 UISettings::values.game_directory_deepscan); +        FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + +                                       DIR_SEP + "game_list");      };      const auto failed = [this]() { @@ -1335,11 +1408,10 @@ void GMainWindow::OnMenuInstallToNAND() {                 QMessageBox::Yes;      }; -    if (filename.endsWith("xci", Qt::CaseInsensitive) || -        filename.endsWith("nsp", Qt::CaseInsensitive)) { - +    if (filename.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || +        filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {          std::shared_ptr<FileSys::NSP> nsp; -        if (filename.endsWith("nsp", Qt::CaseInsensitive)) { +        if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {              nsp = std::make_shared<FileSys::NSP>(                  vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));              if (nsp->IsExtractedType()) @@ -1444,8 +1516,8 @@ void GMainWindow::OnMenuInstallToNAND() {  void GMainWindow::OnMenuSelectGameListRoot() {      QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));      if (!dir_path.isEmpty()) { -        UISettings::values.gamedir = dir_path; -        game_list->PopulateAsync(dir_path, UISettings::values.gamedir_deepscan); +        UISettings::values.game_directory_path = dir_path; +        game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan);      }  } @@ -1467,7 +1539,8 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)                                                                        : FileUtil::UserPath::NANDDir,                                dir_path.toStdString());          Service::FileSystem::CreateFactories(*vfs); -        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +        game_list->PopulateAsync(UISettings::values.game_directory_path, +                                 UISettings::values.game_directory_deepscan);      }  } @@ -1530,6 +1603,11 @@ void GMainWindow::OnLoadComplete() {      loading_screen->OnLoadComplete();  } +void GMainWindow::ErrorDisplayDisplayError(QString body) { +    QMessageBox::critical(this, tr("Error Display"), body); +    emit ErrorDisplayFinished(); +} +  void GMainWindow::OnMenuReportCompatibility() {      if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) {          CompatDB compatdb{this}; @@ -1605,31 +1683,37 @@ void GMainWindow::ToggleWindowMode() {  }  void GMainWindow::OnConfigure() { -    ConfigureDialog configureDialog(this, hotkey_registry); -    auto old_theme = UISettings::values.theme; +    const auto old_theme = UISettings::values.theme;      const bool old_discord_presence = UISettings::values.enable_discord_presence; -    auto result = configureDialog.exec(); -    if (result == QDialog::Accepted) { -        configureDialog.applyConfiguration(); -        if (UISettings::values.theme != old_theme) -            UpdateUITheme(); -        if (UISettings::values.enable_discord_presence != old_discord_presence) -            SetDiscordEnabled(UISettings::values.enable_discord_presence); -        const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); -        if (reload) { -            game_list->PopulateAsync(UISettings::values.gamedir, -                                     UISettings::values.gamedir_deepscan); -        } +    ConfigureDialog configure_dialog(this, hotkey_registry); +    const auto result = configure_dialog.exec(); +    if (result != QDialog::Accepted) { +        return; +    } -        config->Save(); +    configure_dialog.ApplyConfiguration(); +    InitializeHotkeys(); +    if (UISettings::values.theme != old_theme) { +        UpdateUITheme(); +    } +    if (UISettings::values.enable_discord_presence != old_discord_presence) { +        SetDiscordEnabled(UISettings::values.enable_discord_presence);      } + +    const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); +    if (reload) { +        game_list->PopulateAsync(UISettings::values.game_directory_path, +                                 UISettings::values.game_directory_deepscan); +    } + +    config->Save();  }  void GMainWindow::OnLoadAmiibo() { -    const QString extensions{"*.bin"}; +    const QString extensions{QStringLiteral("*.bin")};      const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); -    const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), "", file_filter); +    const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter);      if (filename.isEmpty()) {          return; @@ -1691,7 +1775,7 @@ void GMainWindow::OnCaptureScreenshot() {      QFileDialog png_dialog(this, tr("Capture Screenshot"), UISettings::values.screenshot_path,                             tr("PNG Image (*.png)"));      png_dialog.setAcceptMode(QFileDialog::AcceptSave); -    png_dialog.setDefaultSuffix("png"); +    png_dialog.setDefaultSuffix(QStringLiteral("png"));      if (png_dialog.exec()) {          const QString path = png_dialog.selectedFiles().first();          if (!path.isEmpty()) { @@ -1702,6 +1786,19 @@ void GMainWindow::OnCaptureScreenshot() {      OnStartGame();  } +void GMainWindow::UpdateWindowTitle(const QString& title_name) { +    const QString full_name = QString::fromUtf8(Common::g_build_fullname); +    const QString branch_name = QString::fromUtf8(Common::g_scm_branch); +    const QString description = QString::fromUtf8(Common::g_scm_desc); + +    if (title_name.isEmpty()) { +        setWindowTitle(QStringLiteral("yuzu %1| %2-%3").arg(full_name, branch_name, description)); +    } else { +        setWindowTitle(QStringLiteral("yuzu %1| %4 | %2-%3") +                           .arg(full_name, branch_name, description, title_name)); +    } +} +  void GMainWindow::UpdateStatusBar() {      if (emu_thread == nullptr) {          status_bar_update_timer.stop(); @@ -1741,17 +1838,17 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det             "data, or other bugs.");      switch (result) {      case Core::System::ResultStatus::ErrorSystemFiles: { -        QString message = "yuzu was unable to locate a Switch system archive"; +        QString message = tr("yuzu was unable to locate a Switch system archive");          if (!details.empty()) { -            message.append(tr(": %1. ").arg(details.c_str())); +            message.append(tr(": %1. ").arg(QString::fromStdString(details)));          } else { -            message.append(". "); +            message.append(tr(". "));          }          message.append(common_message);          answer = QMessageBox::question(this, tr("System Archive Not Found"), message,                                         QMessageBox::Yes | QMessageBox::No, QMessageBox::No); -        status_message = "System Archive Missing"; +        status_message = tr("System Archive Missing");          break;      } @@ -1760,7 +1857,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det          message.append(common_message);          answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message,                                         QMessageBox::Yes | QMessageBox::No, QMessageBox::No); -        status_message = "Shared Font Missing"; +        status_message = tr("Shared Font Missing");          break;      } @@ -1776,7 +1873,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det                 "Continuing emulation may result in crashes, corrupted save data, or other "                 "bugs."),              QMessageBox::Yes | QMessageBox::No, QMessageBox::No); -        status_message = "Fatal Error encountered"; +        status_message = tr("Fatal Error encountered");          break;      } @@ -1827,18 +1924,19 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {          };          QString errors; - -        if (!pdm.HasFuses()) +        if (!pdm.HasFuses()) {              errors += tr("- Missing fuses - Cannot derive SBK\n"); -        if (!pdm.HasBoot0()) +        } +        if (!pdm.HasBoot0()) {              errors += tr("- Missing BOOT0 - Cannot derive master keys\n"); -        if (!pdm.HasPackage2()) +        } +        if (!pdm.HasPackage2()) {              errors += tr("- Missing BCPKG2-1-Normal-Main - Cannot derive general keys\n"); -        if (!pdm.HasProdInfo()) +        } +        if (!pdm.HasProdInfo()) {              errors += tr("- Missing PRODINFO - Cannot derive title keys\n"); - +        }          if (!errors.isEmpty()) { -              QMessageBox::warning(                  this, tr("Warning Missing Derivation Components"),                  tr("The following are missing from your configuration that may hinder key " @@ -1846,7 +1944,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {                      errors +                      tr("<br><br>You can get all of these and dump all of your games easily by "                         "following <a href='https://yuzu-emu.org/help/quickstart/'>the " -                       "quickstart guide</a>. Alternatively, you can use another method of dumping" +                       "quickstart guide</a>. Alternatively, you can use another method of dumping "                         "to obtain all of your keys."));          } @@ -1869,31 +1967,34 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {      Service::FileSystem::CreateFactories(*vfs);      if (behavior == ReinitializeKeyBehavior::Warning) { -        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); +        game_list->PopulateAsync(UISettings::values.game_directory_path, +                                 UISettings::values.game_directory_deepscan);      }  } -std::optional<u64> GMainWindow::SelectRomFSDumpTarget( -    const FileSys::RegisteredCacheUnion& installed, u64 program_id) { +std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, +                                                      u64 program_id) {      const auto dlc_entries =          installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); -    std::vector<FileSys::RegisteredCacheEntry> dlc_match; +    std::vector<FileSys::ContentProviderEntry> dlc_match;      dlc_match.reserve(dlc_entries.size());      std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), -                 [&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) { +                 [&program_id, &installed](const FileSys::ContentProviderEntry& entry) {                       return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&                              installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;                   });      std::vector<u64> romfs_tids;      romfs_tids.push_back(program_id); -    for (const auto& entry : dlc_match) +    for (const auto& entry : dlc_match) {          romfs_tids.push_back(entry.title_id); +    }      if (romfs_tids.size() > 1) { -        QStringList list{"Base"}; -        for (std::size_t i = 1; i < romfs_tids.size(); ++i) +        QStringList list{QStringLiteral("Base")}; +        for (std::size_t i = 1; i < romfs_tids.size(); ++i) {              list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF)); +        }          bool ok;          const auto res = QInputDialog::getItem( @@ -1976,6 +2077,18 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {      event->acceptProposedAction();  } +void GMainWindow::keyPressEvent(QKeyEvent* event) { +    if (render_window) { +        render_window->ForwardKeyPressEvent(event); +    } +} + +void GMainWindow::keyReleaseEvent(QKeyEvent* event) { +    if (render_window) { +        render_window->ForwardKeyReleaseEvent(event); +    } +} +  bool GMainWindow::ConfirmChangeGame() {      if (emu_thread == nullptr)          return true; @@ -1993,26 +2106,32 @@ void GMainWindow::filterBarSetChecked(bool state) {  }  void GMainWindow::UpdateUITheme() { +    const QString default_icons = QStringLiteral(":/icons/default"); +    const QString& current_theme = UISettings::values.theme; +    const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second);      QStringList theme_paths(default_theme_paths); -    if (UISettings::values.theme != UISettings::themes[0].second && -        !UISettings::values.theme.isEmpty()) { -        const QString theme_uri(":" + UISettings::values.theme + "/style.qss"); + +    if (is_default_theme || current_theme.isEmpty()) { +        qApp->setStyleSheet({}); +        setStyleSheet({}); +        theme_paths.append(default_icons); +        QIcon::setThemeName(default_icons); +    } else { +        const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss"));          QFile f(theme_uri);          if (f.open(QFile::ReadOnly | QFile::Text)) {              QTextStream ts(&f);              qApp->setStyleSheet(ts.readAll()); -            GMainWindow::setStyleSheet(ts.readAll()); +            setStyleSheet(ts.readAll());          } else {              LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");          } -        theme_paths.append(QStringList{":/icons/default", ":/icons/" + UISettings::values.theme}); -        QIcon::setThemeName(":/icons/" + UISettings::values.theme); -    } else { -        qApp->setStyleSheet(""); -        GMainWindow::setStyleSheet(""); -        theme_paths.append(QStringList{":/icons/default"}); -        QIcon::setThemeName(":/icons/default"); + +        const QString theme_name = QStringLiteral(":/icons/") + current_theme; +        theme_paths.append({default_icons, theme_name}); +        QIcon::setThemeName(theme_name);      } +      QIcon::setThemeSearchPaths(theme_paths);      emit UpdateThemedIcons();  } @@ -2040,10 +2159,11 @@ int main(int argc, char* argv[]) {      SCOPE_EXIT({ MicroProfileShutdown(); });      // Init settings params -    QCoreApplication::setOrganizationName("yuzu team"); -    QCoreApplication::setApplicationName("yuzu"); +    QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); +    QCoreApplication::setApplicationName(QStringLiteral("yuzu")); -    QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); +    // Enables the core to make the qt created contexts current on std::threads +    QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);      QApplication app(argc, argv);      // Qt changes the locale and causes issues in float conversion using std::to_string() when diff --git a/src/yuzu/main.h b/src/yuzu/main.h index e07c892cf..1137bbc7a 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -23,7 +23,6 @@ class EmuThread;  class GameList;  class GImageInfo;  class GraphicsBreakPointsWidget; -class GraphicsSurfaceWidget;  class GRenderWindow;  class LoadingScreen;  class MicroProfileDialog; @@ -37,7 +36,8 @@ struct SoftwareKeyboardParameters;  } // namespace Core::Frontend  namespace FileSys { -class RegisteredCacheUnion; +class ContentProvider; +class ManualContentProvider;  class VfsFilesystem;  } // namespace FileSys @@ -102,7 +102,9 @@ signals:      // Signal that tells widgets to update icons to use the current theme      void UpdateThemedIcons(); -    void ProfileSelectorFinishedSelection(std::optional<Service::Account::UUID> uuid); +    void ErrorDisplayFinished(); + +    void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);      void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);      void SoftwareKeyboardFinishedCheckDialog(); @@ -111,6 +113,7 @@ signals:  public slots:      void OnLoadComplete(); +    void ErrorDisplayDisplayError(QString body);      void ProfileSelectorSelectProfile();      void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);      void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); @@ -120,7 +123,6 @@ private:      void InitializeWidgets();      void InitializeDebugWidgets();      void InitializeRecentFileMenuActions(); -    void InitializeHotkeys();      void SetDefaultUIGeometry();      void RestoreUIState(); @@ -176,6 +178,7 @@ private slots:      /// Called whenever a user selects a game in the game list widget.      void OnGameListLoadFile(QString game_path);      void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); +    void OnTransferableShaderCacheOpenFile(u64 program_id);      void OnGameListDumpRomFS(u64 program_id, const std::string& game_path);      void OnGameListCopyTID(u64 program_id);      void OnGameListNavigateToGamedbEntry(u64 program_id, @@ -195,6 +198,7 @@ private slots:      void OnAbout();      void OnToggleFilterBar();      void OnDisplayTitleBars(bool); +    void InitializeHotkeys();      void ToggleFullscreen();      void ShowFullscreen();      void HideFullscreen(); @@ -204,7 +208,8 @@ private slots:      void OnReinitializeKeys(ReinitializeKeyBehavior behavior);  private: -    std::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, u64 program_id); +    std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); +    void UpdateWindowTitle(const QString& title_name = {});      void UpdateStatusBar();      Ui::MainWindow ui; @@ -232,12 +237,12 @@ private:      // FS      std::shared_ptr<FileSys::VfsFilesystem> vfs; +    std::unique_ptr<FileSys::ManualContentProvider> provider;      // Debugger panes      ProfilerWidget* profilerWidget;      MicroProfileDialog* microProfileDialog;      GraphicsBreakPointsWidget* graphicsBreakpointsWidget; -    GraphicsSurfaceWidget* graphicsSurfaceWidget;      WaitTreeWidget* waitTreeWidget;      QAction* actions_recent_files[max_recent_files_item]; @@ -251,4 +256,8 @@ protected:      void dropEvent(QDropEvent* event) override;      void dragEnterEvent(QDragEnterEvent* event) override;      void dragMoveEvent(QDragMoveEvent* event) override; + +    // Overrides used to forward signals to the render window when the focus moves out. +    void keyPressEvent(QKeyEvent* event) override; +    void keyReleaseEvent(QKeyEvent* event) override;  }; diff --git a/src/yuzu/ui_settings.cpp b/src/yuzu/ui_settings.cpp index a314493fc..4bdc302e0 100644 --- a/src/yuzu/ui_settings.cpp +++ b/src/yuzu/ui_settings.cpp @@ -12,5 +12,4 @@ const Themes themes{{  }};  Values values = {}; -  } // namespace UISettings diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index 82aaeedb0..a62cd6911 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -15,7 +15,12 @@  namespace UISettings {  using ContextualShortcut = std::pair<QString, int>; -using Shortcut = std::pair<QString, ContextualShortcut>; + +struct Shortcut { +    QString name; +    QString group; +    ContextualShortcut shortcut; +};  using Themes = std::array<std::pair<const char*, const char*>, 2>;  extern const Themes themes; @@ -50,8 +55,8 @@ struct Values {      QString roms_path;      QString symbols_path;      QString screenshot_path; -    QString gamedir; -    bool gamedir_deepscan; +    QString game_directory_path; +    bool game_directory_deepscan;      QStringList recent_files;      QString theme; @@ -74,6 +79,7 @@ struct Values {      uint8_t row_1_text_id;      uint8_t row_2_text_id;      std::atomic_bool is_game_list_reload_pending{false}; +    bool cache_game_list;  };  extern Values values; diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp new file mode 100644 index 000000000..bb5f74ec4 --- /dev/null +++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp @@ -0,0 +1,40 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QDialogButtonBox> +#include <QKeySequenceEdit> +#include <QVBoxLayout> +#include "yuzu/util/sequence_dialog/sequence_dialog.h" + +SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) { +    setWindowTitle(tr("Enter a hotkey")); +    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + +    key_sequence = new QKeySequenceEdit; + +    auto* const buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); +    buttons->setCenterButtons(true); + +    auto* const layout = new QVBoxLayout(this); +    layout->addWidget(key_sequence); +    layout->addWidget(buttons); + +    connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); +    connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +SequenceDialog::~SequenceDialog() = default; + +QKeySequence SequenceDialog::GetSequence() const { +    // Only the first key is returned. The other 3, if present, are ignored. +    return QKeySequence(key_sequence->keySequence()[0]); +} + +bool SequenceDialog::focusNextPrevChild(bool next) { +    return false; +} + +void SequenceDialog::closeEvent(QCloseEvent*) { +    reject(); +} diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.h b/src/yuzu/util/sequence_dialog/sequence_dialog.h new file mode 100644 index 000000000..969c77740 --- /dev/null +++ b/src/yuzu/util/sequence_dialog/sequence_dialog.h @@ -0,0 +1,24 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDialog> + +class QKeySequenceEdit; + +class SequenceDialog : public QDialog { +    Q_OBJECT + +public: +    explicit SequenceDialog(QWidget* parent = nullptr); +    ~SequenceDialog() override; + +    QKeySequence GetSequence() const; +    void closeEvent(QCloseEvent*) override; + +private: +    QKeySequenceEdit* key_sequence; +    bool focusNextPrevChild(bool next) override; +}; diff --git a/src/yuzu/util/spinbox.cpp b/src/yuzu/util/spinbox.cpp deleted file mode 100644 index 14ef1e884..000000000 --- a/src/yuzu/util/spinbox.cpp +++ /dev/null @@ -1,278 +0,0 @@ -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -// Copyright 2014 Tony Wasserka -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -//     * Redistributions of source code must retain the above copyright -//       notice, this list of conditions and the following disclaimer. -//     * Redistributions in binary form must reproduce the above copyright -//       notice, this list of conditions and the following disclaimer in the -//       documentation and/or other materials provided with the distribution. -//     * Neither the name of the owner nor the names of its contributors may -//       be used to endorse or promote products derived from this software -//       without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include <cstdlib> -#include <QLineEdit> -#include <QRegExpValidator> -#include "common/assert.h" -#include "yuzu/util/spinbox.h" - -CSpinBox::CSpinBox(QWidget* parent) -    : QAbstractSpinBox(parent), min_value(-100), max_value(100), value(0), base(10), num_digits(0) { -    // TODO: Might be nice to not immediately call the slot. -    //       Think of an address that is being replaced by a different one, in which case a lot -    //       invalid intermediate addresses would be read from during editing. -    connect(lineEdit(), &QLineEdit::textEdited, this, &CSpinBox::OnEditingFinished); - -    UpdateText(); -} - -void CSpinBox::SetValue(qint64 val) { -    auto old_value = value; -    value = std::max(std::min(val, max_value), min_value); - -    if (old_value != value) { -        UpdateText(); -        emit ValueChanged(value); -    } -} - -void CSpinBox::SetRange(qint64 min, qint64 max) { -    min_value = min; -    max_value = max; - -    SetValue(value); -    UpdateText(); -} - -void CSpinBox::stepBy(int steps) { -    auto new_value = value; -    // Scale number of steps by the currently selected digit -    // TODO: Move this code elsewhere and enable it. -    // TODO: Support for num_digits==0, too -    // TODO: Support base!=16, too -    // TODO: Make the cursor not jump back to the end of the line... -    /*if (base == 16 && num_digits > 0) { -        int digit = num_digits - (lineEdit()->cursorPosition() - prefix.length()) - 1; -        digit = std::max(0, std::min(digit, num_digits - 1)); -        steps <<= digit * 4; -    }*/ - -    // Increment "new_value" by "steps", and perform annoying overflow checks, too. -    if (steps < 0 && new_value + steps > new_value) { -        new_value = std::numeric_limits<qint64>::min(); -    } else if (steps > 0 && new_value + steps < new_value) { -        new_value = std::numeric_limits<qint64>::max(); -    } else { -        new_value += steps; -    } - -    SetValue(new_value); -    UpdateText(); -} - -QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const { -    StepEnabled ret = StepNone; - -    if (value > min_value) -        ret |= StepDownEnabled; - -    if (value < max_value) -        ret |= StepUpEnabled; - -    return ret; -} - -void CSpinBox::SetBase(int base) { -    this->base = base; - -    UpdateText(); -} - -void CSpinBox::SetNumDigits(int num_digits) { -    this->num_digits = num_digits; - -    UpdateText(); -} - -void CSpinBox::SetPrefix(const QString& prefix) { -    this->prefix = prefix; - -    UpdateText(); -} - -void CSpinBox::SetSuffix(const QString& suffix) { -    this->suffix = suffix; - -    UpdateText(); -} - -static QString StringToInputMask(const QString& input) { -    QString mask = input; - -    // ... replace any special characters by their escaped counterparts ... -    mask.replace("\\", "\\\\"); -    mask.replace("A", "\\A"); -    mask.replace("a", "\\a"); -    mask.replace("N", "\\N"); -    mask.replace("n", "\\n"); -    mask.replace("X", "\\X"); -    mask.replace("x", "\\x"); -    mask.replace("9", "\\9"); -    mask.replace("0", "\\0"); -    mask.replace("D", "\\D"); -    mask.replace("d", "\\d"); -    mask.replace("#", "\\#"); -    mask.replace("H", "\\H"); -    mask.replace("h", "\\h"); -    mask.replace("B", "\\B"); -    mask.replace("b", "\\b"); -    mask.replace(">", "\\>"); -    mask.replace("<", "\\<"); -    mask.replace("!", "\\!"); - -    return mask; -} - -void CSpinBox::UpdateText() { -    // If a fixed number of digits is used, we put the line edit in insertion mode by setting an -    // input mask. -    QString mask; -    if (num_digits != 0) { -        mask += StringToInputMask(prefix); - -        // For base 10 and negative range, demand a single sign character -        if (HasSign()) -            mask += "X"; // identified as "-" or "+" in the validator - -        // Uppercase digits greater than 9. -        mask += ">"; - -        // Match num_digits digits -        // Digits irrelevant to the chosen number base are filtered in the validator -        mask += QString("H").repeated(std::max(num_digits, 1)); - -        // Switch off case conversion -        mask += "!"; - -        mask += StringToInputMask(suffix); -    } -    lineEdit()->setInputMask(mask); - -    // Set new text without changing the cursor position. This will cause the cursor to briefly -    // appear at the end of the line and then to jump back to its original position. That's -    // a bit ugly, but better than having setText() move the cursor permanently all the time. -    int cursor_position = lineEdit()->cursorPosition(); -    lineEdit()->setText(TextFromValue()); -    lineEdit()->setCursorPosition(cursor_position); -} - -QString CSpinBox::TextFromValue() { -    return prefix + QString(HasSign() ? ((value < 0) ? "-" : "+") : "") + -           QString("%1").arg(std::abs(value), num_digits, base, QLatin1Char('0')).toUpper() + -           suffix; -} - -qint64 CSpinBox::ValueFromText() { -    unsigned strpos = prefix.length(); - -    QString num_string = text().mid(strpos, text().length() - strpos - suffix.length()); -    return num_string.toLongLong(nullptr, base); -} - -bool CSpinBox::HasSign() const { -    return base == 10 && min_value < 0; -} - -void CSpinBox::OnEditingFinished() { -    // Only update for valid input -    QString input = lineEdit()->text(); -    int pos = 0; -    if (QValidator::Acceptable == validate(input, pos)) -        SetValue(ValueFromText()); -} - -QValidator::State CSpinBox::validate(QString& input, int& pos) const { -    if (!prefix.isEmpty() && input.left(prefix.length()) != prefix) -        return QValidator::Invalid; - -    int strpos = prefix.length(); - -    // Empty "numbers" allowed as intermediate values -    if (strpos >= input.length() - HasSign() - suffix.length()) -        return QValidator::Intermediate; - -    DEBUG_ASSERT(base <= 10 || base == 16); -    QString regexp; - -    // Demand sign character for negative ranges -    if (HasSign()) -        regexp += "[+\\-]"; - -    // Match digits corresponding to the chosen number base. -    regexp += QString("[0-%1").arg(std::min(base, 9)); -    if (base == 16) { -        regexp += "a-fA-F"; -    } -    regexp += "]"; - -    // Specify number of digits -    if (num_digits > 0) { -        regexp += QString("{%1}").arg(num_digits); -    } else { -        regexp += "+"; -    } - -    // Match string -    QRegExp num_regexp(regexp); -    int num_pos = strpos; -    QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length()); - -    if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0) -        return QValidator::Invalid; - -    sub_input = sub_input.left(num_regexp.matchedLength()); -    bool ok; -    qint64 val = sub_input.toLongLong(&ok, base); - -    if (!ok) -        return QValidator::Invalid; - -    // Outside boundaries => don't accept -    if (val < min_value || val > max_value) -        return QValidator::Invalid; - -    // Make sure we are actually at the end of this string... -    strpos += num_regexp.matchedLength(); - -    if (!suffix.isEmpty() && input.mid(strpos) != suffix) { -        return QValidator::Invalid; -    } else { -        strpos += suffix.length(); -    } - -    if (strpos != input.length()) -        return QValidator::Invalid; - -    // At this point we can say for sure that the input is fine. Let's fix it up a bit though -    input.replace(num_pos, sub_input.length(), sub_input.toUpper()); - -    return QValidator::Acceptable; -} diff --git a/src/yuzu/util/spinbox.h b/src/yuzu/util/spinbox.h deleted file mode 100644 index 2fa1db3a4..000000000 --- a/src/yuzu/util/spinbox.h +++ /dev/null @@ -1,86 +0,0 @@ -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -// Copyright 2014 Tony Wasserka -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -//     * Redistributions of source code must retain the above copyright -//       notice, this list of conditions and the following disclaimer. -//     * Redistributions in binary form must reproduce the above copyright -//       notice, this list of conditions and the following disclaimer in the -//       documentation and/or other materials provided with the distribution. -//     * Neither the name of the owner nor the names of its contributors may -//       be used to endorse or promote products derived from this software -//       without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#pragma once - -#include <QAbstractSpinBox> -#include <QtGlobal> - -class QVariant; - -/** - * A custom spin box widget with enhanced functionality over Qt's QSpinBox - */ -class CSpinBox : public QAbstractSpinBox { -    Q_OBJECT - -public: -    explicit CSpinBox(QWidget* parent = nullptr); - -    void stepBy(int steps) override; -    StepEnabled stepEnabled() const override; - -    void SetValue(qint64 val); - -    void SetRange(qint64 min, qint64 max); - -    void SetBase(int base); - -    void SetPrefix(const QString& prefix); -    void SetSuffix(const QString& suffix); - -    void SetNumDigits(int num_digits); - -    QValidator::State validate(QString& input, int& pos) const override; - -signals: -    void ValueChanged(qint64 val); - -private slots: -    void OnEditingFinished(); - -private: -    void UpdateText(); - -    bool HasSign() const; - -    QString TextFromValue(); -    qint64 ValueFromText(); - -    qint64 min_value, max_value; - -    qint64 value; - -    QString prefix, suffix; - -    int base; - -    int num_digits; -}; diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index 62c080aff..ef31bc2d2 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp @@ -8,7 +8,7 @@  #include "yuzu/util/util.h"  QFont GetMonospaceFont() { -    QFont font("monospace"); +    QFont font(QStringLiteral("monospace"));      // Automatic fallback to a monospace font on on platforms without a font called "monospace"      font.setStyleHint(QFont::Monospace);      font.setFixedPitch(true); @@ -16,14 +16,16 @@ QFont GetMonospaceFont() {  }  QString ReadableByteSize(qulonglong size) { -    static const std::array<const char*, 6> units = {"B", "KiB", "MiB", "GiB", "TiB", "PiB"}; -    if (size == 0) -        return "0"; -    int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)), -                                     static_cast<int>(units.size())); -    return QString("%L1 %2") +    static constexpr std::array units{"B", "KiB", "MiB", "GiB", "TiB", "PiB"}; +    if (size == 0) { +        return QStringLiteral("0"); +    } + +    const int digit_groups = std::min(static_cast<int>(std::log10(size) / std::log10(1024)), +                                      static_cast<int>(units.size())); +    return QStringLiteral("%L1 %2")          .arg(size / std::pow(1024, digit_groups), 0, 'f', 1) -        .arg(units[digit_groups]); +        .arg(QString::fromUtf8(units[digit_groups]));  }  QPixmap CreateCirclePixmapFromColor(const QColor& color) { | 
