aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFelix (xq) Queißner <git@mq32.de>2020-06-08 00:30:32 +0200
committerFelix (xq) Queißner <git@mq32.de>2020-06-08 00:30:32 +0200
commitf02ccb928fd4ed591d2efe118a571e154f5df68a (patch)
tree2fc7c4037423d074c410f4c53714ddc842d33351 /src
parent425f9d41cd337133d5677744eef937a8a2a61212 (diff)
downloadkristall-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.cpp27
-rw-r--r--src/browsertab.hpp2
-rw-r--r--src/documentstyle.cpp290
-rw-r--r--src/documentstyle.hpp55
-rw-r--r--src/geminiclient.cpp3
-rw-r--r--src/geminirenderer.cpp292
-rw-r--r--src/geminirenderer.hpp48
-rw-r--r--src/gopherclient.cpp80
-rw-r--r--src/gopherclient.hpp40
-rw-r--r--src/gophermaprenderer.cpp164
-rw-r--r--src/gophermaprenderer.hpp25
-rw-r--r--src/icons.qrc6
-rw-r--r--src/icons/content-save-import.svg9
-rw-r--r--src/icons/content-save-move.svg1
-rw-r--r--src/icons/content-save.svg1
-rw-r--r--src/icons/folder-open.svg1
-rw-r--r--src/icons/home.svg1
-rw-r--r--src/icons/plus.svg1
-rw-r--r--src/ioutil.cpp16
-rw-r--r--src/ioutil.hpp11
-rw-r--r--src/kristall.pro8
-rw-r--r--src/mainwindow.hpp2
-rw-r--r--src/settingsdialog.cpp60
-rw-r--r--src/settingsdialog.hpp11
-rw-r--r--src/settingsdialog.ui93
-rw-r--r--src/webclient.cpp3
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;