changeset 29330:b92614cfdfed

add bookmark functionality to the documentation browser (bug #54938) * libgui/src/documentation-bookmarks.cc: new file for the bookmarks tab; (documentation_bookmarks): initialize the tab, the filter and the tree; prepare the bookmark file and read existing bookmarks; (~documentation_bookmarks): empty destructor; (add_bookmark): slot for the adding bookmark action, get current title and url; (add_bookmark): do the adding, possibly as child of a parent item; (add_folder): slot for context menu action for adding a folder, check current position where to insert the new folder; (add_folder): do the folder adding; (filter_bookmarks): filter bookmarks following the changed filter text; (filter_activate): enable/disable filter; (update_filter_history): save a search term when acknowledged by return; (handle_double_click): open the clicked bookmark; (ctx_menu): show context menu at current mouse position; (open): open a bookmark via context menu; (edit): edit a bookmark via context menu; (remove): remove selected bookmarks via context menu; (show_filter): toggle visibility of the filter; (save_settings): save settings and initiate writing the bookmarks; (write_bookmarks): open file and initiate writing all top level items; (write_tree_item): write a single item and its children; (read_bookmarks): open the file, check if it is valid and loop over all top level items for reading them from the file; (read_next_item): read an item and its children in case of a folder; * documentation-bookmarks.h: class documentation_bookmarks derived from QWidget, declaration of all required functions and class variables; * documentation-dock-widget.cc (save_settings): new derived method, emitting a signal for saving setting in child widgets and callinf the original method * documentation-dock-widget.h: derived method save_settings and signal for child widgets * documentation.cc: include documentation-bookmarks.h; (documentation): add bookmark tab, connect signal for saving settings; (add_action): check reveiver before connection the actions signal; (construct_tool_bar): add toolbar button for adding a bookmark, connect its signal the add_bookmark slot and connect signal for saving settings; (notice_settings): add shortcut for adding a bookmark; (update_history): move combining page title and current anchor into a more specific title ... (title_and_anchor): to here; * documentation.h: new function title_and_anchor, new action * gui-preferences-dc.h: new file with constants for some bookmark settings and for the xbel file syntax * gui-preferences-sc.h: default value for bookmark action * bookmark-new.png/bookmark-new.svg: icon for tool bar button * icons_license: add new icon * module.mk: add new files * shortcut-manager.cc (init_data): initialize new short for bookmarking
author Torsten Lilge <ttl-octave@mailbox.org>
date Sun, 10 Jan 2021 14:04:35 +0100
parents 24b9c62b453d
children 3cbc3f96028c
files libgui/src/documentation-bookmarks.cc libgui/src/documentation-bookmarks.h libgui/src/documentation-dock-widget.cc libgui/src/documentation-dock-widget.h libgui/src/documentation.cc libgui/src/documentation.h libgui/src/gui-preferences-dc.h libgui/src/gui-preferences-sc.h libgui/src/icons/bookmark-new.png libgui/src/icons/bookmark-new.svg libgui/src/icons/icons_license libgui/src/module.mk libgui/src/resource.qrc libgui/src/shortcut-manager.cc
diffstat 14 files changed, 1502 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/documentation-bookmarks.cc	Sun Jan 10 14:04:35 2021 +0100
@@ -0,0 +1,547 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2018-2020 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Octave is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if defined (HAVE_CONFIG_H)
+#  include "config.h"
+#endif
+
+#include <QCompleter>
+#include <QMenu>
+#include <QShortcut>
+#include <QVBoxLayout>
+#include <QWidget>
+
+#include "documentation.h"
+#include "documentation-bookmarks.h"
+
+#include "gui-settings.h"
+#include "gui-preferences-global.h"
+#include "gui-preferences-dc.h"
+#include "gui-preferences-sc.h"
+#include "shortcut-manager.h"
+
+#include "defaults.h"
+#include "file-ops.h"
+#include "oct-env.h"
+
+namespace octave
+{
+  documentation_bookmarks::documentation_bookmarks (
+                      documentation *doc, documentation_browser *browser,
+                      base_qobject& oct_qobj, QWidget *p)
+    : QWidget (p),
+      m_doc (doc), m_browser (browser), m_octave_qobj (oct_qobj),
+      m_ctx_menu_item (nullptr)
+  {
+    setObjectName ("documentation_tab_bookmarks");
+
+    resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
+    gui_settings *settings = rmgr.get_settings ();
+
+    // Setup the tree view with the bookmarks
+    m_tree = new QTreeWidget (p);
+
+    m_tree->setContextMenuPolicy (Qt::CustomContextMenu);
+    m_tree->setSelectionMode (QAbstractItemView::ExtendedSelection);
+    m_tree->setSortingEnabled (false);
+    m_tree->setDragEnabled(true);
+    m_tree->viewport()->setAcceptDrops(true);
+    m_tree->setDropIndicatorShown(true);
+    m_tree->setDragDropMode(QAbstractItemView::InternalMove);
+    m_tree->setColumnCount (1);
+    m_tree->setHeaderHidden (true);
+    m_tree->setEditTriggers (QAbstractItemView::EditKeyPressed
+                                      | QAbstractItemView::SelectedClicked);
+
+    connect (m_tree, SIGNAL (customContextMenuRequested (const QPoint &)),
+             this, SLOT (ctx_menu (const QPoint &)));
+    connect (m_tree, SIGNAL (itemDoubleClicked (QTreeWidgetItem*, int)),
+             this, SLOT (handle_double_click (QTreeWidgetItem*, int)));
+
+    // Define the icons for the tree view
+    icon_folder.addPixmap (style ()->standardPixmap(QStyle::SP_DirClosedIcon),
+                           QIcon::Normal, QIcon::Off);
+    icon_folder.addPixmap (style ()->standardPixmap(QStyle::SP_DirOpenIcon),
+                           QIcon::Normal, QIcon::On);
+    icon_bookmark.addPixmap (style ()->standardPixmap(QStyle::SP_FileIcon));
+
+    // Setup and read the bookmarkfile
+    QFileInfo f (settings->fileName ());
+    QString f_path = f.absolutePath ();
+    f.setFile (QDir (f_path), dc_bookmark_file);
+    m_xbel_file.setFileName (f.absoluteFilePath ());
+
+    if (m_xbel_file.exists ())
+      {
+        QString err = read_bookmarks ();
+        if ( !err.isEmpty ())
+          {
+            err.append (tr ("\nNo documentation bookmarks loaded!"));
+            QMessageBox::warning (this,
+                                  tr ("Octave: Loading Documentation Bookmarks"),
+                                  err);
+            m_xbel_file.close ();
+          }
+      }
+
+    // Setup the filter widget
+    m_filter_widget = new QWidget (p);
+    m_filter = new QComboBox (m_filter_widget);
+
+    m_filter->setToolTip (tr ("Enter text to search the bookmarks"));
+    m_filter->setEditable (true);
+    m_filter->setInsertPolicy (QComboBox::NoInsert);
+    m_filter->setMaxCount (10);
+    m_filter->setMaxVisibleItems (10);
+    m_filter->setSizeAdjustPolicy (QComboBox::AdjustToMinimumContentsLengthWithIcon);
+    QSizePolicy size_pol (QSizePolicy::Expanding, QSizePolicy::Preferred);
+    m_filter->setSizePolicy (size_pol);
+    m_filter->completer ()->setCaseSensitivity (Qt::CaseSensitive);
+
+    m_filter->addItems (settings->value (dc_bookmark_filter_mru).toStringList ());
+
+    connect (m_filter, SIGNAL (editTextChanged (const QString &)),
+             this, SLOT (filter_bookmarks (const QString &)));
+    connect (m_filter->lineEdit (), SIGNAL (editingFinished (void)),
+             this, SLOT (update_filter_history (void)));
+
+    m_filter_checkbox = new QCheckBox (m_filter_widget);
+    bool filter_state = settings->value (dc_bookmark_filter_active).toBool ();
+    m_filter_checkbox->setChecked (filter_state);
+    filter_activate (filter_state);
+
+    connect (m_filter_checkbox, SIGNAL (toggled (bool)),
+             this, SLOT (filter_activate (bool)));
+
+    QLabel *filter_label = new QLabel (tr ("Filter"), m_filter_widget);
+    QHBoxLayout *h_box_bm = new QHBoxLayout (m_filter_widget);
+    h_box_bm->addWidget (filter_label);
+    h_box_bm->addWidget (m_filter_checkbox);
+    h_box_bm->addWidget (m_filter);
+    h_box_bm->setMargin (2);
+    m_filter_widget->setLayout (h_box_bm);
+
+    m_filter_shown = settings->value (dc_bookmark_filter_shown).toBool ();
+    m_filter_widget->setVisible (m_filter_shown);
+
+    // Resulting Layout of this widget
+    QVBoxLayout *v_box_bm = new QVBoxLayout (this);
+    v_box_bm->addWidget (m_filter_widget);
+    v_box_bm->addWidget (m_tree);
+    setLayout (v_box_bm);
+  }
+
+  documentation_bookmarks::~documentation_bookmarks (void)
+  { }
+
+  // Slot for adding the current page as a bookmark
+  void documentation_bookmarks::add_bookmark (void)
+  {
+    QUrl url = m_browser->historyUrl (0);
+
+    // Check if bookmark already exists and select if yes
+    QTreeWidgetItemIterator it (m_tree);
+    while (*it)
+      {
+        QUrl url_i = (*it)->data (0, url_role).toUrl ();
+        if (url == url_i)
+          {
+            m_tree->setCurrentItem (*it);
+            (*it)->setExpanded (true);
+            return;
+          }
+        it++;
+      }
+
+    // Add the anchor name to the title of the page and add the bookmark
+    // as top-level-item
+    QString title = m_doc->title_and_anchor (m_browser->historyTitle (0), url);
+    add_bookmark (title, url.toString ());
+  }
+
+  // Function for actually adding a bookmark to the tree
+  void documentation_bookmarks::add_bookmark (const QString& title,
+                                              const QString& url,
+                                              QTreeWidgetItem* item)
+  {
+    // Create new bookmark
+    QTreeWidgetItem *new_item = new QTreeWidgetItem (QStringList (title));
+    new_item->setData (0, tag_role, QVariant (bookmark_tag));
+    new_item->setData (0, url_role, QVariant (url));
+    new_item->setFlags ((new_item->flags () & (~Qt::ItemIsDropEnabled))
+                                            | Qt::ItemIsEditable
+                                            | Qt::ItemIsDragEnabled);
+    new_item->setIcon (0, icon_bookmark);
+
+    // Insert as top level or child item
+    // TODO: Open dialog allowing to select a target folder if this
+    //       bookmark is added manually and not by reading a bookmark file
+    if (item)
+      item->addChild (new_item);
+    else
+      m_tree->addTopLevelItem (new_item);
+  }
+
+  // Slot for adding a folder from the context menu
+  void documentation_bookmarks::add_folder (bool)
+  {
+    QTreeWidgetItem *parent_item = nullptr;
+
+    if (m_ctx_menu_item)
+      {
+        if (m_ctx_menu_item->data (0, tag_role).toInt () == folder_tag)
+          parent_item = m_ctx_menu_item;
+        else
+          {
+            QTreeWidgetItem *p = m_ctx_menu_item->parent ();
+            if (p)
+              parent_item = p;
+          }
+      }
+
+    QTreeWidgetItem *new_folder = add_folder (tr ("New Folder"), parent_item);
+
+    m_tree->setCurrentItem (new_folder);
+    m_tree->editItem (new_folder);
+  }
+
+  // Function for actually adding a folder to the tree
+  QTreeWidgetItem* documentation_bookmarks::add_folder (const QString& folder,
+                                            QTreeWidgetItem *item, bool expanded)
+  {
+    QTreeWidgetItem *new_folder = new QTreeWidgetItem (QStringList (folder));
+    new_folder->setData (0, tag_role, QVariant (folder_tag));
+    new_folder->setFlags (new_folder->flags() | Qt::ItemIsEditable
+                                              | Qt::ItemIsDragEnabled
+                                              | Qt::ItemIsDropEnabled);
+    new_folder->setChildIndicatorPolicy (QTreeWidgetItem::DontShowIndicatorWhenChildless);
+    new_folder->setIcon (0, icon_folder);
+    new_folder->setExpanded (expanded);
+
+    // Insert as top level or child item
+    if (item)
+      item->addChild (new_folder);
+    else
+      m_tree->addTopLevelItem (new_folder);
+
+    return new_folder;
+  }
+
+  void documentation_bookmarks::filter_bookmarks (const QString& pattern)
+  {
+    QTreeWidgetItemIterator it (m_tree);
+
+    while (*it)
+      {
+        if ((*it)->text (0).contains (pattern, Qt::CaseInsensitive))
+          {
+            (*it)->setHidden (false);
+            (*it)->setExpanded (true);
+            QTreeWidgetItem *p = (*it)->parent ();
+            while (p)
+              {
+                p->setHidden (false);
+                p->setExpanded (true);
+                p = p->parent ();
+              }
+          }
+        else
+            (*it)->setHidden (true);
+
+        it++;
+      }
+  }
+
+  void documentation_bookmarks::filter_activate (bool state)
+  {
+    m_filter->setEnabled (state);
+
+    QString pattern;
+    if (state)
+      pattern = m_filter->currentText ();
+
+    filter_bookmarks (pattern);
+  }
+
+  void documentation_bookmarks::update_filter_history (void)
+  {
+    QString text = m_filter->currentText ();   // get current text
+    int index = m_filter->findText (text);     // and its actual index
+
+    if (index > -1)
+      m_filter->removeItem (index);    // remove if already existing
+
+    m_filter->insertItem (0, text);    // (re)insert at beginning
+    m_filter->setCurrentIndex (0);
+  }
+
+  void documentation_bookmarks::handle_double_click (QTreeWidgetItem *item, int)
+  {
+    int tag = item->data (0, tag_role).toInt ();
+
+    if (tag == folder_tag)
+      {
+        item->setExpanded (! item->isExpanded ());
+        return;
+      }
+
+    if (tag == bookmark_tag)
+      {
+        QUrl url = item->data (0, url_role).toUrl ();
+        if (! url.isEmpty ())
+          m_browser->setSource (url);
+        return;
+      }
+  }
+
+  void documentation_bookmarks::ctx_menu (const QPoint& xpos)
+  {
+    QMenu menu (this);
+
+    m_ctx_menu_item = m_tree->itemAt (xpos);
+
+    if (m_ctx_menu_item)
+      {
+        resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
+
+        menu.addAction (tr ("&Open"), this, SLOT (open (bool)));
+        menu.addAction (tr ("&Rename"), this, SLOT (edit (bool)));
+        menu.addAction (rmgr.icon ("window-close"), tr ("Remo&ve"), this,
+                        SLOT (remove (bool)));
+        menu.addSeparator ();
+      }
+
+    menu.addAction (tr ("&Add Folder"), this, SLOT (add_folder (bool)));
+
+    menu.addSeparator ();
+
+    if (m_filter_shown)
+      menu.addAction (tr ("Hide &Filter"), this, SLOT (show_filter (bool)));
+    else
+      menu.addAction (tr ("Show &Filter"), this, SLOT (show_filter (bool)));
+
+    menu.exec (m_tree->mapToGlobal (xpos));
+  }
+
+  void documentation_bookmarks::open (bool)
+  {
+    QList<QTreeWidgetItem *> items = m_tree->selectedItems ();
+
+    if (items.size () > 0)
+      handle_double_click (items.at (0));
+  }
+
+  void documentation_bookmarks::edit (bool)
+  {
+    QList<QTreeWidgetItem *> items = m_tree->selectedItems ();
+
+    if (items.size () > 0)
+      m_tree->editItem (items.at (0));
+  }
+
+  void documentation_bookmarks::remove (bool)
+  {
+    QList<QTreeWidgetItem *> items = m_tree->selectedItems ();
+
+    for (auto it = items.begin () ; it != items.end (); it++)
+      {
+        if (*it)
+          m_tree->takeTopLevelItem (
+                    m_tree->indexOfTopLevelItem (*it));
+      }
+  }
+
+  void documentation_bookmarks::show_filter (bool)
+  {
+    m_filter_shown = ! m_filter_shown;
+    m_filter_widget->setVisible (m_filter_shown);
+  }
+
+  void documentation_bookmarks::save_settings (void)
+  {
+    // Write the bookmarks to the xbel-file
+    write_bookmarks ();
+
+    // Store settings
+    resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
+    gui_settings *settings = rmgr.get_settings ();
+
+    settings->setValue (dc_bookmark_filter_active.key, m_filter_checkbox->isChecked ());
+    settings->setValue (dc_bookmark_filter_shown.key, m_filter_shown);
+
+    QStringList mru;
+    for (int i = 0; i < m_filter->count (); i++)
+      mru.append (m_filter->itemText (i));
+    settings->setValue (dc_bookmark_filter_mru.key, mru);
+
+    settings->sync ();
+  }
+
+  void documentation_bookmarks::write_bookmarks (void)
+  {
+    if (! m_xbel_file.open (QFile::WriteOnly | QFile::Text))
+      {
+        QMessageBox::warning (this, tr("Octave: Saving Documentation Bookmarks"),
+                              tr("Unable to write file %1:\n%2.\n\n"
+                                 "Documentation bookmarks are not saved!\n")
+                                .arg (m_xbel_file.fileName ())
+                                .arg (m_xbel_file.errorString()));
+        return;
+      }
+
+    QXmlStreamWriter xml_writer (&m_xbel_file);
+    xml_writer.setAutoFormatting (true);
+
+    xml_writer.writeStartDocument ();
+    xml_writer.writeDTD (dc_xbel_doctype);
+    xml_writer.writeStartElement (dc_xbel_name_format);
+    xml_writer.writeAttribute (dc_xbel_attr_version, dc_xbel_value_version);
+
+    for (int i = 0; i < m_tree->topLevelItemCount(); i++)
+      write_tree_item (&xml_writer, m_tree->topLevelItem (i));
+
+    xml_writer.writeEndDocument();
+
+    m_xbel_file.flush ();
+    m_xbel_file.close ();
+  }
+
+  void documentation_bookmarks::write_tree_item (QXmlStreamWriter* xml_writer,
+                                                 const QTreeWidgetItem *item)
+  {
+    switch (item->data (0, tag_role).toInt ())
+      {
+        case folder_tag:
+          xml_writer->writeStartElement (dc_xbel_name_folder);
+          xml_writer->writeAttribute (dc_xbel_attr_folded,
+                    item->isExpanded () ? dc_xbel_value_no : dc_xbel_value_yes);
+          xml_writer->writeTextElement (dc_xbel_name_title, item->text(0));
+          for (int i = 0; i < item->childCount (); i++)
+            write_tree_item (xml_writer, item->child (i));
+          xml_writer->writeEndElement ();
+          break;
+
+        case bookmark_tag:
+          xml_writer->writeStartElement (dc_xbel_name_bookmark);
+          xml_writer->writeAttribute (dc_xbel_attr_href, item->data (0, url_role).toString ());
+          xml_writer->writeTextElement (dc_xbel_name_title, item->text (0));
+          xml_writer->writeEndElement ();
+          break;
+      }
+  }
+
+  QString documentation_bookmarks::read_bookmarks (void)
+  {
+    QString error_message;
+
+    // Check the file
+    if (! m_xbel_file.open (QFile::ReadOnly | QFile::Text))
+      {
+        error_message = tr ("Unable to read file %1:\n%2.")
+                            .arg (m_xbel_file.fileName ())
+                            .arg (m_xbel_file.errorString());
+        return error_message;
+      }
+
+    QXmlStreamReader xml_reader (&m_xbel_file);
+
+    if (! xml_reader.readNextStartElement ())
+      {
+        error_message = tr ("No start element found in %1.\n"
+                            "Invalid bookmark file?")
+                            .arg (m_xbel_file.fileName ());
+        return error_message;
+      }
+
+    if (xml_reader.name() != dc_xbel_name_format
+        || xml_reader.attributes ().value (dc_xbel_attr_version) != dc_xbel_value_version)
+      {
+        error_message = tr ("The file\n"
+                            "%1\n"
+                            "is not a valid XBEL file verison 1.0.")
+                            .arg (m_xbel_file.fileName ());
+        return error_message;
+      }
+
+    // Read the elements from the file
+    while (xml_reader.readNextStartElement ())
+      {
+        if (xml_reader.name () == dc_xbel_name_folder)
+          read_next_item (&xml_reader, folder_tag);
+        else if (xml_reader.name () == dc_xbel_name_bookmark)
+          read_next_item (&xml_reader, bookmark_tag);
+        else
+          xml_reader.skipCurrentElement ();
+      }
+
+     m_xbel_file.close ();
+
+     return error_message;
+  }
+
+  void documentation_bookmarks::read_next_item (QXmlStreamReader *xml_reader,
+                                                item_tag tag, QTreeWidgetItem *item)
+  {
+    QString title (tr ("Unknown title"));
+    if (tag == folder_tag)
+      {
+        // Next item is a folder, which might also have children
+        bool expanded = (xml_reader->attributes().value (dc_xbel_attr_folded) == dc_xbel_value_no);
+
+        QTreeWidgetItem *new_folder = add_folder (title, item, expanded);
+
+        // Check elements of this folder for title and for recursively
+        // adding sub-items
+        while (xml_reader->readNextStartElement ())
+          {
+            if (xml_reader->name () == dc_xbel_name_title)
+              {
+                title = xml_reader->readElementText();
+                new_folder->setText (0, title);
+              }
+            else if (xml_reader->name () == dc_xbel_name_folder)
+              read_next_item (xml_reader, folder_tag, new_folder);
+            else if (xml_reader->name () == dc_xbel_name_bookmark)
+              read_next_item (xml_reader, bookmark_tag, new_folder);
+            else
+              xml_reader->skipCurrentElement ();
+          }
+      }
+    else if (tag == bookmark_tag)
+      {
+        // Next item is a bookmark, without children
+        QString url = xml_reader->attributes().value (dc_xbel_attr_href).toString ();
+        while (xml_reader->readNextStartElement ())
+          {
+            if (xml_reader->name() == dc_xbel_name_title)
+              title = xml_reader->readElementText();
+            else
+              xml_reader->skipCurrentElement ();
+          }
+        add_bookmark (title, url, item);
+      }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/documentation-bookmarks.h	Sun Jan 10 14:04:35 2021 +0100
@@ -0,0 +1,124 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2018-2020 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Octave is distributed in the hope that it will be useful, but
+// WiTHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if ! defined (octave_documentation_bookmarks_h)
+#define octave_documentation_bookmarks_h 1
+
+#include <QCheckBox>
+#include <QComboBox>
+#include <QTreeWidget>
+#include <QXmlStreamWriter>
+
+#include "documentation.h"
+#include "octave-qobject.h"
+
+
+namespace octave
+{
+  class base_qobject;
+  class documentation;
+
+  class documentation_bookmarks : public QWidget
+  {
+    Q_OBJECT
+
+  public:
+
+    documentation_bookmarks (
+                documentation *doc, documentation_browser *browser,
+                base_qobject& oct_qobj, QWidget *p = nullptr);
+    ~documentation_bookmarks (void);
+
+  public slots:
+
+    void add_bookmark (void);
+    void add_folder (bool);
+    void save_settings (void);
+
+  private slots:
+
+    void filter_bookmarks (const QString& pattern);
+    void filter_activate (bool state);
+    void update_filter_history (void);
+    void handle_double_click (QTreeWidgetItem *item, int col = 0);
+    void ctx_menu (const QPoint& xpos);
+    void open (bool);
+    void edit (bool);
+    void remove (bool);
+    void show_filter (bool);
+
+  private:
+
+    enum item_role
+    {
+      url_role = Qt::UserRole,
+      tag_role = Qt::UserRole + 1
+    };
+    enum item_tag
+    {
+      bookmark_tag,
+      folder_tag
+    };
+
+    void add_bookmark (const QString& title, const QString& url,
+                       QTreeWidgetItem *item = nullptr);
+    QTreeWidgetItem* add_folder (const QString& folder,
+                                 QTreeWidgetItem *item = nullptr,
+                                 bool expanded = true);
+
+    /*!
+        Writing to and reading bookmarks from an xbel-file as
+        proposed in the qt example
+        [QXmlStream Bookmarks Example](https://doc.qt.io/qt-5/qtxml-streambookmarks-example.html)
+    */
+    void write_bookmarks (void);
+    void write_tree_item (QXmlStreamWriter *xml_writer,
+                          const QTreeWidgetItem *item);
+    QString read_bookmarks (void);
+    void read_next_item (QXmlStreamReader *xml_writer, item_tag tag,
+                         QTreeWidgetItem *item = nullptr);
+
+    documentation *m_doc;
+    documentation_browser *m_browser;
+    base_qobject& m_octave_qobj;
+
+    QComboBox *m_filter;
+    QTreeWidget *m_tree;
+
+    QTreeWidgetItem *m_ctx_menu_item;
+
+    QIcon icon_folder;
+    QIcon icon_bookmark;
+
+    QWidget *m_filter_widget;
+    QCheckBox *m_filter_checkbox;
+    bool m_filter_shown;
+
+    QFile m_xbel_file;
+  };
+
+}
+
+#endif
--- a/libgui/src/documentation-dock-widget.cc	Tue Jan 26 16:41:36 2021 +0100
+++ b/libgui/src/documentation-dock-widget.cc	Sun Jan 10 14:04:35 2021 +0100
@@ -65,6 +65,12 @@
     m_docs->notice_settings (settings);
   }
 
+  void documentation_dock_widget::save_settings (void)
+  {
+    emit save_settings_signal ();
+    octave_dock_widget::save_settings ();
+  }
+
   void documentation_dock_widget::copyClipboard (void)
   {
     m_docs->copyClipboard ();
--- a/libgui/src/documentation-dock-widget.h	Tue Jan 26 16:41:36 2021 +0100
+++ b/libgui/src/documentation-dock-widget.h	Sun Jan 10 14:04:35 2021 +0100
@@ -42,9 +42,14 @@
     documentation_dock_widget (QWidget *parent, base_qobject& oct_qobj);
     ~documentation_dock_widget (void);
 
+  signals:
+
+    void save_settings_signal (void);
+
   public slots:
 
     void notice_settings (const gui_settings *settings);
+    void save_settings (void);
 
   protected slots:
 
--- a/libgui/src/documentation.cc	Tue Jan 26 16:41:36 2021 +0100
+++ b/libgui/src/documentation.cc	Sun Jan 10 14:04:35 2021 +0100
@@ -50,6 +50,7 @@
 #include <QVBoxLayout>
 
 #include "documentation.h"
+#include "documentation-bookmarks.h"
 #include "gui-preferences-global.h"
 #include "gui-preferences-sc.h"
 #include "octave-qobject.h"
@@ -253,6 +254,16 @@
         connect (m_filter->lineEdit (), SIGNAL (editingFinished (void)),
                  this, SLOT(filter_update_history (void)));
 
+        // Bookmarks (own class)
+        documentation_bookmarks *bookmarks
+          = new documentation_bookmarks (this, m_doc_browser, m_octave_qobj, navi);
+        navi->addTab (bookmarks, tr ("Bookmarks"));
+
+        connect (m_action_bookmark, SIGNAL (triggered ()),
+                 bookmarks, SLOT (add_bookmark ()));
+        connect (p, SIGNAL (save_settings_signal (void)),
+                 bookmarks, SLOT (save_settings (void)));
+
         // Search
         QHelpSearchEngine *search_engine = m_help_engine->searchEngine ();
         QHelpSearchQueryWidget *search = search_engine->queryWidget ();
@@ -324,7 +335,9 @@
       r = receiver;
 
     a = new QAction (icon, text, this);
-    connect (a, SIGNAL (triggered ()), r, member);
+
+    if (member)
+      connect (a, SIGNAL (triggered ()), r, member);
 
     if (tool_bar)
       tool_bar->addAction (a);
@@ -414,6 +427,12 @@
     m_action_zoom_original
       = add_action (rmgr.icon ("zoom-original"), tr ("Zoom original"),
                     SLOT (zoom_original (void)), m_doc_browser, m_tool_bar);
+
+    // Bookmarks (connect slots later)
+    m_tool_bar->addSeparator ();
+    m_action_bookmark
+      = add_action (rmgr.icon ("bookmark-new"), tr ("Bookmark current page"),
+                    nullptr, nullptr, m_tool_bar);
   }
 
   void documentation::global_search (void)
@@ -613,6 +632,7 @@
     scmgr.set_shortcut (m_action_go_home, sc_doc_go_home);
     scmgr.set_shortcut (m_action_go_prev, sc_doc_go_back);
     scmgr.set_shortcut (m_action_go_next, sc_doc_go_next);
+    scmgr.set_shortcut (m_action_bookmark, sc_doc_bookmark);
   }
 
   void documentation::copyClipboard (void)
@@ -870,37 +890,9 @@
     // Fill used menu entries
     for (int i = 0; i < count; i++)
       {
-        QString title = m_doc_browser->historyTitle (prev_next*(i+1));
-        title.remove (QRegExp ("\\s*\\(*GNU Octave \\(version [^\\)]*\\)[: \\)]*"));
-
-        // Since the title only contains the section name and not the
-        // specific anchor, extract the latter from the url and append
-        // it to the title
-        QString url = m_doc_browser->historyUrl (prev_next*(i+1)).toString ();
-        if (url.contains ('#'))
-          {
-            // Get the anchor from the url
-            QString anchor = url.split ('#').last ();
-
-            // Remove internal string parts
-            anchor.remove (QRegExp ("^index-"));
-            anchor.remove (QRegExp ("^SEC_"));
-            anchor.remove (QRegExp ("^XREF"));
-            anchor.remove ("Concept-Index_cp_letter-");
-            anchor.replace ("-"," ");
-
-            // replace encoded special chars by their unencoded versions
-            QRegExp rx = QRegExp ("_00([0-7][0-9a-f])");
-            int pos = 0;
-            while ((pos = rx.indexIn(anchor, pos)) != -1)
-              {
-                anchor.replace ("_00"+rx.cap (1), QChar (rx.cap (1).toInt (nullptr,16)));
-                pos += rx.matchedLength();
-              }
-
-            if (title != anchor)
-              title = title + ": " + anchor;
-          }
+        QString title
+          = title_and_anchor (m_doc_browser->historyTitle (prev_next*(i+1)),
+                              m_doc_browser->historyUrl (prev_next*(i+1)));
 
         if (i == 0)
           a->setText (title); // set tool tip for prev/next buttons
@@ -924,8 +916,51 @@
     m_doc_browser->setSource (a->data ().toUrl ());
   }
 
+  // Utility functions
 
+  QString documentation::title_and_anchor (const QString& title, const QUrl& url)
+  {
+    QString retval = title;
+    QString u = url.toString ();
+
+    retval.remove (QRegExp ("\\s*\\(*GNU Octave \\(version [^\\)]*\\)[: \\)]*"));
+
+    // Since the title only contains the section name and not the
+    // specific anchor, extract the latter from the url and append
+    // it to the title
+    if (u.contains ('#'))
+      {
+        // Get the anchor from the url
+        QString anchor = u.split ('#').last ();
+        // Remove internal string parts
+        anchor.remove (QRegExp ("^index-"));
+        anchor.remove (QRegExp ("^SEC_"));
+        anchor.remove (QRegExp ("^XREF"));
+        anchor.remove ("Concept-Index_cp_letter-");
+        anchor.replace ("-"," ");
+
+        // replace encoded special chars by their unencoded versions
+        QRegExp rx = QRegExp ("_00([0-7][0-9a-f])");
+        int pos = 0;
+        while ((pos = rx.indexIn(anchor, pos)) != -1)
+          {
+            anchor.replace ("_00"+rx.cap (1), QChar (rx.cap (1).toInt (nullptr,16)));
+            pos += rx.matchedLength();
+          }
+
+        if (retval != anchor)
+          retval = retval + ": " + anchor;
+      }
+
+    return retval;
+  }
+
+
+
+  //
   // The documentation browser
+  //
+
   documentation_browser::documentation_browser (QHelpEngine *he, QWidget *p)
     : QTextBrowser (p), m_help_engine (he), m_zoom_level (0)
   {
--- a/libgui/src/documentation.h	Tue Jan 26 16:41:36 2021 +0100
+++ b/libgui/src/documentation.h	Sun Jan 10 14:04:35 2021 +0100
@@ -32,6 +32,7 @@
 #include <QSplitter>
 #include <QTextBrowser>
 #include <QToolBar>
+#include <QListWidget>
 #include <QToolButton>
 #include <QWidget>
 #include <QtHelp/QHelpEngine>
@@ -41,6 +42,7 @@
 namespace octave
 {
   class base_qobject;
+  class documentation;
 
   //! Documentation browser derived from Textbrowser
 
@@ -101,6 +103,17 @@
     documentation (QWidget *parent, base_qobject& oct_qobj);
     ~documentation (void);
 
+    /*!
+        Generate a string with page name @p title and current anchor
+        from @p url for using in prev/next or bookmarks menu:
+
+          @param title current title of the page as QString
+          @param url   current url  as QUrl
+
+          @return QString "title: anchor"
+    */
+    QString title_and_anchor (const QString& title, const QUrl& url);
+
   signals:
 
     void show_single_result (const QUrl&);
@@ -172,6 +185,8 @@
     QAction *m_prev_pages_actions[max_history_entries];
     QAction *m_next_pages_actions[max_history_entries];
 
+    QAction *m_action_bookmark;
+
     QAction *m_action_find;
     QShortcut *m_findnext_shortcut;
     QShortcut *m_findprev_shortcut;
@@ -180,6 +195,7 @@
     QAction *m_action_zoom_out;
     QAction *m_action_zoom_original;
   };
+
 }
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/gui-preferences-dc.h	Sun Jan 10 14:04:35 2021 +0100
@@ -0,0 +1,57 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2017-2020 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// This file is part of Octave.
+//
+// Octave is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Octave is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Octave; see the file COPYING.  If not, see
+// <https://www.gnu.org/licenses/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if ! defined (octave_gui_preferences_dc_h)
+#define octave_gui_preferences_dc_h 1
+
+#include "gui-preferences.h"
+
+// documentation properties
+
+const QString
+dc_bookmark_file ("octave-doc-bookmarks.xbel");
+const gui_pref
+dc_bookmark_filter_active ("documentation_widget/filter_active", QVariant (false));
+
+const gui_pref
+dc_bookmark_filter_shown ("documentation_widget/filter_shown", QVariant (true));
+
+const gui_pref
+dc_bookmark_filter_mru ("documentation_widget/bookmark_filter_mru", QVariant ());
+
+// Constants for the xbel file format
+const QLatin1String dc_xbel_doctype ("<!DOCTYPE xbel>");
+const QLatin1String dc_xbel_attr_href ("href");
+const QLatin1String dc_xbel_attr_folded ("folded");
+const QLatin1String dc_xbel_attr_version ("version");
+const QLatin1String dc_xbel_value_version ("1.0");
+const QLatin1String dc_xbel_value_yes ("yes");
+const QLatin1String dc_xbel_value_no ("no");
+const QLatin1String dc_xbel_name_title ("title");
+const QLatin1String dc_xbel_name_folder ("folder");
+const QLatin1String dc_xbel_name_bookmark ("bookmark");
+const QLatin1String dc_xbel_name_format ("xbel");
+
+#endif
--- a/libgui/src/gui-preferences-sc.h	Tue Jan 26 16:41:36 2021 +0100
+++ b/libgui/src/gui-preferences-sc.h	Sun Jan 10 14:04:35 2021 +0100
@@ -255,6 +255,7 @@
 const sc_pref sc_doc_go_home (sc_doc + ":go_home", Qt::AltModifier + Qt::Key_Home);
 const sc_pref sc_doc_go_back (sc_doc + ":go_back", QKeySequence::Back);
 const sc_pref sc_doc_go_next (sc_doc + ":go_next", QKeySequence::Forward);
+const sc_pref sc_doc_bookmark (sc_doc + ":bookmark", CTRL + Qt::Key_D);
 
 
 // Other normal, shortcut related options
Binary file libgui/src/icons/bookmark-new.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/icons/bookmark-new.svg	Sun Jan 10 14:04:35 2021 +0100
@@ -0,0 +1,672 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:export-ydpi="240.00000"
+   inkscape:export-xdpi="240.00000"
+   inkscape:export-filename="/home/jimmac/gfx/novell/pdes/trunk/docs/BIGmime-text.png"
+   sodipodi:docname="bookmark-new.svg"
+   sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/actions"
+   inkscape:version="0.46"
+   sodipodi:version="0.32"
+   id="svg249"
+   height="48.000000px"
+   width="48.000000px"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs3">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 24 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="48 : 24 : 1"
+       inkscape:persp3d-origin="24 : 16 : 1"
+       id="perspective100" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060"
+       id="radialGradient5031"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient5060">
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0"
+         id="stop5062" />
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="1"
+         id="stop5064" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5060"
+       id="radialGradient5029"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
+       cx="605.71429"
+       cy="486.64789"
+       fx="605.71429"
+       fy="486.64789"
+       r="117.14286" />
+    <linearGradient
+       id="linearGradient5048">
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="0"
+         id="stop5050" />
+      <stop
+         id="stop5056"
+         offset="0.5"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="1"
+         id="stop5052" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5048"
+       id="linearGradient5027"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
+       x1="302.85715"
+       y1="366.64789"
+       x2="302.85715"
+       y2="609.50507" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient2906">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop2908" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop2910" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient2896">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop2898" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop2900" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient2598">
+      <stop
+         style="stop-color:#859dbc;stop-opacity:1;"
+         offset="0"
+         id="stop2600" />
+      <stop
+         style="stop-color:#547299;stop-opacity:1;"
+         offset="1"
+         id="stop2602" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient2590">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop2592" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop2594" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5897">
+      <stop
+         style="stop-color:#000000;stop-opacity:0.0000000;"
+         offset="0.0000000"
+         id="stop5899" />
+      <stop
+         id="stop5905"
+         offset="0.50000000"
+         style="stop-color:#000000;stop-opacity:0.56701028;" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0.0000000;"
+         offset="1.0000000"
+         id="stop5901" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient5866">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop5868" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop5870" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4404">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop4406" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop4408" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4542">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop4544" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop4546" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient15662">
+      <stop
+         id="stop15664"
+         offset="0.0000000"
+         style="stop-color:#ffffff;stop-opacity:1.0000000;" />
+      <stop
+         id="stop15666"
+         offset="1.0000000"
+         style="stop-color:#f8f8f8;stop-opacity:1.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient269">
+      <stop
+         id="stop270"
+         offset="0.0000000"
+         style="stop-color:#a3a3a3;stop-opacity:1.0000000;" />
+      <stop
+         id="stop271"
+         offset="1.0000000"
+         style="stop-color:#4c4c4c;stop-opacity:1.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient259">
+      <stop
+         id="stop260"
+         offset="0.0000000"
+         style="stop-color:#fafafa;stop-opacity:1.0000000;" />
+      <stop
+         id="stop261"
+         offset="1.0000000"
+         style="stop-color:#bbbbbb;stop-opacity:1.0000000;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient12512">
+      <stop
+         id="stop12513"
+         offset="0.0000000"
+         style="stop-color:#ffffff;stop-opacity:1.0000000;" />
+      <stop
+         id="stop12517"
+         offset="0.50000000"
+         style="stop-color:#fff520;stop-opacity:0.89108908;" />
+      <stop
+         id="stop12514"
+         offset="1.0000000"
+         style="stop-color:#fff300;stop-opacity:0.0000000;" />
+    </linearGradient>
+    <radialGradient
+       r="14.375000"
+       fy="125.00000"
+       fx="55.000000"
+       cy="125.00000"
+       cx="55.000000"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient278"
+       xlink:href="#linearGradient12512"
+       inkscape:collect="always" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient269"
+       id="radialGradient15656"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.968273,0.000000,0.000000,1.036374,3.250000,0.489522)"
+       cx="8.8244190"
+       cy="3.7561285"
+       fx="8.8244190"
+       fy="3.7561285"
+       r="37.751713" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient259"
+       id="radialGradient15658"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.960493,0.000000,0.000000,1.044769,-0.103553,-0.159183)"
+       cx="33.966679"
+       cy="35.736916"
+       fx="33.966679"
+       fy="35.736916"
+       r="86.708450" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient15662"
+       id="radialGradient15668"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.973033,0.000000,0.000000,1.034937,3.168754,0.555277)"
+       cx="8.1435566"
+       cy="7.2678967"
+       fx="8.1435566"
+       fy="7.2678967"
+       r="38.158695" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4542"
+       id="radialGradient4548"
+       cx="24.306795"
+       cy="42.07798"
+       fx="24.306795"
+       fy="42.07798"
+       r="15.821514"
+       gradientTransform="matrix(1.000000,0.000000,0.000000,0.284916,0.000000,30.08928)"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4404"
+       id="linearGradient4410"
+       x1="16.812500"
+       y1="1.8750000"
+       x2="16.812500"
+       y2="4.7187500"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1.319549,0.000000,0.000000,1.362060,40.38853,-0.362057)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5866"
+       id="linearGradient5872"
+       x1="19.452349"
+       y1="13.174174"
+       x2="19.685436"
+       y2="27.095339"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.224255,0.000000,0.000000,1.282176,0.371569,0.264657)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient5897"
+       id="linearGradient5903"
+       x1="19.000000"
+       y1="9.7738247"
+       x2="19.000000"
+       y2="15.635596"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.319549,0.000000,0.000000,2.133926,-4.476133,-14.64845)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2590"
+       id="linearGradient2596"
+       x1="19.970377"
+       y1="6.1167107"
+       x2="19.970377"
+       y2="2.53125"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.319549,0.000000,0.000000,1.280356,-5.745298,0.249007)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2598"
+       id="linearGradient2604"
+       x1="18.431311"
+       y1="19.119474"
+       x2="18.402472"
+       y2="4.2702327"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.319549,0.000000,0.000000,1.299013,-3.106200,-1.336165)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2896"
+       id="linearGradient2902"
+       x1="14.584077"
+       y1="1.6392649"
+       x2="14.552828"
+       y2="2.4912448"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.000000,0.000000,0.000000,1.594214,0.000000,-0.790249)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2906"
+       id="linearGradient2912"
+       x1="13.354311"
+       y1="1.4866425"
+       x2="14.075844"
+       y2="2.4017651"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.000000,0.000000,0.000000,1.184816,0.000000,-0.727880)" />
+  </defs>
+  <sodipodi:namedview
+     inkscape:window-y="158"
+     inkscape:window-x="433"
+     inkscape:window-height="690"
+     inkscape:window-width="872"
+     inkscape:document-units="px"
+     inkscape:grid-bbox="true"
+     showgrid="false"
+     inkscape:current-layer="layer6"
+     inkscape:cy="16.785697"
+     inkscape:cx="-154.12746"
+     inkscape:zoom="1"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     borderopacity="0.25490196"
+     bordercolor="#666666"
+     pagecolor="#ffffff"
+     id="base"
+     inkscape:showpageshadow="false"
+     showguides="true"
+     inkscape:guide-bbox="true" />
+  <metadata
+     id="metadata4">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title>New Bookmark</dc:title>
+        <dc:subject>
+          <rdf:Bag>
+            <rdf:li>bookmark</rdf:li>
+            <rdf:li>remember</rdf:li>
+            <rdf:li>favorite</rdf:li>
+          </rdf:Bag>
+        </dc:subject>
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
+        <dc:creator>
+          <cc:Agent>
+            <dc:title>Andreas Nilsson</dc:title>
+          </cc:Agent>
+        </dc:creator>
+        <dc:source />
+        <dc:contributor>
+          <cc:Agent>
+            <dc:title>Jakub Steiner</dc:title>
+          </cc:Agent>
+        </dc:contributor>
+        <dc:description>create bookmark action</dc:description>
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/publicdomain/">
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Reproduction" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#Distribution" />
+        <cc:permits
+           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="Shadow">
+    <g
+       style="display:inline"
+       id="g5022"
+       transform="matrix(2.165152e-2,0,0,1.485743e-2,43.0076,42.68539)">
+      <rect
+         y="-150.69685"
+         x="-1559.2523"
+         height="478.35718"
+         width="1339.6335"
+         id="rect4173"
+         style="opacity:0.40206185;color:black;fill:url(#linearGradient5027);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path5058"
+         d="M -219.61876,-150.68038 C -219.61876,-150.68038 -219.61876,327.65041 -219.61876,327.65041 C -76.744594,328.55086 125.78146,220.48075 125.78138,88.454235 C 125.78138,-43.572302 -33.655436,-150.68036 -219.61876,-150.68038 z "
+         style="opacity:0.40206185;color:black;fill:url(#radialGradient5029);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+      <path
+         style="opacity:0.40206185;color:black;fill:url(#radialGradient5031);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+         d="M -1559.2523,-150.68038 C -1559.2523,-150.68038 -1559.2523,327.65041 -1559.2523,327.65041 C -1702.1265,328.55086 -1904.6525,220.48075 -1904.6525,88.454235 C -1904.6525,-43.572302 -1745.2157,-150.68036 -1559.2523,-150.68038 z "
+         id="path5018"
+         sodipodi:nodetypes="cccc" />
+    </g>
+  </g>
+  <g
+     style="display:inline"
+     inkscape:groupmode="layer"
+     inkscape:label="Base"
+     id="layer1">
+    <rect
+       style="color:#000000;fill:url(#radialGradient15658);fill-opacity:1.0000000;fill-rule:nonzero;stroke:url(#radialGradient15656);stroke-width:0.99999982;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:block;overflow:visible"
+       id="rect15391"
+       width="34.875000"
+       height="41.063431"
+       x="6.5000000"
+       y="3.5000000"
+       ry="1.1490481"
+       rx="1.1490486" />
+    <rect
+       style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:nonzero;stroke:url(#radialGradient15668);stroke-width:0.99999958;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:block;overflow:visible"
+       id="rect15660"
+       width="32.937012"
+       height="39.028210"
+       x="7.5024552"
+       y="4.5010486"
+       ry="0.14904849"
+       rx="0.14904852" />
+    <path
+       style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#000000;stroke-width:0.98855311;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.017543854"
+       d="M 11.505723,5.4942766 L 11.505723,43.400869"
+       id="path15672"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.0000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.20467831"
+       d="M 12.500000,5.0205154 L 12.500000,43.038228"
+       id="path15674"
+       sodipodi:nodetypes="cc" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer5"
+     inkscape:label="Text"
+     style="display:inline">
+    <g
+       id="g2188">
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15686"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999994"
+         y="9.0000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15688"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999994"
+         y="11.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15690"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999994"
+         y="13.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15692"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999994"
+         y="15.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15694"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999994"
+         y="17.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15696"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999994"
+         y="19.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15698"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999994"
+         y="21.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15700"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999994"
+         y="23.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15732"
+         width="9.0000057"
+         height="1.0000000"
+         x="15.999986"
+         y="25.000000"
+         rx="0.062003858"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15736"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999986"
+         y="29.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15738"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999986"
+         y="31.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15740"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999986"
+         y="33.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15742"
+         width="20.000006"
+         height="1.0000000"
+         x="15.999986"
+         y="35.000000"
+         rx="0.13778631"
+         ry="0.065390877" />
+      <rect
+         style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;visibility:visible;display:block;overflow:visible"
+         id="rect15744"
+         width="14.000014"
+         height="1.0000000"
+         x="15.999986"
+         y="37.000000"
+         rx="0.096450485"
+         ry="0.065390877" />
+    </g>
+    <path
+       style="opacity:0.28021976;fill:url(#linearGradient5872);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 28.245858,31.324906 L 21.147869,27.133701 L 14.30757,30.8838 L 13.761859,3.9475667 L 28.549598,3.9475667 L 28.245858,31.324906 z "
+       id="path5138"
+       sodipodi:nodetypes="cccccc" />
+    <path
+       style="fill:url(#linearGradient2604);fill-opacity:1;fill-rule:evenodd;stroke:#364878;stroke-width:0.99999988;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;display:inline"
+       d="M 12.427339,3.5180202 C 12.427339,3.5180202 12.240033,0.60520607 15.107867,0.54270607 L 25.119343,0.50728624 C 26.277287,0.50728624 26.581888,1.1910178 26.581888,2.1095589 L 26.581888,29.729916 L 20.545426,24.533862 L 14.674346,29.729916 L 14.591655,3.519629 L 12.427339,3.5180202 z "
+       id="path2204"
+       sodipodi:nodetypes="ccccccccc" />
+    <path
+       style="opacity:0.4450549;fill:url(#linearGradient4410);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 13.030252,3.0117919 C 13.011046,2.225362 13.312918,1.0801307 15.375418,1.0176307 L 25.027906,1 C 25.640922,1 26.090152,1.1674319 26.090152,1.7994802 L 26.060994,10.491851 L 15.317102,10.491851 L 15.192102,2.9993251 C 15.192102,2.9993251 13.030252,3.0117919 13.030252,3.0117919 z "
+       id="path3668"
+       sodipodi:nodetypes="cccccccs" />
+    <rect
+       style="opacity:0.28021976;fill:url(#linearGradient5903);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect5895"
+       width="10.556392"
+       height="12.803556"
+       x="15.317101"
+       y="6.6907959"
+       rx="0.062003858"
+       ry="0.065390877" />
+    <path
+       style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2596);stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.19125683;display:inline"
+       d="M 24.476832,2.2095507 L 25.575535,3.113139 L 25.547445,27.511911 L 20.497463,23.203758 L 15.704084,27.415203 L 15.699081,2.7495618 L 24.476832,2.2095507 z "
+       id="path5969"
+       sodipodi:nodetypes="ccccccc" />
+    <path
+       sodipodi:type="arc"
+       style="color:#000000;fill:url(#radialGradient278);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.25000024;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:block"
+       id="path12511"
+       sodipodi:cx="55"
+       sodipodi:cy="125"
+       sodipodi:rx="14.375"
+       sodipodi:ry="14.375"
+       d="M 69.375 125 A 14.375 14.375 0 1 1  40.625,125 A 14.375 14.375 0 1 1  69.375 125 z"
+       transform="matrix(0.611127,0.000000,0.000000,0.611127,5.632438,-67.28175)"
+       inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/stock_new-16.png"
+       inkscape:export-xdpi="33.852203"
+       inkscape:export-ydpi="33.852203" />
+    <path
+       style="opacity:0.48295456;color:#000000;fill:url(#linearGradient2912);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.10533953;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+       d="M 15.158602,3.9384083 L 15.114407,1.0335178 C 12.983906,1.0335178 12.993087,2.9680775 12.993087,3.9384083 L 15.158602,3.9384083 z "
+       id="path2894"
+       sodipodi:nodetypes="cccc" />
+    <path
+       sodipodi:nodetypes="cccc"
+       id="path2904"
+       d="M 15.158602,3.9384086 L 15.114407,1.8247593 C 12.81631,1.8426926 12.993087,3.9384086 12.993087,3.9384086 L 15.158602,3.9384086 z "
+       style="opacity:0.35795455;color:#000000;fill:url(#linearGradient2902);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.10533953;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+  </g>
+</svg>
--- a/libgui/src/icons/icons_license	Tue Jan 26 16:41:36 2021 +0100
+++ b/libgui/src/icons/icons_license	Sun Jan 10 14:04:35 2021 +0100
@@ -17,6 +17,7 @@
 ===========================================
 
 applications-system.svg
+bookmark-new.svg
 dialog-error.svg
 dialog-information.svg
 dialog-warning.svg
--- a/libgui/src/module.mk	Tue Jan 26 16:41:36 2021 +0100
+++ b/libgui/src/module.mk	Sun Jan 10 14:04:35 2021 +0100
@@ -135,6 +135,7 @@
   %reldir%/moc-dialog.cc \
   %reldir%/moc-documentation-dock-widget.cc \
   %reldir%/moc-documentation.cc \
+  %reldir%/moc-documentation-bookmarks.cc \
   %reldir%/moc-dw-main-window.cc \
   %reldir%/moc-files-dock-widget.cc \
   %reldir%/moc-gui-settings.cc \
@@ -187,9 +188,11 @@
   %reldir%/octave-dock-widget.h \
   %reldir%/documentation-dock-widget.h \
   %reldir%/documentation.h \
+  %reldir%/documentation-bookmarks.h \
   %reldir%/dw-main-window.h \
   %reldir%/gui-preferences-all.h \
   %reldir%/gui-preferences-cs.h \
+  %reldir%/gui-preferences-dc.h \
   %reldir%/gui-preferences-dw.h \
   %reldir%/gui-preferences-ed.h \
   %reldir%/gui-preferences-fb.h \
@@ -247,6 +250,7 @@
   %reldir%/dialog.cc \
   %reldir%/documentation-dock-widget.cc \
   %reldir%/documentation.cc \
+  %reldir%/documentation-bookmarks.cc \
   %reldir%/dw-main-window.cc \
   %reldir%/external-editor-interface.cc \
   %reldir%/files-dock-widget.cc \
--- a/libgui/src/resource.qrc	Tue Jan 26 16:41:36 2021 +0100
+++ b/libgui/src/resource.qrc	Sun Jan 10 14:04:35 2021 +0100
@@ -1,6 +1,7 @@
 <RCC>
     <qresource prefix="/actions">
         <file>icons/applications-system.png</file>
+        <file>icons/bookmark-new.png</file>
         <file>icons/bp-toggle.png</file>
         <file>icons/bp-rm-all.png</file>
         <file>icons/bp-prev.png</file>
--- a/libgui/src/shortcut-manager.cc	Tue Jan 26 16:41:36 2021 +0100
+++ b/libgui/src/shortcut-manager.cc	Sun Jan 10 14:04:35 2021 +0100
@@ -305,6 +305,7 @@
     init (tr ("Go to Homepage"), sc_doc_go_home);
     init (tr ("Go Back one Page"), sc_doc_go_back);
     init (tr ("Go Forward one Page"), sc_doc_go_next);
+    init (tr ("Bookmark this Page"), sc_doc_bookmark);
   }
 
   // write one or all actual shortcut set(s) into a settings file