diff options
| author | Felix (xq) Queißner <git@mq32.de> | 2020-06-07 10:46:23 +0200 |
|---|---|---|
| committer | Felix (xq) Queißner <git@mq32.de> | 2020-06-07 10:46:23 +0200 |
| commit | 425f9d41cd337133d5677744eef937a8a2a61212 (patch) | |
| tree | 6cd5c2603e1499b89aae4fe5e56c6e650cb2117e /src | |
| parent | ec95bb371e54116a2627c162eac3357ec13f06ad (diff) | |
| download | kristall-425f9d41cd337133d5677744eef937a8a2a61212.tar.gz | |
Adds support for light/dark widget theme, adds experiemental support for http style sheets.
Diffstat (limited to 'src')
| -rw-r--r-- | src/browsertab.cpp | 23 | ||||
| -rw-r--r-- | src/documentoutlinemodel.cpp | 3 | ||||
| -rw-r--r-- | src/geminirenderer.cpp | 127 | ||||
| -rw-r--r-- | src/geminirenderer.hpp | 20 | ||||
| -rw-r--r-- | src/kristall.pro | 1 | ||||
| -rw-r--r-- | src/main.cpp | 9 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 34 | ||||
| -rw-r--r-- | src/mainwindow.hpp | 6 | ||||
| -rw-r--r-- | src/settingsdialog.cpp | 29 | ||||
| -rw-r--r-- | src/settingsdialog.hpp | 3 | ||||
| -rw-r--r-- | src/settingsdialog.ui | 18 |
11 files changed, 242 insertions, 31 deletions
diff --git a/src/browsertab.cpp b/src/browsertab.cpp index 8dcbe64..2a24815 100644 --- a/src/browsertab.cpp +++ b/src/browsertab.cpp @@ -199,29 +199,40 @@ void BrowserTab::on_requestComplete(const QByteArray &data, const QString &mime) this->outline.clear(); + auto doc_style = mainWindow->current_style.derive(this->current_location); + + this->ui->text_browser->setStyleSheet(QString("QTextBrowser { background-color: %1; }").arg(doc_style.background_color.name())); + if(mime.startsWith("text/gemini")) { - auto doc= GeminiRenderer{ mainWindow->current_style }.render(data, this->current_location, this->outline); - this->ui->text_browser->setStyleSheet(QString("QTextBrowser { background-color: %1; }").arg(doc->background_color.name())); + auto doc= GeminiRenderer::render( + data, + this->current_location, + doc_style, + this->outline); document = std::move(doc); } else if(mime.startsWith("text/html")) { document = std::make_unique<QTextDocument>(); + + document->setDefaultFont(doc_style.standard_font); + document->setDefaultStyleSheet(doc_style.toStyleSheet()); document->setHtml(QString::fromUtf8(data)); } #if defined(QT_FEATURE_textmarkdownreader) else if(mime.startsWith("text/markdown")) { document = std::make_unique<QTextDocument>(); + document->setDefaultFont(doc_style.standard_font); + document->setDefaultStyleSheet(doc_style.toStyleSheet()); + document->setMarkdown(QString::fromUtf8(data)); } #endif else if(mime.startsWith("text/")) { - QFont monospace; - monospace.setFamily("monospace"); - document = std::make_unique<QTextDocument>(); - document->setDefaultFont(monospace); + document->setDefaultFont(doc_style.standard_font); + document->setDefaultStyleSheet(doc_style.toStyleSheet()); document->setPlainText(QString::fromUtf8(data)); } else if(mime.startsWith("image/")) { diff --git a/src/documentoutlinemodel.cpp b/src/documentoutlinemodel.cpp index 5978f33..e98f072 100644 --- a/src/documentoutlinemodel.cpp +++ b/src/documentoutlinemodel.cpp @@ -151,6 +151,7 @@ int DocumentOutlineModel::rowCount(const QModelIndex &parent) const int DocumentOutlineModel::columnCount(const QModelIndex &parent) const { + Q_UNUSED(parent) return 1; } @@ -188,7 +189,7 @@ DocumentOutlineModel::Node & DocumentOutlineModel::ensureLevel2() auto & parent = ensureLevel1(); if(parent.children.size() == 0) { - root.children.append(Node { + parent.children.append(Node { &parent, "<missing layer>", "", 2, 0, diff --git a/src/geminirenderer.cpp b/src/geminirenderer.cpp index 811a946..ad6a2fc 100644 --- a/src/geminirenderer.cpp +++ b/src/geminirenderer.cpp @@ -5,6 +5,112 @@ #include <QTextBlock> #include <QDebug> #include <cmath> +#include <QList> +#include <QStringList> + +static QString encodeCssFont (const QFont& refFont) +{ + //----------------------------------------------------------------------- + // This function assembles a CSS Font specification string from + // a QFont. This supports most of the QFont attributes settable in + // the Qt 4.8 and Qt 5.3 QFontDialog. + // + // (1) Font Family + // (2) Font Weight (just bold or not) + // (3) Font Style (possibly Italic or Oblique) + // (4) Font Size (in either pixels or points) + // (5) Decorations (possibly Underline or Strikeout) + // + // Not supported: Writing System (e.g. Latin). + // + // See the corresponding decode function, below. + // QFont decodeCssFontString (const QString cssFontStr) + //----------------------------------------------------------------------- + + QStringList fields; // CSS font attribute fields + + // *************************************************** + // *** (1) Font Family: Primary plus Substitutes *** + // *************************************************** + + const QString family = refFont.family(); + + // NOTE [9-2014, Qt 4.8.6]: This isn't what I thought it was. It + // does not return a list of "fallback" font faces (e.g. Georgia, + // Serif for "Times New Roman"). In my testing, this is always + // returning an empty list. + // + QStringList famSubs = QFont::substitutes (family); + + if (!famSubs.contains (family)) + famSubs.prepend (family); + + static const QChar DBL_QUOT ('"'); + const int famCnt = famSubs.count(); + QStringList famList; + for (int inx = 0; inx < famCnt; ++inx) + { + // Place double quotes around family names having space characters, + // but only if double quotes are not already there. + // + const QString fam = famSubs [inx]; + if (fam.contains (' ') && !fam.startsWith (DBL_QUOT)) + famList << (DBL_QUOT + fam + DBL_QUOT); + else + famList << fam; + } + + const QString famStr = QString ("font-family: ") + famList.join (", "); + fields << famStr; + + // ************************************** + // *** (2) Font Weight: Bold or Not *** + // ************************************** + + const bool bold = refFont.bold(); + if (bold) + fields << "font-weight: bold"; + + // **************************************************** + // *** (3) Font Style: possibly Italic or Oblique *** + // **************************************************** + + const QFont::Style style = refFont.style(); + switch (style) + { + case QFont::StyleNormal: break; + case QFont::StyleItalic: fields << "font-style: italic"; break; + case QFont::StyleOblique: fields << "font-style: oblique"; break; + } + + // ************************************************ + // *** (4) Font Size: either Pixels or Points *** + // ************************************************ + + const double sizeInPoints = refFont.pointSizeF(); // <= 0 if not defined. + const int sizeInPixels = refFont.pixelSize(); // <= 0 if not defined. + if (sizeInPoints > 0.0) + fields << QString ("font-size: %1pt") .arg (sizeInPoints); + else if (sizeInPixels > 0) + fields << QString ("font-size: %1px") .arg (sizeInPixels); + + // *********************************************** + // *** (5) Decorations: Underline, Strikeout *** + // *********************************************** + + const bool underline = refFont.underline(); + const bool strikeOut = refFont.strikeOut(); + + if (underline && strikeOut) + fields << "text-decoration: underline line-through"; + else if (underline) + fields << "text-decoration: underline"; + else if (strikeOut) + fields << "text-decoration: line-through"; + + const QString cssFontStr = fields.join ("; "); + return cssFontStr; +} static QByteArray trim_whitespace(QByteArray items) { @@ -182,14 +288,27 @@ GeminiStyle GeminiStyle::derive(const QUrl &url) const return themed; } -GeminiRenderer::GeminiRenderer(GeminiStyle const &_style) : style(_style) +QString GeminiStyle::toStyleSheet() const { + QString css; + + css += QString("p { color: %2; %1 }\n").arg(encodeCssFont (standard_font)).arg(standard_color.name()); + css += QString("a { color: %2; %1 }\n").arg(encodeCssFont (standard_font)).arg(external_link_color.name()); + css += QString("pre { color: %2; %1 }\n").arg(encodeCssFont (preformatted_font)).arg(preformatted_color.name()); + css += QString("h1 { color: %2; %1 }\n").arg(encodeCssFont (h1_font)).arg(h1_color.name()); + css += QString("h2 { color: %2; %1 }\n").arg(encodeCssFont (h2_font)).arg(h2_color.name()); + css += QString("h3 { color: %2; %1 }\n").arg(encodeCssFont (h3_font)).arg(h3_color.name()); + + qDebug() << "CSS → " << css; + return css; } -std::unique_ptr<GeminiDocument> GeminiRenderer::render(const QByteArray &input, QUrl const &root_url, DocumentOutlineModel &outline) +std::unique_ptr<GeminiDocument> GeminiRenderer::render( + const QByteArray &input, + QUrl const &root_url, + GeminiStyle const & themed_style, + DocumentOutlineModel &outline) { - auto themed_style = style.derive(root_url); - QTextCharFormat preformatted; preformatted.setFont(themed_style.preformatted_font); preformatted.setForeground(themed_style.preformatted_color); diff --git a/src/geminirenderer.hpp b/src/geminirenderer.hpp index 2ec1651..c93fd4e 100644 --- a/src/geminirenderer.hpp +++ b/src/geminirenderer.hpp @@ -47,6 +47,10 @@ struct GeminiStyle //! Create a new style with auto-generated colors for the given //! url. The colors are based on the host name GeminiStyle derive(QUrl const & url) const; + + //! Converts this style into a CSS document for + //! non-gemini rendered files. + QString toStyleSheet() const; }; class GeminiDocument : @@ -60,15 +64,19 @@ public: QColor background_color; }; -class GeminiRenderer +struct GeminiRenderer { - GeminiStyle style; -public: - GeminiRenderer(GeminiStyle const & style = GeminiStyle{}); - - std::unique_ptr<GeminiDocument> render( + GeminiRenderer() = delete; + + //! Renders the given byte sequence into a GeminiDocument. + //! @param input The utf8 encoded input string + //! @param root_url The url that is used to resolve relative links + //! @param style The style which is used to render the document + //! @param outline The extracted outline from the document + static std::unique_ptr<GeminiDocument> render( QByteArray const & input, QUrl const & root_url, + GeminiStyle const & style, DocumentOutlineModel & outline ); }; diff --git a/src/kristall.pro b/src/kristall.pro index ed53750..1cac9ac 100644 --- a/src/kristall.pro +++ b/src/kristall.pro @@ -57,4 +57,5 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target RESOURCES += \ + ../lib/BreezeStyleSheets/breeze.qrc \ icons.qrc diff --git a/src/main.cpp b/src/main.cpp index 5063694..fa22cc1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,12 +6,11 @@ int main(int argc, char *argv[]) { - QApplication a(argc, argv); - MainWindow w; + QApplication app(argc, argv); - // w.addNewTab(true, QUrl("gemini://gemini.circumlunar.space/")); + MainWindow w(&app); w.addEmptyTab(true, true); - w.show(); - return a.exec(); + + return app.exec(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cf12877..e8dbd23 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -7,9 +7,12 @@ #include <memory> #include <QShortcut> #include <QKeySequence> +#include <QFile> +#include <QTextStream> -MainWindow::MainWindow(QWidget *parent) : +MainWindow::MainWindow(QApplication * app, QWidget *parent) : QMainWindow(parent), + application(app), settings("xqTechnologies", "Kristall"), ui(new Ui::MainWindow), url_status(new QLabel()) @@ -60,6 +63,8 @@ MainWindow::MainWindow(QWidget *parent) : } settings.endGroup(); } + + reloadTheme(); } MainWindow::~MainWindow() @@ -206,6 +211,7 @@ void MainWindow::on_actionSettings_triggered() dialog.setGeminiStyle(this->current_style); dialog.setStartPage(this->settings.value("start_page").toString()); dialog.setProtocols(this->protocols); + dialog.setUiTheme(this->settings.value("theme").toString()); if(dialog.exec() != QDialog::Accepted) return; @@ -214,9 +220,13 @@ void MainWindow::on_actionSettings_triggered() this->settings.setValue("start_page", url.toString()); } + this->settings.setValue("theme", dialog.uiTheme()); + this->protocols = dialog.protocols(); this->current_style = dialog.geminiStyle(); this->saveSettings(); + + this->reloadTheme(); } void MainWindow::on_actionNew_Tab_triggered() @@ -277,3 +287,25 @@ void MainWindow::on_actionAbout_Qt_triggered() { QMessageBox::aboutQt(this, "Kristall"); } + +void MainWindow::reloadTheme() +{ + QString theme = settings.value("theme").toString(); + if(theme.isEmpty()) + theme = "light"; + + if(theme == "light") + { + QFile file(":/light.qss"); + file.open(QFile::ReadOnly | QFile::Text); + QTextStream stream(&file); + application->setStyleSheet(stream.readAll()); + } + else if(theme == "dark") + { + QFile file(":/dark.qss"); + file.open(QFile::ReadOnly | QFile::Text); + QTextStream stream(&file); + application->setStyleSheet(stream.readAll()); + } +} diff --git a/src/mainwindow.hpp b/src/mainwindow.hpp index e207128..7984549 100644 --- a/src/mainwindow.hpp +++ b/src/mainwindow.hpp @@ -21,7 +21,7 @@ class MainWindow : public QMainWindow Q_OBJECT public: - MainWindow(QWidget *parent = nullptr); + MainWindow(QApplication * app, QWidget *parent = nullptr); ~MainWindow(); BrowserTab * addEmptyTab(bool focus_new, bool load_default); @@ -67,7 +67,11 @@ private slots: void on_actionAbout_Qt_triggered(); +private: + void reloadTheme(); + public: + QApplication * application; QSettings settings; GeminiStyle current_style; ProtocolSetup protocols; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 27a1c12..859f5dd 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -20,6 +20,10 @@ SettingsDialog::SettingsDialog(QWidget *parent) : this->ui->auto_theme->addItem("Dark Theme", QVariant::fromValue<int>(GeminiStyle::AutoDarkTheme)); this->ui->auto_theme->addItem("Light Theme", QVariant::fromValue<int>(GeminiStyle::AutoLightTheme)); + this->ui->ui_theme->clear(); + this->ui->ui_theme->addItem("Light", QVariant::fromValue<QString>("light")); + this->ui->ui_theme->addItem("Dark", QVariant::fromValue<QString>("dark")); + setGeminiStyle(GeminiStyle { }); } @@ -114,7 +118,23 @@ void SettingsDialog::setProtocols(ProtocolSetup const & protocols) #define M(X) \ this->ui->enable_##X->setChecked(protocols.X); PROTOCOLS(M) -#undef M + #undef M +} + +QString SettingsDialog::uiTheme() const +{ + return this->ui->ui_theme->currentData().toString(); +} + +void SettingsDialog::setUiTheme(const QString &theme) +{ + if(theme == "light") + this->ui->ui_theme->setCurrentIndex(0); + else if(theme == "dark") + this->ui->ui_theme->setCurrentIndex(1); + else + this->ui->ui_theme->setCurrentIndex(0); + } void SettingsDialog::reloadStylePreview() @@ -144,10 +164,13 @@ Plain text document here. if(host.length() == 0) host = "preview"; + QUrl url { QUrl(QString("about://%1/foobar").arg(host)) }; + DocumentOutlineModel outline; - auto doc = GeminiRenderer { current_style }.render( + auto doc = GeminiRenderer::render( document, - QUrl(QString("about://%1/foobar").arg(host)), + url, + current_style.derive(url), outline ); diff --git a/src/settingsdialog.hpp b/src/settingsdialog.hpp index cc51c96..eb9d68a 100644 --- a/src/settingsdialog.hpp +++ b/src/settingsdialog.hpp @@ -30,6 +30,9 @@ public: ProtocolSetup protocols() const; void setProtocols(ProtocolSetup const & proto); + QString uiTheme() const; + void setUiTheme(QString const & theme); + private slots: void on_std_change_font_clicked(); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index a26ae70..58c20e5 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -24,28 +24,28 @@ <string>Generic</string> </attribute> <layout class="QFormLayout" name="formLayout"> - <item row="0" column="0"> + <item row="1" column="0"> <widget class="QLabel" name="label_14"> <property name="text"> <string>Start Page:</string> </property> </widget> </item> - <item row="0" column="1"> + <item row="1" column="1"> <widget class="QLineEdit" name="start_page"> <property name="placeholderText"> <string>about://blank</string> </property> </widget> </item> - <item row="1" column="0"> + <item row="2" column="0"> <widget class="QLabel" name="label_16"> <property name="text"> <string>Enabled Protocols</string> </property> </widget> </item> - <item row="1" column="1"> + <item row="2" column="1"> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QCheckBox" name="enable_gemini"> @@ -103,6 +103,16 @@ </item> </layout> </item> + <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> </layout> </widget> <widget class="QWidget" name="style_tab"> |
