diff options
| author | Felix (xq) Queißner <git@mq32.de> | 2020-06-22 21:10:04 +0200 |
|---|---|---|
| committer | Felix (xq) Queißner <git@mq32.de> | 2020-06-22 21:10:04 +0200 |
| commit | 75ec461eeaa851cb5c53f4cfffc434e3e529ed1d (patch) | |
| tree | 3944737340718ca3675381aa06636045d397e780 /src/dialogs | |
| parent | 8dbfb0890560fd1cd698d06fa05ac868c4db8576 (diff) | |
| download | kristall-75ec461eeaa851cb5c53f4cfffc434e3e529ed1d.tar.gz | |
Restructures the project source and cleans up a bit
Diffstat (limited to 'src/dialogs')
| -rw-r--r-- | src/dialogs/certificateiodialog.cpp | 126 | ||||
| -rw-r--r-- | src/dialogs/certificateiodialog.hpp | 50 | ||||
| -rw-r--r-- | src/dialogs/certificateiodialog.ui | 127 | ||||
| -rw-r--r-- | src/dialogs/certificatemanagementdialog.cpp | 308 | ||||
| -rw-r--r-- | src/dialogs/certificatemanagementdialog.hpp | 45 | ||||
| -rw-r--r-- | src/dialogs/certificatemanagementdialog.ui | 294 | ||||
| -rw-r--r-- | src/dialogs/certificateselectiondialog.cpp | 130 | ||||
| -rw-r--r-- | src/dialogs/certificateselectiondialog.hpp | 54 | ||||
| -rw-r--r-- | src/dialogs/certificateselectiondialog.ui | 172 | ||||
| -rw-r--r-- | src/dialogs/newidentitiydialog.cpp | 81 | ||||
| -rw-r--r-- | src/dialogs/newidentitiydialog.hpp | 41 | ||||
| -rw-r--r-- | src/dialogs/newidentitiydialog.ui | 118 | ||||
| -rw-r--r-- | src/dialogs/settingsdialog.cpp | 613 | ||||
| -rw-r--r-- | src/dialogs/settingsdialog.hpp | 143 | ||||
| -rw-r--r-- | src/dialogs/settingsdialog.ui | 940 |
15 files changed, 3242 insertions, 0 deletions
diff --git a/src/dialogs/certificateiodialog.cpp b/src/dialogs/certificateiodialog.cpp new file mode 100644 index 0000000..8c9fcd5 --- /dev/null +++ b/src/dialogs/certificateiodialog.cpp @@ -0,0 +1,126 @@ +#include "certificateiodialog.hpp" +#include "ui_certificateiodialog.h" + +#include <QFileDialog> +#include <QPushButton> +#include <QSsl> + +CertificateIoDialog::CertificateIoDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CertificateIoDialog) +{ + ui->setupUi(this); + + this->ui->key_type->clear(); + this->ui->key_type->addItem("RSA", QVariant::fromValue<int>(QSsl::Rsa)); + this->ui->key_type->addItem("ECDSA", QVariant::fromValue<int>(QSsl::Ec)); + + this->updateUI(); +} + +CertificateIoDialog::~CertificateIoDialog() +{ + delete ui; +} + +void CertificateIoDialog::setIoMode(CertificateIoDialog::IoMode mode) +{ + this->current_mode = mode; + if(mode == Export) { + this->setWindowTitle(tr("Export Certificate")); + } else { + this->setWindowTitle(tr("Import Certificate")); + } + this->ui->key_type->setEnabled(mode == Import); + this->updateUI(); +} + +QSsl::KeyAlgorithm CertificateIoDialog::keyAlgorithm() const +{ + return QSsl::KeyAlgorithm(this->ui->key_type->currentData().toInt()); +} + +void CertificateIoDialog::setKeyAlgorithm(QSsl::KeyAlgorithm alg) +{ + this->ui->key_type->setCurrentIndex(-1); + for(int i = 0; i< this->ui->key_type->count(); i++) { + if(this->ui->key_type->itemData(i).toInt() == int(alg)) { + this->ui->key_type->setCurrentIndex(i); + break; + } + } +} + +QString CertificateIoDialog::keyFileName() const +{ + return this->ui->key_file_name->text(); +} + +QString CertificateIoDialog::certificateFileName() const +{ + return this->ui->certificate_file_name->text(); +} + +void CertificateIoDialog::on_select_certificate_file_button_clicked() +{ + QFileDialog dialog { this }; + + dialog.setNameFilter("Certificate File(*.pem *.der)"); + dialog.setAcceptMode((this->current_mode == Export) ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen); + dialog.selectFile(this->ui->certificate_file_name->text()); + + if(dialog.exec() != QDialog::Accepted) + return; + + this->ui->certificate_file_name->setText(dialog.selectedFiles().first()); + + this->updateUI(); +} + +void CertificateIoDialog::on_select_key_file_button_clicked() +{ + QFileDialog dialog { this }; + + dialog.setNameFilter("Certificate File(*.pem *.der)"); + dialog.setAcceptMode((this->current_mode == Export) ? QFileDialog::AcceptSave : QFileDialog::AcceptOpen); + dialog.selectFile(this->ui->key_file_name->text()); + + if(dialog.exec() != QDialog::Accepted) + return; + + this->ui->key_file_name->setText(dialog.selectedFiles().first()); + + this->updateUI(); +} + +void CertificateIoDialog::on_certificate_file_name_textChanged(const QString &arg1) +{ + Q_UNUSED(arg1) + this->updateUI(); +} + +void CertificateIoDialog::on_key_file_name_textChanged(const QString &arg1) +{ + Q_UNUSED(arg1) + this->updateUI(); +} + +void CertificateIoDialog::updateUI() +{ + QString cert_file_name = certificateFileName(); + QString key_file_name = keyFileName(); + + bool ok = true; + + ok &= (cert_file_name.endsWith(".pem") or cert_file_name.endsWith(".der")); + ok &= (key_file_name.endsWith(".pem") or key_file_name.endsWith(".der")); + + ok &= (this->ui->key_type->currentIndex() >= 0); + + if(current_mode == Import) { + ok &= QFile(cert_file_name).exists(); + ok &= QFile(key_file_name).exists(); + } + + this->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(ok); +} diff --git a/src/dialogs/certificateiodialog.hpp b/src/dialogs/certificateiodialog.hpp new file mode 100644 index 0000000..590b282 --- /dev/null +++ b/src/dialogs/certificateiodialog.hpp @@ -0,0 +1,50 @@ +#ifndef CERTIFICATEIODIALOG_HPP +#define CERTIFICATEIODIALOG_HPP + +#include <QDialog> +#include <QSsl> + +namespace Ui { +class CertificateIoDialog; +} + +class CertificateIoDialog : public QDialog +{ + Q_OBJECT +public: + enum IoMode { + Import, + Export + }; +public: + explicit CertificateIoDialog(QWidget *parent = nullptr); + ~CertificateIoDialog(); + + IoMode mode() const { return this->current_mode; } + void setIoMode(IoMode mode); + + QSsl::KeyAlgorithm keyAlgorithm() const; + void setKeyAlgorithm(QSsl::KeyAlgorithm alg); + + QString keyFileName() const; + QString certificateFileName() const; + +private slots: + void on_select_certificate_file_button_clicked(); + + void on_select_key_file_button_clicked(); + + void on_certificate_file_name_textChanged(const QString &arg1); + + void on_key_file_name_textChanged(const QString &arg1); + +private: + void updateUI(); + +private: + Ui::CertificateIoDialog *ui; + + IoMode current_mode; +}; + +#endif // CERTIFICATEIODIALOG_HPP diff --git a/src/dialogs/certificateiodialog.ui b/src/dialogs/certificateiodialog.ui new file mode 100644 index 0000000..074df28 --- /dev/null +++ b/src/dialogs/certificateiodialog.ui @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CertificateIoDialog</class> + <widget class="QDialog" name="CertificateIoDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>411</width> + <height>160</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Key Type</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="key_type"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="editable"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Key File</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLineEdit" name="key_file_name"/> + </item> + <item> + <widget class="QToolButton" name="select_key_file_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Certificate File</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLineEdit" name="certificate_file_name"/> + </item> + <item> + <widget class="QToolButton" name="select_certificate_file_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CertificateIoDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>CertificateIoDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/dialogs/certificatemanagementdialog.cpp b/src/dialogs/certificatemanagementdialog.cpp new file mode 100644 index 0000000..5141b30 --- /dev/null +++ b/src/dialogs/certificatemanagementdialog.cpp @@ -0,0 +1,308 @@ +#include "certificatemanagementdialog.hpp" +#include "ui_certificatemanagementdialog.h" + +#include "kristall.hpp" + +#include "newidentitiydialog.hpp" +#include "certificateiodialog.hpp" +#include "ioutil.hpp" + +#include <QCryptographicHash> +#include <QMessageBox> + +CertificateManagementDialog::CertificateManagementDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CertificateManagementDialog), + selected_identity { nullptr } +{ + ui->setupUi(this); + + this->ui->certificates->setModel(&global_identities); + this->ui->certificates->expandAll(); + + connect( + this->ui->certificates->selectionModel(), + &QItemSelectionModel::currentChanged, + this, + &CertificateManagementDialog::on_certificates_selected + ); + on_certificates_selected(QModelIndex { }, QModelIndex { }); +} + +CertificateManagementDialog::~CertificateManagementDialog() +{ + delete ui; +} + +void CertificateManagementDialog::on_certificates_selected(QModelIndex const& index, QModelIndex const & previous) +{ + Q_UNUSED(previous); + + selected_identity = global_identities.getMutableIdentity(index); + + this->ui->export_cert_button->setEnabled(selected_identity != nullptr); + + if(selected_identity != nullptr) + { + auto & cert = *selected_identity; + this->ui->groupBox->setEnabled(true); + this->ui->cert_display_name->setText(cert.display_name); + this->ui->cert_common_name->setText(cert.certificate.subjectInfo(QSslCertificate::CommonName).join(", ")); + this->ui->cert_expiration_date->setDateTime(cert.certificate.expiryDate()); + this->ui->cert_livetime->setText(QString("%1 days").arg(QDateTime::currentDateTime().daysTo(cert.certificate.expiryDate()))); + this->ui->cert_fingerprint->setPlainText(toFingerprintString(cert.certificate)); + this->ui->cert_notes->setPlainText(cert.user_notes); + + this->ui->cert_host_filter->setText(cert.host_filter); + this->ui->cert_auto_enable->setEnabled(not cert.host_filter.isEmpty()); + this->ui->cert_auto_enable->setChecked(cert.auto_enable); + + this->ui->delete_cert_button->setEnabled(true); + } + else + { + this->ui->groupBox->setEnabled(false); + this->ui->cert_display_name->setText(""); + this->ui->cert_common_name->setText(""); + this->ui->cert_expiration_date->setDateTime(QDateTime { }); + this->ui->cert_livetime->setText(""); + this->ui->cert_fingerprint->setPlainText(""); + this->ui->cert_host_filter->setText(""); + this->ui->cert_auto_enable->setChecked(false); + + if(auto group_name = global_identities.group(index); not group_name.isEmpty()) { + this->ui->delete_cert_button->setEnabled(global_identities.canDeleteGroup(group_name)); + } else { + this->ui->delete_cert_button->setEnabled(false); + } + } +} + +void CertificateManagementDialog::on_cert_notes_textChanged() +{ + if(this->selected_identity != nullptr) { + this->selected_identity->user_notes = this->ui->cert_notes->toPlainText(); + } +} + +void CertificateManagementDialog::on_cert_display_name_textChanged(const QString &arg1) +{ + Q_UNUSED(arg1) + if(this->selected_identity != nullptr) { + this->selected_identity->display_name = this->ui->cert_display_name->text(); + } +} + +void CertificateManagementDialog::on_delete_cert_button_clicked() +{ + auto index = this->ui->certificates->currentIndex(); + + if(global_identities.getMutableIdentity(index) != nullptr) + { + auto answer = QMessageBox::question( + this, + "Kristall", + "Do you really want to delete this certificate?\r\n\r\nYou will not be able to restore the identity after this!", + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No + ); + if(answer != QMessageBox::Yes) + return; + if(not global_identities.destroyIdentity(index)) { + QMessageBox::warning(this, "Kristall", "Could not destroy identity!"); + } + } + else if(auto group_name = global_identities.group(index); not group_name.isEmpty()) { + + auto answer = QMessageBox::question( + this, + "Kristall", + QString("Do you want to delete the group '%1'").arg(group_name) + ); + if(answer != QMessageBox::Yes) + return; + + if(not global_identities.deleteGroup(group_name)) { + QMessageBox::warning(this, "Kristall", "Could not delete group!"); + } + } +} + +void CertificateManagementDialog::on_export_cert_button_clicked() +{ + if(this->selected_identity == nullptr) + return; + CertificateIoDialog dialog { this }; + + dialog.setKeyAlgorithm(this->selected_identity->private_key.algorithm()); + dialog.setIoMode(CertificateIoDialog::Export); + + if(dialog.exec() != QDialog::Accepted) + return; + + { + QFile cert_file { dialog.certificateFileName() }; + if(not cert_file.open(QFile::WriteOnly)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be found!").arg(dialog.certificateFileName()) + ); + return; + } + + QByteArray cert_blob; + if(dialog.certificateFileName().endsWith(".der")) { + cert_blob = this->selected_identity->certificate.toDer(); + } else { + cert_blob = this->selected_identity->certificate.toPem(); + } + + if(not IoUtil::writeAll(cert_file, cert_blob)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be created found!").arg(dialog.certificateFileName()) + ); + return; + } + } + + { + QFile key_file { dialog.keyFileName() }; + if(not key_file.open(QFile::WriteOnly)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be found!").arg(dialog.keyFileName()) + ); + return; + } + + QByteArray key_blob; + if(dialog.keyFileName().endsWith(".der")) { + key_blob = this->selected_identity->private_key.toDer(); + } else { + key_blob = this->selected_identity->private_key.toPem(); + } + + if(not IoUtil::writeAll(key_file, key_blob)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be created found!").arg(dialog.keyFileName()) + ); + return; + } + } +} + +void CertificateManagementDialog::on_import_cert_button_clicked() +{ + CertificateIoDialog dialog { this }; + + dialog.setIoMode(CertificateIoDialog::Import); + + if(dialog.exec() != QDialog::Accepted) + return; + + QFile cert_file { dialog.certificateFileName() }; + if(not cert_file.open(QFile::ReadOnly)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be found!").arg(dialog.certificateFileName()) + ); + return; + } + + QFile key_file { dialog.keyFileName() }; + if(not key_file.open(QFile::ReadOnly)) { + QMessageBox::warning( + this, + "Kristall", + tr("The file %1 could not be found!").arg(dialog.keyFileName()) + ); + return; + } + + CryptoIdentity ident; + ident.private_key = QSslKey { + &key_file, + dialog.keyAlgorithm(), + dialog.keyFileName().endsWith(".der") ? QSsl::Der : QSsl::Pem, + QSsl::PrivateKey + }; + ident.certificate = QSslCertificate { + &cert_file, + dialog.keyFileName().endsWith(".der") ? QSsl::Der : QSsl::Pem, + }; + ident.user_notes = tr("Imported from:\r\nkey: %1\r\n:cert: %2").arg(dialog.keyFileName()).arg(dialog.certificateFileName()); + ident.display_name = "Imported Certificate"; + ident.auto_enable = false; + ident.host_filter = ""; + ident.is_persistent = true; + + if(ident.private_key.isNull()) { + QMessageBox::warning( + this, + "Kristall", + tr("The key file %1 could not be loaded. Please verify your key file.").arg(dialog.keyFileName()) + ); + return; + } + + if(ident.certificate.isNull()) { + QMessageBox::warning( + this, + "Kristall", + tr("The certificate file %1 could not be loaded. Please verify your certificate.").arg(dialog.keyFileName()) + ); + return; + } + + if(not global_identities.addCertificate(tr("Imported Certificates"), ident)) { + QMessageBox::warning( + this, + "Kristall", + tr("Failed to import the certificate.") + ); + } +} + +void CertificateManagementDialog::on_create_cert_button_clicked() +{ + NewIdentitiyDialog dialog { this }; + + dialog.setGroupName(global_identities.group(this->ui->certificates->currentIndex())); + + if(dialog.exec() != QDialog::Accepted) + return; + + auto id = dialog.createIdentity(); + if(not id.isValid()) + return; + id.is_persistent = true; + + global_identities.addCertificate( + dialog.groupName(), + id); +} + +void CertificateManagementDialog::on_cert_host_filter_textChanged(const QString &host_filter) +{ + if(this->selected_identity != nullptr) { + this->ui->cert_auto_enable->setEnabled(not host_filter.isEmpty()); + this->selected_identity->host_filter = host_filter; + } else { + this->ui->cert_auto_enable->setEnabled(false); + } + +} + +void CertificateManagementDialog::on_cert_auto_enable_clicked(bool checked) +{ + if(this->selected_identity != nullptr) { + this->selected_identity->auto_enable = checked; + } +} diff --git a/src/dialogs/certificatemanagementdialog.hpp b/src/dialogs/certificatemanagementdialog.hpp new file mode 100644 index 0000000..0d7178a --- /dev/null +++ b/src/dialogs/certificatemanagementdialog.hpp @@ -0,0 +1,45 @@ +#ifndef CERTIFICATEMANAGEMENTDIALOG_HPP +#define CERTIFICATEMANAGEMENTDIALOG_HPP + +#include <QDialog> + +#include "cryptoidentity.hpp" + +namespace Ui { +class CertificateManagementDialog; +} + +class CertificateManagementDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CertificateManagementDialog(QWidget *parent = nullptr); + ~CertificateManagementDialog(); + +private slots: + void on_cert_notes_textChanged(); + + void on_cert_display_name_textChanged(const QString &arg1); + + void on_delete_cert_button_clicked(); + + void on_export_cert_button_clicked(); + + void on_import_cert_button_clicked(); + + void on_create_cert_button_clicked(); + + void on_cert_host_filter_textChanged(const QString &arg1); + + void on_cert_auto_enable_clicked(bool checked); + +private: + void on_certificates_selected(const QModelIndex &index, QModelIndex const & previous); +private: + Ui::CertificateManagementDialog *ui; + + CryptoIdentity * selected_identity; +}; + +#endif // CERTIFICATEMANAGEMENTDIALOG_HPP diff --git a/src/dialogs/certificatemanagementdialog.ui b/src/dialogs/certificatemanagementdialog.ui new file mode 100644 index 0000000..82dd788 --- /dev/null +++ b/src/dialogs/certificatemanagementdialog.ui @@ -0,0 +1,294 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CertificateManagementDialog</class> + <widget class="QDialog" name="CertificateManagementDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>731</width> + <height>480</height> + </rect> + </property> + <property name="windowTitle"> + <string>Certificate Manager</string> + </property> + <property name="windowIcon"> + <iconset theme="certificate"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="rightMargin"> + <number>0</number> + </property> + <item> + <widget class="QTreeView" name="certificates"> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="dragEnabled"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::DragDrop</enum> + </property> + <property name="defaultDropAction"> + <enum>Qt::MoveAction</enum> + </property> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QToolButton" name="create_cert_button"> + <property name="toolTip"> + <string>Craete new certificate</string> + </property> + <property name="text"> + <string>Create...</string> + </property> + <property name="icon"> + <iconset theme="create-new"/> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="import_cert_button"> + <property name="toolTip"> + <string>Import certificate</string> + </property> + <property name="text"> + <string>Import...</string> + </property> + <property name="icon"> + <iconset theme="save-import"/> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="export_cert_button"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>Export certificate</string> + </property> + <property name="text"> + <string>Export...</string> + </property> + <property name="icon"> + <iconset theme="save-export"/> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="delete_cert_button"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>Delete certificate</string> + </property> + <property name="text"> + <string>Delete</string> + </property> + <property name="icon"> + <iconset theme="delete-alert"/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="title"> + <string>Certificate</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Display Name</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="cert_display_name"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Common Name</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="cert_common_name"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Expiration Date</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QDateTimeEdit" name="cert_expiration_date"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Expires in</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="cert_livetime"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>??? days</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Fingerprint</string> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Notes</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QPlainTextEdit" name="cert_fingerprint"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QPlainTextEdit" name="cert_notes"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Host Filter</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="cert_host_filter"> + <property name="placeholderText"> + <string>gemini://*</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="cert_auto_enable"> + <property name="toolTip"> + <string>If this is checked, Kristall will automatically enable this certificate when visiting a URL matching the host filter</string> + </property> + <property name="text"> + <string>Auto-Enable Certificate</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CertificateManagementDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>CertificateManagementDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/dialogs/certificateselectiondialog.cpp b/src/dialogs/certificateselectiondialog.cpp new file mode 100644 index 0000000..f4dab38 --- /dev/null +++ b/src/dialogs/certificateselectiondialog.cpp @@ -0,0 +1,130 @@ +#include "certificateselectiondialog.hpp" +#include "ui_certificateselectiondialog.h" + +#include "certificatehelper.hpp" +#include "kristall.hpp" +#include "newidentitiydialog.hpp" + +#include <random> +#include <QDebug> +#include <QItemSelectionModel> + +CertificateSelectionDialog::CertificateSelectionDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CertificateSelectionDialog) +{ + ui->setupUi(this); + this->ui->server_request->setVisible(false); + + this->ui->certificates->setModel(&global_identities); + this->ui->certificates->expandAll(); + + connect(this->ui->certificates->selectionModel(), &QItemSelectionModel::currentChanged, this, &CertificateSelectionDialog::on_currentChanged); +} + +CertificateSelectionDialog::~CertificateSelectionDialog() +{ + delete ui; +} + +void CertificateSelectionDialog::setServerQuery(const QString &query) +{ + this->ui->server_request->setText(query); + this->ui->server_request->setVisible(not query.isEmpty()); +} + +CryptoIdentity CertificateSelectionDialog::identity() const +{ + return cryto_identity; +} + +void CertificateSelectionDialog::on_use_temp_cert_30m_clicked() +{ + acceptTemporaryWithTimeout(QDateTime::currentDateTime().addSecs(1800 * 12)); +} + +void CertificateSelectionDialog::on_use_temp_cert_1h_clicked() +{ + acceptTemporaryWithTimeout(QDateTime::currentDateTime().addSecs(3600)); +} + +void CertificateSelectionDialog::on_use_temp_cert_12h_clicked() +{ + acceptTemporaryWithTimeout(QDateTime::currentDateTime().addSecs(3600 * 12)); +} + +void CertificateSelectionDialog::on_use_temp_cert_24h_clicked() +{ + acceptTemporaryWithTimeout(QDateTime::currentDateTime().addDays(1)); +} + +void CertificateSelectionDialog::on_use_temp_cert_48h_clicked() +{ + acceptTemporaryWithTimeout(QDateTime::currentDateTime().addDays(2)); +} + +void CertificateSelectionDialog::acceptTemporaryWithTimeout(QDateTime timeout) +{ + std::default_random_engine rng; + rng.seed(QDateTime::currentDateTime().toMSecsSinceEpoch()); + + std::uniform_int_distribution<int> distr(0, 255); + + char items[8]; + for(auto & c : items) { + c = distr(rng); + } + + this->cryto_identity = CertificateHelper::createNewIdentity( + QByteArray(items, sizeof items).toBase64(QByteArray::OmitTrailingEquals), + timeout); + + this->accept(); +} + +void CertificateSelectionDialog::on_currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_UNUSED(current) + Q_UNUSED(previous) + auto id = global_identities.getIdentity(current); + + this->ui->use_selected_cert->setEnabled(id.isValid()); +} + +void CertificateSelectionDialog::on_create_new_cert_clicked() +{ + NewIdentitiyDialog dialog { this }; + + if(dialog.exec() != QDialog::Accepted) + return; + + auto id = dialog.createIdentity(); + if(not id.isValid()) + return; + id.is_persistent = true; + + global_identities.addCertificate( + dialog.groupName(), + id); +} + +void CertificateSelectionDialog::on_use_selected_cert_clicked() +{ + auto sel = this->ui->certificates->selectionModel()->currentIndex(); + this->cryto_identity = global_identities.getIdentity(sel); + if(this->cryto_identity.isValid()) { + this->accept(); + } else { + qDebug() << "Tried to use an invalid identity when the button should not be enabled. This is a bug!"; + } +} + +void CertificateSelectionDialog::on_certificates_doubleClicked(const QModelIndex &index) +{ + this->cryto_identity = global_identities.getIdentity(index); + if(this->cryto_identity.isValid()) { + this->accept(); + } else { + qDebug() << "Tried to use an invalid identity when the button should not be enabled. This is a bug!"; + } +} diff --git a/src/dialogs/certificateselectiondialog.hpp b/src/dialogs/certificateselectiondialog.hpp new file mode 100644 index 0000000..e628f5c --- /dev/null +++ b/src/dialogs/certificateselectiondialog.hpp @@ -0,0 +1,54 @@ +#ifndef CERTIFICATESELECTIONDIALOG_HPP +#define CERTIFICATESELECTIONDIALOG_HPP + +#include <QDialog> + +#include "cryptoidentity.hpp" + +namespace Ui { +class CertificateSelectionDialog; +} + +class CertificateSelectionDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CertificateSelectionDialog(QWidget *parent = nullptr); + ~CertificateSelectionDialog(); + + void setServerQuery(QString const & query); + + CryptoIdentity identity() const; + +private slots: + void on_use_temp_cert_30m_clicked(); + + void on_use_temp_cert_1h_clicked(); + + void on_use_temp_cert_12h_clicked(); + + void on_use_temp_cert_24h_clicked(); + + void on_use_temp_cert_48h_clicked(); + + void on_create_new_cert_clicked(); + + void on_use_selected_cert_clicked(); + + void on_certificates_doubleClicked(const QModelIndex &index); + +private: + //! Creates an anonymous identity with a randomly chosen name that + //! will time out on `timeout`, then accepts the dialog. + void acceptTemporaryWithTimeout(QDateTime timeout); + + + void on_currentChanged(const QModelIndex ¤t, const QModelIndex &previous); +private: + Ui::CertificateSelectionDialog *ui; + + CryptoIdentity cryto_identity; +}; + +#endif // CERTIFICATESELECTIONDIALOG_HPP diff --git a/src/dialogs/certificateselectiondialog.ui b/src/dialogs/certificateselectiondialog.ui new file mode 100644 index 0000000..d6128f9 --- /dev/null +++ b/src/dialogs/certificateselectiondialog.ui @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CertificateSelectionDialog</class> + <widget class="QDialog" name="CertificateSelectionDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>474</width> + <height>355</height> + </rect> + </property> + <property name="windowTitle"> + <string>Select client certificate</string> + </property> + <property name="windowIcon"> + <iconset resource="icons.qrc"> + <normaloff>:/icons/certificate.svg</normaloff>:/icons/certificate.svg</iconset> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Select existing certificate:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="server_request"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QTreeView" name="certificates"> + <property name="headerHidden"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="create_new_cert"> + <property name="text"> + <string>Create new identity</string> + </property> + <property name="icon"> + <iconset resource="icons.qrc"> + <normaloff>:/icons/plus.svg</normaloff>:/icons/plus.svg</iconset> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="use_selected_cert"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Use</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Create transient session certificate:</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="use_temp_cert_30m"> + <property name="text"> + <string>30 Minutes</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="use_temp_cert_1h"> + <property name="text"> + <string>1 Hour</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="use_temp_cert_12h"> + <property name="text"> + <string>12 Hours</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="use_temp_cert_24h"> + <property name="text"> + <string>24 Hours</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="use_temp_cert_48h"> + <property name="text"> + <string>48 Hours</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="icons.qrc"/> + </resources> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>CertificateSelectionDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>236</x> + <y>333</y> + </hint> + <hint type="destinationlabel"> + <x>236</x> + <y>177</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/dialogs/newidentitiydialog.cpp b/src/dialogs/newidentitiydialog.cpp new file mode 100644 index 0000000..af2a97e --- /dev/null +++ b/src/dialogs/newidentitiydialog.cpp @@ -0,0 +1,81 @@ +#include "newidentitiydialog.hpp" +#include "ui_newidentitiydialog.h" + +#include "certificatehelper.hpp" +#include "kristall.hpp" + +#include <QPushButton> +#include <QDebug> + +NewIdentitiyDialog::NewIdentitiyDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::NewIdentitiyDialog) +{ + ui->setupUi(this); + + ui->display_name->setText("Unnamed"); + ui->common_name->setText("Unnamed"); + ui->expiration_date->setDate(QDate::currentDate().addYears(1)); + ui->expiration_date->setTime(QTime(12, 00)); + + ui->group->clear(); + for(auto group_name : global_identities.groups()) + { + ui->group->addItem(group_name); + } +} + +NewIdentitiyDialog::~NewIdentitiyDialog() +{ + delete ui; +} + +CryptoIdentity NewIdentitiyDialog::createIdentity() const +{ + auto id = CertificateHelper::createNewIdentity( + this->ui->common_name->text(), + this->ui->expiration_date->dateTime() + ); + id.display_name = this->ui->display_name->text(); + return id; +} + +QString NewIdentitiyDialog::groupName() const +{ + return this->ui->group->currentText(); +} + +void NewIdentitiyDialog::setGroupName(const QString &name) +{ + this->ui->group->setCurrentText(name); +} + +void NewIdentitiyDialog::updateUI() +{ + bool is_ok = true; + + is_ok &= (not this->ui->group->currentText().isEmpty()); + is_ok &= (not this->ui->common_name->text().isEmpty()); + is_ok &= (not this->ui->display_name->text().isEmpty()); + is_ok &= (this->ui->expiration_date->dateTime() > QDateTime::currentDateTime()); + + this->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(is_ok); +} + +void NewIdentitiyDialog::on_group_editTextChanged(const QString &arg1) +{ + Q_UNUSED(arg1); + this->updateUI(); +} + +void NewIdentitiyDialog::on_display_name_textChanged(const QString &arg1) +{ + Q_UNUSED(arg1); + this->updateUI(); +} + +void NewIdentitiyDialog::on_common_name_textChanged(const QString &arg1) +{ + Q_UNUSED(arg1); + this->updateUI(); +} diff --git a/src/dialogs/newidentitiydialog.hpp b/src/dialogs/newidentitiydialog.hpp new file mode 100644 index 0000000..83d740b --- /dev/null +++ b/src/dialogs/newidentitiydialog.hpp @@ -0,0 +1,41 @@ +#ifndef NEWIDENTITIYDIALOG_HPP +#define NEWIDENTITIYDIALOG_HPP + +#include <QDialog> + +#include "cryptoidentity.hpp" + +namespace Ui { +class NewIdentitiyDialog; +} + +class NewIdentitiyDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewIdentitiyDialog(QWidget *parent = nullptr); + ~NewIdentitiyDialog(); + + //! Creates a new identity from the currently set + //! user settings. + CryptoIdentity createIdentity() const; + + QString groupName() const; + void setGroupName(QString const & name); + +private slots: + void on_group_editTextChanged(const QString &arg1); + + void on_display_name_textChanged(const QString &arg1); + + void on_common_name_textChanged(const QString &arg1); + +private: + void updateUI(); + +private: + Ui::NewIdentitiyDialog *ui; +}; + +#endif // NEWIDENTITIYDIALOG_HPP diff --git a/src/dialogs/newidentitiydialog.ui b/src/dialogs/newidentitiydialog.ui new file mode 100644 index 0000000..556ac93 --- /dev/null +++ b/src/dialogs/newidentitiydialog.ui @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>NewIdentitiyDialog</class> + <widget class="QDialog" name="NewIdentitiyDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>328</width> + <height>191</height> + </rect> + </property> + <property name="windowTitle"> + <string>Create new certificate</string> + </property> + <property name="windowIcon"> + <iconset resource="icons.qrc"> + <normaloff>:/icons/certificate.svg</normaloff>:/icons/certificate.svg</iconset> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QFormLayout" name="formLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Display Name</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Expiration Date</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="display_name"/> + </item> + <item row="3" column="1"> + <widget class="QDateTimeEdit" name="expiration_date"/> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="common_name"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Common Name</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Group</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="group"> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="icons.qrc"/> + </resources> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>NewIdentitiyDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>NewIdentitiyDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/dialogs/settingsdialog.cpp b/src/dialogs/settingsdialog.cpp new file mode 100644 index 0000000..ebd1664 --- /dev/null +++ b/src/dialogs/settingsdialog.cpp @@ -0,0 +1,613 @@ +#include "settingsdialog.hpp" +#include "ui_settingsdialog.h" +#include <QFontDialog> +#include <QColorDialog> +#include <QStyle> +#include <QSettings> +#include <QInputDialog> +#include <QFileDialog> +#include <QMessageBox> +#include <QDebug> + +#include <cassert> + +#include "kristall.hpp" + +SettingsDialog::SettingsDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SettingsDialog), + current_style() +{ + ui->setupUi(this); + + static_assert(DocumentStyle::Fixed == 0); + static_assert(DocumentStyle::AutoDarkTheme == 1); + static_assert(DocumentStyle::AutoLightTheme == 2); + + this->ui->auto_theme->clear(); + this->ui->auto_theme->addItem(tr("Disabled"), QVariant::fromValue<int>(DocumentStyle::Fixed)); + this->ui->auto_theme->addItem(tr("Dark Theme"), QVariant::fromValue<int>(DocumentStyle::AutoDarkTheme)); + this->ui->auto_theme->addItem(tr("Light Theme"), QVariant::fromValue<int>(DocumentStyle::AutoLightTheme)); + + this->ui->ui_theme->clear(); + this->ui->ui_theme->addItem(tr("OS Default"), QVariant::fromValue<int>(int(Theme::os_default))); + this->ui->ui_theme->addItem(tr("Light"), QVariant::fromValue<int>(int(Theme::light))); + this->ui->ui_theme->addItem(tr("Dark"), QVariant::fromValue<int>(int(Theme::dark))); + + setGeminiStyle(DocumentStyle { }); + + int items = global_settings.beginReadArray("Themes"); + + this->predefined_styles.clear(); + for(int i = 0; i < items; i++) + { + global_settings.setArrayIndex(i); + + QString name = global_settings.value("name").toString(); + + DocumentStyle style; + style.load(global_settings); + + this->predefined_styles.insert(name, style); + } + + global_settings.endArray(); + + this->ui->presets->clear(); + for(auto const & style_name : this->predefined_styles.keys()) + { + this->ui->presets->addItem(style_name); + } + + if(items > 0) { + on_presets_currentIndexChanged(0); + } else { + this->on_presets_currentIndexChanged(-1); + } + + this->ui->redirection_mode->clear(); + this->ui->redirection_mode->addItem("Ask for cross-scheme or cross-host redirection", int(GenericSettings::WarnOnHostChange | GenericSettings::WarnOnSchemeChange)); + this->ui->redirection_mode->addItem("Ask for cross-scheme redirection", int(GenericSettings::WarnOnSchemeChange)); + this->ui->redirection_mode->addItem("Ask for cross-host redirection", int(GenericSettings::WarnOnHostChange)); + this->ui->redirection_mode->addItem("Ask for all redirection", int(GenericSettings::WarnAlways)); + this->ui->redirection_mode->addItem("Silently redirect everything", int(GenericSettings::WarnNever)); +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +static QString formatFont(QFont const & font) +{ + QString style; + if(font.italic() and font.bold()) + style = "bold, italic"; + else if(font.italic()) + style = "italic"; + else if(font.bold()) + style = "bold"; + else + style = "regular"; + + return QString("%1 (%2pt, %3)") + .arg(font.family()) + .arg(font.pointSizeF()) + .arg(style); +} + +void SettingsDialog::setGeminiStyle(DocumentStyle const &style) +{ + static const QString COLOR_STYLE("border: 1px solid black; padding: 4px; background-color : %1; color : %2;"); + + this->current_style = style; + + this->ui->auto_theme->setCurrentIndex(this->current_style.theme); + + this->ui->page_margin->setValue(this->current_style.margin); + + auto setFontAndColor = [this](QLabel * label, QFont font, QColor color) + { + label->setText(formatFont(font)); + label->setStyleSheet(COLOR_STYLE + .arg(this->current_style.background_color.name()) + .arg(color.name())); + }; + + ui->bg_preview->setStyleSheet(COLOR_STYLE + .arg(this->current_style.background_color.name()) + .arg("#FF00FF")); + + ui->quote_preview->setStyleSheet(COLOR_STYLE + .arg(this->current_style.blockquote_color.name()) + .arg("#FF00FF")); + + ui->link_local_preview->setStyleSheet(COLOR_STYLE + .arg(this->current_style.background_color.name()) + .arg(this->current_style.internal_link_color.name())); + + ui->link_foreign_preview->setStyleSheet(COLOR_STYLE + .arg(this->current_style.background_color.name()) + .arg(this->current_style.external_link_color.name())); + + ui->link_cross_preview->setStyleSheet(COLOR_STYLE + .arg(this->current_style.background_color.name()) + .arg(this->current_style.cross_scheme_link_color.name())); + + setFontAndColor(this->ui->std_preview, this->current_style.standard_font, this->current_style.standard_color); + setFontAndColor(this->ui->pre_preview, this->current_style.preformatted_font, this->current_style.preformatted_color); + setFontAndColor(this->ui->h1_preview, this->current_style.h1_font, this->current_style.h1_color); + setFontAndColor(this->ui->h2_preview, this->current_style.h2_font, this->current_style.h2_color); + setFontAndColor(this->ui->h3_preview, this->current_style.h3_font, this->current_style.h3_color); + + this->reloadStylePreview(); +} + +ProtocolSetup SettingsDialog::protocols() const +{ + ProtocolSetup protocols; +#define M(X) \ + protocols.X = this->ui->enable_##X->isChecked(); + PROTOCOLS(M) +#undef M + return protocols; +} + +void SettingsDialog::setProtocols(ProtocolSetup const & protocols) +{ +#define M(X) \ + this->ui->enable_##X->setChecked(protocols.X); + PROTOCOLS(M) + #undef M +} + +SslTrust SettingsDialog::geminiSslTrust() const +{ + return this->ui->gemini_trust_editor->trust(); +} + +void SettingsDialog::setGeminiSslTrust(const SslTrust &trust) +{ + return this->ui->gemini_trust_editor->setTrust(trust); +} + +SslTrust SettingsDialog::httpsSslTrust() const +{ + return this->ui->https_trust_editor->trust(); +} + +void SettingsDialog::setHttpsSslTrust(const SslTrust &trust) +{ + this->ui->https_trust_editor->setTrust(trust); +} + +void SettingsDialog::setOptions(const GenericSettings &options) +{ + this->current_options = options; + + this->ui->ui_theme->setCurrentIndex(0); + for(int i = 0; i < this->ui->ui_theme->count(); i++) { + if(this->ui->ui_theme->itemData(i).toInt() == int(options.theme)) { + this->ui->ui_theme->setCurrentIndex(i); + break; + } + } + + this->ui->start_page->setText(this->current_options.start_page); + + if(this->current_options.gophermap_display == GenericSettings::PlainText) { + this->ui->gophermap_text->setChecked(true); + } else { + this->ui->gophermap_icon->setChecked(true); + } + + if(this->current_options.text_display == GenericSettings::PlainText) { + this->ui->fancypants_off->setChecked(true); + } else { + this->ui->fancypants_on->setChecked(true); + } + + if(this->current_options.enable_text_decoration) { + this->ui->texthl_on->setChecked(true); + } else { + this->ui->texthl_off->setChecked(true); + } + + if(this->current_options.use_os_scheme_handler) { + this->ui->scheme_os_default->setChecked(true); + } else { + this->ui->scheme_error->setChecked(true); + } + + this->ui->max_redirects->setValue(this->current_options.max_redirections); + + this->ui->redirection_mode->setCurrentIndex(0); + for(int i = 0; i < this->ui->redirection_mode->count(); i++) + { + if(this->ui->redirection_mode->itemData(i).toInt() == this->current_options.redirection_policy) { + this->ui->redirection_mode->setCurrentIndex(i); + break; + } + } + + this->ui->network_timeout->setValue(this->current_options.network_timeout); +} + +GenericSettings SettingsDialog::options() const +{ + return this->current_options; +} + +void SettingsDialog::reloadStylePreview() +{ + QFile document_src { ":/about/style-preview.gemini" }; + bool ok = document_src.open(QFile::ReadOnly); + assert(ok and "failed to find style-preview.gemini!"); + + auto const document = document_src.readAll(); + + QString host = this->ui->preview_url->text(); + if(host.length() == 0) + host = "preview"; + + QUrl url { QUrl(QString("about://%1/foobar").arg(host)) }; + + DocumentOutlineModel outline; + auto doc = GeminiRenderer::render( + document, + url, + current_style.derive(url), + outline + ); + + ui->style_preview->setStyleSheet(QString("QTextBrowser { background-color: %1; }") + .arg(doc->background_color.name())); + ui->style_preview->setDocument(doc.get()); + preview_document = std::move(doc); +} + +void SettingsDialog::updateFont(QFont & input) +{ + QFontDialog dialog { this }; + + dialog.setCurrentFont(input); + + if(dialog.exec() == QDialog::Accepted) { + input = dialog.currentFont(); + setGeminiStyle(current_style); + } +} + +void SettingsDialog::on_std_change_font_clicked() +{ + updateFont(current_style.standard_font); +} + +void SettingsDialog::on_pre_change_font_clicked() +{ + updateFont(current_style.preformatted_font); +} + +void SettingsDialog::on_h1_change_font_clicked() +{ + updateFont(current_style.h1_font); +} + +void SettingsDialog::on_h2_change_font_clicked() +{ + updateFont(current_style.h2_font); +} + +void SettingsDialog::on_h3_change_font_clicked() +{ + updateFont(current_style.h3_font); +} + +void SettingsDialog::updateColor(QColor &input) +{ + QColorDialog dialog { this }; + + dialog.setCurrentColor(input); + + if(dialog.exec() == QDialog::Accepted) { + input = dialog.currentColor(); + setGeminiStyle(current_style); + } +} + +void SettingsDialog::on_std_change_color_clicked() +{ + updateColor(current_style.standard_color); +} + +void SettingsDialog::on_pre_change_color_clicked() +{ + updateColor(current_style.preformatted_color); +} + +void SettingsDialog::on_h1_change_color_clicked() +{ + updateColor(current_style.h1_color); +} + +void SettingsDialog::on_h2_change_color_clicked() +{ + updateColor(current_style.h2_color); +} + +void SettingsDialog::on_h3_change_color_clicked() +{ + updateColor(current_style.h3_color); +} + +void SettingsDialog::on_bg_change_color_clicked() +{ + updateColor(current_style.background_color); +} + +void SettingsDialog::on_link_local_change_color_clicked() +{ + updateColor(current_style.internal_link_color); +} + +void SettingsDialog::on_link_foreign_change_color_clicked() +{ + updateColor(current_style.external_link_color); +} + +void SettingsDialog::on_link_cross_change_color_clicked() +{ + updateColor(current_style.cross_scheme_link_color); +} +void SettingsDialog::on_quote_change_color_clicked() +{ + updateColor(current_style.blockquote_color); +} + +void SettingsDialog::on_link_local_prefix_textChanged(const QString &text) +{ + current_style.internal_link_prefix = text; + reloadStylePreview(); +} + +void SettingsDialog::on_link_foreign_prefix_textChanged(const QString &text) +{ + current_style.external_link_prefix = text; + reloadStylePreview(); +} + +void SettingsDialog::on_auto_theme_currentIndexChanged(int index) +{ + if(index >= 0) { + current_style.theme = DocumentStyle::Theme(index); + reloadStylePreview(); + } +} + +void SettingsDialog::on_preview_url_textChanged(const QString &) +{ + this->reloadStylePreview(); +} + +void SettingsDialog::on_page_margin_valueChanged(double value) +{ + this->current_style.margin = value; + this->reloadStylePreview(); +} + +void SettingsDialog::on_presets_currentIndexChanged(int index) +{ + this->ui->preset_load->setEnabled(index >= 0); + this->ui->preset_save->setEnabled(index >= 0); + this->ui->preset_export->setEnabled(index >= 0); +} + +void SettingsDialog::on_preset_new_clicked() +{ + QInputDialog dlg { this }; + dlg.setInputMode(QInputDialog::TextInput); + dlg.setOkButtonText("Save"); + dlg.setCancelButtonText("Cancel"); + dlg.setLabelText("Enter the name of your new preset:"); + + if(dlg.exec() != QInputDialog::Accepted) + return; + QString name = dlg.textValue(); + + bool override = false; + if(this->predefined_styles.contains(name)) + { + auto response = QMessageBox::question(this, "Kristall", QString("A style with the name '%1' already exists! Replace?").arg(name)); + if(response != QMessageBox::Yes) + return; + override = true; + } + + this->predefined_styles.insert(name, this->current_style); + + if(not override) + { + this->ui->presets->addItem(name); + } +} + +void SettingsDialog::on_preset_save_clicked() +{ + QString name = this->ui->presets->currentText(); + if(name.isEmpty()) + return; + + auto response = QMessageBox::question(this, "Kristall", QString("Do you want to override the style '%1'?").arg(name)); + if(response != QMessageBox::Yes) + return; + + this->predefined_styles.insert(name, this->current_style); +} + + +void SettingsDialog::on_preset_load_clicked() +{ + QString name = this->ui->presets->currentText(); + if(name.isEmpty()) + return; + + auto response = QMessageBox::question(this, "Kristall", QString("Do you want to load the style '%1'?\r\nThis will discard all currently set up values!").arg(name)); + if(response != QMessageBox::Yes) + return; + + this->setGeminiStyle(this->predefined_styles.value(name)); +} + + +void SettingsDialog::on_SettingsDialog_accepted() +{ + global_settings.beginWriteArray("Themes", this->predefined_styles.size()); + + int index = 0; + for(auto const & style_name : this->predefined_styles.keys()) + { + global_settings.setArrayIndex(index); + + global_settings.setValue("name", style_name); + this->predefined_styles.value(style_name).save(global_settings); + + index += 1; + } + global_settings.endArray(); +} + +void SettingsDialog::on_preset_import_clicked() +{ + QFileDialog dialog { this }; + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.selectNameFilter("Kristall Theme (*.kthm)"); + + if(dialog.exec() !=QFileDialog::Accepted) + return; + + QString fileName = dialog.selectedFiles().at(0); + + QSettings import_settings { fileName, QSettings::IniFormat }; + + QString name; + + name = import_settings.value("name").toString(); + + while(name.isEmpty()) + { + QInputDialog dlg { this }; + dlg.setInputMode(QInputDialog::TextInput); + dlg.setOkButtonText("Save"); + dlg.setCancelButtonText("Cancel"); + dlg.setLabelText("Imported preset has no name.\r\nPlease enter a name for the preset:"); + if(dlg.exec() != QDialog::Accepted) + return; + name = dlg.textValue(); + } + + bool override = false; + if(this->predefined_styles.contains(name)) + { + auto response = QMessageBox::question(this, "Kristall", QString("Do you want to override the style '%1'?").arg(name)); + if(response != QMessageBox::Yes) + return; + override = true; + } + + DocumentStyle style; + style.load(import_settings); + + this->predefined_styles.insert(name, style); + + if(not override) + { + this->ui->presets->addItem(name); + } +} + +void SettingsDialog::on_preset_export_clicked() +{ + QString name = this->ui->presets->currentText(); + if(name.isEmpty()) + return; + + QFileDialog dialog { this }; + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.selectNameFilter("Kristall Theme (*.kthm)"); + dialog.selectFile(QString("%1.kthm").arg(name)); + + if(dialog.exec() !=QFileDialog::Accepted) + return; + + QString fileName = dialog.selectedFiles().at(0); + + QSettings export_settings { fileName, QSettings::IniFormat }; + export_settings.setValue("name", name); + this->predefined_styles.value(name).save(export_settings); + export_settings.sync(); +} + +void SettingsDialog::on_start_page_textChanged(const QString &start_page) +{ + this->current_options.start_page = start_page; +} + +void SettingsDialog::on_ui_theme_currentIndexChanged(int index) +{ + this->current_options.theme = Theme(this->ui->ui_theme->itemData(index).toInt()); +} + +void SettingsDialog::on_fancypants_on_clicked() +{ + this->current_options.text_display = GenericSettings::FormattedText; +} + +void SettingsDialog::on_fancypants_off_clicked() +{ + this->current_options.text_display = GenericSettings::PlainText; +} + +void SettingsDialog::on_texthl_on_clicked() +{ + this->current_options.enable_text_decoration = true; +} + +void SettingsDialog::on_texthl_off_clicked() +{ + this->current_options.enable_text_decoration = false; +} + +void SettingsDialog::on_gophermap_icon_clicked() +{ + this->current_options.gophermap_display = GenericSettings::FormattedText; +} + +void SettingsDialog::on_gophermap_text_clicked() +{ + this->current_options.gophermap_display = GenericSettings::PlainText; +} + +void SettingsDialog::on_scheme_os_default_clicked() +{ + this->current_options.use_os_scheme_handler = true; +} + +void SettingsDialog::on_scheme_error_clicked() +{ + this->current_options.use_os_scheme_handler = false; +} + +void SettingsDialog::on_redirection_mode_currentIndexChanged(int index) +{ + this->current_options.redirection_policy = GenericSettings::RedirectionWarning(this->ui->redirection_mode->itemData(index).toInt()); +} + +void SettingsDialog::on_max_redirects_valueChanged(int max_redirections) +{ + this->current_options.max_redirections = max_redirections; +} + +void SettingsDialog::on_network_timeout_valueChanged(int timeout) +{ + this->current_options.network_timeout = timeout; +} diff --git a/src/dialogs/settingsdialog.hpp b/src/dialogs/settingsdialog.hpp new file mode 100644 index 0000000..045b861 --- /dev/null +++ b/src/dialogs/settingsdialog.hpp @@ -0,0 +1,143 @@ +#ifndef SETTINGSDIALOG_HPP +#define SETTINGSDIALOG_HPP + +#include <QDialog> + +#include "renderers/geminirenderer.hpp" +#include "protocolsetup.hpp" +#include "documentstyle.hpp" +#include "ssltrust.hpp" +#include "kristall.hpp" + +namespace Ui { +class SettingsDialog; +} + +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SettingsDialog(QWidget *parent = nullptr); + ~SettingsDialog(); + + void setGeminiStyle(DocumentStyle const & style); + + DocumentStyle geminiStyle() const { + return current_style; + } + + ProtocolSetup protocols() const; + void setProtocols(ProtocolSetup const & proto); + + SslTrust geminiSslTrust() const; + void setGeminiSslTrust(SslTrust const & trust); + + SslTrust httpsSslTrust() const; + void setHttpsSslTrust(SslTrust const & trust); + + GenericSettings options() const; + void setOptions(GenericSettings const & options); + +private slots: + void on_std_change_font_clicked(); + + void on_pre_change_font_clicked(); + + void on_h1_change_font_clicked(); + + void on_h2_change_font_clicked(); + + void on_h3_change_font_clicked(); + + void on_std_change_color_clicked(); + + void on_pre_change_color_clicked(); + + void on_h1_change_color_clicked(); + + void on_h2_change_color_clicked(); + + void on_h3_change_color_clicked(); + + void on_bg_change_color_clicked(); + + void on_link_local_change_color_clicked(); + + void on_link_foreign_change_color_clicked(); + + void on_link_cross_change_color_clicked(); + + void on_link_local_prefix_textChanged(const QString &arg1); + + void on_link_foreign_prefix_textChanged(const QString &arg1); + + void on_auto_theme_currentIndexChanged(int index); + + void on_preview_url_textChanged(const QString &arg1); + + void on_page_margin_valueChanged(double arg1); + + void on_presets_currentIndexChanged(int index); + + void on_preset_new_clicked(); + + void on_SettingsDialog_accepted(); + + void on_quote_change_color_clicked(); + + void on_preset_save_clicked(); + + void on_preset_load_clicked(); + + void on_preset_import_clicked(); + + void on_preset_export_clicked(); + + void on_start_page_textChanged(const QString &arg1); + + void on_ui_theme_currentIndexChanged(int index); + + void on_fancypants_on_clicked(); + + void on_fancypants_off_clicked(); + + void on_texthl_on_clicked(); + + void on_texthl_off_clicked(); + + void on_gophermap_icon_clicked(); + + void on_gophermap_text_clicked(); + + void on_scheme_os_default_clicked(); + + void on_scheme_error_clicked(); + + void on_redirection_mode_currentIndexChanged(int index); + + void on_max_redirects_valueChanged(int arg1); + + void on_network_timeout_valueChanged(int arg1); + +private: + void reloadStylePreview(); + + void updateFont(QFont & input); + + void updateColor(QColor & input); + +private: + Ui::SettingsDialog *ui; + + DocumentStyle current_style; + std::unique_ptr<QTextDocument> preview_document; + + QMap<QString, DocumentStyle> predefined_styles; + + SslTrust current_trust; + + GenericSettings current_options; +}; + +#endif // SETTINGSDIALOG_HPP diff --git a/src/dialogs/settingsdialog.ui b/src/dialogs/settingsdialog.ui new file mode 100644 index 0000000..9acbca6 --- /dev/null +++ b/src/dialogs/settingsdialog.ui @@ -0,0 +1,940 @@ +<?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>850</width> + <height>650</height> + </rect> + </property> + <property name="windowTitle"> + <string>Settings</string> + </property> + <property name="windowIcon"> + <iconset theme="settings"> + <normaloff>.</normaloff>.</iconset> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>2</number> + </property> + <widget class="QWidget" name="generic"> + <attribute name="icon"> + <iconset theme="settings"> + <normaloff>.</normaloff>.</iconset> + </attribute> + <attribute name="title"> + <string>Generic</string> + </attribute> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>UI Theme</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="ui_theme"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string>Start Page:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="start_page"> + <property name="placeholderText"> + <string>about://blank</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>Enabled Protocols</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QCheckBox" name="enable_gemini"> + <property name="text"> + <string>Gemini</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="enable_gopher"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Gopher</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="enable_finger"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Finger</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="enable_http"> + <property name="text"> + <string>HTTP</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="enable_https"> + <property name="text"> + <string>HTTPS</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_19"> + <property name="text"> + <string>Text Rendering</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QRadioButton" name="fancypants_on"> + <property name="text"> + <string>Fancy</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">textRenderingBtnGroup</string> + </attribute> + </widget> + </item> + <item> + <widget class="QRadioButton" name="fancypants_off"> + <property name="text"> + <string>Always plain text</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">textRenderingBtnGroup</string> + </attribute> + </widget> + </item> + </layout> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_18"> + <property name="text"> + <string>Enable text highlights</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QRadioButton" name="texthl_on"> + <property name="text"> + <string>On (Experimental)</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">textHighlightsBtnGroup</string> + </attribute> + </widget> + </item> + <item> + <widget class="QRadioButton" name="texthl_off"> + <property name="text"> + <string>Off</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">textHighlightsBtnGroup</string> + </attribute> + </widget> + </item> + </layout> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_20"> + <property name="text"> + <string>Gopher Map</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QRadioButton" name="gophermap_icon"> + <property name="text"> + <string>Use icons</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">gophermapBtnGroup</string> + </attribute> + </widget> + </item> + <item> + <widget class="QRadioButton" name="gophermap_text"> + <property name="text"> + <string>Use text only</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">gophermapBtnGroup</string> + </attribute> + </widget> + </item> + </layout> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_22"> + <property name="text"> + <string>Unknown Scheme</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QRadioButton" name="scheme_os_default"> + <property name="text"> + <string>Use OS default handler</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">buttonGroup</string> + </attribute> + </widget> + </item> + <item> + <widget class="QRadioButton" name="scheme_error"> + <property name="text"> + <string>Display error message</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">buttonGroup</string> + </attribute> + </widget> + </item> + </layout> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_26"> + <property name="text"> + <string>Max. Number of Redirections</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QSpinBox" name="max_redirects"> + <property name="value"> + <number>5</number> + </property> + </widget> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_27"> + <property name="text"> + <string>Redirection Handling</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QComboBox" name="redirection_mode"/> + </item> + <item row="9" column="0"> + <widget class="QLabel" name="label_28"> + <property name="text"> + <string>Network Timeout</string> + </property> + </widget> + </item> + <item row="9" column="1"> + <widget class="QSpinBox" name="network_timeout"> + <property name="suffix"> + <string> ms</string> + </property> + <property name="minimum"> + <number>100</number> + </property> + <property name="maximum"> + <number>90000</number> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="style_tab"> + <attribute name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </attribute> + <attribute name="title"> + <string>Style</string> + </attribute> + <layout class="QHBoxLayout" name="horizontalLayout_23"> + <item> + <layout class="QFormLayout" name="formLayout_3"> + <property name="leftMargin"> + <number>5</number> + </property> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_13"> + <item> + <widget class="QLabel" name="bg_preview"> + <property name="text"> + <string/> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="bg_change_color"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Background Color</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Standard Font</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_14"> + <item> + <widget class="QLabel" name="std_preview"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="text"> + <string>This text will be displayed for normal text.</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="std_change_font"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="font"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="std_change_color"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Preformatted Font</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_15"> + <item> + <widget class="QLabel" name="pre_preview"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="text"> + <string>This text will be displayed for preformatted text.</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="pre_change_font"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="font"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="pre_change_color"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>H1 Font</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_16"> + <item> + <widget class="QLabel" name="h1_preview"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="text"> + <string>This text will be displayed for a level 1 heading.</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="h1_change_font"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="font"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="h1_change_color"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>H2 Font</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_18"> + <item> + <widget class="QLabel" name="h2_preview"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="text"> + <string>This text will be displayed for a level 2 heading.</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="h2_change_font"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="font"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="h2_change_color"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>H3 Font</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_19"> + <item> + <widget class="QLabel" name="h3_preview"> + <property name="styleSheet"> + <string notr="true">border: 1px solid black;</string> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="text"> + <string>This text will be displayed for a level 3 heading.</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="h3_change_font"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="font"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="h3_change_color"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Local Link Color</string> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Foreign Link Color</string> + </property> + </widget> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Cross-Scheme-Color</string> + </property> + </widget> + </item> + <item row="9" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Local Link Prefix</string> + </property> + </widget> + </item> + <item row="10" column="0"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Extern Link Prefix</string> + </property> + </widget> + </item> + <item row="10" column="1"> + <widget class="QLineEdit" name="link_foreign_prefix"> + <property name="text"> + <string>⇒ </string> + </property> + </widget> + </item> + <item row="9" column="1"> + <widget class="QLineEdit" name="link_local_prefix"> + <property name="text"> + <string>→ </string> + </property> + </widget> + </item> + <item row="6" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_20"> + <item> + <widget class="QLabel" name="link_local_preview"> + <property name="text"> + <string>This is a local reference</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="link_local_change_color"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="7" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_21"> + <item> + <widget class="QLabel" name="link_foreign_preview"> + <property name="text"> + <string>This is a foreign reference</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="link_foreign_change_color"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="8" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_22"> + <item> + <widget class="QLabel" name="link_cross_preview"> + <property name="text"> + <string>This reference is cross-scheme</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="link_cross_change_color"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="12" column="0"> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string>Auto-Theme Generation</string> + </property> + </widget> + </item> + <item row="12" column="1"> + <widget class="QComboBox" name="auto_theme"/> + </item> + <item row="13" column="0"> + <widget class="QLabel" name="label_13"> + <property name="text"> + <string>Page Margin</string> + </property> + </widget> + </item> + <item row="13" column="1"> + <widget class="QDoubleSpinBox" name="page_margin"> + <property name="suffix"> + <string> px</string> + </property> + <property name="decimals"> + <number>0</number> + </property> + <property name="maximum"> + <double>350.000000000000000</double> + </property> + </widget> + </item> + <item row="14" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QComboBox" name="presets"/> + </item> + <item> + <widget class="QToolButton" name="preset_new"> + <property name="toolTip"> + <string>Save as new preset</string> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset theme="create-new"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="preset_save"> + <property name="toolTip"> + <string>Override current preset</string> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset theme="save"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="preset_load"> + <property name="toolTip"> + <string>Load preset</string> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset theme="folder"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="preset_import"> + <property name="toolTip"> + <string>Imports preset…</string> + </property> + <property name="toolTipDuration"> + <number>-1</number> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset theme="save-import"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="preset_export"> + <property name="toolTip"> + <string>Export preset…</string> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset theme="save-export"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="14" column="0"> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string>Presets</string> + </property> + </widget> + </item> + <item row="11" column="0"> + <widget class="QLabel" name="label_21"> + <property name="text"> + <string>Block Quote Background</string> + </property> + </widget> + </item> + <item row="11" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QLabel" name="quote_preview"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="quote_change_color"> + <property name="text"> + <string>...</string> + </property> + <property name="icon"> + <iconset theme="palette"> + <normaloff>.</normaloff>.</iconset> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QTextBrowser" name="style_preview"> + <property name="openLinks"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="preview_url"> + <property name="placeholderText"> + <string>host.name</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="gem_trust_page"> + <attribute name="icon"> + <iconset theme="certificate"> + <normaloff>.</normaloff>.</iconset> + </attribute> + <attribute name="title"> + <string>Gemini TLS</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="SslTrustEditor" name="gemini_trust_editor" native="true"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="https_trust"> + <attribute name="icon"> + <iconset theme="certificate"> + <normaloff>.</normaloff>.</iconset> + </attribute> + <attribute name="title"> + <string>HTTPS TLS</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="SslTrustEditor" name="https_trust_editor" native="true"/> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>SslTrustEditor</class> + <extends>QWidget</extends> + <header>widgets/ssltrusteditor.hpp</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>SettingsDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>325</x> + <y>470</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>SettingsDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>257</x> + <y>470</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> + <buttongroups> + <buttongroup name="buttonGroup"/> + <buttongroup name="textHighlightsBtnGroup"/> + <buttongroup name="gophermapBtnGroup"/> + <buttongroup name="textRenderingBtnGroup"/> + </buttongroups> +</ui> |
