#include "widgets/dialogs/SettingsDialog.hpp" #include "Application.hpp" #include "common/Args.hpp" #include "controllers/commands/CommandController.hpp" #include "controllers/hotkeys/HotkeyController.hpp" #include "singletons/Settings.hpp" #include "util/LayoutCreator.hpp" #include "widgets/helper/Button.hpp" #include "widgets/helper/SettingsDialogTab.hpp" #include "widgets/settingspages/AboutPage.hpp" #include "widgets/settingspages/AccountsPage.hpp" #include "widgets/settingspages/CommandPage.hpp" #include "widgets/settingspages/ExternalToolsPage.hpp" #include "widgets/settingspages/FiltersPage.hpp" #include "widgets/settingspages/GeneralPage.hpp" #include "widgets/settingspages/HighlightingPage.hpp" #include "widgets/settingspages/IgnoresPage.hpp" #include "widgets/settingspages/KeyboardSettingsPage.hpp" #include "widgets/settingspages/ModerationPage.hpp" #include "widgets/settingspages/NicknamesPage.hpp" #include "widgets/settingspages/NotificationPage.hpp" #include "widgets/settingspages/PluginsPage.hpp" #include #include #include namespace chatterino { SettingsDialog::SettingsDialog(QWidget *parent) : BaseWindow( {BaseWindow::Flags::DisableCustomScaling, BaseWindow::Flags::Dialog, BaseWindow::DisableLayoutSave, BaseWindow::DisableStyleSheet}, parent) { this->setObjectName("SettingsDialog"); this->setWindowTitle("Chatterino Settings"); // Disable the ? button in the titlebar until we decide to use it this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); this->resize(915, 600); this->themeChangedEvent(); this->scaleChangedEvent(this->scale()); this->initUi(); this->addTabs(); this->overrideBackgroundColor_ = QColor("#111111"); this->scaleChangedEvent(this->scale()); // execute twice to width of item this->addShortcuts(); this->signalHolder_.managedConnect(getApp()->hotkeys->onItemsUpdated, [this]() { this->clearShortcuts(); this->addShortcuts(); }); } void SettingsDialog::addShortcuts() { this->setSearchPlaceholderText(); HotkeyController::HotkeyMap actions{ {"search", [this](std::vector) -> QString { this->ui_.search->setFocus(); this->ui_.search->selectAll(); return ""; }}, {"delete", nullptr}, {"accept", nullptr}, {"reject", nullptr}, {"scrollPage", nullptr}, {"openTab", nullptr}, }; this->shortcuts_ = getApp()->hotkeys->shortcutsForCategory( HotkeyCategory::PopupWindow, actions, this); } void SettingsDialog::setSearchPlaceholderText() { QString searchHotkey; auto searchSeq = getApp()->hotkeys->getDisplaySequence( HotkeyCategory::PopupWindow, "search"); if (!searchSeq.isEmpty()) { searchHotkey = "(" + searchSeq.toString(QKeySequence::SequenceFormat::NativeText) + ")"; } this->ui_.search->setPlaceholderText("Find in settings... " + searchHotkey); } void SettingsDialog::initUi() { auto outerBox = LayoutCreator(this->getLayoutContainer()) .setLayoutType() .withoutSpacing(); // TOP auto title = outerBox.emplace(); auto edit = LayoutCreator(title.getElement()) .setLayoutType() .withoutMargin() .emplace() .assign(&this->ui_.search); this->setSearchPlaceholderText(); edit->setClearButtonEnabled(true); edit->findChild()->setIcon( QPixmap(":/buttons/clearSearch.png")); QObject::connect(edit.getElement(), &QLineEdit::textChanged, this, &SettingsDialog::filterElements); // CENTER auto centerBox = outerBox.emplace().withoutMargin().withoutSpacing(); // left side (tabs) centerBox.emplace() .assign(&this->ui_.tabContainerContainer) .setLayoutType() .withoutMargin() .assign(&this->ui_.tabContainer); // right side (pages) centerBox.emplace() .assign(&this->ui_.pageStack) .withoutMargin(); this->ui_.pageStack->setContentsMargins(0, 0, 0, 0); outerBox->addSpacing(12); // BOTTOM auto buttons = outerBox.emplace(Qt::Horizontal); { this->ui_.okButton = buttons->addButton("Ok", QDialogButtonBox::YesRole); this->ui_.cancelButton = buttons->addButton("Cancel", QDialogButtonBox::NoRole); } // ---- misc this->ui_.tabContainerContainer->setObjectName("tabWidget"); this->ui_.pageStack->setObjectName("pages"); QObject::connect(this->ui_.okButton, &QPushButton::clicked, this, &SettingsDialog::onOkClicked); QObject::connect(this->ui_.cancelButton, &QPushButton::clicked, this, &SettingsDialog::onCancelClicked); } void SettingsDialog::filterElements(const QString &text) { // filter elements and hide pages for (auto &&tab : this->tabs_) { // filterElements returns true if anything on the page matches the search query tab->setVisible(tab->page()->filterElements(text) || tab->name().contains(text, Qt::CaseInsensitive)); } // find next visible page if (this->lastSelectedByUser_ && this->lastSelectedByUser_->isVisible()) { this->selectTab(this->lastSelectedByUser_, false); } else if (!this->selectedTab_->isVisible()) { for (auto &&tab : this->tabs_) { if (tab->isVisible()) { this->selectTab(tab, false); break; } } } // remove duplicate spaces bool shouldShowSpace = false; for (int i = 0; i < this->ui_.tabContainer->count(); i++) { auto item = this->ui_.tabContainer->itemAt(i); if (auto x = dynamic_cast(item); x) { x->changeSize(10, shouldShowSpace ? int(16 * this->scale()) : 0); shouldShowSpace = false; } else if (item->widget()) { shouldShowSpace |= item->widget()->isVisible(); } } } void SettingsDialog::addTabs() { this->ui_.tabContainer->setSpacing(0); this->ui_.tabContainer->setContentsMargins(0, 20, 0, 20); // Constructors are wrapped in std::function to remove some strain from first time loading. // clang-format off this->addTab([]{return new GeneralPage;}, "General", ":/settings/about.svg"); this->ui_.tabContainer->addSpacing(16); this->addTab([]{return new AccountsPage;}, "Accounts", ":/settings/accounts.svg", SettingsTabId::Accounts); this->addTab([]{return new NicknamesPage;}, "Nicknames", ":/settings/accounts.svg"); this->ui_.tabContainer->addSpacing(16); this->addTab([]{return new CommandPage;}, "Commands", ":/settings/commands.svg"); this->addTab([]{return new HighlightingPage;}, "Highlights", ":/settings/notifications.svg"); this->addTab([]{return new IgnoresPage;}, "Ignores", ":/settings/ignore.svg"); this->addTab([]{return new FiltersPage;}, "Filters", ":/settings/filters.svg"); this->ui_.tabContainer->addSpacing(16); this->addTab([]{return new KeyboardSettingsPage;}, "Hotkeys", ":/settings/keybinds.svg"); this->addTab([]{return new ModerationPage;}, "Moderation", ":/settings/moderation.svg", SettingsTabId::Moderation); this->addTab([]{return new NotificationPage;}, "Live Notifications", ":/settings/notification2.svg"); this->addTab([]{return new ExternalToolsPage;}, "External tools", ":/settings/externaltools.svg"); #ifdef CHATTERINO_HAVE_PLUGINS this->addTab([]{return new PluginsPage;}, "Plugins", ":/settings/plugins.svg"); #endif this->ui_.tabContainer->addStretch(1); this->addTab([]{return new AboutPage;}, "About", ":/settings/about.svg", SettingsTabId(), Qt::AlignBottom); // clang-format on } void SettingsDialog::addTab(std::function page, const QString &name, const QString &iconPath, SettingsTabId id, Qt::Alignment alignment) { auto tab = new SettingsDialogTab(this, std::move(page), name, iconPath, id); this->ui_.tabContainer->addWidget(tab, 0, alignment); this->tabs_.push_back(tab); if (this->tabs_.size() == 1) { this->selectTab(tab); } } void SettingsDialog::selectTab(SettingsDialogTab *tab, bool byUser) { // add page if it's not been added yet [&] { for (int i = 0; i < this->ui_.pageStack->count(); i++) if (this->ui_.pageStack->itemAt(i)->widget() == tab->page()) return; this->ui_.pageStack->addWidget(tab->page()); }(); this->ui_.pageStack->setCurrentWidget(tab->page()); if (this->selectedTab_ != nullptr) { this->selectedTab_->setSelected(false); this->selectedTab_->setStyleSheet("color: #FFF"); } tab->setSelected(true); tab->setStyleSheet( "background: #222; color: #4FC3F7;" // Should this be same as accent color? "/*border: 1px solid #555; border-right: none;*/"); this->selectedTab_ = tab; if (byUser) { this->lastSelectedByUser_ = tab; } } void SettingsDialog::selectTab(SettingsTabId id) { auto t = this->tab(id); assert(t); if (!t) return; this->selectTab(t); } SettingsDialogTab *SettingsDialog::tab(SettingsTabId id) { for (auto &&tab : this->tabs_) if (tab->id() == id) return tab; assert(false); return nullptr; } void SettingsDialog::showDialog(QWidget *parent, SettingsDialogPreference preferredTab) { static SettingsDialog *instance = new SettingsDialog(parent); static bool hasShownBefore = false; if (hasShownBefore) instance->refresh(); hasShownBefore = true; switch (preferredTab) { case SettingsDialogPreference::Accounts: instance->selectTab(SettingsTabId::Accounts); break; case SettingsDialogPreference::ModerationActions: if (auto tab = instance->tab(SettingsTabId::Moderation)) { instance->selectTab(tab); if (auto page = dynamic_cast(tab->page())) { page->selectModerationActions(); } } break; default:; } instance->show(); instance->activateWindow(); instance->raise(); instance->setFocus(); } void SettingsDialog::refresh() { // Resets the cancel button. getSettings()->saveSnapshot(); // Updates tabs. for (auto *tab : this->tabs_) { tab->page()->onShow(); } } void SettingsDialog::scaleChangedEvent(float newDpi) { QFile file(":/qss/settings.qss"); file.open(QFile::ReadOnly); QString styleSheet = QLatin1String(file.readAll()); styleSheet.replace("", QString::number(int(14 * newDpi))); styleSheet.replace("", QString::number(int(14 * newDpi))); for (SettingsDialogTab *tab : this->tabs_) { tab->setFixedHeight(int(30 * newDpi)); } this->setStyleSheet(styleSheet); if (this->ui_.tabContainerContainer) this->ui_.tabContainerContainer->setFixedWidth(int(150 * newDpi)); } void SettingsDialog::themeChangedEvent() { BaseWindow::themeChangedEvent(); QPalette palette; palette.setColor(QPalette::Window, QColor("#111")); this->setPalette(palette); } void SettingsDialog::showEvent(QShowEvent *) { this->ui_.search->setText(""); } ///// Widget creation helpers void SettingsDialog::onOkClicked() { if (!getArgs().dontSaveSettings) { getApp()->commands->save(); pajlada::Settings::SettingManager::gSave(); } this->close(); } void SettingsDialog::onCancelClicked() { for (auto &tab : this->tabs_) { tab->page()->cancel(); } getSettings()->restoreSnapshot(); this->close(); } } // namespace chatterino