From c5ed279da0e74adb79c6c2a3e485cb9668b1c130 Mon Sep 17 00:00:00 2001 From: aqua Date: Wed, 24 Aug 2022 16:38:49 +0300 Subject: SettingsDialog: save settings when changed - connect Restore Defaults button --- scripts/rekonf.py | 93 +++++++++++++++++++------------------ src/application.cpp | 9 ++++ src/main.cpp | 5 ++ src/settings/CMakeLists.txt | 2 +- src/settings/settingsdialog.cpp | 36 ++++++++++++-- src/settings/settingsdialog.h | 5 ++ src/settings/settingsdialog.ui | 32 ------------- src/settings/settingswidget.cpp | 81 ++++++++++++++++++++++++++++++++ src/settings/settingswidgets.hpp | 29 ++++-------- src/settings/test/test_settings.cpp | 37 +++++++++++---- 10 files changed, 219 insertions(+), 110 deletions(-) create mode 100644 src/settings/settingswidget.cpp diff --git a/scripts/rekonf.py b/scripts/rekonf.py index 48706c3d..941c29c7 100755 --- a/scripts/rekonf.py +++ b/scripts/rekonf.py @@ -13,56 +13,59 @@ import sys from xml.etree import ElementTree -def write_int_entry(entry): - '''Add a QSpinBox connected to an Int entry''' - obj = entry.attrib['key'] - default = entry.find('{*}default').text +def write_int_entry(obj, name, default): + """Add a QSpinBox connected to an Int (obj, name, default)""" print(f' auto* { obj } = new QSpinBox(this);') print(f' { obj }->setValue(s->value("{ obj }", { default }).toInt());') - print(f' formLayout->addRow(tr("{ entry.attrib["name"] }"), { obj });') + print(f' connect({ obj }, &QSpinBox::valueChanged, this, [this]() {{ emit changed(); }});') + print(f' formLayout->addRow(tr("{ name }"), { obj });') -def write_bool_entry(entry): - '''Add a QCheckBox connected to a Bool entry''' - obj = entry.attrib['key'] - default = entry.find('{*}default').text - print(f' auto* { obj } = new QCheckBox(tr("{ entry.attrib["name"] }"), this);') +def write_bool_entry(obj, name, default): + """Add a QCheckBox connected to a Bool (obj, name, default)""" + print(f' auto* { obj } = new QCheckBox(tr("{ name }"), this);') print(f' { obj }->setChecked(s->value("{ obj }", { default }).toBool());') + print(f' connect({ obj }, &QCheckBox::stateChanged, this, [this]() {{ emit changed(); }});') print(f' formLayout->addRow(QString(), { obj });') -def write_string_entry(entry): - '''Add a QLineEdit connected to a String entry''' - obj = entry.attrib['key'] - default = entry.find('{*}default').text +def write_string_entry(obj, name, default): + """Add a QLineEdit connected to a String (obj, name, default)""" print(f' auto* {obj} = new QLineEdit(this);') - print(f' {obj}->setText(s->value("{ obj }", "{ default }").toString());') - print(f' formLayout->addRow(tr("{ entry.attrib["name"] }"), { obj });') + print(f' { obj }->setText(s->value("{ obj }", "{ default }").toString());') + print(f' connect({ obj }, &QLineEdit::textChanged, this, [this]() {{ emit changed(); }});') + print(f' formLayout->addRow(tr("{ name }"), { obj });') -def write_font_entry(entry): - '''Add a QFontComboBox connected to a Font entry''' - obj = entry.attrib['key'] - default = entry.find('{*}default').text +def write_font_entry(obj, name, default): + """Add a QFontComboBox connected to a Font (obj, name, default)""" print(f' auto* { obj } = new QFontComboBox(this);') print(f' { obj }->setCurrentFont(s->value("{ obj }", "{ default }").toString());') - print(f' formLayout->addRow(tr("{ entry.attrib["name"] }"), { obj });') + print(f' connect({ obj }, &QFontComboBox::currentFontChanged, this, [this]() {{ emit changed(); }});') + print(f' formLayout->addRow(tr("{ name }"), { obj });') -def write_shortcut_entry(entry): - '''Add a QKeySequenceEdit connected to a Shortcut entry''' - obj = entry.attrib['key'] - default = entry.find('{*}default').text - print(f' auto* { entry.attrib["key"] } = new QKeySequenceEdit(this);') +def write_shortcut_entry(obj, name, default): + """Add a QKeySequenceEdit connected to a Shortcut (obj, name, default)""" + print(f' auto* { obj } = new QKeySequenceEdit(this);') print(f' { obj }->setKeySequence(s->value("{ obj }", "{ default }").toString());') - print(f' formLayout->addRow(tr("{ entry.attrib["name"] }"), { obj });') + print(f' connect({ obj }, &QKeySequenceEdit::keySequenceChanged, this, [this]() {{ emit changed(); }});') + print(f' formLayout->addRow(tr("{ name }"), { obj });') def generate_group_widget(root, group): - '''Generate a class based on the group name''' + """Generate a class based on the group name""" class_group = group.attrib["name"].replace(' ', '') class_name = group.attrib["name"].replace(' ', '') + 'SettingsWidget' + write_entry_fn = { + 'Int': write_int_entry, + 'Bool': write_bool_entry, + 'String': write_string_entry, + 'Font': write_font_entry, + 'Shortcut': write_shortcut_entry + } + # includes print('// Includes') print('#include "settingswidgets.hpp"') @@ -78,8 +81,11 @@ def generate_group_widget(root, group): print(f'#include <{ include.text }>') print('') + # ctor print(f'{ class_name }::{ class_name }(RekonqSettings *s, QWidget *parent) : SettingsWidget(s, parent) {{') - print(f' s->beginGroup("{ class_group }");') + print(f' setObjectName("{ class_group }");') + if class_group != 'General': + print(f' s->beginGroup("{ class_group }");') print(' auto *formLayout = new QFormLayout;') print(' setLayout(formLayout);') print('') @@ -88,28 +94,22 @@ def generate_group_widget(root, group): for entry in group.findall('{http://www.kde.org/standards/kcfg/1.0}entry'): if entry.attrib.get("hidden") == "true": print(f' // hidden entry { entry.attrib.get("name") }') - elif entry.attrib['type'] == 'Int': - write_int_entry(entry) - elif entry.attrib['type'] == 'Bool': - write_bool_entry(entry) - elif entry.attrib['type'] == 'String': - write_string_entry(entry) - elif entry.attrib['type'] == 'Font': - write_font_entry(entry) - elif entry.attrib['type'] == 'Shortcut': - write_shortcut_entry(entry) else: - print(f'#error entry with unknown type { entry.attrib["type"] }') + obj = entry.attrib['key'] + name = entry.attrib["name"] + default = entry.find('{*}default').text + write_entry_fn.get(entry.attrib['type'])(obj, name, default) + print(f' { obj }->setObjectName("{ obj }");') + print(f' { obj }->setProperty("defaultValue", "{ default }");') print('') - print(' s->endGroup();') + if class_group != 'General': + print(' s->endGroup();') print('}\n') - print(f'void { class_name }::save() {{ }}') - print(f'void { class_name }::reset() {{ }}') - -def generate_group_ini(root, group): +def generate_group_ini(group): + """Generate settings in ini format for group""" group_name = group.attrib["name"].replace(' ', '') print(f'[{ group_name }]') for entry in group.findall('{http://www.kde.org/standards/kcfg/1.0}entry'): @@ -122,6 +122,7 @@ def generate_group_ini(root, group): def main(): + """main function""" parser = argparse.ArgumentParser(description='Generate SettingsWidgets') parser.add_argument('file', type=str, help='kcfg file') parser.add_argument('--group', type=str, required=True, help='Group') @@ -134,7 +135,7 @@ def main(): if args.group == 'all': for group in root.findall('{http://www.kde.org/standards/kcfg/1.0}group'): - generate_group_ini(root, group) + generate_group_ini(group) else: for group in root.findall('{http://www.kde.org/standards/kcfg/1.0}group'): if group.attrib["name"] == args.group: diff --git a/src/application.cpp b/src/application.cpp index 814bfab8..a2031de4 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -14,6 +14,7 @@ #include "plugins/rplugininterface.hpp" #include "rekonqwindow.h" #include "settings/settings.hpp" +#include "settings/settingsdialog.h" #include #include #include @@ -33,6 +34,14 @@ Application::Application(int &argc, char *argv[]) : SingleApplication(argc, argv spdlog::info("Loading rekonq configuration: {}", qUtf8Printable(settingsPath)); m_settings = new Settings(settingsPath, this); + if (m_settings->value("FirstRun", true).toBool()) { + spdlog::info("First run"); + if ((new SettingsDialog(m_settings, nullptr))->exec()) { + spdlog::info("new settings saved"); + m_settings->setValue("FirstRun", false); + } + } + // load default plugins for (const auto &location : QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation)) { QDir dir(location + "/plugins"); diff --git a/src/main.cpp b/src/main.cpp index 1d6e30da..51e68716 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,9 +10,14 @@ * ============================================================ */ #include "application.hpp" +#include int main(int argc, char **argv) { +#ifdef QT_DEBUG + spdlog::set_level(spdlog::level::debug); +#endif + // When loading QtWebEngine from a plugin, set Qt::AA_ShareOpenGLContexts using QCoreApplication::setAttribute QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); diff --git a/src/settings/CMakeLists.txt b/src/settings/CMakeLists.txt index f597cfb9..29a011c1 100644 --- a/src/settings/CMakeLists.txt +++ b/src/settings/CMakeLists.txt @@ -18,7 +18,7 @@ add_custom_command(OUTPUT shortcutssettingswidget.cpp DEPENDS ${PROJECT_SOURCE_D add_library(settings STATIC ${PROJECT_SOURCE_DIR}/include/rsettings.hpp settings.cpp settings.hpp settingsdialog.cpp settingsdialog.h settingsdialog.ui - settingswidgets.hpp helpers.hpp + settingswidgets.hpp settingswidget.cpp helpers.hpp generalsettingswidget.cpp appearancesettingswidget.cpp networksettingswidget.cpp shortcutssettingswidget.cpp ) target_link_libraries(settings PUBLIC Qt6::Widgets) diff --git a/src/settings/settingsdialog.cpp b/src/settings/settingsdialog.cpp index 0c54856e..68fa7a26 100644 --- a/src/settings/settingsdialog.cpp +++ b/src/settings/settingsdialog.cpp @@ -13,17 +13,45 @@ #include "settingsdialog.h" #include "settingswidgets.hpp" #include "ui_settingsdialog.h" +#include SettingsDialog::SettingsDialog(RekonqSettings *settings, QWidget *parent) : QDialog(parent), ui(new Ui::SettingsDialog) { ui->setupUi(this); + saveBtn = ui->buttonBox->button(QDialogButtonBox::Save); + Q_CHECK_PTR(saveBtn); + restoreDefaultsBtn = ui->buttonBox->button(QDialogButtonBox::RestoreDefaults); + Q_CHECK_PTR(restoreDefaultsBtn); - ui->stackedWidget->addWidget(new GeneralSettingsWidget(settings, this)); - ui->stackedWidget->addWidget(new AppearanceSettingsWidget(settings, this)); - ui->stackedWidget->addWidget(new NetworkSettingsWidget(settings, this)); - ui->stackedWidget->addWidget(new ShortcutsSettingsWidget(settings, this)); + if (!settings->value("FirstRun", true).toBool()) saveBtn->setEnabled(false); + + addPage(new GeneralSettingsWidget(settings, this)); + addPage(new AppearanceSettingsWidget(settings, this)); + addPage(new NetworkSettingsWidget(settings, this)); + addPage(new ShortcutsSettingsWidget(settings, this)); connect(ui->listWidget, &QListWidget::currentRowChanged, ui->stackedWidget, &QStackedWidget::setCurrentIndex); + + connect(restoreDefaultsBtn, &QPushButton::clicked, this, [this]() { + for (auto *w : ui->stackedWidget->findChildren(QString(), Qt::FindDirectChildrenOnly)) { + w->reset(); + } + }); + + connect(this, &QDialog::accepted, this, [this]() { + for (auto *w : ui->stackedWidget->findChildren(QString(), Qt::FindDirectChildrenOnly)) { + w->save(); + } + }); } SettingsDialog::~SettingsDialog() { delete ui; } + +void SettingsDialog::addPage(SettingsWidget *page) +{ + auto *item = new QListWidgetItem(page->objectName(), ui->listWidget); + item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + ui->stackedWidget->addWidget(page); + + connect(page, &SettingsWidget::changed, this, [this]() { saveBtn->setEnabled(true); }); +} \ No newline at end of file diff --git a/src/settings/settingsdialog.h b/src/settings/settingsdialog.h index 0c793f9e..433857b3 100644 --- a/src/settings/settingsdialog.h +++ b/src/settings/settingsdialog.h @@ -15,6 +15,7 @@ #include class RekonqSettings; +class SettingsWidget; namespace Ui { class SettingsDialog; @@ -28,5 +29,9 @@ public: ~SettingsDialog() override; private: + void addPage(SettingsWidget *page); + Ui::SettingsDialog *ui; + QPushButton *saveBtn; + QPushButton *restoreDefaultsBtn; }; diff --git a/src/settings/settingsdialog.ui b/src/settings/settingsdialog.ui index 1c19380d..14ab30ef 100644 --- a/src/settings/settingsdialog.ui +++ b/src/settings/settingsdialog.ui @@ -33,38 +33,6 @@ Qt::AlignVCenter - - - General - - - AlignCenter - - - - - Appearance - - - AlignCenter - - - - - Network - - - AlignCenter - - - - - Shortcuts - - - AlignCenter - - diff --git a/src/settings/settingswidget.cpp b/src/settings/settingswidget.cpp new file mode 100644 index 00000000..b5a2432c --- /dev/null +++ b/src/settings/settingswidget.cpp @@ -0,0 +1,81 @@ +#include "settingswidgets.hpp" + +#include +#include +#include +#include +#include + +void SettingsWidget::save() +{ + if (objectName() != QLatin1String("General")) m_settings->beginGroup(objectName()); + + // Int + for (const auto *spinbox : findChildren(QString(), Qt::FindDirectChildrenOnly)) { + m_settings->setValue(spinbox->objectName(), spinbox->value()); + } + + // Bool + for (const auto *checkbox : findChildren(QString(), Qt::FindDirectChildrenOnly)) { + m_settings->setValue(checkbox->objectName(), checkbox->isChecked()); + } + + // String + for (const auto *lineedit : findChildren(QString(), Qt::FindDirectChildrenOnly)) { + m_settings->setValue(lineedit->objectName(), lineedit->text()); + } + + // Font + for (const auto *font : findChildren(QString(), Qt::FindDirectChildrenOnly)) { + m_settings->setValue(font->objectName(), font->currentFont().family()); + } + + // Shortcut + for (const auto *shortcut : findChildren(QString(), Qt::FindDirectChildrenOnly)) { + m_settings->setValue(shortcut->objectName(), shortcut->keySequence()); + } + + if (objectName() != QLatin1String("General")) m_settings->endGroup(); +} + +void SettingsWidget::reset() +{ + if (objectName() != QLatin1String("General")) m_settings->beginGroup(objectName()); + + // Int + for (auto *spinbox : findChildren(QString(), Qt::FindDirectChildrenOnly)) { + const auto value = spinbox->property("defaultValue"); + spinbox->setValue(value.toInt()); + m_settings->setValue(spinbox->objectName(), value); + } + + // Bool + for (auto *checkbox : findChildren(QString(), Qt::FindDirectChildrenOnly)) { + const auto value = checkbox->property("defaultValue"); + checkbox->setChecked(value.toBool()); + m_settings->setValue(checkbox->objectName(), value); + } + + // String + for (auto *lineedit : findChildren(QString(), Qt::FindDirectChildrenOnly)) { + const auto value = lineedit->property("defaultValue"); + lineedit->setText(value.toString()); + m_settings->setValue(lineedit->objectName(), value); + } + + // Font + for (auto *font : findChildren(QString(), Qt::FindDirectChildrenOnly)) { + const auto value = font->property("defaultValue"); + font->setFont(QFont(value.toString())); + m_settings->setValue(font->objectName(), value); + } + + // Shortcut + for (auto *shortcut : findChildren(QString(), Qt::FindDirectChildrenOnly)) { + const auto value = shortcut->property("defaultValue"); + shortcut->setKeySequence(value.toString()); + m_settings->setValue(shortcut->objectName(), value); + } + + if (objectName() != QLatin1String("General")) m_settings->endGroup(); +} \ No newline at end of file diff --git a/src/settings/settingswidgets.hpp b/src/settings/settingswidgets.hpp index 27ebe8a2..25fbe0a4 100644 --- a/src/settings/settingswidgets.hpp +++ b/src/settings/settingswidgets.hpp @@ -16,51 +16,42 @@ class SettingsWidget : public QWidget { Q_OBJECT public: - explicit SettingsWidget(RekonqSettings *, QWidget *parent = nullptr) : QWidget(parent) {} + explicit SettingsWidget(RekonqSettings *settings, QWidget *parent = nullptr) : QWidget(parent) + { + m_settings = settings; + } signals: void changed(); + public slots: - virtual void save() = 0; - virtual void reset() = 0; + void save(); + void reset(); + +protected: + RekonqSettings *m_settings; }; class GeneralSettingsWidget final : public SettingsWidget { Q_OBJECT public: explicit GeneralSettingsWidget(RekonqSettings *, QWidget *parent = nullptr); - -public slots: - void save() override; - void reset() override; }; class AppearanceSettingsWidget final : public SettingsWidget { Q_OBJECT public: explicit AppearanceSettingsWidget(RekonqSettings *, QWidget *parent = nullptr); - -public slots: - void save() override; - void reset() override; }; class NetworkSettingsWidget final : public SettingsWidget { Q_OBJECT public: explicit NetworkSettingsWidget(RekonqSettings *, QWidget *parent = nullptr); - -public slots: - void save() override; - void reset() override; }; class ShortcutsSettingsWidget final : public SettingsWidget { Q_OBJECT public: explicit ShortcutsSettingsWidget(RekonqSettings *, QWidget *parent = nullptr); - -public slots: - void save() override; - void reset() override; }; diff --git a/src/settings/test/test_settings.cpp b/src/settings/test/test_settings.cpp index 2ca5ba1a..fdb976f6 100644 --- a/src/settings/test/test_settings.cpp +++ b/src/settings/test/test_settings.cpp @@ -7,22 +7,34 @@ #include #include +#include #include #include -#include - // Software under Test #include "../helpers.hpp" #include "../settings.hpp" #include "../settingsdialog.h" #include "settings_mock.hpp" +using ::testing::_; using ::testing::AtLeast; +using ::testing::ContainerEq; using ::testing::ReturnArg; const char *settingsFile = nullptr; +MATCHER_P(QStringEq, a, "") +{ + *result_listener << "where the arg is " << qUtf8Printable(arg); + return arg.compare(a) == 0; +} +MATCHER_P(QVariantEq, a, "") +{ + *result_listener << "where the arg is " << qUtf8Printable(arg.toString()); + return arg.toString().compare(a) == 0; +} + TEST(settings, getFont) { const auto serif = getFont(QFont::Serif); @@ -60,12 +72,21 @@ TEST(settings, Settings) TEST(settings, SettingsDialog) { - MockSettings settings; - EXPECT_CALL(settings, beginGroup).Times(4); - EXPECT_CALL(settings, endGroup).Times(4); - EXPECT_CALL(settings, value).Times(AtLeast(4)).WillRepeatedly(ReturnArg<1>()); - - SettingsDialog dlg(&settings); + MockSettings mockSettings; + // There are 4 groups in total, but General should not be calling beginGroup/endGroup + // beginGroup/endGroup are called twice: during the ctor and when accepted + EXPECT_CALL(mockSettings, beginGroup).Times(3 * 2); + EXPECT_CALL(mockSettings, endGroup).Times(3 * 2); + // There are 35 settings in total, one of which is hidden and won't be set by the dialog + EXPECT_CALL(mockSettings, value).Times(35).WillRepeatedly(ReturnArg<1>()); + EXPECT_CALL(mockSettings, setValue(_, _)).Times(33); + EXPECT_CALL(mockSettings, setValue(QStringEq("homepage"), QVariantEq("about:blank"))); + + SettingsDialog dlg(&mockSettings); + auto *homepage = dlg.findChild("homepage"); + EXPECT_TRUE(homepage->text() == QLatin1String("http://www.kde.org/")) << qUtf8Printable(homepage->text()); + homepage->setText("about:blank"); + dlg.accept(); } int main(int argc, char **argv) -- cgit v1.2.1