diff options
| author | Felix (xq) Queißner <git@mq32.de> | 2020-06-08 00:30:32 +0200 |
|---|---|---|
| committer | Felix (xq) Queißner <git@mq32.de> | 2020-06-08 00:30:32 +0200 |
| commit | f02ccb928fd4ed591d2efe118a571e154f5df68a (patch) | |
| tree | 2fc7c4037423d074c410f4c53714ddc842d33351 /src | |
| parent | 425f9d41cd337133d5677744eef937a8a2a61212 (diff) | |
| download | kristall-f02ccb928fd4ed591d2efe118a571e154f5df68a.tar.gz | |
Starts to implement gopher protocol and gophermap support. Heavily WIP, but you can already surf on gopherspace!
Diffstat (limited to 'src')
| -rw-r--r-- | src/browsertab.cpp | 27 | ||||
| -rw-r--r-- | src/browsertab.hpp | 2 | ||||
| -rw-r--r-- | src/documentstyle.cpp | 290 | ||||
| -rw-r--r-- | src/documentstyle.hpp | 55 | ||||
| -rw-r--r-- | src/geminiclient.cpp | 3 | ||||
| -rw-r--r-- | src/geminirenderer.cpp | 292 | ||||
| -rw-r--r-- | src/geminirenderer.hpp | 48 | ||||
| -rw-r--r-- | src/gopherclient.cpp | 80 | ||||
| -rw-r--r-- | src/gopherclient.hpp | 40 | ||||
| -rw-r--r-- | src/gophermaprenderer.cpp | 164 | ||||
| -rw-r--r-- | src/gophermaprenderer.hpp | 25 | ||||
| -rw-r--r-- | src/icons.qrc | 6 | ||||
| -rw-r--r-- | src/icons/content-save-import.svg | 9 | ||||
| -rw-r--r-- | src/icons/content-save-move.svg | 1 | ||||
| -rw-r--r-- | src/icons/content-save.svg | 1 | ||||
| -rw-r--r-- | src/icons/folder-open.svg | 1 | ||||
| -rw-r--r-- | src/icons/home.svg | 1 | ||||
| -rw-r--r-- | src/icons/plus.svg | 1 | ||||
| -rw-r--r-- | src/ioutil.cpp | 16 | ||||
| -rw-r--r-- | src/ioutil.hpp | 11 | ||||
| -rw-r--r-- | src/kristall.pro | 8 | ||||
| -rw-r--r-- | src/mainwindow.hpp | 2 | ||||
| -rw-r--r-- | src/settingsdialog.cpp | 60 | ||||
| -rw-r--r-- | src/settingsdialog.hpp | 11 | ||||
| -rw-r--r-- | src/settingsdialog.ui | 93 | ||||
| -rw-r--r-- | src/webclient.cpp | 3 |
26 files changed, 900 insertions, 350 deletions
diff --git a/src/browsertab.cpp b/src/browsertab.cpp index 2a24815..2e02433 100644 --- a/src/browsertab.cpp +++ b/src/browsertab.cpp @@ -3,6 +3,7 @@ #include "mainwindow.hpp" #include "geminirenderer.hpp" #include "settingsdialog.hpp" +#include "gophermaprenderer.hpp" #include <QTabWidget> #include <QMenu> @@ -38,6 +39,10 @@ BrowserTab::BrowserTab(MainWindow * mainWindow) : connect(&gemini_client, &GeminiClient::authorisedCertificateRequested, this, &BrowserTab::on_authorisedCertificateRequested); connect(&gemini_client, &GeminiClient::certificateRejected, this, &BrowserTab::on_certificateRejected); + connect(&gopher_client, &GopherClient::requestComplete, this, &BrowserTab::on_requestComplete); + connect(&gopher_client, &GopherClient::requestFailed, this, &BrowserTab::on_requestFailed); + + this->updateUI(); this->ui->graphics_browser->setVisible(false); @@ -72,6 +77,11 @@ void BrowserTab::navigateTo(const QUrl &url, PushToHistory mode) return; } + if(not gopher_client.cancelRequest()) { + QMessageBox::warning(this, "Kristall", "Failed to cancel running gopher request!"); + return; + } + this->redirection_count = 0; this->successfully_loaded = false; this->push_to_history_after_load = (mode == PushAfterSuccess); @@ -84,6 +94,10 @@ void BrowserTab::navigateTo(const QUrl &url, PushToHistory mode) { web_client.startRequest(url); } + else if(url.scheme() == "gopher") + { + gopher_client.startRequest(url); + } else if(url.scheme() == "about") { this->redirection_count = 0; @@ -204,14 +218,17 @@ void BrowserTab::on_requestComplete(const QByteArray &data, const QString &mime) this->ui->text_browser->setStyleSheet(QString("QTextBrowser { background-color: %1; }").arg(doc_style.background_color.name())); if(mime.startsWith("text/gemini")) { - - auto doc= GeminiRenderer::render( + document = GeminiRenderer::render( data, this->current_location, doc_style, this->outline); - - document = std::move(doc); + } + else if(mime.startsWith("text/gophermap")) { + document = GophermapRenderer::render( + data, + this->current_location, + doc_style); } else if(mime.startsWith("text/html")) { document = std::make_unique<QTextDocument>(); @@ -442,6 +459,8 @@ void BrowserTab::on_text_browser_highlighted(const QUrl &url) void BrowserTab::on_stop_button_clicked() { gemini_client.cancelRequest(); + web_client.cancelRequest(); + gopher_client.cancelRequest(); } void BrowserTab::on_back_button_clicked() diff --git a/src/browsertab.hpp b/src/browsertab.hpp index 655d14e..feb0455 100644 --- a/src/browsertab.hpp +++ b/src/browsertab.hpp @@ -12,6 +12,7 @@ #include "tabbrowsinghistory.hpp" #include "geminirenderer.hpp" #include "webclient.hpp" +#include "gopherclient.hpp" namespace Ui { class BrowserTab; @@ -103,6 +104,7 @@ public: GeminiClient gemini_client; WebClient web_client; + GopherClient gopher_client; int redirection_count = 0; bool push_to_history_after_load = false; diff --git a/src/documentstyle.cpp b/src/documentstyle.cpp new file mode 100644 index 0000000..6a8e8bf --- /dev/null +++ b/src/documentstyle.cpp @@ -0,0 +1,290 @@ +#include "documentstyle.hpp" + +#include <QDebug> +#include <QString> +#include <QStringList> + +#include <QCryptographicHash> +#include <QDebug> + +#include <cmath> + +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; +} + +DocumentStyle::DocumentStyle() : theme(Fixed), + standard_font(), + h1_font(), + h2_font(), + h3_font(), + preformatted_font(), + background_color("#edefff"), + standard_color(0x00, 0x00, 0x00), + preformatted_color(0x00, 0x00, 0x00), + h1_color("#022f90"), + h2_color("#022f90"), + h3_color("#022f90"), + internal_link_color("#0e8fff"), + external_link_color("#0e8fff"), + cross_scheme_link_color("#0960a7"), + internal_link_prefix("→ "), + external_link_prefix("⇒ "), + margin(55.0) +{ + preformatted_font.setFamily("monospace"); + preformatted_font.setPointSizeF(10.0); + + standard_font.setFamily("sans"); + standard_font.setPointSizeF(10.0); + + h1_font.setFamily("sans"); + h1_font.setBold(true); + h1_font.setPointSizeF(20.0); + + h2_font.setFamily("sans"); + h2_font.setBold(true); + h2_font.setPointSizeF(15.0); + + h3_font.setFamily("sans"); + h3_font.setBold(true); + h3_font.setPointSizeF(12.0); +} + +bool DocumentStyle::save(QSettings &settings) const +{ + settings.beginGroup("Theme"); + + settings.setValue("standard_font", standard_font.toString()); + settings.setValue("h1_font", h1_font.toString()); + settings.setValue("h2_font", h2_font.toString()); + settings.setValue("h3_font", h3_font.toString()); + settings.setValue("preformatted_font", preformatted_font.toString()); + + settings.setValue("background_color", background_color.name()); + settings.setValue("standard_color", standard_color.name()); + settings.setValue("preformatted_color", preformatted_color.name()); + settings.setValue("h1_color", h1_color.name()); + settings.setValue("h2_color", h2_color.name()); + settings.setValue("h3_color", h3_color.name()); + settings.setValue("internal_link_color", internal_link_color.name()); + settings.setValue("external_link_color", external_link_color.name()); + settings.setValue("cross_scheme_link_color", cross_scheme_link_color.name()); + + settings.setValue("internal_link_prefix", internal_link_prefix); + settings.setValue("external_link_prefix", external_link_prefix); + + settings.setValue("margins", margin); + settings.setValue("theme", int(theme)); + + settings.endGroup(); + return true; +} + +bool DocumentStyle::load(QSettings &settings) +{ + settings.beginGroup("Theme"); + + if(settings.contains("standard_color")) + { + standard_font.fromString(settings.value("standard_font").toString()); + h1_font.fromString(settings.value("h1_font").toString()); + h2_font.fromString(settings.value("h2_font").toString()); + h3_font.fromString(settings.value("h3_font").toString()); + preformatted_font.fromString(settings.value("preformatted_font").toString()); + + background_color = QColor(settings.value("background_color").toString()); + standard_color = QColor(settings.value("standard_color").toString()); + preformatted_color = QColor(settings.value("preformatted_color").toString()); + h1_color = QColor(settings.value("h1_color").toString()); + h2_color = QColor(settings.value("h2_color").toString()); + h3_color = QColor(settings.value("h3_color").toString()); + internal_link_color = QColor(settings.value("internal_link_color").toString()); + external_link_color = QColor(settings.value("external_link_color").toString()); + cross_scheme_link_color = QColor(settings.value("cross_scheme_link_color").toString()); + + internal_link_prefix = settings.value("internal_link_prefix").toString(); + external_link_prefix = settings.value("external_link_prefix").toString(); + + margin = settings.value("margins").toDouble(); + theme = Theme(settings.value("theme").toInt()); + } + + settings.endGroup(); + return true; +} + +DocumentStyle DocumentStyle::derive(const QUrl &url) const +{ + if (this->theme == Fixed) + return *this; + + QByteArray hash = QCryptographicHash::hash(url.host().toUtf8(), QCryptographicHash::Md5); + + std::array<uint8_t, 16> items; + assert(items.size() == hash.size()); + memcpy(items.data(), hash.data(), items.size()); + + float hue = (items[0] + items[1]) / 510.0; + float saturation = items[2] / 255.0; + + double tmp; + DocumentStyle themed = *this; + switch (this->theme) + { + case AutoDarkTheme: + { + themed.background_color = QColor::fromHslF(hue, saturation, 0.25f); + themed.standard_color = QColor{0xFF, 0xFF, 0xFF}; + + themed.h1_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.75); + themed.h2_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.75); + themed.h3_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.75); + + themed.external_link_color = QColor::fromHslF(std::modf(hue + 0.25, &tmp), 1.0, 0.75); + themed.internal_link_color = themed.external_link_color.lighter(110); + themed.cross_scheme_link_color = themed.external_link_color.darker(110); + + break; + } + + case AutoLightTheme: + { + themed.background_color = QColor::fromHslF(hue, items[2] / 255.0, 0.85); + themed.standard_color = QColor{0x00, 0x00, 0x00}; + + themed.h1_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.25); + themed.h2_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.25); + themed.h3_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.25); + + themed.external_link_color = QColor::fromHslF(std::modf(hue + 0.25, &tmp), 1.0, 0.25); + themed.internal_link_color = themed.external_link_color.darker(110); + themed.cross_scheme_link_color = themed.external_link_color.lighter(110); + + break; + } + + case Fixed: + assert(false); + } + + // Same for all themes + themed.preformatted_color = themed.standard_color; + + return themed; +} + +QString DocumentStyle::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; +} diff --git a/src/documentstyle.hpp b/src/documentstyle.hpp new file mode 100644 index 0000000..db02a36 --- /dev/null +++ b/src/documentstyle.hpp @@ -0,0 +1,55 @@ +#ifndef DOCUMENTSTYLE_HPP +#define DOCUMENTSTYLE_HPP + +#include <QUrl> +#include <QFont> +#include <QColor> +#include <QSettings> + +struct DocumentStyle +{ + enum Theme { + Fixed = 0, + AutoDarkTheme = 1, + AutoLightTheme = 2 + }; + + DocumentStyle(); + + Theme theme; + + QFont standard_font; + QFont h1_font; + QFont h2_font; + QFont h3_font; + QFont preformatted_font; + + QColor background_color; + QColor standard_color; + QColor preformatted_color; + QColor h1_color; + QColor h2_color; + QColor h3_color; + + QColor internal_link_color; + QColor external_link_color; + QColor cross_scheme_link_color; + + QString internal_link_prefix; + QString external_link_prefix; + + double margin; + + bool save(QSettings & settings) const; + bool load(QSettings & settings); + + //! Create a new style with auto-generated colors for the given + //! url. The colors are based on the host name + DocumentStyle derive(QUrl const & url) const; + + //! Converts this style into a CSS document for + //! non-gemini rendered files. + QString toStyleSheet() const; +}; + +#endif // DOCUMENTSTYLE_HPP diff --git a/src/geminiclient.cpp b/src/geminiclient.cpp index 44fa864..fd11f6b 100644 --- a/src/geminiclient.cpp +++ b/src/geminiclient.cpp @@ -18,6 +18,9 @@ GeminiClient::~GeminiClient() bool GeminiClient::startRequest(const QUrl &url) { + if(url.scheme() != "gemini") + return false; + if(socket.isOpen()) return false; diff --git a/src/geminirenderer.cpp b/src/geminirenderer.cpp index ad6a2fc..da2f9e5 100644 --- a/src/geminirenderer.cpp +++ b/src/geminirenderer.cpp @@ -1,117 +1,10 @@ #include "geminirenderer.hpp" #include <QTextList> -#include <QCryptographicHash> #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) { int start = 0; @@ -127,188 +20,15 @@ static QByteArray trim_whitespace(QByteArray items) return items.mid(start, end - start + 1); } -GeminiStyle::GeminiStyle() : theme(Fixed), - standard_font(), - h1_font(), - h2_font(), - h3_font(), - preformatted_font(), - background_color("#edefff"), - standard_color(0x00, 0x00, 0x00), - preformatted_color(0x00, 0x00, 0x00), - h1_color("#022f90"), - h2_color("#022f90"), - h3_color("#022f90"), - internal_link_color("#0e8fff"), - external_link_color("#0e8fff"), - cross_scheme_link_color("#0960a7"), - internal_link_prefix("→ "), - external_link_prefix("⇒ "), - margin(55.0) -{ - preformatted_font.setFamily("monospace"); - preformatted_font.setPointSizeF(10.0); - - standard_font.setFamily("sans"); - standard_font.setPointSizeF(10.0); - - h1_font.setFamily("sans"); - h1_font.setBold(true); - h1_font.setPointSizeF(20.0); - - h2_font.setFamily("sans"); - h2_font.setBold(true); - h2_font.setPointSizeF(15.0); - - h3_font.setFamily("sans"); - h3_font.setBold(true); - h3_font.setPointSizeF(12.0); -} - -bool GeminiStyle::save(QSettings &settings) const -{ - settings.beginGroup("Theme"); - - settings.setValue("standard_font", standard_font.toString()); - settings.setValue("h1_font", h1_font.toString()); - settings.setValue("h2_font", h2_font.toString()); - settings.setValue("h3_font", h3_font.toString()); - settings.setValue("preformatted_font", preformatted_font.toString()); - - settings.setValue("background_color", background_color.name()); - settings.setValue("standard_color", standard_color.name()); - settings.setValue("preformatted_color", preformatted_color.name()); - settings.setValue("h1_color", h1_color.name()); - settings.setValue("h2_color", h2_color.name()); - settings.setValue("h3_color", h3_color.name()); - settings.setValue("internal_link_color", internal_link_color.name()); - settings.setValue("external_link_color", external_link_color.name()); - settings.setValue("cross_scheme_link_color", cross_scheme_link_color.name()); - - settings.setValue("internal_link_prefix", internal_link_prefix); - settings.setValue("external_link_prefix", external_link_prefix); - - settings.setValue("margins", margin); - settings.setValue("theme", int(theme)); - - settings.endGroup(); - return true; -} - -bool GeminiStyle::load(QSettings &settings) -{ - settings.beginGroup("Theme"); - - if(settings.contains("standard_color")) - { - standard_font.fromString(settings.value("standard_font").toString()); - h1_font.fromString(settings.value("h1_font").toString()); - h2_font.fromString(settings.value("h2_font").toString()); - h3_font.fromString(settings.value("h3_font").toString()); - preformatted_font.fromString(settings.value("preformatted_font").toString()); - - background_color = QColor(settings.value("background_color").toString()); - standard_color = QColor(settings.value("standard_color").toString()); - preformatted_color = QColor(settings.value("preformatted_color").toString()); - h1_color = QColor(settings.value("h1_color").toString()); - h2_color = QColor(settings.value("h2_color").toString()); - h3_color = QColor(settings.value("h3_color").toString()); - internal_link_color = QColor(settings.value("internal_link_color").toString()); - external_link_color = QColor(settings.value("external_link_color").toString()); - cross_scheme_link_color = QColor(settings.value("cross_scheme_link_color").toString()); - - internal_link_prefix = settings.value("internal_link_prefix").toString(); - external_link_prefix = settings.value("external_link_prefix").toString(); - - margin = settings.value("margins").toDouble(); - theme = Theme(settings.value("theme").toInt()); - } - - settings.endGroup(); - return true; -} - -GeminiStyle GeminiStyle::derive(const QUrl &url) const -{ - if (this->theme == Fixed) - return *this; - - QByteArray hash = QCryptographicHash::hash(url.host().toUtf8(), QCryptographicHash::Md5); - - std::array<uint8_t, 16> items; - assert(items.size() == hash.size()); - memcpy(items.data(), hash.data(), items.size()); - - float hue = (items[0] + items[1]) / 510.0; - float saturation = items[2] / 255.0; - - double tmp; - GeminiStyle themed = *this; - switch (this->theme) - { - case AutoDarkTheme: - { - themed.background_color = QColor::fromHslF(hue, saturation, 0.25f); - themed.standard_color = QColor{0xFF, 0xFF, 0xFF}; - - themed.h1_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.75); - themed.h2_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.75); - themed.h3_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.75); - - themed.external_link_color = QColor::fromHslF(std::modf(hue + 0.25, &tmp), 1.0, 0.75); - themed.internal_link_color = themed.external_link_color.lighter(110); - themed.cross_scheme_link_color = themed.external_link_color.darker(110); - - break; - } - - case AutoLightTheme: - { - themed.background_color = QColor::fromHslF(hue, items[2] / 255.0, 0.85); - themed.standard_color = QColor{0x00, 0x00, 0x00}; - - themed.h1_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.25); - themed.h2_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.25); - themed.h3_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.25); - - themed.external_link_color = QColor::fromHslF(std::modf(hue + 0.25, &tmp), 1.0, 0.25); - themed.internal_link_color = themed.external_link_color.darker(110); - themed.cross_scheme_link_color = themed.external_link_color.lighter(110); - - break; - } - - case Fixed: - assert(false); - } - - // Same for all themes - themed.preformatted_color = themed.standard_color; - - return themed; -} - -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, - GeminiStyle const & themed_style, + DocumentStyle const & themed_style, DocumentOutlineModel &outline) { + QTextOption no_wrap; + no_wrap.setWrapMode(QTextOption::NoWrap); + QTextCharFormat preformatted; preformatted.setFont(themed_style.preformatted_font); preformatted.setForeground(themed_style.preformatted_color); @@ -345,6 +65,9 @@ std::unique_ptr<GeminiDocument> GeminiRenderer::render( result->setDocumentMargin(themed_style.margin); result->background_color = themed_style.background_color; + + result->setDefaultTextOption(no_wrap); + QTextCursor cursor{result.get()}; QTextBlockFormat non_list_format = cursor.blockFormat(); @@ -371,6 +94,7 @@ std::unique_ptr<GeminiDocument> GeminiRenderer::render( } else { + cursor.block().layout()->setTextOption(no_wrap); cursor.setCharFormat(preformatted); cursor.insertText(line + "\n"); } diff --git a/src/geminirenderer.hpp b/src/geminirenderer.hpp index c93fd4e..07deec6 100644 --- a/src/geminirenderer.hpp +++ b/src/geminirenderer.hpp @@ -7,51 +7,7 @@ #include "documentoutlinemodel.hpp" -struct GeminiStyle -{ - enum Theme { - Fixed = 0, - AutoDarkTheme = 1, - AutoLightTheme = 2 - }; - - GeminiStyle(); - - Theme theme; - - QFont standard_font; - QFont h1_font; - QFont h2_font; - QFont h3_font; - QFont preformatted_font; - - QColor background_color; - QColor standard_color; - QColor preformatted_color; - QColor h1_color; - QColor h2_color; - QColor h3_color; - - QColor internal_link_color; - QColor external_link_color; - QColor cross_scheme_link_color; - - QString internal_link_prefix; - QString external_link_prefix; - - double margin; - - bool save(QSettings & settings) const; - bool load(QSettings & settings); - - //! 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; -}; +#include "documentstyle.hpp" class GeminiDocument : public QTextDocument @@ -76,7 +32,7 @@ struct GeminiRenderer static std::unique_ptr<GeminiDocument> render( QByteArray const & input, QUrl const & root_url, - GeminiStyle const & style, + DocumentStyle const & style, DocumentOutlineModel & outline ); }; diff --git a/src/gopherclient.cpp b/src/gopherclient.cpp new file mode 100644 index 0000000..056ecd8 --- /dev/null +++ b/src/gopherclient.cpp @@ -0,0 +1,80 @@ +#include "gopherclient.hpp" +#include "ioutil.hpp" + +GopherClient::GopherClient(QObject *parent) : QObject(parent) +{ + connect(&socket, &QTcpSocket::connected, this, &GopherClient::on_connected); + connect(&socket, &QTcpSocket::readyRead, this, &GopherClient::on_readRead); + connect(&socket, &QTcpSocket::disconnected, this, &GopherClient::on_finished); +} + +GopherClient::~GopherClient() +{ + +} + +bool GopherClient::startRequest(const QUrl &url) +{ + if(isInProgress()) + return false; + + if(url.scheme() != "gopher") + return false; + + // Second char on the URL path denotes the Gopher type + // See https://tools.ietf.org/html/rfc4266 + QString type = url.path().mid(1, 1); + + mime = "application/octet-stream"; + if(type == "") mime = "text/gophermap"; + else if(type == "0") mime = "text/plain"; + else if(type == "1") mime = "text/gophermap"; + else if(type == "g") mime = "image/gif"; + else if(type == "I") mime = "image/unknown"; + else if(type == "h") mime = "text/html"; + else if(type == "s") mime = "audio/unknown"; + + qDebug() << url << "→" << mime; + + this->requested_url = url; + this->was_cancelled = false; + socket.connectToHost(url.host(), url.port(70)); + + return true; +} + +bool GopherClient::isInProgress() const +{ + return socket.isOpen(); +} + +bool GopherClient::cancelRequest() +{ + was_cancelled = true; + socket.close(); + body.clear(); + return true; +} + +void GopherClient::on_connected() +{ + auto blob = (requested_url.path().mid(2) + "\r\n").toUtf8(); + + IoUtil::writeAll(socket, blob); +} + +void GopherClient::on_readRead() +{ + body.append(socket.readAll()); +} + +void GopherClient::on_finished() +{ + if(not was_cancelled) + { + emit this->requestComplete(this->body, mime); + + was_cancelled = true; + } + body.clear(); +} diff --git a/src/gopherclient.hpp b/src/gopherclient.hpp new file mode 100644 index 0000000..2bf98fc --- /dev/null +++ b/src/gopherclient.hpp @@ -0,0 +1,40 @@ +#ifndef GOPHERCLIENT_HPP +#define GOPHERCLIENT_HPP + +#include <QObject> +#include <QTcpSocket> +#include <QUrl> + +class GopherClient : public QObject +{ + Q_OBJECT +public: + explicit GopherClient(QObject *parent = nullptr); + + ~GopherClient() override; + + bool startRequest(QUrl const & url); + + bool isInProgress() const; + + bool cancelRequest(); + +signals: + void requestComplete(QByteArray const & data, QString const & mime); + + void requestFailed(QString const & message); + +private slots: + void on_connected(); + void on_readRead(); + void on_finished(); + +private: + QTcpSocket socket; + QByteArray body; + QUrl requested_url; + bool was_cancelled; + QString mime; +}; + +#endif // GOPHERCLIENT_HPP diff --git a/src/gophermaprenderer.cpp b/src/gophermaprenderer.cpp new file mode 100644 index 0000000..bcdf11f --- /dev/null +++ b/src/gophermaprenderer.cpp @@ -0,0 +1,164 @@ +#include "gophermaprenderer.hpp" + +#include <QTextList> +#include <QTextBlock> +#include <QList> +#include <QStringList> +#include <QTextImageFormat> + +#include <QDebug> + +//Canonical Types +//0 Text File +//1 Gopher submenu or link to another gopher server +//2 CCSO Nameserver +//3 Error code returned by a Gopher server to indicate failure +//4 BinHex-encoded file (primarily for Macintosh computers) +//5 DOS file +//6 uuencoded file +//7 Gopher full-text search +//8 Telnet +//9 Binary file +//+ Mirror or alternate server (for load balancing or in case of primary server downtime) +//g GIF file +//I Image file +//T Telnet 3270 +//Non-Canonical Types +//h HTML file +//i Informational message +//s Sound file + +std::unique_ptr<QTextDocument> GophermapRenderer::render(const QByteArray &input, const QUrl &root_url, const DocumentStyle &themed_style) +{ + QTextCharFormat standard; + standard.setFont(themed_style.standard_font); + standard.setForeground(themed_style.standard_color); + + QTextCharFormat standard_link; + standard_link.setFont(themed_style.standard_font); + standard_link.setForeground(QBrush(themed_style.internal_link_color)); + + QTextCharFormat external_link; + external_link.setFont(themed_style.standard_font); + external_link.setForeground(QBrush(themed_style.external_link_color)); + + std::unique_ptr<QTextDocument> result = std::make_unique<QTextDocument>(); + result->setDocumentMargin(themed_style.margin); + + QTextCursor cursor{result.get()}; + + QTextBlockFormat non_list_format = cursor.blockFormat(); + + QList<QByteArray> lines = input.split('\n'); + for (auto const & line : lines) + { + if (line.length() < 2) // skip lines without + continue; + + if(line[line.size() - 1] != '\r') + continue; + + auto items = line.mid(1, line.length() - 2).split('\t'); + if(items.size() < 2) // invalid + continue; + + QString icon; + switch (line.at(0)) + { + case '0': // Text File + icon = "text"; + break; + case '1': // Gopher submenu or link to another gopher server + icon = "directory"; + break; + case '2': // CCSO Nameserver + icon = "ns"; + break; + case '3': // Error code returned by a Gopher server to indicate failure + icon = "error"; + break; + case '4': // BinHex-encoded file (primarily for Macintosh computers) + icon = "binhex"; + break; + case '5': // DOS file + icon = "dos"; + break; + case '6': // uuencoded file + icon = "uuencoded"; + break; + case '7': // Gopher full-text search + icon = "full-text"; + break; + case '8': // Telnet + icon = "telnet"; + break; + case '9': // Binary file + icon = "binary"; + break; + case '+': // Mirror or alternate server (for load balancing or in case of primary server downtime) + icon = "mirror"; + break; + case 'g': // GIF file + icon = "gif"; + break; + case 'I': // Image file + icon = "image"; + break; + case 'T': // Telnet 3270 + icon = "telnet"; + break; + //Non-Canonical Types + case 'h': // HTML file + icon = "html"; + break; + case 'i': // Informational message + icon = "informational"; + break; + case 's': // Sound file + icon = "sound"; + break; + default: // unknown + continue; + } + + QString title = items.at(0); + + // 1Phlog /phlog octotherp.org 70 + + + if(line.at(0) == 'i') + { + cursor.insertText(title + "\n", standard); + } + else + { + QString dst_url; + switch(items.size()) + { + case 0: assert(false); + case 1: assert(false); + case 2: + dst_url = root_url.resolved(QUrl(items.at(1))).toString(); + break; + case 3: + dst_url = "gopher://" + items.at(2) + "/" + line.mid(0,1) + items.at(1); + break; + default: + dst_url = "gopher://" + items.at(2) + ":" + items.at(3) + "/" + line.mid(0,1) + items.at(1); + break; + } + + if(not QUrl(dst_url).isValid()) { + // invlaid URL generated + qDebug() << line << dst_url; + } + + + QTextCharFormat fmt = standard_link; + fmt.setAnchor(true); + fmt.setAnchorHref(dst_url); + cursor.insertText(title + "\n", fmt); + } + } + + return result; +} diff --git a/src/gophermaprenderer.hpp b/src/gophermaprenderer.hpp new file mode 100644 index 0000000..1141c34 --- /dev/null +++ b/src/gophermaprenderer.hpp @@ -0,0 +1,25 @@ +#ifndef GOPHERMAPRENDERER_HPP +#define GOPHERMAPRENDERER_HPP + +#include "documentstyle.hpp" + +#include <QTextDocument> + +struct GophermapRenderer +{ + GophermapRenderer() = 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<QTextDocument> render( + QByteArray const & input, + QUrl const & root_url, + DocumentStyle const & style + ); +}; + +#endif // GOPHERMAPRENDERER_HPP diff --git a/src/icons.qrc b/src/icons.qrc index a0daae0..0e7b163 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -11,5 +11,11 @@ <file>icons/palette.svg</file> <file>icons/kristall.svg</file> <file>icons/settings.svg</file> + <file>icons/content-save-move.svg</file> + <file>icons/content-save.svg</file> + <file>icons/home.svg</file> + <file>icons/plus.svg</file> + <file>icons/content-save-import.svg</file> + <file>icons/folder-open.svg</file> </qresource> </RCC> diff --git a/src/icons/content-save-import.svg b/src/icons/content-save-import.svg new file mode 100644 index 0000000..e980bed --- /dev/null +++ b/src/icons/content-save-import.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + height="24" + width="24" + version="1.1"> + <path d="M 5 3 C 3.8954305 3 3 3.8954305 3 5 L 3 19 C 3 20.104569 3.8954305 21 5 21 L 11.810547 21 C 11.420547 20.34 11.170312 19.599844 11.070312 18.839844 C 9.5003125 18.309844 8.6592187 16.599297 9.1992188 15.029297 C 9.6092188 13.829297 10.73 13 12 13 C 12.44 13 12.879297 13.099063 13.279297 13.289062 C 15.569297 11.499063 18.83 11.589063 21 13.539062 L 21 7 L 17 3 L 5 3 z M 5 5 L 15 5 L 15 9 L 5 9 L 5 5 z M 18 14 L 13 18.5 L 18 23 L 18 20 L 22 20 L 22 17 L 18 17 L 18 14 z "/> +</svg> diff --git a/src/icons/content-save-move.svg b/src/icons/content-save-move.svg new file mode 100644 index 0000000..86e5d0a --- /dev/null +++ b/src/icons/content-save-move.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M17,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H11.81C11.42,20.34 11.17,19.6 11.07,18.84C9.5,18.31 8.66,16.6 9.2,15.03C9.61,13.83 10.73,13 12,13C12.44,13 12.88,13.1 13.28,13.29C15.57,11.5 18.83,11.59 21,13.54V7L17,3M15,9H5V5H15V9M13,17H17V14L22,18.5L17,23V20H13V17" /></svg>
\ No newline at end of file diff --git a/src/icons/content-save.svg b/src/icons/content-save.svg new file mode 100644 index 0000000..bbd8d59 --- /dev/null +++ b/src/icons/content-save.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" /></svg>
\ No newline at end of file diff --git a/src/icons/folder-open.svg b/src/icons/folder-open.svg new file mode 100644 index 0000000..09ce7cc --- /dev/null +++ b/src/icons/folder-open.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z" /></svg>
\ No newline at end of file diff --git a/src/icons/home.svg b/src/icons/home.svg new file mode 100644 index 0000000..132e426 --- /dev/null +++ b/src/icons/home.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" /></svg>
\ No newline at end of file diff --git a/src/icons/plus.svg b/src/icons/plus.svg new file mode 100644 index 0000000..2c21839 --- /dev/null +++ b/src/icons/plus.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" /></svg>
\ No newline at end of file diff --git a/src/ioutil.cpp b/src/ioutil.cpp new file mode 100644 index 0000000..047a901 --- /dev/null +++ b/src/ioutil.cpp @@ -0,0 +1,16 @@ +#include "ioutil.hpp" + +bool IoUtil::writeAll(QIODevice &dst, QByteArray const & src) +{ + qint64 offset = 0; + + while(offset < src.size()) + { + qint64 len = dst.write(src.data() + offset, src.size() - offset); + if(len == 0) + return false; + offset += len; + } + + return true; +} diff --git a/src/ioutil.hpp b/src/ioutil.hpp new file mode 100644 index 0000000..487c66e --- /dev/null +++ b/src/ioutil.hpp @@ -0,0 +1,11 @@ +#ifndef IOUTIL_HPP +#define IOUTIL_HPP + +#include <QIODevice> + +struct IoUtil +{ + static bool writeAll(QIODevice & dst, QByteArray const & src); +}; + +#endif // IOUTIL_HPP diff --git a/src/kristall.pro b/src/kristall.pro index 1cac9ac..e6874ed 100644 --- a/src/kristall.pro +++ b/src/kristall.pro @@ -21,9 +21,13 @@ QMAKE_CXXFLAGS += -Wno-unused-parameter SOURCES += \ browsertab.cpp \ documentoutlinemodel.cpp \ + documentstyle.cpp \ favouritecollection.cpp \ geminiclient.cpp \ geminirenderer.cpp \ + gopherclient.cpp \ + gophermaprenderer.cpp \ + ioutil.cpp \ main.cpp \ mainwindow.cpp \ protocolsetup.cpp \ @@ -34,9 +38,13 @@ SOURCES += \ HEADERS += \ browsertab.hpp \ documentoutlinemodel.hpp \ + documentstyle.hpp \ favouritecollection.hpp \ geminiclient.hpp \ geminirenderer.hpp \ + gopherclient.hpp \ + gophermaprenderer.hpp \ + ioutil.hpp \ mainwindow.hpp \ protocolsetup.hpp \ settingsdialog.hpp \ diff --git a/src/mainwindow.hpp b/src/mainwindow.hpp index 7984549..fb1d282 100644 --- a/src/mainwindow.hpp +++ b/src/mainwindow.hpp @@ -73,7 +73,7 @@ private: public: QApplication * application; QSettings settings; - GeminiStyle current_style; + DocumentStyle current_style; ProtocolSetup protocols; private: diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 859f5dd..df14fd5 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -3,6 +3,8 @@ #include <QFontDialog> #include <QColorDialog> #include <QStyle> +#include <QSettings> +#include <QInputDialog> SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent), @@ -11,20 +13,35 @@ SettingsDialog::SettingsDialog(QWidget *parent) : { ui->setupUi(this); - static_assert(GeminiStyle::Fixed == 0); - static_assert(GeminiStyle::AutoDarkTheme == 1); - static_assert(GeminiStyle::AutoLightTheme == 2); + 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("Disabled", QVariant::fromValue<int>(GeminiStyle::Fixed)); - 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->auto_theme->addItem("Disabled", QVariant::fromValue<int>(DocumentStyle::Fixed)); + this->ui->auto_theme->addItem("Dark Theme", QVariant::fromValue<int>(DocumentStyle::AutoDarkTheme)); + this->ui->auto_theme->addItem("Light Theme", QVariant::fromValue<int>(DocumentStyle::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 { }); + setGeminiStyle(DocumentStyle { }); + +// QSettings settings; +// settings.beginGroup("Themes"); +// int items = settings.beginReadArray("Themes"); + +// this->ui->presets->clear(); +// for(int i = 0; i < items; i++) +// { +// settings.setArrayIndex(i); +// this->ui->presets->addItem(settings.value("name").toString(), QVariant::fromValue(i)); +// } + +// settings.endArray(); + + this->on_presets_currentIndexChanged(-1); } SettingsDialog::~SettingsDialog() @@ -50,7 +67,7 @@ static QString formatFont(QFont const & font) .arg(style); } -void SettingsDialog::setGeminiStyle(const GeminiStyle &style) +void SettingsDialog::setGeminiStyle(DocumentStyle const &style) { static const QString COLOR_STYLE("border: 1px solid black; padding: 4px; background-color : %1; color : %2;"); @@ -289,7 +306,7 @@ void SettingsDialog::on_link_foreign_prefix_textChanged(const QString &text) void SettingsDialog::on_auto_theme_currentIndexChanged(int index) { if(index >= 0) { - current_style.theme = GeminiStyle::Theme(index); + current_style.theme = DocumentStyle::Theme(index); reloadStylePreview(); } } @@ -304,3 +321,28 @@ 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(); + + + + +} diff --git a/src/settingsdialog.hpp b/src/settingsdialog.hpp index eb9d68a..26c0c89 100644 --- a/src/settingsdialog.hpp +++ b/src/settingsdialog.hpp @@ -5,6 +5,7 @@ #include "geminirenderer.hpp" #include "protocolsetup.hpp" +#include "documentstyle.hpp" namespace Ui { class SettingsDialog; @@ -18,9 +19,9 @@ public: explicit SettingsDialog(QWidget *parent = nullptr); ~SettingsDialog(); - void setGeminiStyle(GeminiStyle const & style); + void setGeminiStyle(DocumentStyle const & style); - GeminiStyle geminiStyle() const { + DocumentStyle geminiStyle() const { return current_style; } @@ -72,6 +73,10 @@ private slots: void on_page_margin_valueChanged(double arg1); + void on_presets_currentIndexChanged(int index); + + void on_preset_new_clicked(); + private: void reloadStylePreview(); @@ -82,7 +87,7 @@ private: private: Ui::SettingsDialog *ui; - GeminiStyle current_style; + DocumentStyle current_style; std::unique_ptr<QTextDocument> preview_document; }; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 58c20e5..16d6cef 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>800</width> - <height>520</height> + <width>850</width> + <height>540</height> </rect> </property> <property name="windowTitle"> @@ -57,7 +57,7 @@ <item> <widget class="QCheckBox" name="enable_gopher"> <property name="enabled"> - <bool>false</bool> + <bool>true</bool> </property> <property name="text"> <string>Gopher</string> @@ -532,6 +532,93 @@ </property> </widget> </item> + <item row="13" 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 resource="icons.qrc"> + <normaloff>:/icons/plus.svg</normaloff>:/icons/plus.svg</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 resource="icons.qrc"> + <normaloff>:/icons/content-save.svg</normaloff>:/icons/content-save.svg</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 resource="icons.qrc"> + <normaloff>:/icons/folder-open.svg</normaloff>:/icons/folder-open.svg</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 resource="icons.qrc"> + <normaloff>:/icons/content-save-import.svg</normaloff>:/icons/content-save-import.svg</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 resource="icons.qrc"> + <normaloff>:/icons/content-save-move.svg</normaloff>:/icons/content-save-move.svg</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="13" column="0"> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string>Presets</string> + </property> + </widget> + </item> </layout> </item> <item> diff --git a/src/webclient.cpp b/src/webclient.cpp index 6e9b5ab..f3d8daa 100644 --- a/src/webclient.cpp +++ b/src/webclient.cpp @@ -17,6 +17,9 @@ WebClient::~WebClient() bool WebClient::startRequest(const QUrl &url) { + if(url.scheme() != "http" and url.scheme() != "https") + return false; + if(this->current_reply != nullptr) return true; |
