Proof of concept working

master
Melroy van den Berg 2022-02-02 01:42:39 +01:00
parent 454c6ba911
commit 472a81b804
No known key found for this signature in database
GPG Key ID: 71D11FF23454B9D7
4 changed files with 225 additions and 46 deletions

View File

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

View File

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

View File

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

View File

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