Merge branch 'master' of gitlab.melroy.org:dwww/dbrowser
commit
b45c184efd
2
LICENSE
2
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
|
||||
|
|
|
@ -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 |
|
@ -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
|
|
@ -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}
|
||||
)
|
||||
|
|
10
src/about.cc
10
src/about.cc
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ class About: public Gtk::AboutDialog
|
|||
public:
|
||||
About();
|
||||
virtual ~About();
|
||||
|
||||
void show_about();
|
||||
void hide_about(int response);
|
||||
protected:
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
39
src/file.cc
39
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();
|
||||
}
|
||||
|
|
|
@ -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
|
30
src/ipfs.cc
30
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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
class IPFS
|
||||
{
|
||||
public:
|
||||
static int startIPFSDaemon();
|
||||
static int startIPFSDaemon();
|
||||
};
|
||||
#endif
|
41
src/main.cc
41
src/main.cc
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
16
src/menu.cc
16
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;
|
||||
}
|
||||
|
|
34
src/menu.h
34
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<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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue