// Copyright 2017 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "DolphinQt/Settings/InterfacePane.h" #include #include #include #include #include #include #include #include #include #include "Common/CommonPaths.h" #include "Common/FileSearch.h" #include "Common/FileUtil.h" #include "Common/MsgHandler.h" #include "Common/StringUtil.h" #include "Core/Config/MainSettings.h" #include "Core/Config/UISettings.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/QtUtils/SignalBlocking.h" #include "DolphinQt/Settings.h" #include "UICommon/GameFile.h" static QComboBox* MakeLanguageComboBox() { static const struct { const QString name; const char* id; } languages[] = { {QStringLiteral(u"Bahasa Melayu"), "ms"}, // Malay {QStringLiteral(u"Catal\u00E0"), "ca"}, // Catalan {QStringLiteral(u"\u010Ce\u0161tina"), "cs"}, // Czech {QStringLiteral(u"Dansk"), "da"}, // Danish {QStringLiteral(u"Deutsch"), "de"}, // German {QStringLiteral(u"English"), "en"}, // English {QStringLiteral(u"Espa\u00F1ol"), "es"}, // Spanish {QStringLiteral(u"Fran\u00E7ais"), "fr"}, // French {QStringLiteral(u"Hrvatski"), "hr"}, // Croatian {QStringLiteral(u"Italiano"), "it"}, // Italian {QStringLiteral(u"Magyar"), "hu"}, // Hungarian {QStringLiteral(u"Nederlands"), "nl"}, // Dutch {QStringLiteral(u"Norsk bokm\u00E5l"), "nb"}, // Norwegian {QStringLiteral(u"Polski"), "pl"}, // Polish {QStringLiteral(u"Portugu\u00EAs"), "pt"}, // Portuguese {QStringLiteral(u"Portugu\u00EAs (Brasil)"), "pt_BR"}, // Portuguese (Brazil) {QStringLiteral(u"Rom\u00E2n\u0103"), "ro"}, // Romanian {QStringLiteral(u"Srpski"), "sr"}, // Serbian {QStringLiteral(u"Svenska"), "sv"}, // Swedish {QStringLiteral(u"T\u00FCrk\u00E7e"), "tr"}, // Turkish {QStringLiteral(u"\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC"), "el"}, // Greek {QStringLiteral(u"\u0420\u0443\u0441\u0441\u043A\u0438\u0439"), "ru"}, // Russian {QStringLiteral(u"\u0627\u0644\u0639\u0631\u0628\u064A\u0629"), "ar"}, // Arabic {QStringLiteral(u"\u0641\u0627\u0631\u0633\u06CC"), "fa"}, // Farsi {QStringLiteral(u"\uD55C\uAD6D\uC5B4"), "ko"}, // Korean {QStringLiteral(u"\u65E5\u672C\u8A9E"), "ja"}, // Japanese {QStringLiteral(u"\u7B80\u4F53\u4E2D\u6587"), "zh_CN"}, // Simplified Chinese {QStringLiteral(u"\u7E41\u9AD4\u4E2D\u6587"), "zh_TW"}, // Traditional Chinese }; auto* combobox = new QComboBox(); combobox->addItem(QObject::tr(""), QString{}); for (const auto& lang : languages) combobox->addItem(lang.name, QString::fromLatin1(lang.id)); // The default, QComboBox::AdjustToContentsOnFirstShow, causes a noticeable pause when opening the // SettingWindow for the first time. The culprit seems to be non-Latin graphemes in the above // list. QComboBox::AdjustToContents still has some lag but it's much less noticeable. combobox->setSizeAdjustPolicy(QComboBox::AdjustToContents); return combobox; } InterfacePane::InterfacePane(QWidget* parent) : QWidget(parent) { CreateLayout(); LoadConfig(); ConnectLayout(); } void InterfacePane::CreateLayout() { m_main_layout = new QVBoxLayout; // Create layout here CreateUI(); CreateInGame(); m_main_layout->addStretch(1); setLayout(m_main_layout); } void InterfacePane::CreateUI() { auto* groupbox = new QGroupBox(tr("User Interface")); auto* groupbox_layout = new QVBoxLayout; groupbox->setLayout(groupbox_layout); m_main_layout->addWidget(groupbox); auto* combobox_layout = new QFormLayout; combobox_layout->setFormAlignment(Qt::AlignLeft | Qt::AlignTop); combobox_layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); groupbox_layout->addLayout(combobox_layout); m_combobox_language = MakeLanguageComboBox(); combobox_layout->addRow(tr("&Language:"), m_combobox_language); // Theme Combobox m_combobox_theme = new QComboBox; combobox_layout->addRow(tr("&Theme:"), m_combobox_theme); // List avalable themes auto theme_search_results = Common::DoFileSearch({File::GetUserPath(D_THEMES_IDX), File::GetSysDirectory() + THEMES_DIR}); for (const std::string& path : theme_search_results) { const QString qt_name = QString::fromStdString(PathToFileName(path)); m_combobox_theme->addItem(qt_name); } // User Style Combobox m_combobox_userstyle = new QComboBox; m_label_userstyle = new QLabel(tr("User Style:")); combobox_layout->addRow(m_label_userstyle, m_combobox_userstyle); auto userstyle_search_results = Common::DoFileSearch({File::GetUserPath(D_STYLES_IDX)}); m_combobox_userstyle->addItem(tr("(None)"), QString{}); for (const std::string& path : userstyle_search_results) { const QFileInfo file_info(QString::fromStdString(path)); m_combobox_userstyle->addItem(file_info.completeBaseName(), file_info.fileName()); } // Checkboxes m_checkbox_use_builtin_title_database = new QCheckBox(tr("Use Built-In Database of Game Names")); m_checkbox_use_userstyle = new QCheckBox(tr("Use Custom User Style")); m_checkbox_use_covers = new QCheckBox(tr("Download Game Covers from GameTDB.com for Use in Grid Mode")); m_checkbox_show_debugging_ui = new QCheckBox(tr("Show Debugging UI")); m_checkbox_focused_hotkeys = new QCheckBox(tr("Hotkeys Require Window Focus")); m_checkbox_disable_screensaver = new QCheckBox(tr("Inhibit Screensaver During Emulation")); groupbox_layout->addWidget(m_checkbox_use_builtin_title_database); groupbox_layout->addWidget(m_checkbox_use_userstyle); groupbox_layout->addWidget(m_checkbox_use_covers); groupbox_layout->addWidget(m_checkbox_show_debugging_ui); groupbox_layout->addWidget(m_checkbox_focused_hotkeys); groupbox_layout->addWidget(m_checkbox_disable_screensaver); } void InterfacePane::CreateInGame() { auto* groupbox = new QGroupBox(tr("Render Window")); auto* groupbox_layout = new QVBoxLayout; groupbox->setLayout(groupbox_layout); m_main_layout->addWidget(groupbox); m_checkbox_top_window = new QCheckBox(tr("Keep Window on Top")); m_checkbox_confirm_on_stop = new QCheckBox(tr("Confirm on Stop")); m_checkbox_use_panic_handlers = new QCheckBox(tr("Use Panic Handlers")); m_checkbox_enable_osd = new QCheckBox(tr("Show On-Screen Display Messages")); m_checkbox_show_active_title = new QCheckBox(tr("Show Active Title in Window Title")); m_checkbox_pause_on_focus_lost = new QCheckBox(tr("Pause on Focus Loss")); auto* mouse_groupbox = new QGroupBox(tr("Mouse Cursor Visibility")); auto* m_vboxlayout_hide_mouse = new QVBoxLayout; mouse_groupbox->setLayout(m_vboxlayout_hide_mouse); m_radio_cursor_visible_movement = new QRadioButton(tr("On Movement")); m_radio_cursor_visible_movement->setToolTip( tr("Mouse Cursor hides after inactivity and returns upon Mouse Cursor movement.")); m_radio_cursor_visible_never = new QRadioButton(tr("Never")); m_radio_cursor_visible_never->setToolTip( tr("Mouse Cursor will never be visible while a game is running.")); m_radio_cursor_visible_always = new QRadioButton(tr("Always")); m_radio_cursor_visible_always->setToolTip(tr("Mouse Cursor will always be visible.")); m_vboxlayout_hide_mouse->addWidget(m_radio_cursor_visible_movement); m_vboxlayout_hide_mouse->addWidget(m_radio_cursor_visible_never); m_vboxlayout_hide_mouse->addWidget(m_radio_cursor_visible_always); m_checkbox_lock_mouse = new QCheckBox(tr("Lock Mouse Cursor")); m_checkbox_lock_mouse->setToolTip(tr("Will lock the Mouse Cursor to the Render Widget as long as " "it has focus. You can set a hotkey to unlock it.")); mouse_groupbox->setLayout(m_vboxlayout_hide_mouse); groupbox_layout->addWidget(m_checkbox_top_window); groupbox_layout->addWidget(m_checkbox_confirm_on_stop); groupbox_layout->addWidget(m_checkbox_use_panic_handlers); groupbox_layout->addWidget(m_checkbox_enable_osd); groupbox_layout->addWidget(m_checkbox_show_active_title); groupbox_layout->addWidget(m_checkbox_pause_on_focus_lost); groupbox_layout->addWidget(mouse_groupbox); #ifdef _WIN32 groupbox_layout->addWidget(m_checkbox_lock_mouse); #endif } void InterfacePane::ConnectLayout() { connect(m_checkbox_use_builtin_title_database, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_use_covers, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_disable_screensaver, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_show_debugging_ui, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_focused_hotkeys, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_combobox_theme, qOverload(&QComboBox::currentIndexChanged), this, [=](int index) { Settings::Instance().SetThemeName(m_combobox_theme->itemText(index)); }); connect(m_combobox_userstyle, qOverload(&QComboBox::currentIndexChanged), this, &InterfacePane::OnSaveConfig); connect(m_combobox_language, qOverload(&QComboBox::currentIndexChanged), this, &InterfacePane::OnSaveConfig); connect(m_checkbox_top_window, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_confirm_on_stop, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_use_panic_handlers, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_show_active_title, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_enable_osd, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_pause_on_focus_lost, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_radio_cursor_visible_movement, &QRadioButton::toggled, this, &InterfacePane::OnCursorVisibleMovement); connect(m_radio_cursor_visible_never, &QRadioButton::toggled, this, &InterfacePane::OnCursorVisibleNever); connect(m_radio_cursor_visible_always, &QRadioButton::toggled, this, &InterfacePane::OnCursorVisibleAlways); connect(m_checkbox_lock_mouse, &QCheckBox::toggled, &Settings::Instance(), &Settings::SetLockCursor); connect(m_checkbox_use_userstyle, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); } void InterfacePane::LoadConfig() { SignalBlocking(m_checkbox_use_builtin_title_database) ->setChecked(Config::Get(Config::MAIN_USE_BUILT_IN_TITLE_DATABASE)); SignalBlocking(m_checkbox_show_debugging_ui) ->setChecked(Settings::Instance().IsDebugModeEnabled()); SignalBlocking(m_combobox_language) ->setCurrentIndex(m_combobox_language->findData( QString::fromStdString(Config::Get(Config::MAIN_INTERFACE_LANGUAGE)))); SignalBlocking(m_combobox_theme) ->setCurrentIndex( m_combobox_theme->findText(QString::fromStdString(Config::Get(Config::MAIN_THEME_NAME)))); const QString userstyle = Settings::Instance().GetCurrentUserStyle(); const int index = m_combobox_userstyle->findData(QFileInfo(userstyle).fileName()); if (index > 0) SignalBlocking(m_combobox_userstyle)->setCurrentIndex(index); SignalBlocking(m_checkbox_use_userstyle)->setChecked(Settings::Instance().AreUserStylesEnabled()); const bool visible = m_checkbox_use_userstyle->isChecked(); m_combobox_userstyle->setVisible(visible); m_label_userstyle->setVisible(visible); // Render Window Options SignalBlocking(m_checkbox_top_window) ->setChecked(Settings::Instance().IsKeepWindowOnTopEnabled()); SignalBlocking(m_checkbox_confirm_on_stop)->setChecked(Config::Get(Config::MAIN_CONFIRM_ON_STOP)); SignalBlocking(m_checkbox_use_panic_handlers) ->setChecked(Config::Get(Config::MAIN_USE_PANIC_HANDLERS)); SignalBlocking(m_checkbox_enable_osd)->setChecked(Config::Get(Config::MAIN_OSD_MESSAGES)); SignalBlocking(m_checkbox_show_active_title) ->setChecked(Config::Get(Config::MAIN_SHOW_ACTIVE_TITLE)); SignalBlocking(m_checkbox_pause_on_focus_lost) ->setChecked(Config::Get(Config::MAIN_PAUSE_ON_FOCUS_LOST)); SignalBlocking(m_checkbox_use_covers)->setChecked(Config::Get(Config::MAIN_USE_GAME_COVERS)); SignalBlocking(m_checkbox_focused_hotkeys)->setChecked(Config::Get(Config::MAIN_FOCUSED_HOTKEYS)); SignalBlocking(m_radio_cursor_visible_movement) ->setChecked(Settings::Instance().GetCursorVisibility() == Config::ShowCursor::OnMovement); SignalBlocking(m_radio_cursor_visible_always) ->setChecked(Settings::Instance().GetCursorVisibility() == Config::ShowCursor::Constantly); SignalBlocking(m_radio_cursor_visible_never) ->setChecked(Settings::Instance().GetCursorVisibility() == Config::ShowCursor::Never); SignalBlocking(m_checkbox_lock_mouse)->setChecked(Settings::Instance().GetLockCursor()); SignalBlocking(m_checkbox_disable_screensaver) ->setChecked(Config::Get(Config::MAIN_DISABLE_SCREENSAVER)); } void InterfacePane::OnSaveConfig() { Config::SetBase(Config::MAIN_USE_BUILT_IN_TITLE_DATABASE, m_checkbox_use_builtin_title_database->isChecked()); Settings::Instance().SetDebugModeEnabled(m_checkbox_show_debugging_ui->isChecked()); Settings::Instance().SetUserStylesEnabled(m_checkbox_use_userstyle->isChecked()); Settings::Instance().SetCurrentUserStyle(m_combobox_userstyle->currentData().toString()); const bool visible = m_checkbox_use_userstyle->isChecked(); m_combobox_userstyle->setVisible(visible); m_label_userstyle->setVisible(visible); // Render Window Options Settings::Instance().SetKeepWindowOnTop(m_checkbox_top_window->isChecked()); Config::SetBase(Config::MAIN_CONFIRM_ON_STOP, m_checkbox_confirm_on_stop->isChecked()); Config::SetBase(Config::MAIN_USE_PANIC_HANDLERS, m_checkbox_use_panic_handlers->isChecked()); Config::SetBase(Config::MAIN_OSD_MESSAGES, m_checkbox_enable_osd->isChecked()); Config::SetBase(Config::MAIN_SHOW_ACTIVE_TITLE, m_checkbox_show_active_title->isChecked()); Config::SetBase(Config::MAIN_PAUSE_ON_FOCUS_LOST, m_checkbox_pause_on_focus_lost->isChecked()); Common::SetEnableAlert(Config::Get(Config::MAIN_USE_PANIC_HANDLERS)); auto new_language = m_combobox_language->currentData().toString().toStdString(); if (new_language != Config::Get(Config::MAIN_INTERFACE_LANGUAGE)) { Config::SetBase(Config::MAIN_INTERFACE_LANGUAGE, new_language); ModalMessageBox::information( this, tr("Restart Required"), tr("You must restart Dolphin in order for the change to take effect.")); } const bool use_covers = m_checkbox_use_covers->isChecked(); if (use_covers != Config::Get(Config::MAIN_USE_GAME_COVERS)) { Config::SetBase(Config::MAIN_USE_GAME_COVERS, use_covers); Settings::Instance().RefreshMetadata(); } Config::SetBase(Config::MAIN_FOCUSED_HOTKEYS, m_checkbox_focused_hotkeys->isChecked()); Config::SetBase(Config::MAIN_DISABLE_SCREENSAVER, m_checkbox_disable_screensaver->isChecked()); Config::Save(); } void InterfacePane::OnCursorVisibleMovement() { Settings::Instance().SetCursorVisibility(Config::ShowCursor::OnMovement); } void InterfacePane::OnCursorVisibleNever() { Settings::Instance().SetCursorVisibility(Config::ShowCursor::Never); } void InterfacePane::OnCursorVisibleAlways() { Settings::Instance().SetCursorVisibility(Config::ShowCursor::Constantly); }