aboutsummaryrefslogtreecommitdiff
path: root/browsertab.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'browsertab.cpp')
-rw-r--r--browsertab.cpp357
1 files changed, 357 insertions, 0 deletions
diff --git a/browsertab.cpp b/browsertab.cpp
new file mode 100644
index 0000000..7a14c24
--- /dev/null
+++ b/browsertab.cpp
@@ -0,0 +1,357 @@
+#include "browsertab.hpp"
+#include "ui_browsertab.h"
+#include "mainwindow.hpp"
+
+#include <QTabWidget>
+#include <QMenu>
+#include <QMessageBox>
+#include <QInputDialog>
+
+BrowserTab::BrowserTab(MainWindow * mainWindow) :
+ QWidget(nullptr),
+ ui(new Ui::BrowserTab),
+ mainWindow(mainWindow)
+{
+ ui->setupUi(this);
+
+ connect(&gemini_client, &GeminiClient::requestComplete, this, &BrowserTab::on_gemini_complete);
+ connect(&gemini_client, &GeminiClient::protocolViolation, this, &BrowserTab::on_protocolViolation);
+ connect(&gemini_client, &GeminiClient::inputRequired, this, &BrowserTab::on_inputRequired);
+ connect(&gemini_client, &GeminiClient::redirected, this, &BrowserTab::on_redirected);
+ connect(&gemini_client, &GeminiClient::temporaryFailure, this, &BrowserTab::on_temporaryFailure);
+ connect(&gemini_client, &GeminiClient::permanentFailure, this, &BrowserTab::on_permanentFailure);
+ connect(&gemini_client, &GeminiClient::transientCertificateRequested, this, &BrowserTab::on_transientCertificateRequested);
+ connect(&gemini_client, &GeminiClient::authorisedCertificateRequested, this, &BrowserTab::on_authorisedCertificateRequested);
+ connect(&gemini_client, &GeminiClient::certificateRejected, this, &BrowserTab::on_certificateRejected);
+
+ connect(&page, &QWebEnginePage::linkHovered, this, &BrowserTab::on_linkHovered);
+ connect(&page, &GeminiWebPage::navigationRequest, this, &BrowserTab::on_navigationRequest);
+
+ ui->content->setPage(&page);
+}
+
+BrowserTab::~BrowserTab()
+{
+ delete ui;
+}
+
+void BrowserTab::navigateTo(const QUrl &url)
+{
+ if(url.scheme() != "gemini") {
+ QMessageBox::warning(this, "Kristall", "Unsupported uri scheme: " + url.scheme());
+ return;
+ }
+ this->current_location = url;
+ this->ui->url_bar->setText(url.toString());
+
+ if(not gemini_client.cancelRequest()) {
+ QMessageBox::warning(this, "Kristall", "Unsupported uri scheme: " + url.scheme());
+ return;
+ }
+
+ this->redirection_count = 0;
+
+ gemini_client.startRequest(url);
+}
+
+void BrowserTab::on_menu_button_clicked()
+{
+ QMenu menu;
+ connect(menu.addAction("Add Tab"), &QAction::triggered, mainWindow, &MainWindow::addEmptyTab);
+ connect(menu.addAction("Quit"), &QAction::triggered, &QApplication::quit);
+ menu.exec(QCursor::pos());
+}
+
+void BrowserTab::on_url_bar_returnPressed()
+{
+ this->navigateTo(this->ui->url_bar->text());
+}
+
+void BrowserTab::on_content_titleChanged(const QString &title)
+{
+ this->setWindowTitle(title);
+}
+
+void BrowserTab::on_content_loadStarted()
+{
+ this->ui->refresh_button->setEnabled(false);
+}
+
+void BrowserTab::on_content_loadFinished(bool ok)
+{
+ this->ui->refresh_button->setEnabled(true);
+}
+
+void BrowserTab::on_content_urlChanged(const QUrl &url)
+{
+ // qDebug() << "url changed to" << url;
+ // this->ui->url_bar->setText(url.toString());
+}
+
+void BrowserTab::on_refresh_button_clicked()
+{
+ if(current_location.isValid())
+ this->navigateTo(this->current_location);
+}
+
+void BrowserTab::on_gemini_complete(const QByteArray &data, const QString &mime)
+{
+ if(mime.startsWith("text/gemini")) {
+ this->page.setHtml(translateGeminiToHtml(data), this->current_location);
+ } else {
+ this->page.setContent(data, mime);
+ }
+}
+
+void BrowserTab::on_protocolViolation(const QString &reason)
+{
+ this->setErrorMessage(QString("Protocol violation:\n%1").arg(reason));
+}
+
+void BrowserTab::on_inputRequired(const QString &query)
+{
+ QInputDialog dialog { this };
+
+ dialog.setInputMode(QInputDialog::TextInput);
+ dialog.setLabelText(query);
+
+ if(dialog.exec() != QDialog::Accepted) {
+ setErrorMessage(QString("Site requires input:\n%1").arg(query));
+ return;
+ }
+
+ QUrl new_location = current_location;
+ new_location.setQuery(dialog.textValue());
+ this->navigateTo(new_location);
+}
+
+void BrowserTab::on_redirected(const QUrl &uri, bool is_permanent)
+{
+ if(redirection_count >= 5) {
+ setErrorMessage("Too many redirections!");
+ return;
+ }
+ else {
+ if(gemini_client.startRequest(uri)) {
+ redirection_count += 1;
+ this->current_location = uri;
+ this->ui->url_bar->setText(uri.toString());
+ }
+ }
+}
+
+void BrowserTab::on_temporaryFailure(TemporaryFailure reason, const QString &info)
+{
+ switch(reason)
+ {
+ case TemporaryFailure::cgi_error:
+ setErrorMessage(QString("CGI Error\n%1").arg(info));
+ break;
+ case TemporaryFailure::slow_down:
+ setErrorMessage(QString("Slow Down\n%1").arg(info));
+ break;
+ case TemporaryFailure::proxy_error:
+ setErrorMessage(QString("Proxy Error\n%1").arg(info));
+ break;
+ case TemporaryFailure::unspecified:
+ setErrorMessage(QString("Temporary Failure\n%1").arg(info));
+ break;
+ case TemporaryFailure::server_unavailable:
+ setErrorMessage(QString("Server Unavailable\n%1").arg(info));
+ break;
+ }
+}
+
+void BrowserTab::on_permanentFailure(PermanentFailure reason, const QString &info)
+{
+ switch(reason)
+ {
+ case PermanentFailure::gone:
+ setErrorMessage(QString("Gone\n%1").arg(info));
+ break;
+ case PermanentFailure::not_found:
+ setErrorMessage(QString("Not Found\n%1").arg(info));
+ break;
+ case PermanentFailure::bad_request:
+ setErrorMessage(QString("Bad Request\n%1").arg(info));
+ break;
+ case PermanentFailure::unspecified:
+ setErrorMessage(QString("Permanent Failure\n%1").arg(info));
+ break;
+ case PermanentFailure::proxy_request_required:
+ setErrorMessage(QString("Proxy Request Required\n%1").arg(info));
+ break;
+ }
+}
+
+void BrowserTab::on_transientCertificateRequested(const QString &reason)
+{
+ QMessageBox::warning(this, "Kristall", "Transient certificate requirested:\n" + reason);
+}
+
+void BrowserTab::on_authorisedCertificateRequested(const QString &reason)
+{
+ QMessageBox::warning(this, "Kristall", "Authorized certificate requirested:\n" + reason);
+}
+
+void BrowserTab::on_certificateRejected(CertificateRejection reason, const QString &info)
+{
+ switch(reason)
+ {
+ case CertificateRejection::unspecified:
+ setErrorMessage(QString("Certificate Rejected\n%1").arg(info));
+ break;
+ case CertificateRejection::not_accepted:
+ setErrorMessage(QString("Certificate not accepted\n%1").arg(info));
+ break;
+ case CertificateRejection::future_certificate_rejected:
+ setErrorMessage(QString("Certificate is not yet valid\n%1").arg(info));
+ break;
+ case CertificateRejection::expired_certificate_rejected:
+ setErrorMessage(QString("Certificate expired\n%1").arg(info));
+ break;
+ }
+}
+
+void BrowserTab::on_linkHovered(const QString &url)
+{
+ this->mainWindow->setUrlPreview(QUrl(url));
+}
+
+void BrowserTab::on_navigationRequest(const QUrl &url, bool &allow)
+{
+ if(url.scheme() != "gemini") {
+ QMessageBox::warning(this, "Kristall", QString("Unsupported url: %1").arg(url.toString()));
+ }
+ else {
+ this->navigateTo(url);
+ allow = false;
+ }
+}
+
+void BrowserTab::setErrorMessage(const QString &msg)
+{
+ this->page.setContent(QString("An error happened:\n%0").arg(msg).toUtf8(), "text/plain charset=utf-8");
+}
+
+void BrowserTab::pushToHistory(const QUrl &url)
+{
+ this->navigation_history.append(url);
+ this->updateUI();
+}
+
+void BrowserTab::updateUI()
+{
+ this->ui->back_button->setEnabled(this->navigation_history.size() > 0);
+ this->ui->forward_button->setEnabled(false);
+}
+
+QByteArray BrowserTab::translateGeminiToHtml(const QByteArray &input)
+{
+ QByteArray result;
+ result.append(QString(R"html(<!doctype html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ </head>
+ <body>
+)html").toUtf8());
+
+ bool verbatim = false;
+ bool listing = false;
+
+ QList<QByteArray> lines = input.split('\n');
+ for(auto const & line : lines)
+ {
+ if(verbatim) {
+ if(listing) {
+ result.append("</ul>\n");
+ }
+ listing = false;
+
+ if(line.startsWith("```")) {
+ verbatim = false;
+ result.append("</pre><br>\n");
+ }
+ else {
+ result.append(line);
+ }
+ } else {
+ if(line.startsWith("*")) {
+ if(not listing) {
+ result.append("<ul>\n");
+ }
+ listing = true;
+
+ result.append("<li>");
+ result.append(line.mid(1).trimmed());
+ result.append("</li>");
+ continue;
+ } else {
+ if(listing) {
+ result.append("</ul>\n");
+ }
+ listing = false;
+ }
+
+ if(line.startsWith("###")) {
+ result.append("<h3>");
+ result.append(line.mid(3).trimmed());
+ result.append("</h3>");
+ }
+ else if(line.startsWith("##")) {
+ result.append("<h2>");
+ result.append(line.mid(2).trimmed());
+ result.append("</h2>");
+ }
+ else if(line.startsWith("#")) {
+ result.append("<h1>");
+ result.append(line.mid(1).trimmed());
+ result.append("</h1>");
+ }
+ else if(line.startsWith("=>")) {
+ auto const part = line.mid(2).trimmed();
+
+ QByteArray link, title;
+
+ int index = -1;
+ for(int i = 0; i < part.size(); i++) {
+ if(isspace(part[i])) {
+ index = i;
+ break;
+ }
+ }
+
+ if(index > 0) {
+ link = part.mid(0, index);
+ title = part.mid(index + 1);
+ } else {
+ link = part;
+ title = part;
+ }
+
+ // qDebug() << link << title;
+
+ result.append("<a href=\"");
+ result.append(link);
+ result.append("\">");
+ result.append(title);
+ result.append("</a><br>\n");
+ }
+ else if(line.startsWith("```")) {
+ verbatim = true;
+ result.append("<pre>");
+ }
+ else {
+ result.append(line);
+ result.append("<br>\n");
+ }
+ }
+ }
+
+ result.append(QString(R"html(
+ </body>
+</html>
+)html").toUtf8());
+ return result;
+}