Fully working textview, removing render area files

master
Melroy van den Berg 2021-02-12 23:30:39 +01:00
parent b93c5bc31f
commit 90b3cbbb48
6 changed files with 46 additions and 647 deletions

View File

@ -6,7 +6,7 @@ About::About()
devs.push_back("Melroy van den Berg <melroy@melroy.org>");
logo.set("../../misc/browser_logo_small.png");
set_name("DBrowser");
set_program_name("DWeb Browser");
set_version("0.1.0");
set_comments("The fastest decentralized & distributed Browser on planet Earth.");
set_logo(logo.get_pixbuf());
@ -15,6 +15,7 @@ About::About()
set_authors(devs);
set_artists(devs);
set_license_type(Gtk::License::LICENSE_MIT_X11);
set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
show_all_children();
}

View File

@ -55,6 +55,10 @@ Draw::Draw()
heading3.set_weight(Pango::WEIGHT_BOLD);
heading4.set_size(fontSize * PANGO_SCALE_LARGE);
heading4.set_weight(Pango::WEIGHT_BOLD);
heading5.set_size(fontSize * PANGO_SCALE_MEDIUM);
heading5.set_weight(Pango::WEIGHT_BOLD);
heading6.set_size(fontSize * PANGO_SCALE_MEDIUM);
heading6.set_weight(Pango::WEIGHT_BOLD);
}
void Draw::showMessage(const std::string &message, const std::string &detailed_info)
@ -124,6 +128,11 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type)
}
else
{
// Last list level new line
if (listLevel == 1)
{
addText("\n");
}
listLevel--;
}
if (listLevel == 0)
@ -137,7 +146,8 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type)
{
if (entering)
{
// TODO: Indent for each list level
// Add tab
addText(" ");
if (listType == cmark_list_type::CMARK_BULLET_LIST)
{
bulletListLevel++;
@ -200,24 +210,13 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type)
case CMARK_NODE_THEMATIC_BREAK:
{
/*
TODO: Can we draw a line in textview?
line.margin_end_x = 20;
line.height = 0.2;
line.hex_color = "2e2e2e";
line.cap = Cairo::LineCap::LINE_CAP_ROUND;
cr->set_line_cap(line.cap);
cr->set_source_rgb(r, g, b);
cr->set_line_width(line.height);
cr->move_to(line.start_x, line.start_y);
cr->line_to(endX, line.end_y);
cr->stroke();
*/
addText("\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015");
}
break;
case CMARK_NODE_PARAGRAPH:
if (listLevel == 0) {
if (listLevel == 0)
{
// Add new line, but not when listing is enabled
addText("\n");
}
@ -236,7 +235,7 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type)
std::string text = cmark_node_get_literal(node);
if (bulletListLevel > 0)
{
text.insert(0, "\u2022 ");
text.insert(0, std::string(bulletListLevel, '\u0009') + "\u2022 ");
}
else if (orderedListLevel > 0)
{
@ -249,7 +248,7 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type)
{
number = std::to_string(orderedListCounters[orderedListLevel]) + ". ";
}
text.insert(0, number);
text.insert(0, std::string(orderedListLevel, '\u0009') + number);
}
if (headingLevel > 0)
@ -268,8 +267,14 @@ void Draw::processNode(cmark_node *node, cmark_event_type ev_type)
case 4:
addHeading4(text);
break;
case 5:
addHeading5(text);
break;
case 6:
addHeading6(text);
break;
default:
addHeading4(text); // fallback
addHeading5(text); // fallback
break;
}
}
@ -361,6 +366,16 @@ void Draw::addHeading4(const std::string &text)
addMarkupText("\n<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>");
@ -391,6 +406,9 @@ void Draw::clear()
gdk_threads_add_idle((GSourceFunc)clearIdle, buffer);
}
/**
* Add text on Idle Call function
*/
gboolean Draw::addTextIdle(struct DispatchData *data)
{
GtkTextIter end_iter;
@ -400,6 +418,9 @@ gboolean Draw::addTextIdle(struct DispatchData *data)
return FALSE;
}
/**
* Clear Text on Idle Call function
*/
gboolean Draw::clearIdle(GtkTextBuffer *textBuffer)
{
GtkTextIter start_iter, end_iter;
@ -427,15 +448,3 @@ std::string const Draw::intToRoman(int num)
}
return res;
}
/**
* Convert hex string to seperate RGB values
*/
void Draw::hexToRGB(const std::string &hex, double &r, double &g, double &b)
{
unsigned int intR, intG, intB;
sscanf(hex.c_str(), "%02x%02x%02x", &intR, &intG, &intB);
r = intR / 255.0;
g = intG / 255.0;
b = intB / 255.0;
}

View File

@ -23,6 +23,8 @@ private:
void addHeading2(const std::string &text);
void addHeading3(const std::string &text);
void addHeading4(const std::string &text);
void addHeading5(const std::string &text);
void addHeading6(const std::string &text);
void addItalic(const std::string &text);
void addBold(const std::string &text);
void addBoldItalic(const std::string &text);
@ -32,7 +34,6 @@ private:
static gboolean addTextIdle(struct DispatchData *data);
static gboolean clearIdle(GtkTextBuffer *textBuffer);
std::string const intToRoman(int num);
void hexToRGB(const std::string& hex, double &r, double &g, double &b);
int fontSize;
std::string fontFamily;
@ -53,6 +54,8 @@ private:
Pango::FontDescription heading2;
Pango::FontDescription heading3;
Pango::FontDescription heading4;
Pango::FontDescription heading5;
Pango::FontDescription heading6;
};
#endif

View File

@ -16,7 +16,7 @@ MainWindow::MainWindow()
finalRequestPath(""),
currentContent("")
{
set_title("DBrowser");
set_title("DWeb Browser");
set_default_size(1000, 800);
set_position(Gtk::WIN_POS_CENTER);

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