2021-02-11 14:20:23 -08:00
|
|
|
#include "draw.h"
|
2021-02-12 09:07:37 -08:00
|
|
|
#include "node.h"
|
2021-02-15 10:03:07 -08:00
|
|
|
#include "mainwindow.h"
|
2021-02-15 07:26:30 -08:00
|
|
|
#include <gdk/gdkthreads.h>
|
2021-02-15 15:04:41 -08:00
|
|
|
#include <gdk/gdkselection.h>
|
2021-02-15 07:26:30 -08:00
|
|
|
#include <iostream>
|
2021-02-12 09:07:37 -08:00
|
|
|
#define PANGO_SCALE_XXX_LARGE ((double)1.98)
|
2021-02-11 14:20:23 -08:00
|
|
|
|
2021-02-12 09:07:37 -08:00
|
|
|
struct DispatchData
|
|
|
|
{
|
2021-02-11 14:37:05 -08:00
|
|
|
GtkTextBuffer *buffer;
|
|
|
|
std::string text;
|
2021-02-15 08:10:13 -08:00
|
|
|
std::string url;
|
2021-02-11 14:37:05 -08:00
|
|
|
};
|
|
|
|
|
2021-02-15 10:03:07 -08:00
|
|
|
Draw::Draw(MainWindow &mainWindow)
|
|
|
|
: mainWindow(mainWindow),
|
|
|
|
buffer(Glib::unwrap(this->get_buffer())),
|
2021-02-15 07:26:30 -08:00
|
|
|
fontSize(10 * PANGO_SCALE),
|
2021-02-12 13:40:58 -08:00
|
|
|
fontFamily("Ubuntu Monospace"),
|
2021-02-12 12:46:41 -08:00
|
|
|
headingLevel(0),
|
|
|
|
listLevel(0),
|
|
|
|
isBold(false),
|
|
|
|
isItalic(false),
|
|
|
|
bulletListLevel(0),
|
|
|
|
orderedListLevel(0),
|
|
|
|
isOrderedList(false),
|
2021-02-15 08:10:13 -08:00
|
|
|
isLink(false),
|
2021-02-12 12:46:41 -08:00
|
|
|
defaultFont(fontFamily),
|
|
|
|
bold(fontFamily),
|
|
|
|
italic(fontFamily),
|
|
|
|
boldItalic(fontFamily),
|
|
|
|
heading1(fontFamily),
|
|
|
|
heading2(fontFamily),
|
|
|
|
heading3(fontFamily),
|
|
|
|
heading4(fontFamily)
|
2021-02-11 14:20:23 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
this->disableEdit();
|
2021-02-12 09:07:37 -08:00
|
|
|
set_indent(15);
|
|
|
|
set_left_margin(10);
|
|
|
|
set_right_margin(10);
|
|
|
|
set_top_margin(5);
|
|
|
|
set_bottom_margin(5);
|
|
|
|
set_monospace(true);
|
|
|
|
set_app_paintable(true);
|
|
|
|
|
2021-02-12 13:40:58 -08:00
|
|
|
defaultFont.set_size(fontSize);
|
|
|
|
bold.set_size(fontSize);
|
2021-02-12 09:07:37 -08:00
|
|
|
bold.set_weight(Pango::WEIGHT_BOLD);
|
2021-02-12 13:40:58 -08:00
|
|
|
italic.set_size(fontSize);
|
2021-02-12 09:07:37 -08:00
|
|
|
italic.set_style(Pango::Style::STYLE_ITALIC);
|
2021-02-12 13:40:58 -08:00
|
|
|
boldItalic.set_size(fontSize);
|
2021-02-12 09:07:37 -08:00
|
|
|
boldItalic.set_weight(Pango::WEIGHT_BOLD);
|
|
|
|
boldItalic.set_style(Pango::Style::STYLE_ITALIC);
|
|
|
|
|
2021-02-12 13:40:58 -08:00
|
|
|
heading1.set_size(fontSize * PANGO_SCALE_XXX_LARGE);
|
2021-02-12 09:07:37 -08:00
|
|
|
heading1.set_weight(Pango::WEIGHT_BOLD);
|
2021-02-12 13:40:58 -08:00
|
|
|
heading2.set_size(fontSize * PANGO_SCALE_XX_LARGE);
|
2021-02-12 09:07:37 -08:00
|
|
|
heading2.set_weight(Pango::WEIGHT_BOLD);
|
2021-02-12 13:40:58 -08:00
|
|
|
heading3.set_size(fontSize * PANGO_SCALE_X_LARGE);
|
2021-02-12 09:07:37 -08:00
|
|
|
heading3.set_weight(Pango::WEIGHT_BOLD);
|
2021-02-12 13:40:58 -08:00
|
|
|
heading4.set_size(fontSize * PANGO_SCALE_LARGE);
|
2021-02-12 09:07:37 -08:00
|
|
|
heading4.set_weight(Pango::WEIGHT_BOLD);
|
2021-02-12 14:30:39 -08:00
|
|
|
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);
|
2021-02-15 10:03:07 -08:00
|
|
|
|
2021-02-16 15:06:33 -08:00
|
|
|
// Connect Signals
|
2021-02-15 10:03:07 -08:00
|
|
|
signal_event_after().connect(sigc::mem_fun(this, &Draw::event_after));
|
2021-02-16 15:06:33 -08:00
|
|
|
signal_populate_popup().connect(sigc::mem_fun(this, &Draw::populate_popup));
|
2021-02-15 10:03:07 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Links can be activated by clicking or touching the screen.
|
|
|
|
*/
|
|
|
|
void Draw::event_after(GdkEvent *ev)
|
|
|
|
{
|
|
|
|
gdouble ex, ey;
|
|
|
|
Gtk::TextBuffer::iterator iter;
|
|
|
|
int x, y;
|
|
|
|
|
|
|
|
if (ev->type == GDK_BUTTON_RELEASE)
|
|
|
|
{
|
|
|
|
GdkEventButton *event;
|
|
|
|
event = (GdkEventButton *)ev;
|
|
|
|
if (event->button != GDK_BUTTON_PRIMARY)
|
|
|
|
return;
|
|
|
|
ex = event->x;
|
|
|
|
ey = event->y;
|
|
|
|
}
|
|
|
|
else if (ev->type == GDK_TOUCH_END)
|
|
|
|
{
|
|
|
|
GdkEventTouch *event;
|
|
|
|
event = (GdkEventTouch *)ev;
|
|
|
|
ex = event->x;
|
|
|
|
ey = event->y;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Get the textview coordinates and retrieve an iterator
|
|
|
|
window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_WIDGET, ex, ey, x, y);
|
|
|
|
get_iter_at_location(iter, x, y);
|
|
|
|
// Find the links
|
|
|
|
followLink(iter);
|
|
|
|
}
|
|
|
|
|
2021-02-16 15:06:33 -08:00
|
|
|
/**
|
|
|
|
* Adapt right-click menu in textview
|
|
|
|
*/
|
|
|
|
void Draw::populate_popup(Gtk::Menu *menu)
|
|
|
|
{
|
|
|
|
auto items = menu->get_children();
|
|
|
|
for (auto *item : items)
|
|
|
|
{
|
|
|
|
Gtk::MenuItem *menuItem = static_cast<Gtk::MenuItem *>(item);
|
|
|
|
std::string name = menuItem->get_label();
|
|
|
|
if (name.compare("Cu_t") == 0)
|
|
|
|
{
|
|
|
|
menuItem->set_label("Cu_t (Ctrl+X)");
|
|
|
|
}
|
|
|
|
else if (name.compare("_Copy") == 0)
|
|
|
|
{
|
|
|
|
menuItem->set_label("_Copy (Ctrl+C)");
|
|
|
|
}
|
|
|
|
else if (name.compare("_Paste") == 0)
|
|
|
|
{
|
|
|
|
menuItem->set_label("_Paste (Ctrl+V)");
|
|
|
|
}
|
|
|
|
else if (name.compare("_Delete") == 0)
|
|
|
|
{
|
|
|
|
menuItem->set_label("_Delete (Del)");
|
|
|
|
}
|
|
|
|
else if (name.compare("Select _All") == 0)
|
|
|
|
{
|
|
|
|
menuItem->set_label("Select _All (Ctrl+A)");
|
|
|
|
}
|
|
|
|
else if (name.compare("Insert _Emoji") == 0)
|
|
|
|
{
|
|
|
|
item->hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Gtk::MenuItem *sourceCodeMenuItem = Gtk::manage(new Gtk::MenuItem("View Source", true));
|
|
|
|
sourceCodeMenuItem->signal_activate().connect(source_code);
|
|
|
|
sourceCodeMenuItem->show();
|
|
|
|
menu->append(*sourceCodeMenuItem);
|
|
|
|
}
|
|
|
|
|
2021-02-18 15:44:21 -08:00
|
|
|
/************************************************
|
|
|
|
* Private methods
|
|
|
|
************************************************/
|
|
|
|
|
|
|
|
void Draw::disableEdit()
|
|
|
|
{
|
|
|
|
set_editable(false);
|
|
|
|
set_cursor_visible(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::enableEdit()
|
|
|
|
{
|
|
|
|
set_editable(true);
|
|
|
|
set_cursor_visible(true);
|
|
|
|
}
|
|
|
|
|
2021-02-15 10:03:07 -08:00
|
|
|
/**
|
|
|
|
* Search for links
|
|
|
|
*/
|
|
|
|
void Draw::followLink(Gtk::TextBuffer::iterator &iter)
|
|
|
|
{
|
|
|
|
auto tags = iter.get_tags();
|
|
|
|
for (auto const &tag : tags)
|
|
|
|
{
|
|
|
|
char *url = static_cast<char *>(tag->get_data("url"));
|
|
|
|
if (url != 0 && (strlen(url) > 0))
|
|
|
|
{
|
|
|
|
// Get the URL
|
|
|
|
mainWindow.doRequest(url, true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-02-11 14:20:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::showMessage(const std::string &message, const std::string &detailed_info)
|
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
if (get_editable())
|
|
|
|
this->disableEdit();
|
|
|
|
this->clearOnThread();
|
2021-02-12 09:07:37 -08:00
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
insertHeading1(message);
|
|
|
|
insertText(detailed_info);
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show start page
|
|
|
|
*/
|
|
|
|
void Draw::showStartPage()
|
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
if (get_editable())
|
|
|
|
this->disableEdit();
|
|
|
|
this->clearOnThread();
|
2021-02-12 09:07:37 -08:00
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
insertHeading1("Welcome to the Decentralized Web (DWeb)");
|
2021-02-15 10:03:07 -08:00
|
|
|
insertText("See also the: ");
|
|
|
|
insertLink("Example page on IPFS", "ipfs://QmQzhn6hEfbYdCfwzYFsSt3eWpubVKA1dNqsgUwci5vHwq");
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process AST document and draw the text in the GTK TextView
|
|
|
|
*/
|
|
|
|
void Draw::processDocument(cmark_node *root_node)
|
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
if (get_editable())
|
|
|
|
this->disableEdit();
|
|
|
|
this->clearOnThread();
|
2021-02-12 09:07:37 -08:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-15 15:04:41 -08:00
|
|
|
void Draw::selectAll()
|
|
|
|
{
|
2021-02-17 12:44:02 -08:00
|
|
|
if (has_focus())
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->select_range(buffer->begin(), buffer->end());
|
|
|
|
}
|
2021-02-15 15:04:41 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::cut()
|
|
|
|
{
|
2021-02-17 12:44:02 -08:00
|
|
|
if (has_focus())
|
2021-02-15 15:04:41 -08:00
|
|
|
{
|
2021-02-17 12:44:02 -08:00
|
|
|
bool isEditable = get_editable();
|
|
|
|
if (isEditable)
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
auto clipboard = get_clipboard("CLIPBOARD");
|
|
|
|
buffer->cut_clipboard(clipboard);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
auto clipboard = get_clipboard("CLIPBOARD");
|
|
|
|
buffer->copy_clipboard(clipboard);
|
|
|
|
}
|
2021-02-15 15:04:41 -08:00
|
|
|
}
|
2021-02-17 12:44:02 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::copy()
|
|
|
|
{
|
|
|
|
if (has_focus())
|
2021-02-15 15:04:41 -08:00
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
auto clipboard = get_clipboard("CLIPBOARD");
|
|
|
|
buffer->copy_clipboard(clipboard);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::paste()
|
|
|
|
{
|
|
|
|
bool isEditable = get_editable();
|
2021-02-17 12:44:02 -08:00
|
|
|
bool hasFocus = has_focus();
|
|
|
|
if (isEditable && hasFocus)
|
2021-02-15 15:04:41 -08:00
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
auto clipboard = get_clipboard("CLIPBOARD");
|
|
|
|
buffer->paste_clipboard(clipboard);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-17 12:11:07 -08:00
|
|
|
void Draw::del()
|
|
|
|
{
|
|
|
|
bool isEditable = get_editable();
|
2021-02-17 12:44:02 -08:00
|
|
|
bool hasFocus = has_focus();
|
|
|
|
if (isEditable && hasFocus)
|
2021-02-17 12:11:07 -08:00
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
Gtk::TextBuffer::iterator begin, end;
|
|
|
|
if (buffer->get_selection_bounds(begin, end))
|
|
|
|
{
|
|
|
|
buffer->erase(begin, end);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 15:44:21 -08:00
|
|
|
void Draw::newDocument()
|
|
|
|
{
|
|
|
|
this->clearBuffer();
|
|
|
|
enableEdit();
|
|
|
|
grab_focus(); // Claim focus on text view
|
|
|
|
}
|
|
|
|
|
2021-02-19 10:38:05 -08:00
|
|
|
/*************************************************************
|
|
|
|
* Editor signals
|
|
|
|
*************************************************************/
|
|
|
|
|
2021-02-19 13:58:55 -08:00
|
|
|
void Draw::make_heading(int headingLevel)
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
std::string heading = std::string(headingLevel, '#');
|
|
|
|
buffer->insert_at_cursor(heading + " ");
|
|
|
|
}
|
|
|
|
|
2021-02-19 10:38:05 -08:00
|
|
|
void Draw::make_bold()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("**text**");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::make_italic()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("*text*");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::make_strikethrough()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("~~text~~");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::make_super()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("^text^");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::make_sub()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("~text~");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::make_inline_code()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("`code`");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::make_quote()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("\n> text");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::make_code_block()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("\n\n```python\ncode\n```\n\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::insert_link()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("[link](ipfs://youraddress)");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::insert_image()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("![alt](ipfs://image.jpg)");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::insert_bullet_list()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("\n* item");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::insert_numbered_list()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->insert_at_cursor("\n1. item");
|
|
|
|
}
|
|
|
|
|
|
|
|
void Draw::make_highlight()
|
2021-02-18 15:44:21 -08:00
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
2021-02-19 10:38:05 -08:00
|
|
|
buffer->insert_at_cursor("==text==");
|
2021-02-18 15:44:21 -08:00
|
|
|
}
|
|
|
|
|
2021-02-12 09:07:37 -08:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
{
|
2021-02-12 14:30:39 -08:00
|
|
|
// Last list level new line
|
|
|
|
if (listLevel == 1)
|
|
|
|
{
|
2021-02-15 08:10:13 -08:00
|
|
|
insertText("\n");
|
2021-02-12 14:30:39 -08:00
|
|
|
}
|
2021-02-12 09:07:37 -08:00
|
|
|
listLevel--;
|
|
|
|
}
|
|
|
|
if (listLevel == 0)
|
|
|
|
{
|
|
|
|
// Reset bullet/ordered levels
|
|
|
|
bulletListLevel = 0;
|
|
|
|
orderedListLevel = 0;
|
|
|
|
isOrderedList = false;
|
|
|
|
}
|
|
|
|
else if (listLevel > 0)
|
|
|
|
{
|
|
|
|
if (entering)
|
|
|
|
{
|
|
|
|
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:
|
2021-02-12 13:40:58 -08:00
|
|
|
// Line break for each item
|
|
|
|
if (entering)
|
2021-02-15 08:10:13 -08:00
|
|
|
insertText("\n");
|
2021-02-12 09:07:37 -08:00
|
|
|
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:
|
|
|
|
{
|
2021-02-15 08:10:13 -08:00
|
|
|
insertText("\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015");
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CMARK_NODE_PARAGRAPH:
|
2021-02-12 14:30:39 -08:00
|
|
|
if (listLevel == 0)
|
|
|
|
{
|
2021-02-15 08:10:13 -08:00
|
|
|
// insert new line, but not when listing is enabled
|
|
|
|
insertText("\n");
|
2021-02-12 13:40:58 -08:00
|
|
|
}
|
2021-02-12 09:07:37 -08:00
|
|
|
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);
|
2021-02-15 08:10:13 -08:00
|
|
|
// Insert tabs & bullet/number
|
2021-02-12 09:07:37 -08:00
|
|
|
if (bulletListLevel > 0)
|
|
|
|
{
|
2021-02-12 14:30:39 -08:00
|
|
|
text.insert(0, std::string(bulletListLevel, '\u0009') + "\u2022 ");
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
else if (orderedListLevel > 0)
|
|
|
|
{
|
|
|
|
std::string number;
|
|
|
|
if (orderedListLevel % 2 == 0)
|
|
|
|
{
|
2021-02-15 08:10:13 -08:00
|
|
|
number = Draw::intToRoman(orderedListCounters[orderedListLevel]) + " ";
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
number = std::to_string(orderedListCounters[orderedListLevel]) + ". ";
|
|
|
|
}
|
2021-02-12 14:30:39 -08:00
|
|
|
text.insert(0, std::string(orderedListLevel, '\u0009') + number);
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
// Unsert headings
|
2021-02-12 09:07:37 -08:00
|
|
|
if (headingLevel > 0)
|
|
|
|
{
|
|
|
|
switch (headingLevel)
|
|
|
|
{
|
|
|
|
case 1:
|
2021-02-15 08:10:13 -08:00
|
|
|
insertHeading1(text);
|
2021-02-12 09:07:37 -08:00
|
|
|
break;
|
|
|
|
case 2:
|
2021-02-15 08:10:13 -08:00
|
|
|
insertHeading2(text);
|
2021-02-12 09:07:37 -08:00
|
|
|
break;
|
|
|
|
case 3:
|
2021-02-15 08:10:13 -08:00
|
|
|
insertHeading3(text);
|
2021-02-12 09:07:37 -08:00
|
|
|
break;
|
|
|
|
case 4:
|
2021-02-15 08:10:13 -08:00
|
|
|
insertHeading4(text);
|
2021-02-12 09:07:37 -08:00
|
|
|
break;
|
2021-02-12 14:30:39 -08:00
|
|
|
case 5:
|
2021-02-15 08:10:13 -08:00
|
|
|
insertHeading5(text);
|
2021-02-12 14:30:39 -08:00
|
|
|
break;
|
|
|
|
case 6:
|
2021-02-15 08:10:13 -08:00
|
|
|
insertHeading6(text);
|
2021-02-12 14:30:39 -08:00
|
|
|
break;
|
2021-02-12 09:07:37 -08:00
|
|
|
default:
|
2021-02-15 08:10:13 -08:00
|
|
|
insertHeading5(text); // fallback
|
2021-02-12 09:07:37 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-02-15 08:10:13 -08:00
|
|
|
// Bold/italic text
|
2021-02-12 09:07:37 -08:00
|
|
|
else if (isBold && isItalic)
|
|
|
|
{
|
2021-02-15 08:10:13 -08:00
|
|
|
insertBoldItalic(text);
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
else if (isBold)
|
|
|
|
{
|
2021-02-15 08:10:13 -08:00
|
|
|
insertBold(text);
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
else if (isItalic)
|
|
|
|
{
|
2021-02-15 08:10:13 -08:00
|
|
|
insertItalic(text);
|
|
|
|
}
|
|
|
|
// URL
|
|
|
|
else if (isLink)
|
|
|
|
{
|
|
|
|
insertLink(text, linkURL);
|
|
|
|
linkURL = "";
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-02-15 08:10:13 -08:00
|
|
|
// Normal text only
|
|
|
|
insertText(text);
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CMARK_NODE_LINEBREAK:
|
2021-02-12 13:40:58 -08:00
|
|
|
// Hard brake
|
2021-02-15 08:10:13 -08:00
|
|
|
insertText("\n");
|
2021-02-12 09:07:37 -08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CMARK_NODE_SOFTBREAK:
|
2021-02-12 13:40:58 -08:00
|
|
|
// only insert space
|
2021-02-15 08:10:13 -08:00
|
|
|
insertText(" ");
|
2021-02-12 09:07:37 -08:00
|
|
|
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:
|
2021-02-15 08:10:13 -08:00
|
|
|
isLink = entering;
|
|
|
|
if (entering)
|
|
|
|
{
|
|
|
|
linkURL = cmark_node_get_url(node);
|
|
|
|
}
|
2021-02-12 09:07:37 -08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CMARK_NODE_IMAGE:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CMARK_NODE_FOOTNOTE_REFERENCE:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CMARK_NODE_FOOTNOTE_DEFINITION:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-18 15:44:21 -08:00
|
|
|
/**
|
|
|
|
* Insert markup text - thread safe
|
|
|
|
*/
|
2021-02-15 08:10:13 -08:00
|
|
|
void Draw::insertText(const std::string &text)
|
2021-02-12 09:07:37 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
insertMarkupTextOnThread("<span font_desc=\"" + defaultFont.to_string() + "\">" + text + "</span>");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Insert url link - thread safe
|
|
|
|
*/
|
|
|
|
void Draw::insertLink(const std::string &text, const std::string &url)
|
|
|
|
{
|
|
|
|
DispatchData *data = g_new0(struct DispatchData, 1);
|
|
|
|
data->buffer = buffer;
|
|
|
|
data->text = text;
|
|
|
|
data->url = url;
|
|
|
|
gdk_threads_add_idle((GSourceFunc)insertLinkIdle, data);
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
void Draw::insertHeading1(const std::string &text)
|
2021-02-12 09:07:37 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
insertMarkupTextOnThread("\n<span font_desc=\"" + heading1.to_string() + "\">" + text + "</span>\n");
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
void Draw::insertHeading2(const std::string &text)
|
2021-02-12 09:07:37 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
insertMarkupTextOnThread("\n<span font_desc=\"" + heading2.to_string() + "\">" + text + "</span>\n");
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
void Draw::insertHeading3(const std::string &text)
|
2021-02-12 09:07:37 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
insertMarkupTextOnThread("\n<span font_desc=\"" + heading3.to_string() + "\">" + text + "</span>\n");
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
void Draw::insertHeading4(const std::string &text)
|
2021-02-12 09:07:37 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
insertMarkupTextOnThread("\n<span font_desc=\"" + heading4.to_string() + "\">" + text + "</span>\n");
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
void Draw::insertHeading5(const std::string &text)
|
2021-02-12 14:30:39 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
insertMarkupTextOnThread("\n<span font_desc=\"" + heading5.to_string() + "\">" + text + "</span>\n");
|
2021-02-12 14:30:39 -08:00
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
void Draw::insertHeading6(const std::string &text)
|
2021-02-12 14:30:39 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
insertMarkupTextOnThread("\n<span foreground=\"gray\" font_desc=\"" + heading6.to_string() + "\">" + text + "</span>\n");
|
2021-02-12 14:30:39 -08:00
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
void Draw::insertBold(const std::string &text)
|
2021-02-12 09:07:37 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
insertMarkupTextOnThread("<span font_desc=\"" + bold.to_string() + "\">" + text + "</span>");
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
void Draw::insertItalic(const std::string &text)
|
2021-02-12 09:07:37 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
insertMarkupTextOnThread("<span font_desc=\"" + italic.to_string() + "\">" + text + "</span>");
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
void Draw::insertBoldItalic(const std::string &text)
|
2021-02-12 09:07:37 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
insertMarkupTextOnThread("<span font_desc=\"" + boldItalic.to_string() + "\">" + text + "</span>");
|
2021-02-15 08:10:13 -08:00
|
|
|
}
|
|
|
|
|
2021-02-18 15:44:21 -08:00
|
|
|
/******************************************************
|
|
|
|
* Helper functions below
|
|
|
|
*****************************************************/
|
2021-02-11 14:20:23 -08:00
|
|
|
|
2021-02-18 15:44:21 -08:00
|
|
|
/**
|
|
|
|
* Insert markup pango text - thread safe
|
|
|
|
*/
|
|
|
|
void Draw::insertMarkupTextOnThread(const std::string &text)
|
2021-02-11 14:20:23 -08:00
|
|
|
{
|
2021-02-12 09:07:37 -08:00
|
|
|
DispatchData *data = g_new0(struct DispatchData, 1);
|
|
|
|
data->buffer = buffer;
|
|
|
|
data->text = text;
|
2021-02-18 15:44:21 -08:00
|
|
|
gdk_threads_add_idle((GSourceFunc)insertTextIdle, data);
|
2021-02-11 14:20:23 -08:00
|
|
|
}
|
|
|
|
|
2021-02-18 15:44:21 -08:00
|
|
|
/**
|
|
|
|
* Clear buffer - thread-safe
|
|
|
|
*/
|
|
|
|
void Draw::clearOnThread()
|
2021-02-11 14:20:23 -08:00
|
|
|
{
|
2021-02-18 15:44:21 -08:00
|
|
|
gdk_threads_add_idle((GSourceFunc)clearBufferIdle, buffer);
|
2021-02-12 09:07:37 -08:00
|
|
|
}
|
|
|
|
|
2021-02-12 14:30:39 -08:00
|
|
|
/**
|
2021-02-15 08:10:13 -08:00
|
|
|
* Insert text on Idle Call function
|
2021-02-12 14:30:39 -08:00
|
|
|
*/
|
2021-02-15 08:10:13 -08:00
|
|
|
gboolean Draw::insertTextIdle(struct DispatchData *data)
|
2021-02-12 09:07:37 -08:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-02-15 08:10:13 -08:00
|
|
|
/**
|
|
|
|
* Insert link url on Idle Call function
|
|
|
|
*/
|
|
|
|
gboolean Draw::insertLinkIdle(struct DispatchData *data)
|
|
|
|
{
|
|
|
|
GtkTextIter end_iter;
|
|
|
|
GtkTextTag *tag;
|
|
|
|
gtk_text_buffer_get_end_iter(data->buffer, &end_iter);
|
|
|
|
tag = gtk_text_buffer_create_tag(data->buffer, NULL,
|
2021-02-16 15:06:33 -08:00
|
|
|
"foreground", "#1a0dab",
|
2021-02-15 08:10:13 -08:00
|
|
|
"underline", PANGO_UNDERLINE_SINGLE,
|
|
|
|
NULL);
|
|
|
|
g_object_set_data(G_OBJECT(tag), "url", g_strdup(data->url.c_str()));
|
|
|
|
gtk_text_buffer_insert_with_tags(data->buffer, &end_iter, data->text.c_str(), -1, tag, NULL);
|
|
|
|
g_free(data);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2021-02-12 14:30:39 -08:00
|
|
|
/**
|
2021-02-18 15:44:21 -08:00
|
|
|
* clearOnThread Text on Idle Call function
|
2021-02-12 14:30:39 -08:00
|
|
|
*/
|
2021-02-18 15:44:21 -08:00
|
|
|
gboolean Draw::clearBufferIdle(GtkTextBuffer *textBuffer)
|
2021-02-12 09:07:37 -08:00
|
|
|
{
|
|
|
|
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;
|
2021-02-11 14:20:23 -08:00
|
|
|
}
|
|
|
|
|
2021-02-18 15:44:21 -08:00
|
|
|
/**
|
|
|
|
* Clear buffer
|
|
|
|
*/
|
|
|
|
void Draw::clearBuffer()
|
|
|
|
{
|
|
|
|
auto buffer = get_buffer();
|
|
|
|
buffer->erase(buffer->begin(), buffer->end());
|
|
|
|
}
|
|
|
|
|
2021-02-12 09:07:37 -08:00
|
|
|
/**
|
2021-02-15 08:10:13 -08:00
|
|
|
* Convert number to roman numerals
|
2021-02-12 09:07:37 -08:00
|
|
|
*/
|
|
|
|
std::string const Draw::intToRoman(int num)
|
2021-02-11 14:20:23 -08:00
|
|
|
{
|
2021-02-12 09:07:37 -08:00
|
|
|
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;
|
2021-02-11 14:20:23 -08:00
|
|
|
}
|