From d4f0222697da5a2fcb2eac68f835cad1c146552f Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Tue, 1 Feb 2022 01:11:54 +0100 Subject: [PATCH 1/5] Initial setup of ToC --- src/CMakeLists.txt | 1 + src/mainwindow.cc | 96 +++++++++++++++++---- src/mainwindow.h | 15 +++- src/menu.cc | 6 ++ src/menu.h | 2 + src/schema/org.libreweb.browser.gschema.xml | 8 +- src/toc-model-cols.h | 23 +++++ 7 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 src/toc-model-cols.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 87e81c5..c4c25a8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,6 +24,7 @@ set(HEADERS middleware-i.h middleware.h toolbar-button.h + toc-model-cols.h mainwindow.h md-parser.h menu.h diff --git a/src/mainwindow.cc b/src/mainwindow.cc index dba745c..5de3e5f 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -36,6 +36,7 @@ MainWindow::MainWindow(const std::string& timeout) m_vboxSettings(Gtk::ORIENTATION_VERTICAL), m_vboxIconTheme(Gtk::ORIENTATION_VERTICAL), m_searchMatchCase("Match _Case", true), + m_tocButton("Show table of contents (Ctrl+Shift+T)", true), m_backButton("Go back one page (Alt+Left arrow)", true), m_forwardButton("Go forward one page (Alt+Right arrow)", true), m_refreshButton("Reload current page (Ctrl+R)", true), @@ -113,33 +114,59 @@ MainWindow::MainWindow(const std::string& timeout) // Load the default font family and font size updateCSS(); - // Browser text main drawing area + // Table of contents + 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); + + m_scrolledToc.add(tocTreeView); + m_scrolledToc.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + m_tocTreeModel = Gtk::TreeStore::create(m_tocColumns); + tocTreeView.set_model(m_tocTreeModel); + // Browser main drawing area m_scrolledWindowMain.add(m_draw_main); m_scrolledWindowMain.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); // Secondary drawing area m_draw_secondary.setViewSourceMenuItem(false); m_scrolledWindowSecondary.add(m_draw_secondary); m_scrolledWindowSecondary.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); - m_paned.pack1(m_scrolledWindowMain, true, false); - m_paned.pack2(m_scrolledWindowSecondary, true, true); + m_panedDraw.pack1(m_scrolledWindowMain, true, false); + m_panedDraw.pack2(m_scrolledWindowSecondary, true, true); + + // Add table of contents to horizontal paned + m_panedRoot.pack1(m_scrolledToc, true, false); + m_panedRoot.pack2(m_panedDraw, true, false); m_vbox.pack_start(m_menu, false, false, 0); m_vbox.pack_start(m_hboxBrowserToolbar, false, false, 6); m_vbox.pack_start(m_hboxStandardEditorToolbar, false, false, 6); m_vbox.pack_start(m_hboxFormattingEditorToolbar, false, false, 6); - m_vbox.pack_start(m_paned, true, true, 0); + m_vbox.pack_start(m_panedRoot, true, true, 0); add(m_vbox); show_all_children(); - // Hide by default the replace entry, editor box & secondary text view + // Hide by default the replace entry, editor box, ToC & secondary text view m_searchReplaceEntry.hide(); m_hboxStandardEditorToolbar.hide(); m_hboxFormattingEditorToolbar.hide(); + m_scrolledToc.hide(); m_scrolledWindowSecondary.hide(); // 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(); @@ -317,9 +344,9 @@ void MainWindow::loadStoredSettings() if (!isInstalled()) { // Relative to the binary path - std::vector relativePath{"..", "src", "gsettings"}; + std::vector relativePath{".."}; std::string schemaDir = Glib::build_path(G_DIR_SEPARATOR_S, relativePath); - std::cout << "INFO: Try to find the schema file using the following directory first: " << schemaDir << std::endl; + std::cout << "INFO: Binary not installed. Try to find the gschema file one directory up (..)." << std::endl; Glib::setenv("GSETTINGS_SCHEMA_DIR", schemaDir); } @@ -346,7 +373,8 @@ void MainWindow::loadStoredSettings() m_draw_main.set_left_margin(margins); m_draw_main.set_right_margin(margins); m_draw_main.set_indent(indent); - + int tocDividerPosition = m_settings->get_int("position-divider-toc"); + m_panedRoot.set_position(tocDividerPosition); iconTheme_ = m_settings->get_string("icon-theme"); useCurrentGTKIconTheme_ = m_settings->get_boolean("icon-gtk-theme"); brightnessScale_ = m_settings->get_double("brightness"); @@ -355,8 +383,7 @@ void MainWindow::loadStoredSettings() else { std::cerr << "ERROR: Gsettings schema file could not be found!" << std::endl; - // Fallback settings if schema isn't found, - // for adjustment controls + // Adjustment controls int margins = 20; int indent = 0; m_spacingAdjustment->set_value(0); @@ -366,6 +393,8 @@ void MainWindow::loadStoredSettings() m_draw_main.set_left_margin(margins); m_draw_main.set_right_margin(margins); m_draw_main.set_indent(indent); + // ToC paned divider + m_panedRoot.set_position(300); } } @@ -375,6 +404,7 @@ void MainWindow::loadStoredSettings() void MainWindow::setGTKIcons() { // Toolbox buttons + m_tocIcon.set_from_icon_name("view-list-symbolic", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); m_backIcon.set_from_icon_name("go-previous", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); m_forwardIcon.set_from_icon_name("go-next", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); m_refreshIcon.set_from_icon_name("view-refresh", Gtk::IconSize(Gtk::ICON_SIZE_MENU)); @@ -424,6 +454,7 @@ void MainWindow::loadIcons() else { // Toolbox buttons + m_tocIcon.set(Gdk::Pixbuf::create_from_file(getIconImageFromTheme("square_list", "editor"), iconSize_, iconSize_)); m_backIcon.set(Gdk::Pixbuf::create_from_file(getIconImageFromTheme("right_arrow_1", "arrows"), iconSize_, iconSize_)->flip()); m_forwardIcon.set(Gdk::Pixbuf::create_from_file(getIconImageFromTheme("right_arrow_1", "arrows"), iconSize_, iconSize_)); m_refreshIcon.set(Gdk::Pixbuf::create_from_file(getIconImageFromTheme("reload_centered", "arrows"), iconSize_ * 1.13, iconSize_)); @@ -510,6 +541,7 @@ void MainWindow::initButtons() m_settingsButton.set_relief(Gtk::RELIEF_NONE); // Add icons to the toolbar buttons + m_tocButton.add(m_tocIcon); m_backButton.add(m_backIcon); m_forwardButton.add(m_forwardIcon); m_refreshButton.add(m_refreshIcon); @@ -542,7 +574,8 @@ void MainWindow::initButtons() * Adding the buttons to the boxes */ // Browser Toolbar - m_backButton.set_margin_left(6); + m_tocButton.set_margin_left(6); + m_hboxBrowserToolbar.pack_start(m_tocButton, false, false, 0); m_hboxBrowserToolbar.pack_start(m_backButton, false, false, 0); m_hboxBrowserToolbar.pack_start(m_forwardButton, false, false, 0); m_hboxBrowserToolbar.pack_start(m_refreshButton, false, false, 0); @@ -837,7 +870,8 @@ void MainWindow::initSignals() { // Window signals signal_delete_event().connect(sigc::mem_fun(this, &MainWindow::delete_window)); - + // Table of contents + tocTreeView.signal_row_activated().connect(sigc::mem_fun(this, &MainWindow::on_toc_row_activated)); // Menu & toolbar signals m_menu.new_doc.connect(sigc::mem_fun(this, &MainWindow::new_doc)); /*!< Menu item for new document */ m_menu.open.connect(sigc::mem_fun(this, &MainWindow::open)); /*!< Menu item for opening existing document */ @@ -860,12 +894,14 @@ void MainWindow::initSignals() m_menu.forward.connect(sigc::mem_fun(this, &MainWindow::forward)); /*!< Menu item for next page */ m_menu.reload.connect(sigc::mem_fun(this, &MainWindow::refreshRequest)); /*!< Menu item for reloading the page */ m_menu.home.connect(sigc::mem_fun(this, &MainWindow::go_home)); /*!< Menu item for home page */ + m_menu.toc.connect(sigc::mem_fun(this, &MainWindow::show_toc)); /*!< Menu item for table of contents */ 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_draw_main.source_code.connect(sigc::mem_fun(this, &MainWindow::show_source_code_dialog)); /*!< Open source code dialog */ m_about.signal_response().connect(sigc::mem_fun(m_about, &About::hide_about)); /*!< Close about dialog */ m_addressBar.signal_activate().connect(sigc::mem_fun(this, &MainWindow::address_bar_activate)); /*!< User pressed enter the address bar */ + m_tocButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::show_toc)); /*!< Button for showing Table of Contents */ m_backButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::back)); /*!< Button for previous page */ m_forwardButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::forward)); /*!< Button for next page */ m_refreshButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::refreshRequest)); /*!< Button for reloading the page */ @@ -922,10 +958,12 @@ bool MainWindow::delete_window(GdkEventAny* any_event __attribute__((unused))) m_settings->set_int("width", get_width()); m_settings->set_int("height", get_height()); m_settings->set_boolean("maximized", is_maximized()); + if (m_panedRoot.get_position() > 0) + m_settings->set_int("position-divider-toc", m_panedRoot.get_position()); // Only store a divider value bigger than zero, // because the secondary draw window is hidden by default, resulting into a zero value. - if (m_paned.get_position() > 0) - m_settings->set_int("position-divider", m_paned.get_position()); + if (m_panedDraw.get_position() > 0) + m_settings->set_int("position-divider-draw", m_panedDraw.get_position()); // Fullscreen will be availible with gtkmm-4.0 // m_settings->set_boolean("fullscreen", is_fullscreen()); m_settings->set_string("font-family", fontFamily_); @@ -1091,6 +1129,19 @@ void MainWindow::selectAll() } } +/** + * \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) +{ + const auto iter = m_tocTreeModel->get_iter(path); + 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; + } +} + /** * \brief Trigger when user selected 'new document' from menu item */ @@ -1430,6 +1481,17 @@ void MainWindow::go_home() middleware_.doRequest("about:home", true, false, true); } +/** + * \brief Show/hide table of contents + */ +void MainWindow::show_toc() +{ + if (m_scrolledToc.is_visible()) + m_scrolledToc.hide(); + else + m_scrolledToc.show(); +} + /** * \brief Copy the IPFS Client ID to clipboard */ @@ -1644,7 +1706,7 @@ void MainWindow::enableEdit() int location = 0; int positionSettings = 42; if (m_settings) - positionSettings = m_settings->get_int("position-divider"); + positionSettings = m_settings->get_int("position-divider-draw"); int currentWidth, _ = 0; get_size(currentWidth, _); // If position from settings is still default (42) or too big, @@ -1657,8 +1719,10 @@ void MainWindow::enableEdit() { location = positionSettings; } - m_paned.set_position(location); + m_panedDraw.set_position(location); + // Disable Table of Contents during edit + m_scrolledToc.hide(); // Enabled secondary text view (on the right) m_scrolledWindowSecondary.show(); // Disable "view source" menu item diff --git a/src/mainwindow.h b/src/mainwindow.h index b2df9cf..f08fe42 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -6,6 +6,7 @@ #include "menu.h" #include "middleware.h" #include "source-code-dialog.h" +#include "toc-model-cols.h" #include "toolbar-button.h" #include @@ -34,6 +35,9 @@ #include #include #include +#include +#include +#include #include #include @@ -67,6 +71,7 @@ protected: void paste(); void del(); void selectAll(); + void on_toc_row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* column); void new_doc(); void open(); void open_and_edit(); @@ -78,6 +83,7 @@ protected: void on_save_as_dialog_response(int response_id, Gtk::FileChooserDialog* dialog); void publish(); void go_home(); + void show_toc(); void copy_client_id(); void copy_client_public_key(); void address_bar_activate(); @@ -118,7 +124,10 @@ protected: Draw m_draw_secondary; SourceCodeDialog m_sourceCodeDialog; About m_about; - Gtk::HPaned m_paned; + Gtk::TreeView tocTreeView; + Glib::RefPtr m_tocTreeModel; + Gtk::HPaned m_panedRoot; + Gtk::HPaned m_panedDraw; Gtk::SearchBar m_search; Gtk::SearchBar m_searchReplace; Gtk::SearchEntry m_searchEntry; @@ -152,6 +161,7 @@ protected: Gtk::Grid m_statusGrid; Gtk::Grid m_activityStatusGrid; Gtk::Grid m_settingsGrid; + ToolbarButton m_tocButton; ToolbarButton m_backButton; ToolbarButton m_forwardButton; ToolbarButton m_refreshButton; @@ -184,6 +194,7 @@ protected: Gtk::Image m_zoomOutImage; Gtk::Image m_zoomInImage; Gtk::Image m_brightnessImage; + Gtk::Image m_tocIcon; Gtk::Image m_backIcon; Gtk::Image m_forwardIcon; Gtk::Image m_refreshIcon; @@ -244,6 +255,7 @@ protected: Gtk::Label m_themeLabel; Gtk::Label m_iconThemeLabel; std::unique_ptr m_contentPublishedDialog; + Gtk::ScrolledWindow m_scrolledToc; Gtk::ScrolledWindow m_scrolledWindowMain; Gtk::ScrolledWindow m_scrolledWindowSecondary; Gtk::SeparatorMenuItem m_separator1; @@ -256,6 +268,7 @@ protected: Gtk::Separator m_separator8; Gtk::Separator m_separator9; Gtk::Separator m_separator10; + TocModelCols m_tocColumns; private: Middleware middleware_; diff --git a/src/menu.cc b/src/menu.cc index 8916bf4..9477b59 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -78,6 +78,10 @@ Menu::Menu(const Glib::RefPtr& accelgroup) auto homePageMenuItem = createMenuItem("_Homepage"); homePageMenuItem->add_accelerator("activate", accelgroup, GDK_KEY_Home, Gdk::ModifierType::MOD1_MASK, Gtk::AccelFlags::ACCEL_VISIBLE); homePageMenuItem->signal_activate().connect(home); + auto tocMenuItem = createMenuItem("_Table of Contents"); + tocMenuItem->add_accelerator("activate", accelgroup, GDK_KEY_T, Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::SHIFT_MASK, + Gtk::AccelFlags::ACCEL_VISIBLE); + tocMenuItem->signal_activate().connect(toc); auto sourceCodeMenuItem = createMenuItem("View _Source"); sourceCodeMenuItem->signal_activate().connect(source_code); @@ -114,6 +118,8 @@ Menu::Menu(const Glib::RefPtr& accelgroup) m_viewSubmenu.append(*reloadMenuItem); m_viewSubmenu.append(*homePageMenuItem); m_viewSubmenu.append(m_separator7); + m_viewSubmenu.append(*tocMenuItem); + m_viewSubmenu.append(m_separator8); m_viewSubmenu.append(*sourceCodeMenuItem); m_helpSubmenu.append(*aboutMenuItem); diff --git a/src/menu.h b/src/menu.h index 4100d27..bff44ae 100644 --- a/src/menu.h +++ b/src/menu.h @@ -35,6 +35,7 @@ public: sigc::signal forward; sigc::signal reload; sigc::signal home; + sigc::signal toc; sigc::signal source_code; sigc::signal about; @@ -62,6 +63,7 @@ protected: Gtk::SeparatorMenuItem m_separator5; Gtk::SeparatorMenuItem m_separator6; Gtk::SeparatorMenuItem m_separator7; + Gtk::SeparatorMenuItem m_separator8; private: Gtk::MenuItem* createMenuItem(const Glib::ustring& label_text); diff --git a/src/schema/org.libreweb.browser.gschema.xml b/src/schema/org.libreweb.browser.gschema.xml index 9610500..8ca3b1b 100644 --- a/src/schema/org.libreweb.browser.gschema.xml +++ b/src/schema/org.libreweb.browser.gschema.xml @@ -17,9 +17,13 @@ false Is window fullscreen - + + 300 + Position of paned divider for ToC and draw windows + + 42 - Position of paned divider + Position of paned divider for primary and secondary draw window "Sans" diff --git a/src/toc-model-cols.h b/src/toc-model-cols.h new file mode 100644 index 0000000..01400bd --- /dev/null +++ b/src/toc-model-cols.h @@ -0,0 +1,23 @@ +#ifndef TOC_MODEL_COLS_H +#define TOC_MODEL_COLS_H + +#include + +/** + * \class TocModelCols + * \brief Table of Contents Model Columns + */ +class TocModelCols : public Gtk::TreeModel::ColumnRecord +{ +public: + TocModelCols() + { + add(m_col_id); + add(m_col_heading); + } + + Gtk::TreeModelColumn m_col_id; + Gtk::TreeModelColumn m_col_heading; +}; + +#endif From 454c6ba9110d726d7e0e7c6e1f0fb83f51f2a01a Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Tue, 1 Feb 2022 21:45:28 +0100 Subject: [PATCH 2/5] Add text wrapping feature. This is fast and easy to add. Why not? --- src/mainwindow.cc | 54 +++++++++++++++++++++++++++++++++++++---------- src/mainwindow.h | 8 +++++++ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 5de3e5f..8315b24 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -36,6 +36,10 @@ MainWindow::MainWindow(const std::string& timeout) m_vboxSettings(Gtk::ORIENTATION_VERTICAL), m_vboxIconTheme(Gtk::ORIENTATION_VERTICAL), m_searchMatchCase("Match _Case", true), + m_wrapNone(m_wrappingGroup, "None"), + m_wrapChar(m_wrappingGroup, "Char"), + m_wrapWord(m_wrappingGroup, "Word"), + m_wrapWordChar(m_wrappingGroup, "Word+Char"), m_tocButton("Show table of contents (Ctrl+Shift+T)", true), m_backButton("Go back one page (Alt+Left arrow)", true), m_forwardButton("Go forward one page (Alt+Right arrow)", true), @@ -76,6 +80,7 @@ MainWindow::MainWindow(const std::string& timeout) m_spacingLabel("Spacing"), m_marginsLabel("Margins"), m_indentLabel("Indent"), + m_textWrappingLabel("Wrapping"), m_themeLabel("Dark Theme"), m_iconThemeLabel("Active Theme"), // Private members @@ -774,7 +779,17 @@ void MainWindow::initSettingsPopover() m_hboxSetingsBrightness.pack_end(m_scaleSettingsBrightness); // Dark theme switch m_themeSwitch.set_active(useDarkTheme_); // Override with current dark theme preference - // Spin buttons + // Settings buttons + m_wrapWordChar.set_active(true); // Default wrapping mode + m_fontLabel.set_tooltip_text("Font familiy"); + m_spacingLabel.set_tooltip_text("Text spacing"); + m_marginsLabel.set_tooltip_text("Text margins"); + m_indentLabel.set_tooltip_text("Text indentation"); + m_textWrappingLabel.set_tooltip_text("Text wrapping"); + m_wrapNone.set_tooltip_text("No wrapping"); + m_wrapChar.set_tooltip_text("Character wrapping"); + m_wrapWord.set_tooltip_text("Word wrapping"); + m_wrapWordChar.set_tooltip_text("Word wrapping (+ character)"); m_spacingSpinButton.set_adjustment(m_spacingAdjustment); m_marginsSpinButton.set_adjustment(m_marginsAdjustment); m_indentSpinButton.set_adjustment(m_indentAdjustment); @@ -782,27 +797,34 @@ void MainWindow::initSettingsPopover() m_spacingLabel.set_xalign(1); m_marginsLabel.set_xalign(1); m_indentLabel.set_xalign(1); + m_textWrappingLabel.set_xalign(1); m_themeLabel.set_xalign(1); m_fontLabel.get_style_context()->add_class("dim-label"); m_spacingLabel.get_style_context()->add_class("dim-label"); m_marginsLabel.get_style_context()->add_class("dim-label"); m_indentLabel.get_style_context()->add_class("dim-label"); + m_textWrappingLabel.get_style_context()->add_class("dim-label"); m_themeLabel.get_style_context()->add_class("dim-label"); m_settingsGrid.set_margin_start(6); m_settingsGrid.set_margin_top(6); m_settingsGrid.set_margin_bottom(6); m_settingsGrid.set_row_spacing(10); m_settingsGrid.set_column_spacing(10); - m_settingsGrid.attach(m_fontLabel, 0, 0); - m_settingsGrid.attach(m_fontButton, 1, 0); - m_settingsGrid.attach(m_spacingLabel, 0, 1); - m_settingsGrid.attach(m_spacingSpinButton, 1, 1); - m_settingsGrid.attach(m_marginsLabel, 0, 2); - m_settingsGrid.attach(m_marginsSpinButton, 1, 2); - m_settingsGrid.attach(m_indentLabel, 0, 3); - m_settingsGrid.attach(m_indentSpinButton, 1, 3); - m_settingsGrid.attach(m_themeLabel, 0, 4); - m_settingsGrid.attach(m_themeSwitch, 1, 4); + m_settingsGrid.attach(m_fontLabel, 0, 0, 1); + m_settingsGrid.attach(m_fontButton, 1, 0, 2); + m_settingsGrid.attach(m_spacingLabel, 0, 1, 1); + m_settingsGrid.attach(m_spacingSpinButton, 1, 1, 2); + m_settingsGrid.attach(m_marginsLabel, 0, 2, 1); + m_settingsGrid.attach(m_marginsSpinButton, 1, 2, 2); + m_settingsGrid.attach(m_indentLabel, 0, 3, 1); + m_settingsGrid.attach(m_indentSpinButton, 1, 3, 2); + m_settingsGrid.attach(m_textWrappingLabel, 0, 4, 1); + m_settingsGrid.attach(m_wrapNone, 1, 4, 1); + m_settingsGrid.attach(m_wrapChar, 2, 4, 1); + m_settingsGrid.attach(m_wrapWord, 1, 5, 1); + m_settingsGrid.attach(m_wrapWordChar, 2, 5, 1); + m_settingsGrid.attach(m_themeLabel, 0, 6, 1); + m_settingsGrid.attach(m_themeSwitch, 1, 6, 2); // Icon theme (+ submenu) m_iconThemeButton.set_label("Icon Theme"); m_iconThemeButton.property_menu_name() = "icon-theme"; @@ -942,6 +964,11 @@ void MainWindow::initSignals() m_spacingSpinButton.signal_value_changed().connect(sigc::mem_fun(this, &MainWindow::on_spacing_changed)); m_marginsSpinButton.signal_value_changed().connect(sigc::mem_fun(this, &MainWindow::on_margins_changed)); m_indentSpinButton.signal_value_changed().connect(sigc::mem_fun(this, &MainWindow::on_indent_changed)); + m_wrapNone.signal_toggled().connect(sigc::bind(sigc::mem_fun(this, &MainWindow::on_wrap_toggled), Gtk::WrapMode::WRAP_NONE)); + 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)); @@ -1962,6 +1989,11 @@ void MainWindow::on_indent_changed() m_draw_main.set_indent(m_indentSpinButton.get_value_as_int()); } +void MainWindow::on_wrap_toggled(Gtk::WrapMode mode) +{ + m_draw_main.set_wrap_mode(mode); +} + void MainWindow::on_brightness_changed() { brightnessScale_ = m_scaleSettingsBrightness.get_value(); diff --git a/src/mainwindow.h b/src/mainwindow.h index f08fe42..2ed1698 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,7 @@ protected: void on_spacing_changed(); void on_margins_changed(); void on_indent_changed(); + void on_wrap_toggled(Gtk::WrapMode mode); void on_brightness_changed(); void on_theme_changed(); void on_icon_theme_activated(Gtk::ListBoxRow* row); @@ -155,6 +157,11 @@ protected: Gtk::SpinButton m_spacingSpinButton; Gtk::SpinButton m_marginsSpinButton; Gtk::SpinButton m_indentSpinButton; + Gtk::RadioButton::Group m_wrappingGroup; + Gtk::RadioButton m_wrapNone; + Gtk::RadioButton m_wrapChar; + Gtk::RadioButton m_wrapWord; + Gtk::RadioButton m_wrapWordChar; Gtk::ModelButton m_iconThemeButton; Gtk::ModelButton m_aboutButton; Gtk::ModelButton m_iconThemeBackButton; @@ -252,6 +259,7 @@ protected: Gtk::Label m_spacingLabel; Gtk::Label m_marginsLabel; Gtk::Label m_indentLabel; + Gtk::Label m_textWrappingLabel; Gtk::Label m_themeLabel; Gtk::Label m_iconThemeLabel; std::unique_ptr m_contentPublishedDialog; From 472a81b8044c9c79ece9667b9f91d34ebf933d9b Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Wed, 2 Feb 2022 01:42:39 +0100 Subject: [PATCH 3/5] Proof of concept working --- src/draw.cc | 81 ++++++++++++++------ src/draw.h | 6 +- src/mainwindow.cc | 176 ++++++++++++++++++++++++++++++++++++++----- src/toc-model-cols.h | 8 +- 4 files changed, 225 insertions(+), 46 deletions(-) diff --git a/src/draw.cc b/src/draw.cc index 990c65f..4b4ec1d 100644 --- a/src/draw.cc +++ b/src/draw.cc @@ -3,6 +3,7 @@ #include "node.h" #include "syntax_extension.h" #include +#include #include #include #include @@ -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 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> 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 const& tagNames) { @@ -1491,6 +1522,21 @@ void Draw::insertTagText(const Glib::ustring& text, std::vector 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 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 diff --git a/src/draw.h b/src/draw.h index b333309..5209622 100644 --- a/src/draw.h +++ b/src/draw.h @@ -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> getHeadings(); // Signals editor calls void make_heading(int headingLevel); @@ -104,6 +105,7 @@ private: Glib::RefPtr textCursor; bool hovingOverLink; bool isUserAction; + std::vector> headingsToc; std::vector undoPool; std::vector 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 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); }; diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 8315b24..be1dce7 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -2,6 +2,7 @@ #include "menu.h" #include "project_config.h" +#include #include #include #include @@ -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 headerMark : headings) + { + Glib::ustring heading = static_cast(headerMark->get_data("name")); + auto level = reinterpret_cast(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 diff --git a/src/toc-model-cols.h b/src/toc-model-cols.h index 01400bd..6233148 100644 --- a/src/toc-model-cols.h +++ b/src/toc-model-cols.h @@ -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 m_col_id; + Gtk::TreeModelColumn m_col_iter; + Gtk::TreeModelColumn m_col_level; Gtk::TreeModelColumn m_col_heading; + Gtk::TreeModelColumn m_col_valid; }; #endif From ba8dbbfe057fd906c7aee2cb04dfc2d3f83868f5 Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Fri, 4 Feb 2022 22:31:33 +0100 Subject: [PATCH 4/5] Add top window header to table of contents side panel --- src/mainwindow.cc | 92 +++++++++++++++++++++++++++-------------------- src/mainwindow.h | 11 ++++-- 2 files changed, 62 insertions(+), 41 deletions(-) diff --git a/src/mainwindow.cc b/src/mainwindow.cc index be1dce7..36a55b9 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -31,7 +31,8 @@ MainWindow::MainWindow(const std::string& timeout) m_draw_main(middleware_), m_draw_secondary(middleware_), m_about(*this), - m_vbox(Gtk::ORIENTATION_VERTICAL, 0), + m_vboxMain(Gtk::ORIENTATION_VERTICAL, 0), + m_vboxToc(Gtk::ORIENTATION_VERTICAL), m_vboxSearch(Gtk::ORIENTATION_VERTICAL), m_vboxStatus(Gtk::ORIENTATION_VERTICAL), m_vboxSettings(Gtk::ORIENTATION_VERTICAL), @@ -41,7 +42,8 @@ MainWindow::MainWindow(const std::string& timeout) m_wrapChar(m_wrappingGroup, "Char"), m_wrapWord(m_wrappingGroup, "Word"), m_wrapWordChar(m_wrappingGroup, "Word+Char"), - m_tocButton("Show table of contents (Ctrl+Shift+T)", true), + m_closeTocWindowButton("Close table of contents"), + m_openTocButton("Show table of contents (Ctrl+Shift+T)", true), m_backButton("Go back one page (Alt+Left arrow)", true), m_forwardButton("Go forward one page (Alt+Right arrow)", true), m_refreshButton("Reload current page (Ctrl+R)", true), @@ -67,6 +69,7 @@ MainWindow::MainWindow(const std::string& timeout) m_bulletListButton("Add a bullet list"), m_numberedListButton("Add a numbered list"), m_highlightButton("Add highlight text"), + m_tableOfContentsLabel("Table of Contents"), m_networkHeadingLabel("IPFS Network"), m_networkRateHeadingLabel("Network rate"), m_connectivityLabel("Status:"), @@ -105,11 +108,12 @@ MainWindow::MainWindow(const std::string& timeout) loadStoredSettings(); loadIcons(); - initButtons(); + initToolbarButtons(); setTheme(); initSearchPopover(); initStatusPopover(); initSettingsPopover(); + initTableofContents(); initSignals(); // Add custom CSS Provider to draw textviews @@ -120,17 +124,6 @@ MainWindow::MainWindow(const std::string& timeout) // Load the default font family and font size 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(2); - - m_scrolledToc.add(tocTreeView); - m_scrolledToc.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); - m_tocTreeModel = Gtk::TreeStore::create(m_tocColumns); - tocTreeView.set_model(m_tocTreeModel); // Browser main drawing area m_scrolledWindowMain.add(m_draw_main); m_scrolledWindowMain.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); @@ -140,25 +133,25 @@ MainWindow::MainWindow(const std::string& timeout) m_scrolledWindowSecondary.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); m_panedDraw.pack1(m_scrolledWindowMain, true, false); m_panedDraw.pack2(m_scrolledWindowSecondary, true, true); - - // Add table of contents to horizontal paned - m_panedRoot.pack1(m_scrolledToc, true, false); + // Left the vbox for the table of contents, + // right the main drawing paned windows (primary/secondary). + m_panedRoot.pack1(m_vboxToc, true, false); m_panedRoot.pack2(m_panedDraw, true, false); - - m_vbox.pack_start(m_menu, false, false, 0); - m_vbox.pack_start(m_hboxBrowserToolbar, false, false, 6); - m_vbox.pack_start(m_hboxStandardEditorToolbar, false, false, 6); - m_vbox.pack_start(m_hboxFormattingEditorToolbar, false, false, 6); - m_vbox.pack_start(m_panedRoot, true, true, 0); - add(m_vbox); + // Main virtual box + m_vboxMain.pack_start(m_menu, false, false, 0); + m_vboxMain.pack_start(m_hboxBrowserToolbar, false, false, 6); + m_vboxMain.pack_start(m_hboxStandardEditorToolbar, false, false, 6); + m_vboxMain.pack_start(m_hboxFormattingEditorToolbar, false, false, 6); + m_vboxMain.pack_start(m_panedRoot, true, true, 0); + add(m_vboxMain); show_all_children(); - // Hide by default the replace entry, editor box, ToC & secondary text view + // Hide by default the table of contents, secondary textview, replace entry and editor toolbars + m_vboxToc.hide(); + m_scrolledWindowSecondary.hide(); m_searchReplaceEntry.hide(); m_hboxStandardEditorToolbar.hide(); m_hboxFormattingEditorToolbar.hide(); - m_scrolledToc.hide(); - m_scrolledWindowSecondary.hide(); // Grap focus to input field by default m_addressBar.grab_focus(); @@ -169,7 +162,7 @@ MainWindow::MainWindow(const std::string& timeout) #else std::cout << "INFO: Running as Debug mode, opening test.md." << std::endl; // Load test file during development - middleware_.doRequest("file://../../invalid_headers.md"); + middleware_.doRequest("file://../../test.md"); #endif } @@ -627,7 +620,7 @@ void MainWindow::loadIcons() /** * Init all buttons / comboboxes from the toolbars */ -void MainWindow::initButtons() +void MainWindow::initToolbarButtons() { // Add icons to the toolbar editor buttons m_openButton.add(m_openIcon); @@ -679,7 +672,7 @@ void MainWindow::initButtons() m_settingsButton.set_relief(Gtk::RELIEF_NONE); // Add icons to the toolbar buttons - m_tocButton.add(m_tocIcon); + m_openTocButton.add(m_tocIcon); m_backButton.add(m_backIcon); m_forwardButton.add(m_forwardIcon); m_refreshButton.add(m_refreshIcon); @@ -712,8 +705,8 @@ void MainWindow::initButtons() * Adding the buttons to the boxes */ // Browser Toolbar - m_tocButton.set_margin_left(6); - m_hboxBrowserToolbar.pack_start(m_tocButton, false, false, 0); + m_openTocButton.set_margin_left(6); + m_hboxBrowserToolbar.pack_start(m_openTocButton, false, false, 0); m_hboxBrowserToolbar.pack_start(m_backButton, false, false, 0); m_hboxBrowserToolbar.pack_start(m_forwardButton, false, false, 0); m_hboxBrowserToolbar.pack_start(m_refreshButton, false, false, 0); @@ -877,7 +870,29 @@ void MainWindow::initStatusPopover() } /** - * Init the settings pop-over + * \brief Init table of contents window (left side-panel) + */ +void MainWindow::initTableofContents() +{ + m_closeTocWindowButton.set_image_from_icon_name("window-close-symbolic", Gtk::IconSize(Gtk::ICON_SIZE_SMALL_TOOLBAR)); + m_tableOfContentsLabel.set_margin_start(6); + m_hboxToc.pack_start(m_tableOfContentsLabel, false, false); + m_hboxToc.pack_end(m_closeTocWindowButton, false, false); + 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(2); + m_scrolledToc.add(tocTreeView); + m_scrolledToc.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + m_tocTreeModel = Gtk::TreeStore::create(m_tocColumns); + tocTreeView.set_model(m_tocTreeModel); + m_vboxToc.pack_start(m_hboxToc, Gtk::PackOptions::PACK_SHRINK); + m_vboxToc.pack_end(m_scrolledToc); +} + +/** + * \brief Init the settings pop-over */ void MainWindow::initSettingsPopover() { @@ -1026,6 +1041,7 @@ void MainWindow::initSignals() // Window signals signal_delete_event().connect(sigc::mem_fun(this, &MainWindow::delete_window)); // Table of contents + m_closeTocWindowButton.signal_clicked().connect(sigc::mem_fun(m_vboxToc, &Gtk::Widget::hide)); tocTreeView.signal_row_activated().connect(sigc::mem_fun(this, &MainWindow::on_toc_row_activated)); // Menu & toolbar signals m_menu.new_doc.connect(sigc::mem_fun(this, &MainWindow::new_doc)); /*!< Menu item for new document */ @@ -1056,7 +1072,7 @@ void MainWindow::initSignals() m_draw_main.source_code.connect(sigc::mem_fun(this, &MainWindow::show_source_code_dialog)); /*!< Open source code dialog */ m_about.signal_response().connect(sigc::mem_fun(m_about, &About::hide_about)); /*!< Close about dialog */ m_addressBar.signal_activate().connect(sigc::mem_fun(this, &MainWindow::address_bar_activate)); /*!< User pressed enter the address bar */ - m_tocButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::show_toc)); /*!< Button for showing Table of Contents */ + m_openTocButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::show_toc)); /*!< Button for showing Table of Contents */ m_backButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::back)); /*!< Button for previous page */ m_forwardButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::forward)); /*!< Button for next page */ m_refreshButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::refreshRequest)); /*!< Button for reloading the page */ @@ -1650,10 +1666,10 @@ void MainWindow::go_home() */ void MainWindow::show_toc() { - if (m_scrolledToc.is_visible()) - m_scrolledToc.hide(); + if (m_vboxToc.is_visible()) + m_vboxToc.hide(); else - m_scrolledToc.show(); + m_vboxToc.show(); } /** @@ -1886,7 +1902,7 @@ void MainWindow::enableEdit() m_panedDraw.set_position(location); // Disable Table of Contents during edit & clear ToC - m_scrolledToc.hide(); + m_vboxToc.hide(); m_tocTreeModel->clear(); // Enabled secondary text view (on the right) m_scrolledWindowSecondary.show(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 2ed1698..bc9d59b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -134,11 +134,13 @@ protected: Gtk::SearchBar m_searchReplace; Gtk::SearchEntry m_searchEntry; Gtk::Entry m_searchReplaceEntry; - Gtk::Box m_vbox; + Gtk::Box m_vboxMain; Gtk::Box m_hboxBrowserToolbar; Gtk::Box m_hboxStandardEditorToolbar; Gtk::Box m_hboxFormattingEditorToolbar; Gtk::Box m_hboxSearch; + Gtk::Box m_hboxToc; + Gtk::Box m_vboxToc; Gtk::Box m_vboxSearch; Gtk::Box m_vboxStatus; Gtk::Box m_vboxSettings; @@ -168,7 +170,8 @@ protected: Gtk::Grid m_statusGrid; Gtk::Grid m_activityStatusGrid; Gtk::Grid m_settingsGrid; - ToolbarButton m_tocButton; + ToolbarButton m_closeTocWindowButton; + ToolbarButton m_openTocButton; ToolbarButton m_backButton; ToolbarButton m_forwardButton; ToolbarButton m_refreshButton; @@ -238,6 +241,7 @@ protected: Gtk::ModelButton m_copyIDButton; Gtk::ModelButton m_copyPublicKeyButton; Gtk::Switch m_themeSwitch; + Gtk::Label m_tableOfContentsLabel; Gtk::Label m_networkHeadingLabel; Gtk::Label m_networkRateHeadingLabel; Gtk::Label m_connectivityLabel; @@ -298,11 +302,12 @@ private: void loadStoredSettings(); void setGTKIcons(); void loadIcons(); - void initButtons(); + void initToolbarButtons(); void setTheme(); void initSearchPopover(); void initStatusPopover(); void initSettingsPopover(); + void initTableofContents(); void initSignals(); bool isInstalled(); void enableEdit(); From 6333e127318c6bae2f693c79ec862ff1364763fe Mon Sep 17 00:00:00 2001 From: Melroy van den Berg Date: Fri, 4 Feb 2022 22:58:55 +0100 Subject: [PATCH 5/5] Implement toc in editor mode as well --- src/draw.cc | 2 +- src/draw.h | 2 +- src/mainwindow.cc | 431 +++++++++++++++++++++++----------------------- src/mainwindow.h | 7 +- src/middleware.cc | 2 +- 5 files changed, 227 insertions(+), 217 deletions(-) diff --git a/src/draw.cc b/src/draw.cc index 4b4ec1d..17f8000 100644 --- a/src/draw.cc +++ b/src/draw.cc @@ -277,7 +277,7 @@ void Draw::setMessage(const Glib::ustring& message, const Glib::ustring& details /** * \brief Draw homepage */ -void Draw::showStartPage() +void Draw::showHomepage() { if (get_editable()) this->disableEdit(); diff --git a/src/draw.h b/src/draw.h index 5209622..612475c 100644 --- a/src/draw.h +++ b/src/draw.h @@ -39,7 +39,7 @@ public: explicit Draw(MiddlewareInterface& middleware); void setMessage(const Glib::ustring& message, const Glib::ustring& details = ""); - void showStartPage(); + void showHomepage(); void setDocument(cmark_node* rootNode); void setViewSourceMenuItem(bool isEnabled); void newDocument(); diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 36a55b9..e47b47a 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -28,7 +28,7 @@ MainWindow::MainWindow(const std::string& timeout) m_indentAdjustment(Gtk::Adjustment::create(0, 0, 1000, 5, 10)), m_drawCSSProvider(Gtk::CssProvider::create()), m_menu(m_accelGroup), - m_draw_main(middleware_), + m_draw_primary(middleware_), m_draw_secondary(middleware_), m_about(*this), m_vboxMain(Gtk::ORIENTATION_VERTICAL, 0), @@ -117,24 +117,24 @@ MainWindow::MainWindow(const std::string& timeout) initSignals(); // Add custom CSS Provider to draw textviews - auto styleMain = m_draw_main.get_style_context(); + auto stylePrimary = m_draw_primary.get_style_context(); auto styleSecondary = m_draw_secondary.get_style_context(); - styleMain->add_provider(m_drawCSSProvider, GTK_STYLE_PROVIDER_PRIORITY_USER); + stylePrimary->add_provider(m_drawCSSProvider, GTK_STYLE_PROVIDER_PRIORITY_USER); styleSecondary->add_provider(m_drawCSSProvider, GTK_STYLE_PROVIDER_PRIORITY_USER); // Load the default font family and font size updateCSS(); - // Browser main drawing area - m_scrolledWindowMain.add(m_draw_main); - m_scrolledWindowMain.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + // Primary drawing area + m_scrolledWindowPrimary.add(m_draw_primary); + m_scrolledWindowPrimary.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); // Secondary drawing area m_draw_secondary.setViewSourceMenuItem(false); m_scrolledWindowSecondary.add(m_draw_secondary); m_scrolledWindowSecondary.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); - m_panedDraw.pack1(m_scrolledWindowMain, true, false); + m_panedDraw.pack1(m_scrolledWindowPrimary, true, false); m_panedDraw.pack2(m_scrolledWindowSecondary, true, true); // Left the vbox for the table of contents, - // right the main drawing paned windows (primary/secondary). + // right the drawing paned windows (primary/secondary). m_panedRoot.pack1(m_vboxToc, true, false); m_panedRoot.pack2(m_panedDraw, true, false); // Main virtual box @@ -251,11 +251,11 @@ void MainWindow::refreshRequest() } /** - * \brief Show startpage + * \brief Show home page */ -void MainWindow::showStartpage() +void MainWindow::showHomepage() { - m_draw_main.showStartPage(); + m_draw_primary.showHomepage(); } /** @@ -264,155 +264,18 @@ void MainWindow::showStartpage() */ void MainWindow::setText(const Glib::ustring& content) { - m_draw_main.setText(content); + m_draw_primary.setText(content); } /** - * \brief Set markdown document (cmark). cmark_node pointer will be freed automatically. + * \brief Set markdown document (common mark) on primary window. cmark_node pointer will be freed automatically. + * And set the ToC. * \param rootNode cmark root data struct */ 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 headerMark : headings) - { - Glib::ustring heading = static_cast(headerMark->get_data("name")); - auto level = reinterpret_cast(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(); + m_draw_primary.setDocument(rootNode); + setTableofContents(m_draw_primary.getHeadings()); } /** @@ -422,7 +285,7 @@ void MainWindow::setDocument(cmark_node* rootNode) */ void MainWindow::setMessage(const Glib::ustring& message, const Glib::ustring& details) { - m_draw_main.setMessage(message, details); + m_draw_primary.setMessage(message, details); } /** @@ -501,9 +364,9 @@ void MainWindow::loadStoredSettings() m_spacingAdjustment->set_value(fontSpacing_); m_marginsAdjustment->set_value(margins); m_indentAdjustment->set_value(indent); - m_draw_main.set_left_margin(margins); - m_draw_main.set_right_margin(margins); - m_draw_main.set_indent(indent); + m_draw_primary.set_left_margin(margins); + m_draw_primary.set_right_margin(margins); + m_draw_primary.set_indent(indent); int tocDividerPosition = m_settings->get_int("position-divider-toc"); m_panedRoot.set_position(tocDividerPosition); iconTheme_ = m_settings->get_string("icon-theme"); @@ -521,9 +384,9 @@ void MainWindow::loadStoredSettings() m_marginsAdjustment->set_value(margins); m_indentAdjustment->set_value(indent); // For drawing - m_draw_main.set_left_margin(margins); - m_draw_main.set_right_margin(margins); - m_draw_main.set_indent(indent); + m_draw_primary.set_left_margin(margins); + m_draw_primary.set_right_margin(margins); + m_draw_primary.set_indent(indent); // ToC paned divider m_panedRoot.set_position(300); } @@ -1052,8 +915,8 @@ void MainWindow::initSignals() m_menu.save_as.connect(sigc::mem_fun(this, &MainWindow::save_as)); /*!< Menu item for save document as */ m_menu.publish.connect(sigc::mem_fun(this, &MainWindow::publish)); /*!< Menu item for publishing */ m_menu.quit.connect(sigc::mem_fun(this, &MainWindow::close)); /*!< close main window and therefor closes the app */ - m_menu.undo.connect(sigc::mem_fun(m_draw_main, &Draw::undo)); /*!< Menu item for undo text */ - m_menu.redo.connect(sigc::mem_fun(m_draw_main, &Draw::redo)); /*!< Menu item for redo text */ + m_menu.undo.connect(sigc::mem_fun(m_draw_primary, &Draw::undo)); /*!< Menu item for undo text */ + m_menu.redo.connect(sigc::mem_fun(m_draw_primary, &Draw::redo)); /*!< Menu item for redo text */ m_menu.cut.connect(sigc::mem_fun(this, &MainWindow::cut)); /*!< Menu item for cut text */ m_menu.copy.connect(sigc::mem_fun(this, &MainWindow::copy)); /*!< Menu item for copy text */ m_menu.paste.connect(sigc::mem_fun(this, &MainWindow::paste)); /*!< Menu item for paste text */ @@ -1069,7 +932,7 @@ void MainWindow::initSignals() 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_draw_main.source_code.connect(sigc::mem_fun(this, &MainWindow::show_source_code_dialog)); /*!< Open source code dialog */ + m_draw_primary.source_code.connect(sigc::mem_fun(this, &MainWindow::show_source_code_dialog)); /*!< Open source code dialog */ m_about.signal_response().connect(sigc::mem_fun(m_about, &About::hide_about)); /*!< Close about dialog */ m_addressBar.signal_activate().connect(sigc::mem_fun(this, &MainWindow::address_bar_activate)); /*!< User pressed enter the address bar */ m_openTocButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::show_toc)); /*!< Button for showing Table of Contents */ @@ -1086,22 +949,22 @@ void MainWindow::initSignals() m_cutButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::cut)); m_copyButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::copy)); m_pasteButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::paste)); - m_undoButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::undo)); - m_redoButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::redo)); + m_undoButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::undo)); + m_redoButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::redo)); m_headingsComboBox.signal_changed().connect(sigc::mem_fun(this, &MainWindow::get_heading)); - m_boldButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::make_bold)); - m_italicButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::make_italic)); - m_strikethroughButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::make_strikethrough)); - m_superButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::make_super)); - m_subButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::make_sub)); - m_linkButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::insert_link)); - m_imageButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::insert_image)); + m_boldButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::make_bold)); + m_italicButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::make_italic)); + m_strikethroughButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::make_strikethrough)); + m_superButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::make_super)); + m_subButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::make_sub)); + m_linkButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::insert_link)); + m_imageButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::insert_image)); m_emojiButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::insert_emoji)); - m_quoteButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::make_quote)); - m_codeButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::make_code)); - m_bulletListButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::insert_bullet_list)); - m_numberedListButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::insert_numbered_list)); - m_highlightButton.signal_clicked().connect(sigc::mem_fun(m_draw_main, &Draw::make_highlight)); + m_quoteButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::make_quote)); + m_codeButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::make_code)); + m_bulletListButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::insert_bullet_list)); + m_numberedListButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::insert_numbered_list)); + m_highlightButton.signal_clicked().connect(sigc::mem_fun(m_draw_primary, &Draw::make_highlight)); // Status pop-over buttons m_copyIDButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::copy_client_id)); m_copyPublicKeyButton.signal_clicked().connect(sigc::mem_fun(this, &MainWindow::copy_client_public_key)); @@ -1159,9 +1022,9 @@ bool MainWindow::delete_window(GdkEventAny* any_event __attribute__((unused))) */ void MainWindow::cut() { - if (m_draw_main.has_focus()) + if (m_draw_primary.has_focus()) { - m_draw_main.cut(); + m_draw_primary.cut(); } else if (m_draw_secondary.has_focus()) { @@ -1183,9 +1046,9 @@ void MainWindow::cut() void MainWindow::copy() { - if (m_draw_main.has_focus()) + if (m_draw_primary.has_focus()) { - m_draw_main.copy(); + m_draw_primary.copy(); } else if (m_draw_secondary.has_focus()) { @@ -1207,9 +1070,9 @@ void MainWindow::copy() void MainWindow::paste() { - if (m_draw_main.has_focus()) + if (m_draw_primary.has_focus()) { - m_draw_main.paste(); + m_draw_primary.paste(); } else if (m_draw_secondary.has_focus()) { @@ -1231,9 +1094,9 @@ void MainWindow::paste() void MainWindow::del() { - if (m_draw_main.has_focus()) + if (m_draw_primary.has_focus()) { - m_draw_main.del(); + m_draw_primary.del(); } else if (m_draw_secondary.has_focus()) { @@ -1282,9 +1145,9 @@ void MainWindow::del() void MainWindow::selectAll() { - if (m_draw_main.has_focus()) + if (m_draw_primary.has_focus()) { - m_draw_main.selectAll(); + m_draw_primary.selectAll(); } else if (m_draw_secondary.has_focus()) { @@ -1317,7 +1180,10 @@ void MainWindow::on_toc_row_activated(const Gtk::TreeModel::Path& path, __attrib { Gtk::TextIter textIter = row[m_tocColumns.m_col_iter]; // Scroll to to mark iterator - m_draw_main.scroll_to(textIter); + if (isEditorEnabled()) + m_draw_secondary.scroll_to(textIter); + else + m_draw_primary.scroll_to(textIter); } } } @@ -1472,7 +1338,7 @@ void MainWindow::edit() if (!isEditorEnabled()) enableEdit(); - m_draw_main.setText(middleware_.getContent()); + m_draw_primary.setText(middleware_.getContent()); // Set title set_title("Untitled * - " + appName_); } @@ -1711,7 +1577,7 @@ void MainWindow::on_search() { // Forward search, find and select std::string text = m_searchEntry.get_text(); - auto buffer = m_draw_main.get_buffer(); + auto buffer = m_draw_primary.get_buffer(); Gtk::TextBuffer::iterator iter = buffer->get_iter_at_mark(buffer->get_mark("insert")); Gtk::TextBuffer::iterator start, end; bool matchCase = m_searchMatchCase.get_active(); @@ -1723,7 +1589,7 @@ void MainWindow::on_search() if (iter.forward_search(text, flags, start, end)) { buffer->select_range(end, start); - m_draw_main.scroll_to(start); + m_draw_primary.scroll_to(start); } else { @@ -1733,7 +1599,7 @@ void MainWindow::on_search() if (secondIter.forward_search(text, flags, start, end)) { buffer->select_range(end, start); - m_draw_main.scroll_to(start); + m_draw_primary.scroll_to(start); } } } @@ -1743,9 +1609,9 @@ void MainWindow::on_search() */ void MainWindow::on_replace() { - if (m_draw_main.get_editable()) + if (m_draw_primary.get_editable()) { - auto buffer = m_draw_main.get_buffer(); + auto buffer = m_draw_primary.get_buffer(); Gtk::TextBuffer::iterator startIter = buffer->get_iter_at_mark(buffer->get_mark("insert")); Gtk::TextBuffer::iterator endIter = buffer->get_iter_at_mark(buffer->get_mark("selection_bound")); if (startIter != endIter) @@ -1767,8 +1633,8 @@ void MainWindow::on_replace() void MainWindow::address_bar_activate() { middleware_.doRequest(m_addressBar.get_text(), false); - // When user actually entered the address bar, focus on the main draw - m_draw_main.grab_focus(); + // When user actually entered the address bar, focus on the primary draw + m_draw_primary.grab_focus(); } /** @@ -1836,6 +1702,149 @@ void MainWindow::forward() } } +/** + * \brief Fill-in table of contents and show + */ +void MainWindow::setTableofContents(std::vector> headings) +{ + Gtk::TreeRow heading1Row, heading2Row, heading3Row, heading4Row, heading5Row; + int previousLevel = 1; // Default heading 1 + for (const Glib::RefPtr headerMark : headings) + { + Glib::ustring heading = static_cast(headerMark->get_data("name")); + auto level = reinterpret_cast(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(); +} + /** * \brief Determing if browser is installed to the installation directory at runtime * \return true if the current running process is installed (to the installed prefix path) @@ -1878,7 +1887,7 @@ bool MainWindow::isInstalled() void MainWindow::enableEdit() { // Inform the Draw class that we are creating a new document - m_draw_main.newDocument(); + m_draw_primary.newDocument(); // Show editor toolbars m_hboxStandardEditorToolbar.show(); m_hboxFormattingEditorToolbar.show(); @@ -1901,15 +1910,12 @@ void MainWindow::enableEdit() } m_panedDraw.set_position(location); - // Disable Table of Contents during edit & clear ToC - m_vboxToc.hide(); - m_tocTreeModel->clear(); // Enabled secondary text view (on the right) m_scrolledWindowSecondary.show(); // Disable "view source" menu item - m_draw_main.setViewSourceMenuItem(false); + m_draw_primary.setViewSourceMenuItem(false); // Connect changed signal - textChangedSignalHandler_ = m_draw_main.get_buffer()->signal_changed().connect(sigc::mem_fun(this, &MainWindow::editor_changed_text)); + textChangedSignalHandler_ = m_draw_primary.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) @@ -1931,7 +1937,7 @@ void MainWindow::disableEdit() // Disconnect text changed signal textChangedSignalHandler_.disconnect(); // Show "view source" menu item again - m_draw_main.setViewSourceMenuItem(true); + m_draw_primary.setViewSourceMenuItem(true); m_draw_secondary.clear(); // Disable publish menu item m_menu.setPublishMenuSensitive(false); @@ -1985,7 +1991,7 @@ std::string MainWindow::getIconImageFromTheme(const std::string& iconName, const } /** - * \brief Update the CSS provider data on the main draw text view + * \brief Update the CSS provider data */ void MainWindow::updateCSS() { @@ -2044,14 +2050,17 @@ void MainWindow::editor_changed_text() // TODO: Just execute the code below in a signal_idle call? // So it will never block the GUI thread. Or is this already running in another context + // Clear table of contents (ToC) + m_tocTreeModel->clear(); // Retrieve text from editor and parse the markdown contents - middleware_.setContent(m_draw_main.getText()); + middleware_.setContent(m_draw_primary.getText()); cmark_node* doc = middleware_.parseContent(); /* // Can be enabled to show the markdown format in terminal: std::string md = Parser::renderMarkdown(doc); std::cout << "Markdown:\n" << md << std::endl;*/ // Show the document as a preview on the right side text-view panel m_draw_secondary.setDocument(doc); + setTableofContents(m_draw_secondary.getHeadings()); } /** @@ -2077,7 +2086,7 @@ void MainWindow::get_heading() try { int headingLevel = std::stoi(active, &sz, 10); - m_draw_main.make_heading(headingLevel); + m_draw_primary.make_heading(headingLevel); } catch (const std::invalid_argument&) { @@ -2094,7 +2103,7 @@ void MainWindow::get_heading() void MainWindow::insert_emoji() { // Note: The "insert-emoji" signal is not exposed in Gtkmm library (at least not in gtk3) - g_signal_emit_by_name(m_draw_main.gobj(), "insert-emoji"); + g_signal_emit_by_name(m_draw_primary.gobj(), "insert-emoji"); } void MainWindow::on_zoom_out() @@ -2134,18 +2143,18 @@ void MainWindow::on_spacing_changed() void MainWindow::on_margins_changed() { - m_draw_main.set_left_margin(m_marginsSpinButton.get_value_as_int()); - m_draw_main.set_right_margin(m_marginsSpinButton.get_value_as_int()); + m_draw_primary.set_left_margin(m_marginsSpinButton.get_value_as_int()); + m_draw_primary.set_right_margin(m_marginsSpinButton.get_value_as_int()); } void MainWindow::on_indent_changed() { - m_draw_main.set_indent(m_indentSpinButton.get_value_as_int()); + m_draw_primary.set_indent(m_indentSpinButton.get_value_as_int()); } void MainWindow::on_wrap_toggled(Gtk::WrapMode mode) { - m_draw_main.set_wrap_mode(mode); + m_draw_primary.set_wrap_mode(mode); } void MainWindow::on_brightness_changed() diff --git a/src/mainwindow.h b/src/mainwindow.h index bc9d59b..25fa92a 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -58,7 +58,7 @@ public: void startedRequest(); void finishedRequest(); void refreshRequest(); - void showStartpage(); + void showHomepage(); void setText(const Glib::ustring& content); void setDocument(cmark_node* rootNode); void setMessage(const Glib::ustring& message, const Glib::ustring& details = ""); @@ -122,7 +122,7 @@ protected: // Child widgets Menu m_menu; - Draw m_draw_main; + Draw m_draw_primary; Draw m_draw_secondary; SourceCodeDialog m_sourceCodeDialog; About m_about; @@ -268,7 +268,7 @@ protected: Gtk::Label m_iconThemeLabel; std::unique_ptr m_contentPublishedDialog; Gtk::ScrolledWindow m_scrolledToc; - Gtk::ScrolledWindow m_scrolledWindowMain; + Gtk::ScrolledWindow m_scrolledWindowPrimary; Gtk::ScrolledWindow m_scrolledWindowSecondary; Gtk::SeparatorMenuItem m_separator1; Gtk::SeparatorMenuItem m_separator2; @@ -310,6 +310,7 @@ private: void initTableofContents(); void initSignals(); bool isInstalled(); + void setTableofContents(std::vector> headings); void enableEdit(); void disableEdit(); bool isEditorEnabled(); diff --git a/src/middleware.cc b/src/middleware.cc index 5d833da..4af79b9 100644 --- a/src/middleware.cc +++ b/src/middleware.cc @@ -254,7 +254,7 @@ void Middleware::processRequest(const std::string& path, bool isParseContent) // Handle homepage else if (requestPath_.compare("about:home") == 0) { - Glib::signal_idle().connect_once(sigc::mem_fun(mainWindow, &MainWindow::showStartpage)); + Glib::signal_idle().connect_once(sigc::mem_fun(mainWindow, &MainWindow::showHomepage)); } // Handle disk or IPFS file paths else