Mercurial > octave
diff libgui/src/documentation-bookmarks.cc @ 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 | |
children | 7854d5752dd2 |
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); + } + } + +}