diff options
-rw-r--r-- | CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/main.cpp | 4 | ||||
-rw-r--r-- | src/mainwindow.cpp | 16 | ||||
-rw-r--r-- | src/mainwindow.h | 4 | ||||
-rw-r--r-- | src/mainwindow.ui | 12 | ||||
-rw-r--r-- | src/settings_interface.cpp | 120 | ||||
-rw-r--r-- | src/settings_interface.h | 46 | ||||
-rw-r--r-- | src/settingsdialog.cpp | 106 | ||||
-rw-r--r-- | src/settingsdialog.h | 43 | ||||
-rw-r--r-- | src/settingsdialog.ui | 221 |
10 files changed, 577 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d09544..f5e6f18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,11 @@ set(PROJECT_SOURCES src/finddialog.cpp src/finddialog.h src/finddialog.ui + src/settingsdialog.cpp + src/settingsdialog.h + src/settingsdialog.ui + src/settings_interface.cpp + src/settings_interface.h src/archive/base_archive.cpp src/archive/base_archive.h src/archive/mastodon.cpp diff --git a/src/main.cpp b/src/main.cpp index 81f3024..02d36c1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,10 @@ int main(int argc, char *argv[]) { QApplication app(argc, argv); + QCoreApplication::setOrganizationName("ConfuSomu"); + QCoreApplication::setOrganizationDomain("twilightsparkle.space"); + QCoreApplication::setApplicationName("ActorViewer"); + QCommandLineParser parser; CommandLineParsedOptions result = parse_command_line(parser, app); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4fc3970..4943775 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4,6 +4,7 @@ #include "src/finddialog.h" #include "src/list_item.h" #include "src/command_line.h" +#include "src/settingsdialog.h" #include <QFileDialog> #include <QMessageBox> @@ -56,6 +57,21 @@ void MainWindow::on_actionOpen_triggered(bool checked) { } } +void MainWindow::on_actionSettings_triggered(bool checked) { + if (!settings_dialog) { + settings_dialog = new SettingsDialog(this); + connect(settings_dialog, &QDialog::finished, this, &MainWindow::settingsDialog_done); + } + settings_dialog->open(); +} + +void MainWindow::settingsDialog_done(int result) { + if (result == QDialog::Accepted) { + // TODO: reload settings + } + delete settings_dialog; settings_dialog = nullptr; +} + void MainWindow::on_actionQuit_triggered(bool checked) { QCoreApplication::quit(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index fced119..4c7e034 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -7,6 +7,7 @@ #include <variant> #include "archive/base_archive.h" +#include "src/settingsdialog.h" #include "types.h" #include "command_line.h" #include "finddialog.h" @@ -34,6 +35,7 @@ signals: private slots: void on_actionOpen_triggered(bool checked); + void on_actionSettings_triggered(bool checked); void on_actionQuit_triggered(bool checked); void on_actionAbout_triggered(bool checked); @@ -62,8 +64,10 @@ private: void finish_listWidget_itemActivated(const QString& status_info); void finish_open_file(const Archive::InitError& parse_error); + void settingsDialog_done(int result); FindDialog* find_dialog = nullptr; + SettingsDialog* settings_dialog = nullptr; QString open_file_filename; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index cd351d4..292930d 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -96,6 +96,7 @@ p, li { white-space: pre-wrap; } <string>File</string> </property> <addaction name="actionOpen"/> + <addaction name="actionSettings"/> <addaction name="actionQuit"/> </widget> <widget class="QMenu" name="menuHelp"> @@ -259,6 +260,17 @@ p, li { white-space: pre-wrap; } <string>Ctrl+F</string> </property> </action> + <action name="actionSettings"> + <property name="text"> + <string>Preferences…</string> + </property> + <property name="shortcut"> + <string>Ctrl+Shift+,</string> + </property> + <property name="menuRole"> + <enum>QAction::PreferencesRole</enum> + </property> + </action> </widget> <tabstops> <tabstop>listWidget</tabstop> diff --git a/src/settings_interface.cpp b/src/settings_interface.cpp new file mode 100644 index 0000000..e14da92 --- /dev/null +++ b/src/settings_interface.cpp @@ -0,0 +1,120 @@ +#include "settings_interface.h" +#include <QDebug> +#include <qvariant.h> + +const QVariant default_setting(const QString &key); + +SettingsInterface::~SettingsInterface() { + delete modified; modified = nullptr; +} + +// Read a setting key and return the value associated with it. +// See SettingsInterface::write_setting() for updating a key's value. +const QVariant SettingsInterface::read_setting(const QString &key) { + qDebug() << "Read value of" << key; + if (must_clear.contains(key)) + return default_setting(key); + if (modified and modified->contains(key)) + return modified->value(key); + return qt_settings.value(key, default_setting(key)); +} + +// Write a new value for key. Isn't written to disk until you use SettingsInterface::commit(). +void SettingsInterface::write_setting(const QString &key, const QVariant &value) { + if (not modified) modified = new QHash<QString, QVariant>; + modified->insert(key, value); + qDebug() << "New modified key value:" << key << value; + + // As the key has a new value it doesn't have to be cleared and thus if all keys were to be cleared it isn't true anymore + must_clear.removeOne(key); + must_clear_all = false; +} + +// Clear a setting and use the default value instead +void SettingsInterface::clear_setting(const QString &key) { + must_clear.append(key); +} + +// Clear all settings and only use the default values +void SettingsInterface::clear_all() { + // Optimization to be able to clear all settings faster + must_clear_all = true; + + // Construct a list of all settings in case one gets (written) a new value and thus must not be removed afterwards + must_clear.append(qt_settings.allKeys()); + if (modified) + must_clear.append(modified->keys()); + must_clear.removeDuplicates(); +} + +// Write the changed settings into the internal QSetting memeber. They are then written to disk. +// A return value of false indicates that an error occured while writing the config to disk. +bool SettingsInterface::commit() { + /* If everything has to be cleared, it is cleared and synced. + * If nothing has been modified or has to removed, then quits. + * If a few key(s) have to be removed they are removed, after that + * if key(s) have been modified/added they are added, then synced. + */ + + if (must_clear_all) { + qt_settings.clear(); + must_clear_all = false; + goto sync; + } else if ((not modified or (modified and modified->isEmpty())) + and must_clear.isEmpty()) + return true; + + if (not must_clear.isEmpty()) { + for (int i = 0; i < must_clear.size(); ++i) + qt_settings.remove(must_clear.at(i)); + must_clear.clear(); + } + if (not modified) goto sync; + + { + QHash<QString, QVariant>::const_iterator i = modified->constBegin(); + while (i != modified->constEnd()) { + qt_settings.setValue(i.key(), i.value()); + qDebug() << "Wrote to disk" << i.key() << i.value(); + ++i; + } + modified->clear(); + } + +sync: + qt_settings.sync(); + qDebug() << "Sync done"; + return qt_settings.status() == QSettings::NoError; +} + +// whether the key been changed from its default value. +bool SettingsInterface::is_default(const QString &key) { + return ((modified and modified->contains(key)) + or qt_settings.contains(key)); +} + +// Whether the key been modified and not yet been commited to disk. +bool SettingsInterface::is_uncommited(const QString &key) { + if (not modified) return false; + return modified->contains(key); +} + +// Return the default setting value for key. +const QVariant default_setting(const QString &key) { + // This is not ideal as a compile time created key-value store would be better. Though won't it end up with making the same code? + if (key == "ui/timezone") + return AppSettingsTypes::Timezone::LOCALTIME; + else if (key == "net/access_internet") + return true; + else if (key == "net/download_emoji") + return true; + else if (key == "net/download_attachments") + return true; + else if (key == "net/instance/type") + return AppSettingsTypes::InstanceType::MASTODON; + else if (key == "net/instance/address" + or key == "net/instance/token") + return ""; + else + return -1; +} diff --git a/src/settings_interface.h b/src/settings_interface.h new file mode 100644 index 0000000..e3935d0 --- /dev/null +++ b/src/settings_interface.h @@ -0,0 +1,46 @@ +#pragma once + +#include <QString> +#include <QVariant> +#include <QSettings> + +/* struct AppSettings { + enum {LOCALTIME, UTC} ui_timezone = LOCALTIME; + bool can_access_internet = true; + bool can_download_emoji = true; + bool can_download_attachments = true; + struct { + enum {MASTODON} type = MASTODON; + QString address; + QString token; + } instance; +}; */ + +namespace AppSettingsTypes { + enum Timezone {LOCALTIME = 0, UTC}; + enum InstanceType {MASTODON = 0}; +} +// Declare metatypes for use with QVariant +Q_DECLARE_METATYPE(AppSettingsTypes::Timezone); +Q_DECLARE_METATYPE(AppSettingsTypes::InstanceType); + +class SettingsInterface { +public: + ~SettingsInterface(); + const QVariant read_setting(const QString &key); + void write_setting(const QString &key, const QVariant &value); + void clear_setting(const QString &key); + void clear_all(); + bool commit(); + bool is_default(const QString &key); + bool is_uncommited(const QString &key); + +private: + QSettings qt_settings; + QHash<QString, QVariant>* modified = nullptr; + + // Optimization to commit a clear_all() faster + bool must_clear_all = false; + // List of keys to remove from the QSettings + QStringList must_clear; +}; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp new file mode 100644 index 0000000..0c64641 --- /dev/null +++ b/src/settingsdialog.cpp @@ -0,0 +1,106 @@ +#include "settingsdialog.h" +#include "settings_interface.h" +#include <QPushButton> +#include <QDialogButtonBox> + +SettingsDialog::SettingsDialog(QWidget* parent) + : QDialog(parent, Qt::Dialog), ui(new Ui::SettingsDialog) +{ + ui->setupUi(this); + + // Create connections for each dialog button box button, as this is the best way to do it + connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &SettingsDialog::defaults_button_clicked); + connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &SettingsDialog::apply_button_clicked); + connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &SettingsDialog::cancel_button_clicked); + + update_ui(); +} + +SettingsDialog::~SettingsDialog() { + delete ui; +} + +/* ----- General tab ----- */ + +void SettingsDialog::on_timezoneCombo_activated(int index) { + if (update_ui_in_progress) return; + settings_interface.write_setting("ui/timezone", index); +} + +/* ----- Network tab ----- */ + +void SettingsDialog::on_connectToTheInternetCheckBox_clicked(bool checked) { + if (update_ui_in_progress) return; + settings_interface.write_setting("net/access_internet", checked); +} + +void SettingsDialog::on_DownloadCustomEmojiCheckBox_clicked(bool checked) { + if (update_ui_in_progress) return; + settings_interface.write_setting("net/download_emoji", checked); +} + +void SettingsDialog::on_downloadAttachmentsCheckBox_clicked(bool checked) { + if (update_ui_in_progress) return; + settings_interface.write_setting("net/download_attachments", checked); +} + +void SettingsDialog::on_instanceTypeComboBox_activated(int index) { + if (update_ui_in_progress) return; + settings_interface.write_setting("net/instance/type", index); +} + +void SettingsDialog::on_instanceAddressLineEdit_editingFinished() { + if (update_ui_in_progress) return; + settings_interface.write_setting("net/instance/address", ui->instanceAddressLineEdit->text()); +} + +void SettingsDialog::on_tokenLineEdit_editingFinished() { + if (update_ui_in_progress) return; + settings_interface.write_setting("net/instance/token", ui->tokenLineEdit->text()); +} + +void SettingsDialog::on_instanceActionsLabel_linkActivated(const QString& link) { + if (link == "action:fill-address") { + update_ui_in_progress = true; + // TODO + update_ui_in_progress = false; + } else if (link == "action:request-token") { + // TODO + } +} + +/* ----- Dialog & setting saving/restoring ----- */ + +// OK button pressed, save and apply new settings +void SettingsDialog::apply_button_clicked() { + settings_interface.commit(); + emit QDialog::accept(); // Closes the dialog with return code +} + +// Cancel button pressed, discard new settings +void SettingsDialog::cancel_button_clicked() { + emit QDialog::reject(); // Closes the dialog with return code +} + +// Restore defaults button pressed, reset settings +void SettingsDialog::defaults_button_clicked() { + settings_interface.clear_all(); + update_ui(); +} + +// Update the UI's setting values with the values in settings_interface. +void SettingsDialog::update_ui() { + update_ui_in_progress = true; + // General tab + ui->timezoneCombo->setCurrentIndex(settings_interface.read_setting("ui/timezone").value<AppSettingsTypes::Timezone>()); + + // Network tab + ui->connectToTheInternetCheckBox->setChecked(settings_interface.read_setting("net/access_internet").value<bool>()); + ui->DownloadCustomEmojiCheckBox->setChecked(settings_interface.read_setting("net/download_emoji").value<bool>()); + ui->downloadAttachmentsCheckBox->setChecked(settings_interface.read_setting("net/download_attachments").value<bool>()); + // Instance frame + ui->instanceTypeComboBox->setCurrentIndex(settings_interface.read_setting("net/instance/type").value<AppSettingsTypes::InstanceType>()); + ui->instanceAddressLineEdit->setText(settings_interface.read_setting("net/instance/address").value<QString>()); + ui->tokenLineEdit->setText(settings_interface.read_setting("net/instance/token").value<QString>()); + update_ui_in_progress = false; +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h new file mode 100644 index 0000000..e320a08 --- /dev/null +++ b/src/settingsdialog.h @@ -0,0 +1,43 @@ +#pragma once +#include <QDialog> +#include "./ui_settingsdialog.h" +#include "settings_interface.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class SettingsDialog; } +QT_END_NAMESPACE + +class SettingsDialog : public QDialog { + Q_OBJECT + +public: + SettingsDialog(QWidget *parent = nullptr); + ~SettingsDialog(); + + static void initialize_default_settings(); + +public slots: + void defaults_button_clicked(); + void apply_button_clicked(); + void cancel_button_clicked(); + +private slots: + // General tab: + void on_timezoneCombo_activated(int index); + // Network tab: + void on_connectToTheInternetCheckBox_clicked(bool checked); + void on_DownloadCustomEmojiCheckBox_clicked(bool checked); + void on_downloadAttachmentsCheckBox_clicked(bool checked); + void on_instanceTypeComboBox_activated(int index); + void on_instanceAddressLineEdit_editingFinished(); + void on_tokenLineEdit_editingFinished(); + void on_instanceActionsLabel_linkActivated(const QString& link); + +private: + Ui::SettingsDialog* ui; + + SettingsInterface settings_interface; + bool update_ui_in_progress = false; + + void update_ui(); +}; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui new file mode 100644 index 0000000..b2c1c69 --- /dev/null +++ b/src/settingsdialog.ui @@ -0,0 +1,221 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SettingsDialog</class> + <widget class="QDialog" name="SettingsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>364</height> + </rect> + </property> + <property name="windowTitle"> + <string>Preferences</string> + </property> + <property name="modal"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="general"> + <attribute name="title"> + <string>General</string> + </attribute> + <layout class="QFormLayout" name="formLayoutWidget"> + <item row="0" column="0"> + <widget class="QLabel" name="timezoneLabel"> + <property name="text"> + <string>Time zone to use when formatting dates</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="timezoneCombo"> + <item> + <property name="text"> + <string>Local Time</string> + </property> + </item> + <item> + <property name="text"> + <string>UTC</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="network"> + <attribute name="title"> + <string>Network</string> + </attribute> + <layout class="QVBoxLayout" name="formLayoutWidget2"> + <item> + <layout class="QFormLayout" name="formLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="DownloadCustomEmojiLabel"> + <property name="text"> + <string>Download custom emoji</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="DownloadCustomEmojiCheckBox"> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="downloadAttachmentsLabel"> + <property name="text"> + <string>Download remote attachments</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="downloadAttachmentsCheckBox"> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="connectToTheInternetLabel"> + <property name="text"> + <string>Connect to the internet</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="connectToTheInternetCheckBox"> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Instance with whom to fetch posts with</string> + </property> + <layout class="QFormLayout" name="formLayout_2"> + <item row="0" column="1"> + <widget class="QComboBox" name="instanceTypeComboBox"> + <item> + <property name="text"> + <string>Mastodon</string> + </property> + </item> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="instanceTypeLabel"> + <property name="text"> + <string>Instance type</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="instanceAddressLabel"> + <property name="text"> + <string>Instance Address</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="instanceAddressLineEdit"/> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="tokenLabel"> + <property name="text"> + <string>Token</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="tokenLineEdit"/> + </item> + <item row="4" column="0" colspan="2"> + <widget class="QLabel" name="instanceActionsLabel"> + <property name="text"> + <string><html><head/><body><p>Actions: <a href="action:fill-address"><span style=" text-decoration: underline; color:#d2737f;">auto-fill address</span></a> and <a href="action:request-token"><span style=" text-decoration: underline; color:#d2737f;">request token</span></a>.</p></body></html></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::RestoreDefaults</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <tabstops> + <tabstop>tabWidget</tabstop> + <tabstop>timezoneCombo</tabstop> + <tabstop>connectToTheInternetCheckBox</tabstop> + <tabstop>DownloadCustomEmojiCheckBox</tabstop> + <tabstop>downloadAttachmentsCheckBox</tabstop> + <tabstop>instanceTypeComboBox</tabstop> + <tabstop>instanceAddressLineEdit</tabstop> + <tabstop>tokenLineEdit</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>SettingsDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>255</x> + <y>304</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>SettingsDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>323</x> + <y>304</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> |