Proof of concept working
parent
454c6ba911
commit
472a81b804
81
src/draw.cc
81
src/draw.cc
|
@ -3,6 +3,7 @@
|
|||
#include "node.h"
|
||||
#include "syntax_extension.h"
|
||||
#include <cmark-gfm.h>
|
||||
#include <cstdint>
|
||||
#include <gdkmm/window.h>
|
||||
#include <glibmm.h>
|
||||
#include <gtkmm/textiter.h>
|
||||
|
@ -339,7 +340,7 @@ void Draw::newDocument()
|
|||
{
|
||||
this->undoPool.clear();
|
||||
this->redoPool.clear();
|
||||
this->clearText();
|
||||
this->clear();
|
||||
|
||||
enableEdit();
|
||||
grab_focus(); // Claim focus on text view
|
||||
|
@ -363,12 +364,17 @@ void Draw::setText(const Glib::ustring& text)
|
|||
}
|
||||
|
||||
/**
|
||||
* \brief Clear all text on the screen
|
||||
* Clear text-buffer & clear marks
|
||||
*/
|
||||
void Draw::clearText()
|
||||
void Draw::clear()
|
||||
{
|
||||
auto buffer = get_buffer();
|
||||
buffer->erase(buffer->begin(), buffer->end());
|
||||
for (const Glib::RefPtr<Gtk::TextMark> mark : headingsToc)
|
||||
{
|
||||
buffer->delete_mark(mark);
|
||||
}
|
||||
headingsToc.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -425,6 +431,9 @@ void Draw::redo()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Cut text into clipboard
|
||||
*/
|
||||
void Draw::cut()
|
||||
{
|
||||
if (get_editable())
|
||||
|
@ -439,12 +448,18 @@ void Draw::cut()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Copy text into clipboard
|
||||
*/
|
||||
void Draw::copy()
|
||||
{
|
||||
auto clipboard = get_clipboard("CLIPBOARD");
|
||||
get_buffer()->copy_clipboard(clipboard);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Paste text from clipboard
|
||||
*/
|
||||
void Draw::paste()
|
||||
{
|
||||
if (get_editable())
|
||||
|
@ -454,6 +469,9 @@ void Draw::paste()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Delete selected text (only if textview is editable)
|
||||
*/
|
||||
void Draw::del()
|
||||
{
|
||||
if (get_editable())
|
||||
|
@ -474,12 +492,23 @@ void Draw::del()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Select all text
|
||||
*/
|
||||
void Draw::selectAll()
|
||||
{
|
||||
auto buffer = get_buffer();
|
||||
buffer->select_range(buffer->begin(), buffer->end());
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return the Texr mark headings for Table of contents
|
||||
*/
|
||||
std::vector<Glib::RefPtr<Gtk::TextMark>> Draw::getHeadings()
|
||||
{
|
||||
return headingsToc;
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Editor signals calls
|
||||
*************************************************************/
|
||||
|
@ -1440,19 +1469,16 @@ void Draw::insertText(std::string text, const Glib::ustring& url, CodeTypeEnum c
|
|||
default:
|
||||
break;
|
||||
}
|
||||
// Already add the mark for the heading
|
||||
addHeadingMark(text, headingLevel);
|
||||
}
|
||||
if (isQuote)
|
||||
{
|
||||
tagNames.push_back("quote");
|
||||
}
|
||||
|
||||
// Insert URL
|
||||
if (!url.empty())
|
||||
{
|
||||
this->insertLink(text, url);
|
||||
}
|
||||
// Insert text/heading
|
||||
else
|
||||
// Insert text (+ headings)
|
||||
if (url.empty())
|
||||
{
|
||||
// Special case for code blocks within quote
|
||||
if ((codeType == Draw::CodeTypeEnum::CODE_TYPE_CODE_BLOCK) && isQuote)
|
||||
|
@ -1473,16 +1499,21 @@ void Draw::insertText(std::string text, const Glib::ustring& url, CodeTypeEnum c
|
|||
insertTagText("\uFF5C ", "quote");
|
||||
insertTagText(text, tagNames);
|
||||
}
|
||||
// Just insert text/heading the normal way
|
||||
else
|
||||
{
|
||||
// Just insert text (default) - which includes headings as well
|
||||
insertTagText(text, tagNames);
|
||||
}
|
||||
}
|
||||
// Insert URL
|
||||
else
|
||||
{
|
||||
insertLink(text, url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert pango text with tags
|
||||
* Insert pango text with multiple tags
|
||||
*/
|
||||
void Draw::insertTagText(const Glib::ustring& text, std::vector<Glib::ustring> const& tagNames)
|
||||
{
|
||||
|
@ -1491,6 +1522,21 @@ void Draw::insertTagText(const Glib::ustring& text, std::vector<Glib::ustring> c
|
|||
buffer->insert_with_tags_by_name(endIter, text, tagNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Add mark for heading (ToC)
|
||||
*/
|
||||
void Draw::addHeadingMark(const Glib::ustring& text, int headingLevel)
|
||||
{
|
||||
auto buffer = get_buffer();
|
||||
auto endIter = buffer->end();
|
||||
// Make anonymous marks, to avoid existing naming conflicts
|
||||
Glib::RefPtr<Gtk::TextMark> textMark = Gtk::TextMark::create();
|
||||
textMark->set_data("name", g_strdup(text.c_str()));
|
||||
textMark->set_data("level", (void*)(intptr_t)headingLevel);
|
||||
buffer->add_mark(textMark, endIter);
|
||||
headingsToc.push_back(textMark);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert pango text with a single tag name
|
||||
*/
|
||||
|
@ -1538,17 +1584,6 @@ void Draw::truncateText(int charsTruncated)
|
|||
buffer->erase(beginIter, endIter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear text-buffer
|
||||
*/
|
||||
void Draw::clear()
|
||||
{
|
||||
auto buffer = get_buffer();
|
||||
auto beginIter = buffer->begin();
|
||||
auto endIter = buffer->end();
|
||||
buffer->erase(beginIter, endIter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at all tags covering the position (x, y) in the text view,
|
||||
* and if one of them is a link, change the cursor to the "hands" cursor
|
||||
|
|
|
@ -45,7 +45,7 @@ public:
|
|||
void newDocument();
|
||||
Glib::ustring getText() const;
|
||||
void setText(const Glib::ustring& text);
|
||||
void clearText();
|
||||
void clear();
|
||||
void undo();
|
||||
void redo();
|
||||
void cut();
|
||||
|
@ -53,6 +53,7 @@ public:
|
|||
void paste();
|
||||
void del();
|
||||
void selectAll();
|
||||
std::vector<Glib::RefPtr<Gtk::TextMark>> getHeadings();
|
||||
|
||||
// Signals editor calls
|
||||
void make_heading(int headingLevel);
|
||||
|
@ -104,6 +105,7 @@ private:
|
|||
Glib::RefPtr<Gdk::Cursor> textCursor;
|
||||
bool hovingOverLink;
|
||||
bool isUserAction;
|
||||
std::vector<Glib::RefPtr<Gtk::TextMark>> headingsToc;
|
||||
|
||||
std::vector<UndoRedoData> undoPool;
|
||||
std::vector<UndoRedoData> redoPool;
|
||||
|
@ -120,11 +122,11 @@ private:
|
|||
void encodeText(std::string& string) const;
|
||||
void insertText(std::string text, const Glib::ustring& url = "", CodeTypeEnum codeType = CodeTypeEnum::CODE_TYPE_NONE);
|
||||
void insertTagText(const Glib::ustring& text, std::vector<Glib::ustring> const& tagNames);
|
||||
void addHeadingMark(const Glib::ustring& text, int headingLevel);
|
||||
void insertTagText(const Glib::ustring& text, const Glib::ustring& tagName);
|
||||
void insertMarkupText(const Glib::ustring& text);
|
||||
void insertLink(const Glib::ustring& text, const Glib::ustring& url);
|
||||
void truncateText(int charsTruncated);
|
||||
void clear();
|
||||
void changeCursor(int x, int y);
|
||||
static Glib::ustring intToRoman(int num);
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "menu.h"
|
||||
#include "project_config.h"
|
||||
#include <cstdint>
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
#include <giomm/file.h>
|
||||
#include <giomm/notification.h>
|
||||
|
@ -120,10 +121,11 @@ MainWindow::MainWindow(const std::string& timeout)
|
|||
updateCSS();
|
||||
|
||||
// Table of contents
|
||||
tocTreeView.append_column("Level", m_tocColumns.m_col_level);
|
||||
tocTreeView.append_column("Name", m_tocColumns.m_col_heading);
|
||||
tocTreeView.set_activate_on_single_click(true);
|
||||
tocTreeView.set_headers_visible(false);
|
||||
tocTreeView.set_tooltip_column(1);
|
||||
tocTreeView.set_tooltip_column(2);
|
||||
|
||||
m_scrolledToc.add(tocTreeView);
|
||||
m_scrolledToc.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
|
||||
|
@ -161,24 +163,13 @@ MainWindow::MainWindow(const std::string& timeout)
|
|||
// Grap focus to input field by default
|
||||
m_addressBar.grab_focus();
|
||||
|
||||
// Example of TOC filling
|
||||
auto row = *(m_tocTreeModel->append());
|
||||
row[m_tocColumns.m_col_id] = 1;
|
||||
row[m_tocColumns.m_col_heading] = "Billy Bob";
|
||||
|
||||
auto childrow = *(m_tocTreeModel->append(row.children()));
|
||||
childrow[m_tocColumns.m_col_id] = 11;
|
||||
childrow[m_tocColumns.m_col_heading] = "Billy Bob Junior";
|
||||
|
||||
tocTreeView.expand_all();
|
||||
|
||||
// Show homepage if debugging is disabled
|
||||
#ifdef NDEBUG
|
||||
go_home();
|
||||
#else
|
||||
std::cout << "INFO: Running as Debug mode, opening test.md." << std::endl;
|
||||
// Load test file during development
|
||||
middleware_.doRequest("file://../../test.md");
|
||||
middleware_.doRequest("file://../../invalid_headers.md");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -220,6 +211,9 @@ void MainWindow::preRequest(const std::string& path, const std::string& title, b
|
|||
m_menu.setBackMenuSensitive(currentHistoryIndex_ > 0);
|
||||
m_forwardButton.set_sensitive(currentHistoryIndex_ < history_.size() - 1);
|
||||
m_menu.setForwardMenuSensitive(currentHistoryIndex_ < history_.size() - 1);
|
||||
|
||||
// Clear table of contents (ToC)
|
||||
m_tocTreeModel->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -287,6 +281,145 @@ void MainWindow::setText(const Glib::ustring& content)
|
|||
void MainWindow::setDocument(cmark_node* rootNode)
|
||||
{
|
||||
m_draw_main.setDocument(rootNode);
|
||||
|
||||
// Retrieve headings for ToC
|
||||
auto headings = m_draw_main.getHeadings();
|
||||
Gtk::TreeRow heading1Row, heading2Row, heading3Row, heading4Row, heading5Row;
|
||||
int previousLevel = 1; // Default heading 1
|
||||
for (const Glib::RefPtr<Gtk::TextMark> headerMark : headings)
|
||||
{
|
||||
Glib::ustring heading = static_cast<char*>(headerMark->get_data("name"));
|
||||
auto level = reinterpret_cast<std::intptr_t>(headerMark->get_data("level"));
|
||||
switch (level)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
heading1Row = *(m_tocTreeModel->append());
|
||||
heading1Row[m_tocColumns.m_col_iter] = headerMark->get_iter();
|
||||
heading1Row[m_tocColumns.m_col_level] = level;
|
||||
heading1Row[m_tocColumns.m_col_heading] = heading;
|
||||
heading1Row[m_tocColumns.m_col_valid] = true;
|
||||
// Reset
|
||||
if (previousLevel > 1)
|
||||
{
|
||||
heading2Row = Gtk::TreeRow();
|
||||
heading3Row = Gtk::TreeRow();
|
||||
heading4Row = Gtk::TreeRow();
|
||||
heading5Row = Gtk::TreeRow();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (heading1Row->get_model_gobject() == nullptr)
|
||||
{
|
||||
// Add missing heading as top-level
|
||||
heading1Row = *(m_tocTreeModel->append());
|
||||
heading1Row[m_tocColumns.m_col_level] = 1;
|
||||
heading1Row[m_tocColumns.m_col_heading] = "-Missing heading-";
|
||||
heading1Row[m_tocColumns.m_col_valid] = false;
|
||||
}
|
||||
heading2Row = *(m_tocTreeModel->append(heading1Row.children()));
|
||||
heading2Row[m_tocColumns.m_col_iter] = headerMark->get_iter();
|
||||
heading2Row[m_tocColumns.m_col_level] = level;
|
||||
heading2Row[m_tocColumns.m_col_heading] = heading;
|
||||
heading2Row[m_tocColumns.m_col_valid] = true;
|
||||
// Reset
|
||||
if (previousLevel > 2)
|
||||
{
|
||||
heading3Row = Gtk::TreeRow();
|
||||
heading4Row = Gtk::TreeRow();
|
||||
heading5Row = Gtk::TreeRow();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
if (heading2Row->get_model_gobject() == nullptr)
|
||||
{
|
||||
// Add missing heading as top-level
|
||||
heading2Row = *(m_tocTreeModel->append(heading1Row.children()));
|
||||
heading2Row[m_tocColumns.m_col_level] = 2;
|
||||
heading2Row[m_tocColumns.m_col_heading] = "-Missing heading-";
|
||||
heading2Row[m_tocColumns.m_col_valid] = false;
|
||||
}
|
||||
heading3Row = *(m_tocTreeModel->append(heading2Row.children()));
|
||||
heading3Row[m_tocColumns.m_col_iter] = headerMark->get_iter();
|
||||
heading3Row[m_tocColumns.m_col_level] = level;
|
||||
heading3Row[m_tocColumns.m_col_heading] = heading;
|
||||
heading3Row[m_tocColumns.m_col_valid] = true;
|
||||
// Reset
|
||||
if (previousLevel > 3)
|
||||
{
|
||||
heading4Row = Gtk::TreeRow();
|
||||
heading5Row = Gtk::TreeRow();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
if (heading3Row->get_model_gobject() == nullptr)
|
||||
{
|
||||
// Add missing heading as top-level
|
||||
heading3Row = *(m_tocTreeModel->append(heading2Row.children()));
|
||||
heading3Row[m_tocColumns.m_col_level] = 3;
|
||||
heading3Row[m_tocColumns.m_col_heading] = "-Missing heading-";
|
||||
heading3Row[m_tocColumns.m_col_valid] = false;
|
||||
}
|
||||
heading4Row = *(m_tocTreeModel->append(heading3Row.children()));
|
||||
heading4Row[m_tocColumns.m_col_iter] = headerMark->get_iter();
|
||||
heading4Row[m_tocColumns.m_col_level] = level;
|
||||
heading4Row[m_tocColumns.m_col_heading] = heading;
|
||||
heading4Row[m_tocColumns.m_col_valid] = true;
|
||||
// Reset
|
||||
if (previousLevel > 4)
|
||||
{
|
||||
heading5Row = Gtk::TreeRow();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
if (heading4Row->get_model_gobject() == nullptr)
|
||||
{
|
||||
// Add missing heading as top-level
|
||||
heading4Row = *(m_tocTreeModel->append(heading3Row.children()));
|
||||
heading4Row[m_tocColumns.m_col_level] = 4;
|
||||
heading4Row[m_tocColumns.m_col_heading] = "-Missing heading-";
|
||||
heading4Row[m_tocColumns.m_col_valid] = false;
|
||||
}
|
||||
heading5Row = *(m_tocTreeModel->append(heading4Row.children()));
|
||||
heading5Row[m_tocColumns.m_col_iter] = headerMark->get_iter();
|
||||
heading5Row[m_tocColumns.m_col_level] = level;
|
||||
heading5Row[m_tocColumns.m_col_heading] = heading;
|
||||
heading5Row[m_tocColumns.m_col_valid] = true;
|
||||
break;
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
if (heading5Row->get_model_gobject() == nullptr)
|
||||
{
|
||||
// Add missing heading as top-level
|
||||
heading5Row = *(m_tocTreeModel->append(heading4Row.children()));
|
||||
heading5Row[m_tocColumns.m_col_level] = 5;
|
||||
heading5Row[m_tocColumns.m_col_heading] = "- Missing heading -";
|
||||
heading5Row[m_tocColumns.m_col_valid] = false;
|
||||
}
|
||||
auto heading6Row = *(m_tocTreeModel->append(heading5Row.children()));
|
||||
heading6Row[m_tocColumns.m_col_iter] = headerMark->get_iter();
|
||||
heading6Row[m_tocColumns.m_col_level] = level;
|
||||
heading6Row[m_tocColumns.m_col_heading] = heading;
|
||||
heading6Row[m_tocColumns.m_col_valid] = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cerr << "ERROR: Out of range heading level detected." << std::endl;
|
||||
break;
|
||||
}
|
||||
previousLevel = level;
|
||||
}
|
||||
tocTreeView.columns_autosize();
|
||||
tocTreeView.expand_all();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -968,7 +1101,6 @@ void MainWindow::initSignals()
|
|||
m_wrapChar.signal_toggled().connect(sigc::bind(sigc::mem_fun(this, &MainWindow::on_wrap_toggled), Gtk::WrapMode::WRAP_CHAR));
|
||||
m_wrapWord.signal_toggled().connect(sigc::bind(sigc::mem_fun(this, &MainWindow::on_wrap_toggled), Gtk::WrapMode::WRAP_WORD));
|
||||
m_wrapWordChar.signal_toggled().connect(sigc::bind(sigc::mem_fun(this, &MainWindow::on_wrap_toggled), Gtk::WrapMode::WRAP_WORD_CHAR));
|
||||
// TODO word_char..
|
||||
m_themeSwitch.property_active().signal_changed().connect(sigc::mem_fun(this, &MainWindow::on_theme_changed));
|
||||
m_iconThemeListBox.signal_row_activated().connect(sigc::mem_fun(this, &MainWindow::on_icon_theme_activated));
|
||||
m_aboutButton.signal_clicked().connect(sigc::mem_fun(m_about, &About::show_about));
|
||||
|
@ -1157,7 +1289,7 @@ void MainWindow::selectAll()
|
|||
}
|
||||
|
||||
/**
|
||||
* \brief Triggered when user clicked on the column in TOC
|
||||
* \brief Triggered when user clicked on the column in ToC
|
||||
*/
|
||||
void MainWindow::on_toc_row_activated(const Gtk::TreeModel::Path& path, __attribute__((unused)) Gtk::TreeViewColumn* column)
|
||||
{
|
||||
|
@ -1165,7 +1297,12 @@ void MainWindow::on_toc_row_activated(const Gtk::TreeModel::Path& path, __attrib
|
|||
if (iter)
|
||||
{
|
||||
const auto row = *iter;
|
||||
std::cout << "Row activated: ID=" << row[m_tocColumns.m_col_id] << ", Name=" << row[m_tocColumns.m_col_heading] << std::endl;
|
||||
if (row[m_tocColumns.m_col_valid])
|
||||
{
|
||||
Gtk::TextIter textIter = row[m_tocColumns.m_col_iter];
|
||||
// Scroll to to mark iterator
|
||||
m_draw_main.scroll_to(textIter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1748,14 +1885,15 @@ void MainWindow::enableEdit()
|
|||
}
|
||||
m_panedDraw.set_position(location);
|
||||
|
||||
// Disable Table of Contents during edit
|
||||
// Disable Table of Contents during edit & clear ToC
|
||||
m_scrolledToc.hide();
|
||||
m_tocTreeModel->clear();
|
||||
// Enabled secondary text view (on the right)
|
||||
m_scrolledWindowSecondary.show();
|
||||
// Disable "view source" menu item
|
||||
m_draw_main.setViewSourceMenuItem(false);
|
||||
// Connect changed signal
|
||||
textChangedSignalHandler_ = m_draw_main.get_buffer().get()->signal_changed().connect(sigc::mem_fun(this, &MainWindow::editor_changed_text));
|
||||
textChangedSignalHandler_ = m_draw_main.get_buffer()->signal_changed().connect(sigc::mem_fun(this, &MainWindow::editor_changed_text));
|
||||
// Enable publish menu item
|
||||
m_menu.setPublishMenuSensitive(true);
|
||||
// Disable edit menu item (you are already editing)
|
||||
|
@ -1778,7 +1916,7 @@ void MainWindow::disableEdit()
|
|||
textChangedSignalHandler_.disconnect();
|
||||
// Show "view source" menu item again
|
||||
m_draw_main.setViewSourceMenuItem(true);
|
||||
m_draw_secondary.clearText();
|
||||
m_draw_secondary.clear();
|
||||
// Disable publish menu item
|
||||
m_menu.setPublishMenuSensitive(false);
|
||||
// Enable edit menu item
|
||||
|
|
|
@ -12,12 +12,16 @@ class TocModelCols : public Gtk::TreeModel::ColumnRecord
|
|||
public:
|
||||
TocModelCols()
|
||||
{
|
||||
add(m_col_id);
|
||||
add(m_col_iter);
|
||||
add(m_col_level);
|
||||
add(m_col_heading);
|
||||
add(m_col_valid);
|
||||
}
|
||||
|
||||
Gtk::TreeModelColumn<int> m_col_id;
|
||||
Gtk::TreeModelColumn<Gtk::TextIter> m_col_iter;
|
||||
Gtk::TreeModelColumn<int> m_col_level;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_col_heading;
|
||||
Gtk::TreeModelColumn<bool> m_col_valid;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue