Merge branch 'master' of gitlab.melroy.org:dwww/dbrowser

master
Melroy van den Berg 2021-02-12 23:52:03 +01:00
commit b45c184efd
24 changed files with 831 additions and 917 deletions

View File

@ -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

View File

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -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

View File

@ -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}
)

View File

@ -1,19 +1,21 @@
#include "about.h"
About::About() {
About::About()
{
std::vector<Glib::ustring> devs;
devs.push_back("Melroy van den Berg <melroy@melroy.org>");
logo.set("../../misc/browser_logo_small.png");
set_name("DBrowser");
set_version("0.1.0");
set_program_name("DWeb Browser");
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/");
set_website("https://melroy.org/");
set_copyright("Copyright © 2020-2021 Melroy van den Berg");
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();
}

View File

@ -13,7 +13,6 @@ class About: public Gtk::AboutDialog
public:
About();
virtual ~About();
void show_about();
void hide_about(int response);
protected:

450
src/draw.cc Normal file
View File

@ -0,0 +1,450 @@
#include "draw.h"
#include <gdk/gdkthreads.h>
#include "node.h"
#define PANGO_SCALE_XXX_LARGE ((double)1.98)
struct DispatchData
{
GtkTextBuffer *buffer;
std::string text;
};
Draw::Draw()
: fontSize(10 * PANGO_SCALE),
fontFamily("Ubuntu Monospace"),
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);
defaultFont.set_size(fontSize);
bold.set_size(fontSize);
bold.set_weight(Pango::WEIGHT_BOLD);
italic.set_size(fontSize);
italic.set_style(Pango::Style::STYLE_ITALIC);
boldItalic.set_size(fontSize);
boldItalic.set_weight(Pango::WEIGHT_BOLD);
boldItalic.set_style(Pango::Style::STYLE_ITALIC);
heading1.set_size(fontSize * PANGO_SCALE_XXX_LARGE);
heading1.set_weight(Pango::WEIGHT_BOLD);
heading2.set_size(fontSize * PANGO_SCALE_XX_LARGE);
heading2.set_weight(Pango::WEIGHT_BOLD);
heading3.set_size(fontSize * PANGO_SCALE_X_LARGE);
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)
{
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
{
// Last list level new line
if (listLevel == 1)
{
addText("\n");
}
listLevel--;
}
if (listLevel == 0)
{
// Reset bullet/ordered levels
bulletListLevel = 0;
orderedListLevel = 0;
isOrderedList = false;
}
else if (listLevel > 0)
{
if (entering)
{
// Add tab
addText(" ");
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:
// Line break for each item
if (entering)
addText("\n");
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:
{
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)
{
// Add new line, but not when listing is enabled
addText("\n");
}
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
std::string text = cmark_node_get_literal(node);
if (bulletListLevel > 0)
{
text.insert(0, std::string(bulletListLevel, '\u0009') + "\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, std::string(orderedListLevel, '\u0009') + 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;
case 5:
addHeading5(text);
break;
case 6:
addHeading6(text);
break;
default:
addHeading5(text); // fallback
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:
// Hard brake
addText("\n");
break;
case CMARK_NODE_SOFTBREAK:
// only insert space
addText(" ");
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("<span font_desc=\"" + defaultFont.to_string() + "\">" + text + "</span>");
}
void Draw::addHeading1(const std::string &text)
{
addMarkupText("\n<span font_desc=\"" + heading1.to_string() + "\">" + text + "</span>\n");
}
void Draw::addHeading2(const std::string &text)
{
addMarkupText("\n<span font_desc=\"" + heading2.to_string() + "\">" + text + "</span>\n");
}
void Draw::addHeading3(const std::string &text)
{
addMarkupText("\n<span font_desc=\"" + heading3.to_string() + "\">" + text + "</span>\n");
}
void Draw::addHeading4(const std::string &text)
{
addMarkupText("\n<span font_desc=\"" + heading4.to_string() + "\">" + text + "</span>\n");
}
void Draw::addHeading5(const std::string &text)
{
addMarkupText("\n<span font_desc=\"" + heading5.to_string() + "\">" + text + "</span>\n");
}
void Draw::addHeading6(const std::string &text)
{
addMarkupText("\n<span foreground=\"gray\" font_desc=\"" + heading6.to_string() + "\">" + text + "</span>\n");
}
void Draw::addBold(const std::string &text)
{
addMarkupText("<span font_desc=\"" + bold.to_string() + "\">" + text + "</span>");
}
void Draw::addItalic(const std::string &text)
{
addMarkupText("<span font_desc=\"" + italic.to_string() + "\">" + text + "</span>");
}
void Draw::addBoldItalic(const std::string &text)
{
addMarkupText("<span font_desc=\"" + boldItalic.to_string() + "\">" + text + "</span>");
}
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)addTextIdle, data);
}
void Draw::clear()
{
auto buffer = Glib::unwrap(this->get_buffer());
gdk_threads_add_idle((GSourceFunc)clearIdle, buffer);
}
/**
* Add text on Idle Call function
*/
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;
}
/**
* Clear Text on Idle Call function
*/
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;
}
/**
* 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;
}

61
src/draw.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef DRAW_H
#define DRAW_H
#include <gtkmm/textview.h>
#include <pangomm/layout.h>
#include <cmark-gfm.h>
struct DispatchData;
class Draw : public Gtk::TextView
{
public:
Draw();
void showMessage(const std::string &message, const std::string &detailed_info = "");
void showStartPage();
void processDocument(cmark_node *root_node);
private:
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 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);
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);
int fontSize;
std::string fontFamily;
int headingLevel;
int listLevel;
bool isBold;
bool isItalic;
int bulletListLevel;
int orderedListLevel;
bool isOrderedList;
std::map<int,int> 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;
Pango::FontDescription heading5;
Pango::FontDescription heading6;
};
#endif

View File

@ -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();
}

View File

@ -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

View File

@ -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);
}
/*

View File

@ -10,6 +10,6 @@
class IPFS
{
public:
static int startIPFSDaemon();
static int startIPFSDaemon();
};
#endif

View File

@ -4,25 +4,26 @@
int main(int argc, char *argv[])
{
pid_t child_pid = fork();
if (child_pid == 0)
{
// Run by child process
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");
}
}

View File

@ -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("DWeb Browser");
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);
// Main browser rendering area
m_scrolledWindow.add(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,18 +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);
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()));
}
// 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()));
}
}
/**
@ -176,20 +192,23 @@ void MainWindow::fetchFromIPFS()
*/
void MainWindow::openFromDisk()
{
try {
currentContent = m_file.read(finalRequestPath);
cmark_node *doc = Parser::parseContent(currentContent);
m_renderArea.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_renderArea.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();
}

View File

@ -8,55 +8,56 @@
#include <gtkmm/button.h>
#include <gtkmm/entry.h>
#include <thread>
#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
{
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;
RenderArea m_renderArea;
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

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

View File

@ -11,29 +11,29 @@
* \class Menu
* \brief The top main-menu
*/
class Menu: public Gtk::MenuBar
class Menu : public Gtk::MenuBar
{
public:
sigc::signal<void> reload;
sigc::signal<void> source_code;
sigc::signal<void> quit;
sigc::signal<void> about;
sigc::signal<void> reload;
sigc::signal<void> source_code;
sigc::signal<void> quit;
sigc::signal<void> 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

View File

@ -1,528 +0,0 @@
#include "render-area.h"
#include "node.h"
#include <gtkmm/widget.h>
#include <stdio.h>
#include <algorithm>
#include <chrono>
#include <iostream>
#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<float> 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<ms>(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<Cairo::Context>& 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<text_struct>::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<line_struct>::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;
}

View File

@ -1,86 +0,0 @@
#ifndef RENDER_AREA_H
#define RENDER_AREA_H
#include <gtkmm/drawingarea.h>
#include <pangomm/layout.h>
#include <cmark-gfm.h>
struct text_struct {
int x;
int y;
Glib::RefPtr<Pango::Layout> 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<text_struct> m_textList;
std::list<line_struct> m_lines;
// Override default signal handler:
bool on_draw(const Cairo::RefPtr<Cairo::Context>& 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<int,int> 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

View File

@ -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<Gtk::TextBuffer> buffer = m_sourceCode.get_buffer();
buffer->set_text(text);
Glib::RefPtr<Gtk::TextBuffer> buffer = m_sourceCode.get_buffer();
buffer->set_text(text);
}
void SourceCodeDialog::hide_dialog(__attribute__((unused)) int response)

View File

@ -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