From 465b59214196a914ac28bd6385f94ddc41a55cc2 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Thu, 11 Feb 2021 23:04:50 +0100 Subject: [PATCH 01/11] First stable start --- src/CMakeLists.txt | 4 ++-- src/main.cc | 1 + src/mainwindow.cc | 23 ++++++++++++++++------- src/mainwindow.h | 4 ++-- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index efd4208..8192bde 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,7 +38,7 @@ set(HEADERS mainwindow.h menu.h md-parser.h - render-area.h + draw.h source-code-dialog.h ) @@ -50,7 +50,7 @@ set(SOURCES mainwindow.cc menu.cc md-parser.cc - render-area.cc + draw.cc source-code-dialog.cc ${HEADERS} ) diff --git a/src/main.cc b/src/main.cc index 63e6b6e..43b9343 100644 --- a/src/main.cc +++ b/src/main.cc @@ -8,6 +8,7 @@ int main(int argc, char *argv[]) if (child_pid == 0) { // Run by child process + printf("INFO: Starting IPFS daemon.\n"); return IPFS::startIPFSDaemon(); } else if (child_pid > 0 ) diff --git a/src/mainwindow.cc b/src/mainwindow.cc index a0805c7..cb2ee80 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -62,8 +62,8 @@ MainWindow::MainWindow() m_hbox_bar.pack_start(m_inputField, true, true , 8); m_vbox.pack_start(m_hbox_bar, false, false, 6); - // Main browser rendering area - m_scrolledWindow.add(m_renderArea); + // Browser text drawing area + m_scrolledWindow.add(m_draw); // m_renderArea m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); m_vbox.pack_end(m_scrolledWindow, true, true, 0); @@ -83,7 +83,7 @@ void MainWindow::go_home() this->finalRequestPath = ""; this->currentContent = ""; this->m_inputField.set_text(""); - m_renderArea.showStartPage(); + //m_renderArea.showStartPage(); } /** @@ -161,12 +161,18 @@ void MainWindow::fetchFromIPFS() try { currentContent = m_file.fetch(finalRequestPath); cmark_node* doc = Parser::parseContent(currentContent); - m_renderArea.processDocument(doc); + + /*Glib::RefPtr buffer = m_draw.get_buffer(); + Gtk::TextBuffer::iterator end_iter = buffer->end(); + buffer->insert(end_iter, "Hello world");*/ + m_draw.showMessage("OK"); + + //m_renderArea.processDocument(doc); cmark_node_free(doc); } catch (const std::runtime_error &error) { std::cerr << "Error: IPFS request failed, with message: " << error.what() << std::endl; // Show not found (or any other issue) - m_renderArea.showMessage("Page not found!", "Detailed error message: " + std::string(error.what())); + m_draw.showMessage("Page not found!", "Detailed error message: " + std::string(error.what())); } } @@ -179,11 +185,14 @@ void MainWindow::openFromDisk() try { currentContent = m_file.read(finalRequestPath); cmark_node *doc = Parser::parseContent(currentContent); - m_renderArea.processDocument(doc); + // TODO.. + /*Glib::RefPtr buffer = m_draw.get_buffer(); + Gtk::TextBuffer::iterator end_iter = buffer->end(); + buffer->insert(end_iter, "Welcome");*/ cmark_node_free(doc); } catch (const std::runtime_error &error) { std::cerr << "Error: File request failed, with message: " << error.what() << std::endl; - m_renderArea.showMessage("Page not found!", "Detailed error message: " + std::string(error.what())); + m_draw.showMessage("Page not found!", "Detailed error message: " + std::string(error.what())); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 82dd0fc..66c0603 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -8,11 +8,11 @@ #include #include #include -#include "render-area.h" #include "menu.h" #include "file.h" #include "about.h" #include "source-code-dialog.h" +#include "draw.h" class MainWindow : public Gtk::Window { @@ -44,7 +44,7 @@ protected: Gtk::Image refreshIcon; Gtk::Image homeIcon; Gtk::ScrolledWindow m_scrolledWindow; - RenderArea m_renderArea; + Draw m_draw; SourceCodeDialog m_sourceCodeDialog; About m_about; private: From 2e79060a55868fee9a2b4ba6fa485f38d58c110a Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Thu, 11 Feb 2021 23:20:23 +0100 Subject: [PATCH 02/11] working --- src/draw.cc | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/draw.h | 28 +++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/draw.cc create mode 100644 src/draw.h diff --git a/src/draw.cc b/src/draw.cc new file mode 100644 index 0000000..d08f919 --- /dev/null +++ b/src/draw.cc @@ -0,0 +1,59 @@ +#include "draw.h" +#include +#include +#include + +// https://github.com/GNOME/gtkmm/blob/master/demos/gtk-demo/example_textview.cc + +Draw::Draw() +{ + set_editable(false); + set_indent(15); + set_left_margin(10); + set_right_margin(10); + set_top_margin(5); + set_bottom_margin(5); + set_monospace(true); + set_cursor_visible(false); + set_app_paintable(true); +} + +void Draw::showMessage(const std::string &message, const std::string &detailed_info) +{ + clear(); + addMarkupText("" + message + "\n\n"); + addMarkupText("" + detailed_info + ""); +} + +void Draw::addMarkupText(const std::string &text) +{ + auto buffer = Glib::unwrap(this->get_buffer()); + DispatchData *data = g_new0(struct DispatchData, 1); + data->buffer = buffer; + data->text = text; + gdk_threads_add_idle((GSourceFunc)addText, data); +} + +void Draw::clear() +{ + auto buffer = Glib::unwrap(this->get_buffer()); + gdk_threads_add_idle((GSourceFunc)clearBuffer, buffer); +} + +gboolean Draw::addText(struct DispatchData *data) +{ + GtkTextIter end_iter; + gtk_text_buffer_get_end_iter(data->buffer, &end_iter); + gtk_text_buffer_insert_markup(data->buffer, &end_iter, data->text.c_str(), -1); + g_free(data); + return FALSE; +} + +gboolean Draw::clearBuffer(GtkTextBuffer *textBuffer) +{ + GtkTextIter start_iter, end_iter; + gtk_text_buffer_get_start_iter(textBuffer, &start_iter); + gtk_text_buffer_get_end_iter(textBuffer, &end_iter); + gtk_text_buffer_delete(textBuffer, &start_iter, &end_iter); + return FALSE; +} \ No newline at end of file diff --git a/src/draw.h b/src/draw.h new file mode 100644 index 0000000..9602956 --- /dev/null +++ b/src/draw.h @@ -0,0 +1,28 @@ +#ifndef DRAW_H +#define DRAW_H + +#include + +struct DispatchData { + GtkTextBuffer *buffer; + std::string text; +}; + +class Draw : public Gtk::TextView +{ +public: + Draw(); + void showMessage(const std::string &message, const std::string &detailed_info = ""); + +protected: + + +private: + void addMarkupText(const std::string &text); + void clear(); + + static gboolean addText(struct DispatchData *data); + static gboolean clearBuffer(GtkTextBuffer *textBuffer); +}; + +#endif \ No newline at end of file From e6041ec635f9c8f429deb9fb7ab617afe46b0a9e Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Thu, 11 Feb 2021 23:25:27 +0100 Subject: [PATCH 03/11] Clean up --- src/draw.cc | 4 ---- src/draw.h | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/draw.cc b/src/draw.cc index d08f919..a6363c7 100644 --- a/src/draw.cc +++ b/src/draw.cc @@ -1,9 +1,5 @@ #include "draw.h" #include -#include -#include - -// https://github.com/GNOME/gtkmm/blob/master/demos/gtk-demo/example_textview.cc Draw::Draw() { diff --git a/src/draw.h b/src/draw.h index 9602956..c8cbdc6 100644 --- a/src/draw.h +++ b/src/draw.h @@ -13,14 +13,10 @@ class Draw : public Gtk::TextView public: Draw(); void showMessage(const std::string &message, const std::string &detailed_info = ""); - -protected: - - -private: void addMarkupText(const std::string &text); void clear(); +private: static gboolean addText(struct DispatchData *data); static gboolean clearBuffer(GtkTextBuffer *textBuffer); }; From ec016535879163d9320255e90f74211ce7908fd3 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Thu, 11 Feb 2021 23:37:05 +0100 Subject: [PATCH 04/11] make struct only known in the class itself --- src/draw.cc | 5 +++++ src/draw.h | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/draw.cc b/src/draw.cc index a6363c7..3cd7370 100644 --- a/src/draw.cc +++ b/src/draw.cc @@ -1,6 +1,11 @@ #include "draw.h" #include +struct DispatchData { + GtkTextBuffer *buffer; + std::string text; +}; + Draw::Draw() { set_editable(false); diff --git a/src/draw.h b/src/draw.h index c8cbdc6..989d2aa 100644 --- a/src/draw.h +++ b/src/draw.h @@ -3,10 +3,7 @@ #include -struct DispatchData { - GtkTextBuffer *buffer; - std::string text; -}; +struct DispatchData; class Draw : public Gtk::TextView { From 5705dc0800eac9cce4cea00cf8b9ba703e380205 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Fri, 12 Feb 2021 18:07:37 +0100 Subject: [PATCH 05/11] Working document parser (first version) --- src/draw.cc | 438 ++++++++++++++++++++++++++++++++++++++++++---- src/draw.h | 49 +++++- src/ipfs.cc | 30 ++-- src/mainwindow.cc | 274 +++++++++++++++-------------- 4 files changed, 605 insertions(+), 186 deletions(-) diff --git a/src/draw.cc b/src/draw.cc index 3cd7370..f68ab74 100644 --- a/src/draw.cc +++ b/src/draw.cc @@ -1,60 +1,432 @@ #include "draw.h" #include +#include "node.h" +#define PANGO_SCALE_XXX_LARGE ((double)1.98) -struct DispatchData { +struct DispatchData +{ GtkTextBuffer *buffer; std::string text; }; -Draw::Draw() +Draw::Draw() : fontSize(10), + fontFamily("Ubuntu"), + headingLevel(0), + listLevel(0), + isBold(false), + isItalic(false), + bulletListLevel(0), + orderedListLevel(0), + isOrderedList(false), + defaultFont(fontFamily), + bold(fontFamily), + italic(fontFamily), + boldItalic(fontFamily), + heading1(fontFamily), + heading2(fontFamily), + heading3(fontFamily), + heading4(fontFamily) { - set_editable(false); - set_indent(15); - set_left_margin(10); - set_right_margin(10); - set_top_margin(5); - set_bottom_margin(5); - set_monospace(true); - set_cursor_visible(false); - set_app_paintable(true); + set_editable(false); + set_indent(15); + set_left_margin(10); + set_right_margin(10); + set_top_margin(5); + set_bottom_margin(5); + set_monospace(true); + set_cursor_visible(false); + set_app_paintable(true); + + defaultFont.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); + bold.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); + bold.set_weight(Pango::WEIGHT_BOLD); + italic.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); + italic.set_style(Pango::Style::STYLE_ITALIC); + boldItalic.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); + boldItalic.set_weight(Pango::WEIGHT_BOLD); + boldItalic.set_style(Pango::Style::STYLE_ITALIC); + + heading1.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_XXX_LARGE); + heading1.set_weight(Pango::WEIGHT_BOLD); + heading2.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_XX_LARGE); + heading2.set_weight(Pango::WEIGHT_BOLD); + heading3.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_X_LARGE); + heading3.set_weight(Pango::WEIGHT_BOLD); + heading4.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_LARGE); + heading4.set_weight(Pango::WEIGHT_BOLD); } void Draw::showMessage(const std::string &message, const std::string &detailed_info) { - clear(); - addMarkupText("" + message + "\n\n"); - addMarkupText("" + detailed_info + ""); + this->clear(); + + addHeading1(message); + addText(detailed_info); +} + +/** + * Show start page + */ +void Draw::showStartPage() +{ + this->clear(); + + addHeading1("Welcome to the Decentralized Web (DWeb)"); + addText("For the test example, go to: ipfs://QmQzhn6hEfbYdCfwzYFsSt3eWpubVKA1dNqsgUwci5vHwq"); +} + +/** + * Process AST document and draw the text in the GTK TextView + */ +void Draw::processDocument(cmark_node *root_node) +{ + this->clear(); + + // Loop over AST nodes + cmark_event_type ev_type; + cmark_iter *iter = cmark_iter_new(root_node); + while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) + { + cmark_node *cur = cmark_iter_get_node(iter); + processNode(cur, ev_type); + } +} + +/** + * Process and parse each node in the AST + */ +void Draw::processNode(cmark_node *node, cmark_event_type ev_type) +{ + bool entering = (ev_type == CMARK_EVENT_ENTER); + + switch (node->type) + { + case CMARK_NODE_DOCUMENT: + if (entering) + { + // Reset + headingLevel = 0; + bulletListLevel = 0; + listLevel = 0; + } + break; + + case CMARK_NODE_BLOCK_QUOTE: + break; + + case CMARK_NODE_LIST: + { + cmark_list_type listType = node->as.list.list_type; + + if (entering) + { + listLevel++; + } + else + { + listLevel--; + } + if (listLevel == 0) + { + // Reset bullet/ordered levels + bulletListLevel = 0; + orderedListLevel = 0; + isOrderedList = false; + } + else if (listLevel > 0) + { + if (entering) + { + // TODO: Indent for each list level + if (listType == cmark_list_type::CMARK_BULLET_LIST) + { + bulletListLevel++; + } + else if (listType == cmark_list_type::CMARK_ORDERED_LIST) + { + orderedListLevel++; + // Create the counter (and reset to zero) + orderedListCounters[orderedListLevel] = 0; + } + } + else + { + // TODO: Un-indent list level again + if (listType == cmark_list_type::CMARK_BULLET_LIST) + { + bulletListLevel--; + } + else if (listType == cmark_list_type::CMARK_ORDERED_LIST) + { + orderedListLevel--; + } + } + + isOrderedList = (orderedListLevel > 0) && (bulletListLevel <= 0); + } + } + break; + + case CMARK_NODE_ITEM: + if (entering && isOrderedList) + { + // Increasement ordered list counter + orderedListCounters[orderedListLevel]++; + } + break; + + case CMARK_NODE_HEADING: + if (entering) + { + headingLevel = node->as.heading.level; + } + else + { + headingLevel = 0; // reset + } + break; + + case CMARK_NODE_CODE_BLOCK: + break; + + case CMARK_NODE_HTML_BLOCK: + break; + + case CMARK_NODE_CUSTOM_BLOCK: + break; + + case CMARK_NODE_THEMATIC_BREAK: + { + /* + TODO: Can we draw a line in textview? + line.margin_end_x = 20; + line.height = 0.2; + line.hex_color = "2e2e2e"; + line.cap = Cairo::LineCap::LINE_CAP_ROUND; + cr->set_line_cap(line.cap); + cr->set_source_rgb(r, g, b); + cr->set_line_width(line.height); + cr->move_to(line.start_x, line.start_y); + cr->line_to(endX, line.end_y); + cr->stroke(); + */ + } + break; + + case CMARK_NODE_PARAGRAPH: + break; + + case CMARK_NODE_TEXT: + { + // Instead of creating seperate pango layouts we may want to use Pango attributes, + // for changing parts of the text. Which is most likely be faster. + // https://developer.gnome.org/pango/stable/pango-Text-Attributes.html + // Pango is using a simple parser for parsing (X)HTML: + // https://developer.gnome.org/glib/stable/glib-Simple-XML-Subset-Parser.html + // We can use simular parse functions and using their own 'OpenTag' struct containing a list of Pango attributes: + // https://gitlab.gnome.org/GNOME/pango/-/blob/master/pango/pango-markup.c#L515 + + // For some reason Pango::Layout:create objects doesn't show up in cairo content + std::string text = cmark_node_get_literal(node); + if (bulletListLevel > 0) + { + text.insert(0, "\u2022 "); + } + else if (orderedListLevel > 0) + { + std::string number; + if (orderedListLevel % 2 == 0) + { + number = intToRoman(orderedListCounters[orderedListLevel]) + " "; + } + else + { + number = std::to_string(orderedListCounters[orderedListLevel]) + ". "; + } + text.insert(0, number); + } + + if (headingLevel > 0) + { + switch (headingLevel) + { + case 1: + addHeading1(text); + break; + case 2: + addHeading2(text); + break; + case 3: + addHeading3(text); + break; + case 4: + addHeading4(text); + break; + default: + break; + } + } + else if (isBold && isItalic) + { + addBoldItalic(text); + } + else if (isBold) + { + addBold(text); + } + else if (isItalic) + { + addItalic(text); + } + else + { + addText(text); + } + } + break; + + case CMARK_NODE_LINEBREAK: + // TODO: Hard line break (\n ?) + break; + + case CMARK_NODE_SOFTBREAK: + // ignore + break; + + case CMARK_NODE_CODE: + break; + + case CMARK_NODE_HTML_INLINE: + break; + + case CMARK_NODE_CUSTOM_INLINE: + break; + + case CMARK_NODE_STRONG: + isBold = entering; + break; + + case CMARK_NODE_EMPH: + isItalic = entering; + break; + + case CMARK_NODE_LINK: + break; + + case CMARK_NODE_IMAGE: + break; + + case CMARK_NODE_FOOTNOTE_REFERENCE: + break; + + case CMARK_NODE_FOOTNOTE_DEFINITION: + break; + default: + assert(false); + break; + } +} + +void Draw::addText(const std::string &text) +{ + addMarkupText("" + text + ""); +} + +void Draw::addHeading1(const std::string &text) +{ + addMarkupText("" + text + "\n\n"); +} + +void Draw::addHeading2(const std::string &text) +{ + addMarkupText("" + text + "\n\n"); +} + +void Draw::addHeading3(const std::string &text) +{ + addMarkupText("" + text + "\n\n"); +} + +void Draw::addHeading4(const std::string &text) +{ + addMarkupText("" + text + "\n\n"); +} + +void Draw::addBold(const std::string &text) +{ + addMarkupText("" + text + "\n\n"); +} + +void Draw::addItalic(const std::string &text) +{ + addMarkupText("" + text + "\n\n"); +} + +void Draw::addBoldItalic(const std::string &text) +{ + addMarkupText("" + text + "\n\n"); } void Draw::addMarkupText(const std::string &text) { - auto buffer = Glib::unwrap(this->get_buffer()); - DispatchData *data = g_new0(struct DispatchData, 1); - data->buffer = buffer; - data->text = text; - gdk_threads_add_idle((GSourceFunc)addText, data); + auto buffer = Glib::unwrap(this->get_buffer()); + DispatchData *data = g_new0(struct DispatchData, 1); + data->buffer = buffer; + data->text = text; + gdk_threads_add_idle((GSourceFunc)addTextIdle, data); } void Draw::clear() { - auto buffer = Glib::unwrap(this->get_buffer()); - gdk_threads_add_idle((GSourceFunc)clearBuffer, buffer); + auto buffer = Glib::unwrap(this->get_buffer()); + gdk_threads_add_idle((GSourceFunc)clearIdle, buffer); } -gboolean Draw::addText(struct DispatchData *data) +gboolean Draw::addTextIdle(struct DispatchData *data) { - GtkTextIter end_iter; - gtk_text_buffer_get_end_iter(data->buffer, &end_iter); - gtk_text_buffer_insert_markup(data->buffer, &end_iter, data->text.c_str(), -1); - g_free(data); - return FALSE; + GtkTextIter end_iter; + gtk_text_buffer_get_end_iter(data->buffer, &end_iter); + gtk_text_buffer_insert_markup(data->buffer, &end_iter, data->text.c_str(), -1); + g_free(data); + return FALSE; } -gboolean Draw::clearBuffer(GtkTextBuffer *textBuffer) +gboolean Draw::clearIdle(GtkTextBuffer *textBuffer) { - GtkTextIter start_iter, end_iter; - gtk_text_buffer_get_start_iter(textBuffer, &start_iter); - gtk_text_buffer_get_end_iter(textBuffer, &end_iter); - gtk_text_buffer_delete(textBuffer, &start_iter, &end_iter); - return FALSE; + GtkTextIter start_iter, end_iter; + gtk_text_buffer_get_start_iter(textBuffer, &start_iter); + gtk_text_buffer_get_end_iter(textBuffer, &end_iter); + gtk_text_buffer_delete(textBuffer, &start_iter, &end_iter); + return FALSE; +} + +/** + * Convert number to roman number + */ +std::string const Draw::intToRoman(int num) +{ + static const int values[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; + static const std::string numerals[] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}; + std::string res; + for (int i = 0; i < 13; ++i) + { + while (num >= values[i]) + { + num -= values[i]; + res += numerals[i]; + } + } + return res; +} + +/** + * Convert hex string to seperate RGB values + */ +void Draw::hexToRGB(const std::string &hex, double &r, double &g, double &b) +{ + unsigned int intR, intG, intB; + sscanf(hex.c_str(), "%02x%02x%02x", &intR, &intG, &intB); + r = intR / 255.0; + g = intG / 255.0; + b = intB / 255.0; } \ No newline at end of file diff --git a/src/draw.h b/src/draw.h index 989d2aa..4082e18 100644 --- a/src/draw.h +++ b/src/draw.h @@ -2,20 +2,57 @@ #define DRAW_H #include +#include +#include struct DispatchData; class Draw : public Gtk::TextView { public: - Draw(); - void showMessage(const std::string &message, const std::string &detailed_info = ""); - void addMarkupText(const std::string &text); - void clear(); + Draw(); + void showMessage(const std::string &message, const std::string &detailed_info = ""); + void showStartPage(); + void processDocument(cmark_node *root_node); private: - static gboolean addText(struct DispatchData *data); - static gboolean clearBuffer(GtkTextBuffer *textBuffer); + void processNode(cmark_node *node, cmark_event_type ev_type); + // Helper functions for adding text + void addText(const std::string &text); + void addHeading1(const std::string &text); + void addHeading2(const std::string &text); + void addHeading3(const std::string &text); + void addHeading4(const std::string &text); + void addItalic(const std::string &text); + void addBold(const std::string &text); + void addBoldItalic(const std::string &text); + + void addMarkupText(const std::string &text); + void clear(); + static gboolean addTextIdle(struct DispatchData *data); + static gboolean clearIdle(GtkTextBuffer *textBuffer); + std::string const intToRoman(int num); + void hexToRGB(const std::string& hex, double &r, double &g, double &b); + + int fontSize; + std::string fontFamily; + int headingLevel; + int listLevel; + bool isBold; + bool isItalic; + int bulletListLevel; + int orderedListLevel; + bool isOrderedList; + std::map orderedListCounters; + + Pango::FontDescription defaultFont; + Pango::FontDescription bold; + Pango::FontDescription italic; + Pango::FontDescription boldItalic; + Pango::FontDescription heading1; + Pango::FontDescription heading2; + Pango::FontDescription heading3; + Pango::FontDescription heading4; }; #endif \ No newline at end of file diff --git a/src/ipfs.cc b/src/ipfs.cc index a549b61..834dda4 100644 --- a/src/ipfs.cc +++ b/src/ipfs.cc @@ -16,24 +16,24 @@ namespace n_fs = ::std::filesystem; int IPFS::startIPFSDaemon() { - // Be sure to kill any running daemons - std::system("killall -q ipfs"); + // Be sure to kill any running daemons + std::system("killall -q ipfs"); - /// open /dev/null for writing - int fd = open("/dev/null", O_WRONLY); + /// open /dev/null for writing + int fd = open("/dev/null", O_WRONLY); - dup2(fd, 1); // make stdout a copy of fd (> /dev/null) - dup2(fd, 2); // ..and same with stderr - close(fd); // close fd + dup2(fd, 1); // make stdout a copy of fd (> /dev/null) + dup2(fd, 2); // ..and same with stderr + close(fd); // close fd - // stdout and stderr now write to /dev/null - // Ready to call exec to start IPFS Daemon - std::string currentPath = n_fs::current_path().string(); - std::string executable = currentPath.append("/../../go-ipfs/ipfs"); - const char* exe = executable.c_str(); - std::cout << "Info: Starting IPFS Daemon from: " << exe << std::endl; - char *proc[] = { strdup(exe), strdup("daemon"), strdup("--init"), strdup("--migrate"), NULL}; - return execv(exe, proc); + // stdout and stderr now write to /dev/null + // Ready to call exec to start IPFS Daemon + std::string currentPath = n_fs::current_path().string(); + std::string executable = currentPath.append("/../../go-ipfs/ipfs"); + const char *exe = executable.c_str(); + std::cout << "Info: Starting IPFS Daemon from: " << exe << std::endl; + char *proc[] = {strdup(exe), strdup("daemon"), strdup("--init"), strdup("--migrate"), NULL}; + return execv(exe, proc); } /* diff --git a/src/mainwindow.cc b/src/mainwindow.cc index cb2ee80..eff38e2 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -8,82 +8,82 @@ #include "md-parser.h" -MainWindow::MainWindow() -: m_vbox(Gtk::ORIENTATION_VERTICAL, 0), - m_hbox_bar(Gtk::ORIENTATION_HORIZONTAL, 0), - m_requestThread(nullptr), - requestPath(""), - finalRequestPath(""), - currentContent("") +MainWindow::MainWindow() + : m_vbox(Gtk::ORIENTATION_VERTICAL, 0), + m_hbox_bar(Gtk::ORIENTATION_HORIZONTAL, 0), + m_requestThread(nullptr), + requestPath(""), + finalRequestPath(""), + currentContent("") { - set_title("DBrowser"); - set_default_size(1000, 800); - set_position(Gtk::WIN_POS_CENTER); + set_title("DBrowser"); + set_default_size(1000, 800); + set_position(Gtk::WIN_POS_CENTER); - // Connect signals - m_menu.quit.connect(sigc::mem_fun(this, &MainWindow::hide)); /*!< hide main window and therefor closes the app */ - m_menu.reload.connect(sigc::mem_fun(this, &MainWindow::refresh)); /*!< Menu item for reloading the page */ - m_menu.source_code.connect(sigc::mem_fun(this, &MainWindow::show_source_code_dialog)); /*!< Source code dialog */ - m_sourceCodeDialog.signal_response().connect(sigc::mem_fun(m_sourceCodeDialog, &SourceCodeDialog::hide_dialog)); /*!< Close source code dialog */ - m_menu.about.connect(sigc::mem_fun(m_about, &About::show_about)); /*!< Display about dialog */ - m_about.signal_response().connect(sigc::mem_fun(m_about, &About::hide_about)); /*!< Close about dialog */ - m_refreshButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::refresh)); /*!< Button for reloading the page */ - m_homeButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::go_home)); /*!< Button for home page */ - m_inputField.signal_activate().connect(sigc::mem_fun(this, &MainWindow::input_activate)); /*!< User pressed enter in the input */ + // Connect signals + m_menu.quit.connect(sigc::mem_fun(this, &MainWindow::hide)); /*!< hide main window and therefor closes the app */ + m_menu.reload.connect(sigc::mem_fun(this, &MainWindow::refresh)); /*!< Menu item for reloading the page */ + m_menu.source_code.connect(sigc::mem_fun(this, &MainWindow::show_source_code_dialog)); /*!< Source code dialog */ + m_sourceCodeDialog.signal_response().connect(sigc::mem_fun(m_sourceCodeDialog, &SourceCodeDialog::hide_dialog)); /*!< Close source code dialog */ + m_menu.about.connect(sigc::mem_fun(m_about, &About::show_about)); /*!< Display about dialog */ + m_about.signal_response().connect(sigc::mem_fun(m_about, &About::hide_about)); /*!< Close about dialog */ + m_refreshButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::refresh)); /*!< Button for reloading the page */ + m_homeButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::go_home)); /*!< Button for home page */ + m_inputField.signal_activate().connect(sigc::mem_fun(this, &MainWindow::input_activate)); /*!< User pressed enter in the input */ - m_vbox.pack_start(m_menu, false, false, 0); + m_vbox.pack_start(m_menu, false, false, 0); - // Horizontal bar - auto styleBack = m_backButton.get_style_context(); - styleBack->add_class("circular"); - auto styleForward = m_forwardButton.get_style_context(); - styleForward->add_class("circular"); - auto styleRefresh = m_refreshButton.get_style_context(); - styleRefresh->add_class("circular"); - m_backButton.set_relief(Gtk::RELIEF_NONE); - m_forwardButton.set_relief(Gtk::RELIEF_NONE); - m_refreshButton.set_relief(Gtk::RELIEF_NONE); - m_homeButton.set_relief(Gtk::RELIEF_NONE); + // Horizontal bar + auto styleBack = m_backButton.get_style_context(); + styleBack->add_class("circular"); + auto styleForward = m_forwardButton.get_style_context(); + styleForward->add_class("circular"); + auto styleRefresh = m_refreshButton.get_style_context(); + styleRefresh->add_class("circular"); + m_backButton.set_relief(Gtk::RELIEF_NONE); + m_forwardButton.set_relief(Gtk::RELIEF_NONE); + m_refreshButton.set_relief(Gtk::RELIEF_NONE); + m_homeButton.set_relief(Gtk::RELIEF_NONE); - // Add icons to buttons - backIcon.set_from_icon_name("go-previous", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); - m_backButton.add(backIcon); - forwardIcon.set_from_icon_name("go-next", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); - m_forwardButton.add(forwardIcon); - refreshIcon.set_from_icon_name("view-refresh", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); - m_refreshButton.add(refreshIcon); - homeIcon.set_from_icon_name("go-home", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); - m_homeButton.add(homeIcon); + // Add icons to buttons + backIcon.set_from_icon_name("go-previous", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); + m_backButton.add(backIcon); + forwardIcon.set_from_icon_name("go-next", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); + m_forwardButton.add(forwardIcon); + refreshIcon.set_from_icon_name("view-refresh", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); + m_refreshButton.add(refreshIcon); + homeIcon.set_from_icon_name("go-home", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); + m_homeButton.add(homeIcon); - m_hbox_bar.pack_start(m_backButton, false, false , 0); - m_hbox_bar.pack_start(m_forwardButton, false, false , 0); - m_hbox_bar.pack_start(m_refreshButton, false, false , 0); - m_hbox_bar.pack_start(m_homeButton, false, false , 0); - m_hbox_bar.pack_start(m_inputField, true, true , 8); - m_vbox.pack_start(m_hbox_bar, false, false, 6); + m_hbox_bar.pack_start(m_backButton, false, false, 0); + m_hbox_bar.pack_start(m_forwardButton, false, false, 0); + m_hbox_bar.pack_start(m_refreshButton, false, false, 0); + m_hbox_bar.pack_start(m_homeButton, false, false, 0); + m_hbox_bar.pack_start(m_inputField, true, true, 8); + m_vbox.pack_start(m_hbox_bar, false, false, 6); - // Browser text drawing area - m_scrolledWindow.add(m_draw); // m_renderArea - m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + // Browser text drawing area + m_scrolledWindow.add(m_draw); + m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); - m_vbox.pack_end(m_scrolledWindow, true, true, 0); - add(m_vbox); - show_all_children(); + m_vbox.pack_end(m_scrolledWindow, true, true, 0); + add(m_vbox); + show_all_children(); - // Grap focus to input field by default - m_inputField.grab_focus(); + // Grap focus to input field by default + m_inputField.grab_focus(); - // Show start page by default - go_home(); + // Show start page by default + go_home(); } void MainWindow::go_home() { - this->requestPath = ""; - this->finalRequestPath = ""; - this->currentContent = ""; - this->m_inputField.set_text(""); - //m_renderArea.showStartPage(); + this->requestPath = ""; + this->finalRequestPath = ""; + this->currentContent = ""; + this->m_inputField.set_text(""); + m_draw.showStartPage(); } /** @@ -91,20 +91,22 @@ void MainWindow::go_home() */ void MainWindow::input_activate() { - // QmQzhn6hEfbYdCfwzYFsSt3eWpubVKA1dNqsgUwci5vHwq + // QmQzhn6hEfbYdCfwzYFsSt3eWpubVKA1dNqsgUwci5vHwq - // Stop running thread (if applicable) - if (m_requestThread) { - if(m_requestThread->joinable()) { - pthread_cancel(m_requestThread->native_handle()); - m_requestThread->join(); - delete m_requestThread; - m_requestThread = nullptr; + // Stop running thread (if applicable) + if (m_requestThread) + { + if (m_requestThread->joinable()) + { + pthread_cancel(m_requestThread->native_handle()); + m_requestThread->join(); + delete m_requestThread; + m_requestThread = nullptr; + } } - } - if (m_requestThread == nullptr) - m_requestThread = new std::thread(&MainWindow::doRequest, this, m_inputField.get_text()); + if (m_requestThread == nullptr) + m_requestThread = new std::thread(&MainWindow::doRequest, this, m_inputField.get_text()); } /** @@ -112,8 +114,8 @@ void MainWindow::input_activate() */ void MainWindow::refresh() { - if (m_requestThread == nullptr) - m_requestThread = new std::thread(&MainWindow::doRequest, this, ""); + if (m_requestThread == nullptr) + m_requestThread = new std::thread(&MainWindow::doRequest, this, ""); } /** @@ -122,32 +124,43 @@ void MainWindow::refresh() */ void MainWindow::doRequest(const std::string &path) { - currentContent = ""; - if (!path.empty()) { - requestPath = path; - } - if (requestPath.empty()) { - std::cerr << "Info: Empty request path." << std::endl; - } else { - // Check if CID - if (requestPath.rfind("ipfs://", 0) == 0) { - finalRequestPath = requestPath; - finalRequestPath.erase(0, 7); - fetchFromIPFS(); - } else if((requestPath.length() == 46) && (requestPath.rfind("Qm", 0) == 0)) { - // CIDv0 - finalRequestPath = requestPath; - fetchFromIPFS(); - } else if (requestPath.rfind("file://", 0) == 0) { - finalRequestPath = requestPath; - finalRequestPath.erase(0, 7); - openFromDisk(); - } else { - // IPFS as fallback / CIDv1 - finalRequestPath = requestPath; - fetchFromIPFS(); + currentContent = ""; + if (!path.empty()) + { + requestPath = path; + } + if (requestPath.empty()) + { + std::cerr << "Info: Empty request path." << std::endl; + } + else + { + // Check if CID + if (requestPath.rfind("ipfs://", 0) == 0) + { + finalRequestPath = requestPath; + finalRequestPath.erase(0, 7); + fetchFromIPFS(); + } + else if ((requestPath.length() == 46) && (requestPath.rfind("Qm", 0) == 0)) + { + // CIDv0 + finalRequestPath = requestPath; + fetchFromIPFS(); + } + else if (requestPath.rfind("file://", 0) == 0) + { + finalRequestPath = requestPath; + finalRequestPath.erase(0, 7); + openFromDisk(); + } + else + { + // IPFS as fallback / CIDv1 + finalRequestPath = requestPath; + fetchFromIPFS(); + } } - } } /** @@ -156,24 +169,21 @@ void MainWindow::doRequest(const std::string &path) */ void MainWindow::fetchFromIPFS() { - // TODO: Execute the code in a seperate thread/process? - // Since otherwise this may block the UI if it takes too long! - try { - currentContent = m_file.fetch(finalRequestPath); - cmark_node* doc = Parser::parseContent(currentContent); - - /*Glib::RefPtr buffer = m_draw.get_buffer(); - Gtk::TextBuffer::iterator end_iter = buffer->end(); - buffer->insert(end_iter, "Hello world");*/ - m_draw.showMessage("OK"); - - //m_renderArea.processDocument(doc); - cmark_node_free(doc); - } catch (const std::runtime_error &error) { - std::cerr << "Error: IPFS request failed, with message: " << error.what() << std::endl; - // Show not found (or any other issue) - m_draw.showMessage("Page not found!", "Detailed error message: " + std::string(error.what())); - } + // TODO: Execute the code in a seperate thread/process? + // Since otherwise this may block the UI if it takes too long! + try + { + currentContent = m_file.fetch(finalRequestPath); + cmark_node *doc = Parser::parseContent(currentContent); + m_draw.processDocument(doc); + cmark_node_free(doc); + } + catch (const std::runtime_error &error) + { + std::cerr << "Error: IPFS request failed, with message: " << error.what() << std::endl; + // Show not found (or any other issue) + m_draw.showMessage("Page not found!", "Detailed error message: " + std::string(error.what())); + } } /** @@ -182,23 +192,23 @@ void MainWindow::fetchFromIPFS() */ void MainWindow::openFromDisk() { - try { - currentContent = m_file.read(finalRequestPath); - cmark_node *doc = Parser::parseContent(currentContent); - // TODO.. - /*Glib::RefPtr buffer = m_draw.get_buffer(); - Gtk::TextBuffer::iterator end_iter = buffer->end(); - buffer->insert(end_iter, "Welcome");*/ - cmark_node_free(doc); - } catch (const std::runtime_error &error) { - std::cerr << "Error: File request failed, with message: " << error.what() << std::endl; - m_draw.showMessage("Page not found!", "Detailed error message: " + std::string(error.what())); - } + try + { + currentContent = m_file.read(finalRequestPath); + cmark_node *doc = Parser::parseContent(currentContent); + m_draw.processDocument(doc); + cmark_node_free(doc); + } + catch (const std::runtime_error &error) + { + std::cerr << "Error: File request failed, with message: " << error.what() << std::endl; + m_draw.showMessage("Page not found!", "Detailed error message: " + std::string(error.what())); + } } /// Show source code dialog window with the current content void MainWindow::show_source_code_dialog() { - m_sourceCodeDialog.setText(currentContent); - m_sourceCodeDialog.run(); + m_sourceCodeDialog.setText(currentContent); + m_sourceCodeDialog.run(); } \ No newline at end of file From 302de5c2e0253848c2b3a855ce77fdcbb0b3d702 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Fri, 12 Feb 2021 21:46:41 +0100 Subject: [PATCH 06/11] Tabs with Micha style --- src/about.cc | 5 +-- src/about.h | 1 - src/draw.cc | 35 +++++++++---------- src/file.cc | 39 +++++++++++---------- src/file.h | 7 ++-- src/ipfs.h | 2 +- src/main.cc | 42 +++++++++++------------ src/mainwindow.h | 71 ++++++++++++++++++++------------------- src/md-parser.cc | 24 +++++++------ src/md-parser.h | 19 +++++------ src/menu.cc | 16 +++++---- src/menu.h | 34 +++++++++---------- src/source-code-dialog.cc | 20 +++++------ src/source-code-dialog.h | 7 ++-- 14 files changed, 165 insertions(+), 157 deletions(-) diff --git a/src/about.cc b/src/about.cc index 3665365..8040cf6 100644 --- a/src/about.cc +++ b/src/about.cc @@ -1,6 +1,7 @@ #include "about.h" -About::About() { +About::About() +{ std::vector devs; devs.push_back("Melroy van den Berg "); logo.set("../../misc/browser_logo_small.png"); @@ -9,7 +10,7 @@ About::About() { set_version("0.1.0"); set_comments("The fastest decentralized & distributed Browser on planet Earth."); set_logo(logo.get_pixbuf()); - set_website("https://melroy.org/"); + set_website("https://melroy.org/"); set_copyright("Copyright © 2020-2021 Melroy van den Berg"); set_authors(devs); set_artists(devs); diff --git a/src/about.h b/src/about.h index 300e138..93eb740 100644 --- a/src/about.h +++ b/src/about.h @@ -13,7 +13,6 @@ class About: public Gtk::AboutDialog public: About(); virtual ~About(); - void show_about(); void hide_about(int response); protected: diff --git a/src/draw.cc b/src/draw.cc index f68ab74..004b10b 100644 --- a/src/draw.cc +++ b/src/draw.cc @@ -9,23 +9,24 @@ struct DispatchData std::string text; }; -Draw::Draw() : fontSize(10), - fontFamily("Ubuntu"), - headingLevel(0), - listLevel(0), - isBold(false), - isItalic(false), - bulletListLevel(0), - orderedListLevel(0), - isOrderedList(false), - defaultFont(fontFamily), - bold(fontFamily), - italic(fontFamily), - boldItalic(fontFamily), - heading1(fontFamily), - heading2(fontFamily), - heading3(fontFamily), - heading4(fontFamily) +Draw::Draw() + : fontSize(10), + fontFamily("Ubuntu"), + headingLevel(0), + listLevel(0), + isBold(false), + isItalic(false), + bulletListLevel(0), + orderedListLevel(0), + isOrderedList(false), + defaultFont(fontFamily), + bold(fontFamily), + italic(fontFamily), + boldItalic(fontFamily), + heading1(fontFamily), + heading2(fontFamily), + heading3(fontFamily), + heading4(fontFamily) { set_editable(false); set_indent(15); diff --git a/src/file.cc b/src/file.cc index 8c7ed08..d18f003 100644 --- a/src/file.cc +++ b/src/file.cc @@ -12,26 +12,31 @@ namespace n_fs = ::std::experimental::filesystem; namespace n_fs = ::std::filesystem; #endif -File::File() {} +File::File() +{ +} /** * Get file from disk * \param path File path * \return AST model of markdown file (cmark_node) */ -std::string const File::read(const std::string& path) +std::string const File::read(const std::string &path) { - if(n_fs::exists(path) && n_fs::is_regular_file(path)) { - std::ifstream inFile; - inFile.open(path, std::ifstream::in); + if (n_fs::exists(path) && n_fs::is_regular_file(path)) + { + std::ifstream inFile; + inFile.open(path, std::ifstream::in); - std::stringstream strStream; - strStream << inFile.rdbuf(); - return strStream.str(); - } else { - // File doesn't exists or isn't a file - throw std::runtime_error("File does not exists or isn't a regular file."); - } + std::stringstream strStream; + strStream << inFile.rdbuf(); + return strStream.str(); + } + else + { + // File doesn't exists or isn't a file + throw std::runtime_error("File does not exists or isn't a regular file."); + } } /** @@ -40,10 +45,10 @@ std::string const File::read(const std::string& path) * \throw runtime error when something goes wrong * \return AST model of markdown file (cmark_node) */ -std::string const File::fetch(const std::string& path) +std::string const File::fetch(const std::string &path) { - ipfs::Client client("localhost", 5001, "6s"); - std::stringstream contents; - client.FilesGet(path, &contents); - return contents.str(); + ipfs::Client client("localhost", 5001, "6s"); + std::stringstream contents; + client.FilesGet(path, &contents); + return contents.str(); } diff --git a/src/file.h b/src/file.h index 51a690e..b6bf00e 100644 --- a/src/file.h +++ b/src/file.h @@ -10,9 +10,8 @@ class File { public: - File(); - - std::string const read(const std::string& path); /*!< Read file from disk */ - std::string const fetch(const std::string& path); /*!< Fetch file from IPFS network */ + File(); + std::string const read(const std::string &path); /*!< Read file from disk */ + std::string const fetch(const std::string &path); /*!< Fetch file from IPFS network */ }; #endif \ No newline at end of file diff --git a/src/ipfs.h b/src/ipfs.h index 2dc9742..a95ab2e 100644 --- a/src/ipfs.h +++ b/src/ipfs.h @@ -10,6 +10,6 @@ class IPFS { public: - static int startIPFSDaemon(); + static int startIPFSDaemon(); }; #endif \ No newline at end of file diff --git a/src/main.cc b/src/main.cc index 43b9343..cec6ed1 100644 --- a/src/main.cc +++ b/src/main.cc @@ -4,26 +4,26 @@ int main(int argc, char *argv[]) { - pid_t child_pid = fork(); - if (child_pid == 0) - { - // Run by child process - printf("INFO: Starting IPFS daemon.\n"); - return IPFS::startIPFSDaemon(); - } - else if (child_pid > 0 ) - { - // Parent process (child_pid is PID of child) - auto app = Gtk::Application::create(argc, argv, "org.melroy.browser"); + pid_t child_pid = fork(); + if (child_pid == 0) + { + // Run by child process + printf("INFO: Starting IPFS daemon.\n"); + return IPFS::startIPFSDaemon(); + } + else if (child_pid > 0) + { + // Parent process (child_pid is PID of child) + auto app = Gtk::Application::create(argc, argv, "org.melroy.browser"); - MainWindow window; - int exitCode = app->run(window); - // Kill also the child - kill(child_pid, SIGTERM); - return exitCode; - } - else // PID < 0, error - { - printf("ERROR: fork failed.\n"); - } + MainWindow window; + int exitCode = app->run(window); + // Kill also the child + kill(child_pid, SIGTERM); + return exitCode; + } + else // PID < 0, error + { + printf("ERROR: fork failed.\n"); + } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 66c0603..b690fcf 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -17,46 +17,47 @@ class MainWindow : public Gtk::Window { public: - MainWindow(); + MainWindow(); protected: - // Signal handlers: - // Our new improved on_button_clicked(). (see below) - void go_home(); - void input_activate(); - void refresh(); - void on_button_clicked(Glib::ustring data); - void show_about(); - void hide_about(int response); - void show_source_code_dialog(); + // Signal handlers: + // Our new improved on_button_clicked(). (see below) + void go_home(); + void input_activate(); + void refresh(); + void on_button_clicked(Glib::ustring data); + void show_about(); + void hide_about(int response); + void show_source_code_dialog(); + + // Child widgets + Menu m_menu; + Gtk::Box m_vbox; + Gtk::Box m_hbox_bar; + Gtk::Button m_backButton; + Gtk::Button m_forwardButton; + Gtk::Button m_refreshButton; + Gtk::Button m_homeButton; + Gtk::Entry m_inputField; + Gtk::Image backIcon; + Gtk::Image forwardIcon; + Gtk::Image refreshIcon; + Gtk::Image homeIcon; + Gtk::ScrolledWindow m_scrolledWindow; + Draw m_draw; + SourceCodeDialog m_sourceCodeDialog; + About m_about; - // Child widgets - Menu m_menu; - Gtk::Box m_vbox; - Gtk::Box m_hbox_bar; - Gtk::Button m_backButton; - Gtk::Button m_forwardButton; - Gtk::Button m_refreshButton; - Gtk::Button m_homeButton; - Gtk::Entry m_inputField; - Gtk::Image backIcon; - Gtk::Image forwardIcon; - Gtk::Image refreshIcon; - Gtk::Image homeIcon; - Gtk::ScrolledWindow m_scrolledWindow; - Draw m_draw; - SourceCodeDialog m_sourceCodeDialog; - About m_about; private: - File m_file; - std::thread *m_requestThread; - std::string requestPath; - std::string finalRequestPath; - std::string currentContent; + File m_file; + std::thread *m_requestThread; + std::string requestPath; + std::string finalRequestPath; + std::string currentContent; - void doRequest(const std::string &path); - void fetchFromIPFS(); - void openFromDisk(); + void doRequest(const std::string &path); + void fetchFromIPFS(); + void openFromDisk(); }; #endif \ No newline at end of file diff --git a/src/md-parser.cc b/src/md-parser.cc index 0cdfd12..b4a4427 100644 --- a/src/md-parser.cc +++ b/src/md-parser.cc @@ -10,24 +10,25 @@ static const int OPTIONS = CMARK_OPT_DEFAULT; /// Meyers Singleton -Parser::Parser()= default; +Parser::Parser() = default; /// Destructor -Parser::~Parser()= default; +Parser::~Parser() = default; /** * \brief Get singleton instance * \return Helper reference (singleton) */ -Parser& Parser::getInstance() { - static Parser instance; - return instance; +Parser &Parser::getInstance() +{ + static Parser instance; + return instance; } /** * Parse markdown file from string content * @return AST structure (of type cmark_node) */ -cmark_node * Parser::parseContent(const std::string &content) +cmark_node *Parser::parseContent(const std::string &content) { //cmark_node *doc; // Parse to AST with cmark @@ -37,7 +38,7 @@ cmark_node * Parser::parseContent(const std::string &content) //addMarkdownExtension(parser, "strikethrough"); //addMarkdownExtension(parser, "table"); - const char *data = content.c_str(); + const char *data = content.c_str(); // TODO: Copy cmark_parse_document() to be able to add extensions to the parser return cmark_parse_document(data, strlen(data), OPTIONS); } @@ -56,8 +57,9 @@ std::string const Parser::renderHTML(cmark_node *node) /** * This is a function that will make enabling extensions easier */ -void Parser::addMarkdownExtension(cmark_parser *parser, const char *extName) { - cmark_syntax_extension *ext = cmark_find_syntax_extension(extName); - if ( ext ) - cmark_parser_attach_syntax_extension(parser, ext); +void Parser::addMarkdownExtension(cmark_parser *parser, const char *extName) +{ + cmark_syntax_extension *ext = cmark_find_syntax_extension(extName); + if (ext) + cmark_parser_attach_syntax_extension(parser, ext); } diff --git a/src/md-parser.h b/src/md-parser.h index 207471e..4b6958f 100644 --- a/src/md-parser.h +++ b/src/md-parser.h @@ -11,18 +11,17 @@ class Parser { public: - // Singleton - static Parser& getInstance(); - - static cmark_node * parseContent(const std::string &content); - static std::string const renderHTML(cmark_node *node); + // Singleton + static Parser &getInstance(); + static cmark_node *parseContent(const std::string &content); + static std::string const renderHTML(cmark_node *node); private: - Parser(); - ~Parser(); - Parser(const Parser&)= delete; - Parser& operator=(const Parser&)= delete; + Parser(); + ~Parser(); + Parser(const Parser &) = delete; + Parser &operator=(const Parser &) = delete; - static void addMarkdownExtension(cmark_parser *parser, const char *extName); + static void addMarkdownExtension(cmark_parser *parser, const char *extName); }; #endif diff --git a/src/menu.cc b/src/menu.cc index 3e83605..1b039f1 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -1,9 +1,9 @@ #include "menu.h" Menu::Menu() -: m_file("_File", true), - m_view("_View", true), - m_help("_Help", true) + : m_file("_File", true), + m_view("_View", true), + m_help("_Help", true) { // File submenu auto quit_menuitem = createMenuItem("_Quit"); @@ -22,7 +22,7 @@ Menu::Menu() // Add items to sub-menus m_file_submenu.append(*quit_menuitem); m_view_submenu.append(*reload_menuitem); - m_view_submenu.append(*source_code_menuitem); + m_view_submenu.append(*source_code_menuitem); m_help_submenu.append(*about_menuitem); // Add sub-menus to menus @@ -35,14 +35,16 @@ Menu::Menu() append(m_help); } -Menu::~Menu() { +Menu::~Menu() +{ } /** * \brief Helper method for creating a menu with an image * \return GTKWidget menu item pointer */ -Gtk::MenuItem* Menu::createMenuItem(const Glib::ustring& label_text) { - Gtk::MenuItem* item = Gtk::manage(new Gtk::MenuItem(label_text, true)); +Gtk::MenuItem *Menu::createMenuItem(const Glib::ustring &label_text) +{ + Gtk::MenuItem *item = Gtk::manage(new Gtk::MenuItem(label_text, true)); return item; } diff --git a/src/menu.h b/src/menu.h index fcc0529..e48ed4e 100644 --- a/src/menu.h +++ b/src/menu.h @@ -11,29 +11,29 @@ * \class Menu * \brief The top main-menu */ -class Menu: public Gtk::MenuBar +class Menu : public Gtk::MenuBar { public: - sigc::signal reload; - sigc::signal source_code; - sigc::signal quit; - sigc::signal about; + sigc::signal reload; + sigc::signal source_code; + sigc::signal quit; + sigc::signal about; - Menu(); - virtual ~Menu(); - Gtk::Menu* GetMachineMenu(); + Menu(); + virtual ~Menu(); + Gtk::Menu *GetMachineMenu(); protected: - // Child widgets - Gtk::MenuItem m_file; - Gtk::MenuItem m_view; - Gtk::MenuItem m_help; - Gtk::Menu m_file_submenu; /*!< File sub menu */ - Gtk::Menu m_view_submenu; /*!< Help sub menu */ - Gtk::Menu m_help_submenu; /*!< Help sub menu */ - Gtk::SeparatorMenuItem m_separator1; + // Child widgets + Gtk::MenuItem m_file; + Gtk::MenuItem m_view; + Gtk::MenuItem m_help; + Gtk::Menu m_file_submenu; /*!< File sub menu */ + Gtk::Menu m_view_submenu; /*!< Help sub menu */ + Gtk::Menu m_help_submenu; /*!< Help sub menu */ + Gtk::SeparatorMenuItem m_separator1; private: - Gtk::MenuItem* createMenuItem(const Glib::ustring& label_text); + Gtk::MenuItem *createMenuItem(const Glib::ustring &label_text); }; #endif \ No newline at end of file diff --git a/src/source-code-dialog.cc b/src/source-code-dialog.cc index 4f1325b..d579592 100644 --- a/src/source-code-dialog.cc +++ b/src/source-code-dialog.cc @@ -3,16 +3,16 @@ SourceCodeDialog::SourceCodeDialog() { - set_title("View source code"); - set_default_size(700, 750); + set_title("View source code"); + set_default_size(700, 750); - m_scrolledWindow.add(m_sourceCode); - m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + m_scrolledWindow.add(m_sourceCode); + m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); - auto vbox = get_content_area(); - vbox->pack_start(m_scrolledWindow, true, true, 0); + auto vbox = get_content_area(); + vbox->pack_start(m_scrolledWindow, true, true, 0); - show_all_children(); + show_all_children(); } SourceCodeDialog::~SourceCodeDialog() {} @@ -20,10 +20,10 @@ SourceCodeDialog::~SourceCodeDialog() {} * Set multi-line code source * \param[in] text Source code text */ -void SourceCodeDialog::setText(const std::string& text) +void SourceCodeDialog::setText(const std::string &text) { - Glib::RefPtr buffer = m_sourceCode.get_buffer(); - buffer->set_text(text); + Glib::RefPtr buffer = m_sourceCode.get_buffer(); + buffer->set_text(text); } void SourceCodeDialog::hide_dialog(__attribute__((unused)) int response) diff --git a/src/source-code-dialog.h b/src/source-code-dialog.h index 21a4bc3..f80d87a 100644 --- a/src/source-code-dialog.h +++ b/src/source-code-dialog.h @@ -11,16 +11,15 @@ class SourceCodeDialog : public Gtk::Dialog public: SourceCodeDialog(); virtual ~SourceCodeDialog(); - - void setText(const std::string& text); - + void setText(const std::string &text); void hide_dialog(int response); + protected: // Child widgets Gtk::ScrolledWindow m_scrolledWindow; Gtk::TextView m_sourceCode; -private: +private: }; #endif \ No newline at end of file From b93c5bc31fdba31e79d967d240d2d1e27747b55e Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Fri, 12 Feb 2021 22:40:58 +0100 Subject: [PATCH 07/11] Almost fixed again --- src/draw.cc | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/draw.cc b/src/draw.cc index 004b10b..b6aa189 100644 --- a/src/draw.cc +++ b/src/draw.cc @@ -10,8 +10,8 @@ struct DispatchData }; Draw::Draw() - : fontSize(10), - fontFamily("Ubuntu"), + : fontSize(10 * PANGO_SCALE), + fontFamily("Ubuntu Monospace"), headingLevel(0), listLevel(0), isBold(false), @@ -38,22 +38,22 @@ Draw::Draw() set_cursor_visible(false); set_app_paintable(true); - defaultFont.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); - bold.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); + defaultFont.set_size(fontSize); + bold.set_size(fontSize); bold.set_weight(Pango::WEIGHT_BOLD); - italic.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); + italic.set_size(fontSize); italic.set_style(Pango::Style::STYLE_ITALIC); - boldItalic.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); + boldItalic.set_size(fontSize); boldItalic.set_weight(Pango::WEIGHT_BOLD); boldItalic.set_style(Pango::Style::STYLE_ITALIC); - heading1.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_XXX_LARGE); + heading1.set_size(fontSize * PANGO_SCALE_XXX_LARGE); heading1.set_weight(Pango::WEIGHT_BOLD); - heading2.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_XX_LARGE); + heading2.set_size(fontSize * PANGO_SCALE_XX_LARGE); heading2.set_weight(Pango::WEIGHT_BOLD); - heading3.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_X_LARGE); + heading3.set_size(fontSize * PANGO_SCALE_X_LARGE); heading3.set_weight(Pango::WEIGHT_BOLD); - heading4.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_LARGE); + heading4.set_size(fontSize * PANGO_SCALE_LARGE); heading4.set_weight(Pango::WEIGHT_BOLD); } @@ -118,7 +118,6 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) case CMARK_NODE_LIST: { cmark_list_type listType = node->as.list.list_type; - if (entering) { listLevel++; @@ -169,6 +168,9 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) break; case CMARK_NODE_ITEM: + // Line break for each item + if (entering) + addText("\n"); if (entering && isOrderedList) { // Increasement ordered list counter @@ -215,6 +217,10 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) break; case CMARK_NODE_PARAGRAPH: + if (listLevel == 0) { + // Add new line, but not when listing is enabled + addText("\n"); + } break; case CMARK_NODE_TEXT: @@ -227,7 +233,6 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) // We can use simular parse functions and using their own 'OpenTag' struct containing a list of Pango attributes: // https://gitlab.gnome.org/GNOME/pango/-/blob/master/pango/pango-markup.c#L515 - // For some reason Pango::Layout:create objects doesn't show up in cairo content std::string text = cmark_node_get_literal(node); if (bulletListLevel > 0) { @@ -264,6 +269,7 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) addHeading4(text); break; default: + addHeading4(text); // fallback break; } } @@ -287,11 +293,13 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) break; case CMARK_NODE_LINEBREAK: - // TODO: Hard line break (\n ?) + // Hard brake + addText("\n"); break; case CMARK_NODE_SOFTBREAK: - // ignore + // only insert space + addText(" "); break; case CMARK_NODE_CODE: @@ -335,37 +343,37 @@ void Draw::addText(const std::string &text) void Draw::addHeading1(const std::string &text) { - addMarkupText("" + text + "\n\n"); + addMarkupText("\n" + text + "\n"); } void Draw::addHeading2(const std::string &text) { - addMarkupText("" + text + "\n\n"); + addMarkupText("\n" + text + "\n"); } void Draw::addHeading3(const std::string &text) { - addMarkupText("" + text + "\n\n"); + addMarkupText("\n" + text + "\n"); } void Draw::addHeading4(const std::string &text) { - addMarkupText("" + text + "\n\n"); + addMarkupText("\n" + text + "\n"); } void Draw::addBold(const std::string &text) { - addMarkupText("" + text + "\n\n"); + addMarkupText("" + text + ""); } void Draw::addItalic(const std::string &text) { - addMarkupText("" + text + "\n\n"); + addMarkupText("" + text + ""); } void Draw::addBoldItalic(const std::string &text) { - addMarkupText("" + text + "\n\n"); + addMarkupText("" + text + ""); } void Draw::addMarkupText(const std::string &text) From 90b3cbbb48a73b65da2034e377cd15d9be6f1751 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Fri, 12 Feb 2021 23:30:39 +0100 Subject: [PATCH 08/11] Fully working textview, removing render area files --- src/about.cc | 3 +- src/draw.cc | 69 +++--- src/draw.h | 5 +- src/mainwindow.cc | 2 +- src/render-area.cc | 528 --------------------------------------------- src/render-area.h | 86 -------- 6 files changed, 46 insertions(+), 647 deletions(-) delete mode 100644 src/render-area.cc delete mode 100644 src/render-area.h diff --git a/src/about.cc b/src/about.cc index 8040cf6..f5d6416 100644 --- a/src/about.cc +++ b/src/about.cc @@ -6,7 +6,7 @@ About::About() devs.push_back("Melroy van den Berg "); logo.set("../../misc/browser_logo_small.png"); - set_name("DBrowser"); + set_program_name("DWeb Browser"); set_version("0.1.0"); set_comments("The fastest decentralized & distributed Browser on planet Earth."); set_logo(logo.get_pixbuf()); @@ -15,6 +15,7 @@ About::About() set_authors(devs); set_artists(devs); set_license_type(Gtk::License::LICENSE_MIT_X11); + set_position(Gtk::WIN_POS_CENTER_ON_PARENT); show_all_children(); } diff --git a/src/draw.cc b/src/draw.cc index b6aa189..ab0040c 100644 --- a/src/draw.cc +++ b/src/draw.cc @@ -55,6 +55,10 @@ Draw::Draw() heading3.set_weight(Pango::WEIGHT_BOLD); heading4.set_size(fontSize * PANGO_SCALE_LARGE); heading4.set_weight(Pango::WEIGHT_BOLD); + heading5.set_size(fontSize * PANGO_SCALE_MEDIUM); + heading5.set_weight(Pango::WEIGHT_BOLD); + heading6.set_size(fontSize * PANGO_SCALE_MEDIUM); + heading6.set_weight(Pango::WEIGHT_BOLD); } void Draw::showMessage(const std::string &message, const std::string &detailed_info) @@ -124,6 +128,11 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) } else { + // Last list level new line + if (listLevel == 1) + { + addText("\n"); + } listLevel--; } if (listLevel == 0) @@ -137,7 +146,8 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) { if (entering) { - // TODO: Indent for each list level + // Add tab + addText(" "); if (listType == cmark_list_type::CMARK_BULLET_LIST) { bulletListLevel++; @@ -200,24 +210,13 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) case CMARK_NODE_THEMATIC_BREAK: { - /* - TODO: Can we draw a line in textview? - line.margin_end_x = 20; - line.height = 0.2; - line.hex_color = "2e2e2e"; - line.cap = Cairo::LineCap::LINE_CAP_ROUND; - cr->set_line_cap(line.cap); - cr->set_source_rgb(r, g, b); - cr->set_line_width(line.height); - cr->move_to(line.start_x, line.start_y); - cr->line_to(endX, line.end_y); - cr->stroke(); - */ + addText("\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015"); } break; case CMARK_NODE_PARAGRAPH: - if (listLevel == 0) { + if (listLevel == 0) + { // Add new line, but not when listing is enabled addText("\n"); } @@ -236,7 +235,7 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) std::string text = cmark_node_get_literal(node); if (bulletListLevel > 0) { - text.insert(0, "\u2022 "); + text.insert(0, std::string(bulletListLevel, '\u0009') + "\u2022 "); } else if (orderedListLevel > 0) { @@ -249,7 +248,7 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) { number = std::to_string(orderedListCounters[orderedListLevel]) + ". "; } - text.insert(0, number); + text.insert(0, std::string(orderedListLevel, '\u0009') + number); } if (headingLevel > 0) @@ -268,8 +267,14 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type) case 4: addHeading4(text); break; + case 5: + addHeading5(text); + break; + case 6: + addHeading6(text); + break; default: - addHeading4(text); // fallback + addHeading5(text); // fallback break; } } @@ -361,6 +366,16 @@ void Draw::addHeading4(const std::string &text) addMarkupText("\n" + text + "\n"); } +void Draw::addHeading5(const std::string &text) +{ + addMarkupText("\n" + text + "\n"); +} + +void Draw::addHeading6(const std::string &text) +{ + addMarkupText("\n" + text + "\n"); +} + void Draw::addBold(const std::string &text) { addMarkupText("" + text + ""); @@ -391,6 +406,9 @@ void Draw::clear() gdk_threads_add_idle((GSourceFunc)clearIdle, buffer); } +/** + * Add text on Idle Call function + */ gboolean Draw::addTextIdle(struct DispatchData *data) { GtkTextIter end_iter; @@ -400,6 +418,9 @@ gboolean Draw::addTextIdle(struct DispatchData *data) return FALSE; } +/** + * Clear Text on Idle Call function + */ gboolean Draw::clearIdle(GtkTextBuffer *textBuffer) { GtkTextIter start_iter, end_iter; @@ -427,15 +448,3 @@ std::string const Draw::intToRoman(int num) } return res; } - -/** - * Convert hex string to seperate RGB values - */ -void Draw::hexToRGB(const std::string &hex, double &r, double &g, double &b) -{ - unsigned int intR, intG, intB; - sscanf(hex.c_str(), "%02x%02x%02x", &intR, &intG, &intB); - r = intR / 255.0; - g = intG / 255.0; - b = intB / 255.0; -} \ No newline at end of file diff --git a/src/draw.h b/src/draw.h index 4082e18..9dde7e6 100644 --- a/src/draw.h +++ b/src/draw.h @@ -23,6 +23,8 @@ private: void addHeading2(const std::string &text); void addHeading3(const std::string &text); void addHeading4(const std::string &text); + void addHeading5(const std::string &text); + void addHeading6(const std::string &text); void addItalic(const std::string &text); void addBold(const std::string &text); void addBoldItalic(const std::string &text); @@ -32,7 +34,6 @@ private: static gboolean addTextIdle(struct DispatchData *data); static gboolean clearIdle(GtkTextBuffer *textBuffer); std::string const intToRoman(int num); - void hexToRGB(const std::string& hex, double &r, double &g, double &b); int fontSize; std::string fontFamily; @@ -53,6 +54,8 @@ private: Pango::FontDescription heading2; Pango::FontDescription heading3; Pango::FontDescription heading4; + Pango::FontDescription heading5; + Pango::FontDescription heading6; }; #endif \ No newline at end of file diff --git a/src/mainwindow.cc b/src/mainwindow.cc index eff38e2..1e448f7 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -16,7 +16,7 @@ MainWindow::MainWindow() finalRequestPath(""), currentContent("") { - set_title("DBrowser"); + set_title("DWeb Browser"); set_default_size(1000, 800); set_position(Gtk::WIN_POS_CENTER); diff --git a/src/render-area.cc b/src/render-area.cc deleted file mode 100644 index 96051ca..0000000 --- a/src/render-area.cc +++ /dev/null @@ -1,528 +0,0 @@ -#include "render-area.h" -#include "node.h" - -#include -#include -#include -#include -#include - -#define PANGO_SCALE_XXX_LARGE ((double)1.98) - -RenderArea::RenderArea() -: currentX(0), - currentY(0), - sceneMarginX(25), - sceneMarginY(15), - currentXList(sceneMarginX), - headingLevel(0), - listLevel(0), - wordSpacing(4), // spacing may depend on the font - highestWidth(0), - highestHeight(0), - paragraphMargin(5), - headingMargin(10), - listMargin(5), - horizontalLineMargin(2), - listXOffset(20), - isBold(false), - isItalic(false), - bulletListLevel(0), - orderedListLevel(0), - isOrderedList(false), - fontSize(10), - fontFamily("Ubuntu"), - pageWidth(0), - pageHeight(0) -{ - // Default size - set_size_request(200, 400); - createPangoContexts(); -} - -RenderArea::~RenderArea() -{ -} - -void RenderArea::createPangoContexts() -{ - defaultFont.set_family(fontFamily); - defaultFont.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); - - boldFont.set_family(fontFamily); - boldFont.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); - boldFont.set_weight(Pango::WEIGHT_BOLD); - - italicFont.set_family(fontFamily); - italicFont.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); - italicFont.set_style(Pango::Style::STYLE_ITALIC); - - boldItalicFont.set_family(fontFamily); - boldItalicFont.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_MEDIUM); - boldItalicFont.set_weight(Pango::WEIGHT_BOLD); - boldItalicFont.set_style(Pango::Style::STYLE_ITALIC); - - heading1Font.set_family(fontFamily); - heading1Font.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_XXX_LARGE); - heading1Font.set_weight(Pango::WEIGHT_BOLD); - heading2Font.set_family(fontFamily); - heading2Font.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_XX_LARGE); - heading2Font.set_weight(Pango::WEIGHT_BOLD); - heading3Font.set_family(fontFamily); - heading3Font.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_X_LARGE); - heading3Font.set_weight(Pango::WEIGHT_BOLD); - heading4Font.set_family(fontFamily); - heading4Font.set_size(fontSize * PANGO_SCALE * PANGO_SCALE_LARGE); - heading4Font.set_weight(Pango::WEIGHT_BOLD); -} - -/** - * Clean-up render area fields - */ -void RenderArea::clear() -{ - m_textList.clear(); - m_lines.clear(); -} - -/** - * Process AST document and eventually render to screen (drawing area) - */ -void RenderArea::processDocument(cmark_node *root_node) -{ - this->clear(); - - /*typedef std::chrono::high_resolution_clock Time; - typedef std::chrono::milliseconds ms; - typedef std::chrono::duration fsec; - auto t0 = Time::now();*/ - - // Loop over AST nodes - cmark_event_type ev_type; - cmark_iter *iter = cmark_iter_new(root_node); - while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { - cmark_node *cur = cmark_iter_get_node(iter); - processNode(cur, ev_type); - } - - /*auto t1 = Time::now(); - fsec fs = t1 - t0; - ms d = std::chrono::duration_cast(fs); - std::cout << fs.count() << "s\n"; - std::cout << d.count() << "ms\n";*/ - - // Change drawing area - set_size_request(pageWidth, pageHeight); - this->redraw(); -} - -/** - * Show a message on screen - * \param message Message to be displayed - */ -void RenderArea::showMessage(const std::string &message, const std::string &detailed_info) -{ - this->clear(); - - auto layout = create_pango_layout(message); - layout->set_font_description(heading1Font); - text_struct textStruct; - textStruct.x = 40; - textStruct.y = 20; - textStruct.layout = layout; - m_textList.push_back(textStruct); - - if (!detailed_info.empty()) { - auto detail_layout = create_pango_layout(detailed_info); - detail_layout->set_font_description(defaultFont); - text_struct textStructDetail; - textStructDetail.x = 40; - textStructDetail.y = 70; - textStructDetail.layout = detail_layout; - m_textList.push_back(textStructDetail); - } - - set_size_request(800, 800); - - this->redraw(); -} - -/** - * Show start page - */ -void RenderArea::showStartPage() -{ - this->clear(); - - auto layout = create_pango_layout("Welcome to the Decentralized Web (DWeb)"); - layout->set_font_description(heading1Font); - text_struct textStruct; - textStruct.x = 40; - textStruct.y = 20; - textStruct.layout = layout; - m_textList.push_back(textStruct); - - auto detail_layout = create_pango_layout("For the test example, go to: ipfs://QmQzhn6hEfbYdCfwzYFsSt3eWpubVKA1dNqsgUwci5vHwq"); - detail_layout->set_font_description(defaultFont); - text_struct textStructDetail; - textStructDetail.x = 40; - textStructDetail.y = 70; - textStructDetail.layout = detail_layout; - m_textList.push_back(textStructDetail); - - set_size_request(700, 800); - - this->redraw(); -} - -/** - * Calculates the drawing locations and parse each node in the AST - */ -void RenderArea::processNode(cmark_node *node, cmark_event_type ev_type) -{ - bool entering = (ev_type == CMARK_EVENT_ENTER); - - switch (node->type) { - case CMARK_NODE_DOCUMENT: - if (entering) { - // Reset on start - currentX = sceneMarginX; - currentY = sceneMarginY; - headingLevel = 0; - bulletListLevel = 0; - listLevel = 0; - highestHeight = 0; - highestWidth = 0; - pageWidth = 0; - pageHeight = 0; - } else { - // Document end, set page width & height - pageWidth = highestWidth; - pageHeight = currentY + sceneMarginY; - } - break; - - case CMARK_NODE_BLOCK_QUOTE: - break; - - case CMARK_NODE_LIST: { - cmark_list_type listType = node->as.list.list_type; - - if (entering) { - if (listLevel == 0) { - currentY += listMargin; // First level Y margin - } - listLevel++; - } else { - if (listLevel == 1) { - currentY += listMargin; // First level Y margin - } - listLevel--; - } - if (listLevel == 0) { - // Reset X to be safe - currentX = sceneMarginX; - currentXList = currentX; - // Reset bullet/ordered levels - bulletListLevel = 0; - orderedListLevel = 0; - isOrderedList = false; - } else if (listLevel > 0) { - if (entering) { - currentXList += listXOffset; - if (listType == cmark_list_type::CMARK_BULLET_LIST) { - bulletListLevel++; - } else if(listType == cmark_list_type::CMARK_ORDERED_LIST) { - orderedListLevel++; - // Create the counter (and reset to zero) - orderedListCounters[orderedListLevel] = 0; - } - } else { - currentXList -= listXOffset; - if (listType == cmark_list_type::CMARK_BULLET_LIST) { - bulletListLevel--; - } else if(listType == cmark_list_type::CMARK_ORDERED_LIST) { - orderedListLevel--; - } - } - - isOrderedList = (orderedListLevel > 0) && (bulletListLevel <= 0); - } - } - break; - - case CMARK_NODE_ITEM: - // Line break for list items - currentY += highestHeight; - // Reset heighest high (Y-axis) - highestHeight = 0; - // Set new node item to the correct X position - currentX = currentXList; - - if (entering && isOrderedList) { - // Increasement ordered list counter - orderedListCounters[orderedListLevel]++; - } - break; - - case CMARK_NODE_HEADING: - if (entering) { - headingLevel = node->as.heading.level; - } else { - headingLevel = 0; // reset - } - // Move to left again - currentX = sceneMarginX; - // New heading - currentY += highestHeight + headingMargin; - - // Reset heighest high (Y-axis) - highestHeight = 0; - - break; - - case CMARK_NODE_CODE_BLOCK: - break; - - case CMARK_NODE_HTML_BLOCK: - break; - - case CMARK_NODE_CUSTOM_BLOCK: - break; - - case CMARK_NODE_THEMATIC_BREAK: { - currentY += horizontalLineMargin; - line_struct line; - line.start_x = 20; - line.start_y = currentY; - line.end_x = -1; // auto-size width - line.end_y = currentY; - line.margin_end_x = 20; - line.height = 0.2; - line.hex_color = "2e2e2e"; - line.cap = Cairo::LineCap::LINE_CAP_ROUND; - m_lines.push_back(line); - currentY += horizontalLineMargin; - } - break; - - case CMARK_NODE_PARAGRAPH: - // Skip paragraph if listing is enabled - if (listLevel == 0) { - // Move to left again - currentX = sceneMarginX; - // New paragraph - currentY += highestHeight + paragraphMargin; - - // Reset heighest high (Y-axis) - highestHeight = 0; - } - break; - - case CMARK_NODE_TEXT: { - // Instead of creating seperate pango layouts we may want to use Pango attributes, - // for changing parts of the text. Which is most likely be faster. - // https://developer.gnome.org/pango/stable/pango-Text-Attributes.html - // Pango is using a simple parser for parsing (X)HTML: - // https://developer.gnome.org/glib/stable/glib-Simple-XML-Subset-Parser.html - // We can use simular parse functions and using their own 'OpenTag' struct containing a list of Pango attributes: - // https://gitlab.gnome.org/GNOME/pango/-/blob/master/pango/pango-markup.c#L515 - - // For some reason Pango::Layout:create objects doesn't show up in cairo content - std::string text = cmark_node_get_literal(node); - if (bulletListLevel > 0) { - text.insert(0, "\u2022 "); - } else if(orderedListLevel > 0) { - std::string number; - if (orderedListLevel % 2 == 0) { - number = intToRoman(orderedListCounters[orderedListLevel]) + " "; - } else { - number = std::to_string(orderedListCounters[orderedListLevel]) + ". "; - } - text.insert(0, number); - } - - auto layout = create_pango_layout(text); - - if (headingLevel > 0) { - switch (headingLevel) - { - case 1: - layout->set_font_description(heading1Font); - break; - case 2: - layout->set_font_description(heading2Font); - break; - case 3: - layout->set_font_description(heading3Font); - break; - case 4: - layout->set_font_description(heading4Font); - break; - default: - break; - } - } else if (isBold && isItalic) { - layout->set_font_description(boldItalicFont); - } else if (isBold) { - layout->set_font_description(boldFont); - } else if (isItalic) { - layout->set_font_description(italicFont); - } else { - layout->set_font_description(defaultFont); - } - //layout->set_width(100); - int textWidth; - int textHeight; - layout->get_pixel_size(textWidth, textHeight); - - // Add text to list - text_struct textStruct; - textStruct.x = currentX; - textStruct.y = currentY; - textStruct.layout = layout; - m_textList.push_back(textStruct); - - // Increase X with text width - currentX += textWidth; - - if (textHeight > highestHeight) { - highestHeight = textHeight; - } - if (currentX > highestWidth) { - highestWidth = currentX; - } - } - break; - - case CMARK_NODE_LINEBREAK: - // Move to left again - currentX = sceneMarginX; - // Line break (no soft break) - currentY += highestHeight; - - // Reset heighest high (Y-axis) - highestHeight = 0; - break; - - case CMARK_NODE_SOFTBREAK: - // ignore - // Only insert a space between the words - currentX += wordSpacing; - break; - - case CMARK_NODE_CODE: - break; - - case CMARK_NODE_HTML_INLINE: - break; - - case CMARK_NODE_CUSTOM_INLINE: - break; - - case CMARK_NODE_STRONG: - isBold = entering; - break; - - case CMARK_NODE_EMPH: - isItalic = entering; - break; - - case CMARK_NODE_LINK: - break; - - case CMARK_NODE_IMAGE: - break; - - case CMARK_NODE_FOOTNOTE_REFERENCE: - break; - - case CMARK_NODE_FOOTNOTE_DEFINITION: - break; - default: - assert(false); - break; - } -} - -/** - * Force redraw - */ -void RenderArea::redraw() -{ - queue_draw_area(0, 0, get_allocation().get_width(), get_allocation().get_height()); -} - -/** - * Overrided method of GTK DrawingArea - */ -bool RenderArea::on_draw(const Cairo::RefPtr& cr) -{ - Gtk::Allocation allocation = get_allocation(); - // Total drawing area size - const int width = allocation.get_width(); - const int height = allocation.get_height(); - - // White background - cr->set_source_rgb(1.0, 1.0, 1.0); - cr->rectangle(0, 0, width, height); - cr->fill(); - - // Set to black for text - cr->set_source_rgb(0.0, 0.0, 0.0); - - // Draw text - std::list::iterator textIt; - for(textIt = m_textList.begin(); textIt != m_textList.end(); ++textIt) { - auto text = (*textIt); - cr->move_to(text.x, text.y); - text.layout->show_in_cairo_context(cr); - } - - // Draw lines - std::list::iterator lineIt; - for(lineIt = m_lines.begin(); lineIt != m_lines.end(); ++lineIt) { - auto line = (*lineIt); - double r, g, b; - hexToRGB(line.hex_color, r, g, b); - int endX = line.end_x; - if (line.end_x == -1) - endX = width - line.margin_end_x; - cr->set_line_cap(line.cap); - cr->set_source_rgb(r, g, b); - cr->set_line_width(line.height); - cr->move_to(line.start_x, line.start_y); - cr->line_to(endX, line.end_y); - cr->stroke(); - } - return true; -} - -/** - * Convert number to roman number - */ -std::string const RenderArea::intToRoman(int num) -{ - static const int values[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 }; - static const std::string numerals[] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" }; - std::string res; - for (int i = 0; i < 13; ++i) { - while (num >= values[i]) { - num -= values[i]; - res += numerals[i]; - } - } - return res; -} - -/** - * Convert hex string to seperate RGB values - */ -void RenderArea::hexToRGB(const std::string& hex, double &r, double &g, double &b) -{ - unsigned int intR, intG, intB; - sscanf(hex.c_str(), "%02x%02x%02x", &intR, &intG, &intB); - r = intR / 255.0; - g = intG / 255.0; - b = intB / 255.0; -} \ No newline at end of file diff --git a/src/render-area.h b/src/render-area.h deleted file mode 100644 index 60d0d17..0000000 --- a/src/render-area.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef RENDER_AREA_H -#define RENDER_AREA_H - -#include -#include -#include - -struct text_struct { - int x; - int y; - Glib::RefPtr layout; -}; - -struct line_struct { - int start_x; - int start_y; - int end_x; // -1 means auto-size - int end_y; - int margin_end_x; - double height; - std::string hex_color; - Cairo::LineCap cap; -}; - -class RenderArea : public Gtk::DrawingArea -{ -public: - RenderArea(); - virtual ~RenderArea(); - - void processDocument(cmark_node *root_node); - void showMessage(const std::string &message, const std::string &detailed_info = ""); - void showStartPage(); - -protected: - std::list m_textList; - std::list m_lines; - - // Override default signal handler: - bool on_draw(const Cairo::RefPtr& cr) override; - -private: - int currentX; - int currentY; - int sceneMarginX; - int sceneMarginY; - int currentXList; - int headingLevel; - int listLevel; - int wordSpacing; - int highestWidth; - int highestHeight; - int paragraphMargin; - int headingMargin; - int listMargin; - int horizontalLineMargin; - int listXOffset; - bool isBold; - bool isItalic; - int bulletListLevel; - int orderedListLevel; - bool isOrderedList; - std::map orderedListCounters; - int fontSize; - std::string fontFamily; - int pageWidth; - int pageHeight; - - Pango::FontDescription defaultFont; - Pango::FontDescription boldFont; - Pango::FontDescription italicFont; - Pango::FontDescription boldItalicFont; - Pango::FontDescription heading1Font; - Pango::FontDescription heading2Font; - Pango::FontDescription heading3Font; - Pango::FontDescription heading4Font; - - void createPangoContexts(); - void clear(); - void processNode(cmark_node *node, cmark_event_type ev_type); - void redraw(); - std::string const intToRoman(int num); - void hexToRGB(const std::string& hex, double &r, double &g, double &b); -}; - -#endif \ No newline at end of file From 581ab8ec740b903693ebd7a791989f88674a730e Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Fri, 12 Feb 2021 23:36:12 +0100 Subject: [PATCH 09/11] Release 0.2.0/media/melroy/Data/Projects/browser/build/bin/browser --- LICENSE | 2 +- src/about.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 7dbed8d..7050e5e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Decentralized World Wide Web +Copyright (c) 2020-2021 Decentralized World Wide Web Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/about.cc b/src/about.cc index f5d6416..1cbf1c0 100644 --- a/src/about.cc +++ b/src/about.cc @@ -7,7 +7,7 @@ About::About() logo.set("../../misc/browser_logo_small.png"); set_program_name("DWeb Browser"); - set_version("0.1.0"); + set_version("0.2.0"); set_comments("The fastest decentralized & distributed Browser on planet Earth."); set_logo(logo.get_pixbuf()); set_website("https://melroy.org/"); From 88c2bd9c8ed619688cf3b83f2553578c5aff6613 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Fri, 12 Feb 2021 23:39:01 +0100 Subject: [PATCH 10/11] new screenshot --- docs/browser_screenshot.png | Bin 25773 -> 66283 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/browser_screenshot.png b/docs/browser_screenshot.png index 0a8d7190dd9322feac18787a62ae786205f888f7..f4feb186569de4511cf8dad7c1601279b0042ff9 100644 GIT binary patch literal 66283 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%0DjS~|`Nho^wCE1nH$F82N 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 literal 25773 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 iANmphGkG9O`?lbxWYNNzN6I% Date: Fri, 12 Feb 2021 23:48:48 +0100 Subject: [PATCH 11/11] Update markdown --- README.md | 10 +++++----- docs/diagrams.md | 21 +++++---------------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index effceff..d272d97 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# DBrowser +# DWeb Browser Decentralized Browser; a revolution of the WWW. @@ -6,7 +6,7 @@ What would you do different; if you could **reinvent** The Internet in 21st cent I was inspired by Douglas Engelbart, Tim Berners-Lee and Ted Nelson as well as projects like IPFS, Jekyll, ARPANET, and more. -*Note:* Project is WIP! +*Note:* Project is still WIP! ![Browser Screenshot](./docs/browser_screenshot.png) @@ -33,13 +33,13 @@ The current success criteria: Decentralized Browser written in C++20 with C libraries. And using the [cmark-gfm](https://github.com/github/cmark-gfm) library, used for CommonMark (markdown) parsing. -Browser is using GTK as UI library including Pango & Cairo for text drawing and manipulation. +Browser is using GTK 3 as UI library including Pango & Cairo for text drawing and manipulation. For now we will use markdown as the source of the site. No HTML and JavaScript anymore, content is king after all. ### Development Environment -I'm using VSCodium editor, with the following extensions installed: `C/C++`, `CMake`, `CMake Tools`, `PlantUML`, `Markdown All in One` and `GitLab Workflow`. +I'm using VSCodium editor, with the following extensions installed: `C/C++`, `CMake`, `CMake Tools`, `PlantUML`, `Markdown All in One`, `vscode-icons` and `GitLab Workflow`. ### Build Dependencies @@ -61,7 +61,7 @@ There existing several design and/or research diagrams of Browser by using Plant We are currently in an exploration phase about which 2D (vector) graphics rendering library we should use. -*Currently:* GTK + Cairo + Pango, which works fine and is fast! +**Findings:** Using GTK 3 + Cairo + Pango, which works fine and is very fast! See [research document](docs/research.md) (also in markdown) for more information and conclusions based on facts. diff --git a/docs/diagrams.md b/docs/diagrams.md index e0c8fa8..1847cb5 100644 --- a/docs/diagrams.md +++ b/docs/diagrams.md @@ -19,29 +19,18 @@ List of sequence/UML diagrams for design and/or research reasons. Could may be u ## Text on Screen Flowcharts -Folowing diagrams are only applicable when you need to draw text on the screen without supporting library. +Following diagram display the high-level flow/activity of drawing text on a screen. -However, most GUI libraries does support you in drawing text on a screen. +Most GUI libraries do have a text layout/rendering engine built-in. -**Prepare steps** - -```plantuml -(*) --> "Load fonts glyphs (ttf)/default font" ---> "Create Font Atlas" ---> "Create Viewport" --->[Ready for render] (*) -``` - -**Text rendering** +### Drawing Activity Diagram* ```plantuml (*) --> "Get file online /\nRead from disk" -->[Content in memory] "Parse document" --->[AST model output] "Convert AST to Bitmap struct" --->[Ready for painter] "Paint text on Viewport" +-->[AST model output] "Convert AST to Text/Pango Markup" +-->[Ready for draw] "Add text to TextView buffer" -->[Text visable on Screen] (*) ``` -*ttf* = TrueType Font - *AST* = Abstract Syntax Tree \ No newline at end of file