mirror of
https://github.com/RetroShare/RetroShare.git
synced 2025-04-29 11:26:09 -04:00
464 lines
15 KiB
C++
464 lines
15 KiB
C++
/*******************************************************************************
|
|
* retroshare-gui/src/gui/help/browser/helpbrowser.cpp *
|
|
* *
|
|
* Copyright (c) 2008, defnax <retroshare.project@gmail.com> *
|
|
* Copyright (c) 2008, Matt Edman, Justin Hipple *
|
|
* *
|
|
* This program is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the GNU Affero General Public License as *
|
|
* published by the Free Software Foundation, either version 3 of the *
|
|
* License, or (at your option) any later version. *
|
|
* *
|
|
* This program 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 Affero General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Affero General Public License *
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
|
* *
|
|
*******************************************************************************/
|
|
|
|
/*
|
|
** \file helpbrowser.cpp
|
|
** \version $Id: helpbrowser.cpp 2362 2008-02-29 04:30:11Z edmanm $
|
|
** \brief Displays a list of help topics and content
|
|
*/
|
|
|
|
#include <QDomDocument>
|
|
#include <QDir>
|
|
#include <rshare.h>
|
|
#include "gui/settings/rsharesettings.h"
|
|
|
|
#include "helpbrowser.h"
|
|
|
|
|
|
#define LEFT_PANE_INDEX 0
|
|
#define NO_STRETCH 0
|
|
#define MINIMUM_PANE_SIZE 1
|
|
|
|
/* Names of elements and attributes in the XML file */
|
|
#define ELEMENT_CONTENTS "Contents"
|
|
#define ELEMENT_TOPIC "Topic"
|
|
#define ATTRIBUTE_TOPIC_ID "id"
|
|
#define ATTRIBUTE_TOPIC_HTML "html"
|
|
#define ATTRIBUTE_TOPIC_NAME "name"
|
|
#define ATTRIBUTE_TOPIC_SECTION "section"
|
|
|
|
/* Define two roles used to store data associated with a topic item */
|
|
#define ROLE_TOPIC_ID Qt::UserRole
|
|
#define ROLE_TOPIC_QRC_PATH (Qt::UserRole+1)
|
|
|
|
static HelpBrowser *helpBrowser = NULL;
|
|
|
|
/** Constuctor. This will probably do more later */
|
|
HelpBrowser::HelpBrowser(QWidget *parent)
|
|
: RWindow("HelpBrowser", parent)
|
|
{
|
|
/* Invoke Qt Designer generated QObject setup routine */
|
|
ui.setupUi(this);
|
|
#if defined(Q_OS_MAC)
|
|
ui.actionHome->setShortcut(QString("Shift+Ctrl+H"));
|
|
#endif
|
|
#if !defined(Q_OS_WIN)
|
|
ui.actionClose->setShortcut(QString("Ctrl+W"));
|
|
#endif
|
|
|
|
helpBrowser = this;
|
|
|
|
setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
/* Hide Search frame */
|
|
ui.frmFind->setHidden(true);
|
|
|
|
/* Set the splitter pane sizes so that only the txtBrowser pane expands
|
|
* and set to arbitrary sizes (the minimum sizes will take effect */
|
|
QList<int> sizes;
|
|
sizes.append(MINIMUM_PANE_SIZE);
|
|
sizes.append(MINIMUM_PANE_SIZE);
|
|
ui.splitter->setSizes(sizes);
|
|
ui.splitter->setStretchFactor(LEFT_PANE_INDEX, NO_STRETCH);
|
|
|
|
connect(ui.treeContents,
|
|
SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
|
|
this, SLOT(contentsItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
|
|
|
|
connect(ui.treeSearch,
|
|
SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
|
|
this, SLOT(searchItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
|
|
|
|
/* Connect the navigation actions to their slots */
|
|
connect(ui.actionHome, SIGNAL(triggered()), ui.txtBrowser, SLOT(home()));
|
|
connect(ui.actionBack, SIGNAL(triggered()), ui.txtBrowser, SLOT(backward()));
|
|
connect(ui.actionForward, SIGNAL(triggered()), ui.txtBrowser, SLOT(forward()));
|
|
connect(ui.txtBrowser, SIGNAL(backwardAvailable(bool)),
|
|
ui.actionBack, SLOT(setEnabled(bool)));
|
|
connect(ui.txtBrowser, SIGNAL(forwardAvailable(bool)),
|
|
ui.actionForward, SLOT(setEnabled(bool)));
|
|
connect(ui.btnFindNext, SIGNAL(clicked()), this, SLOT(findNext()));
|
|
connect(ui.btnFindPrev, SIGNAL(clicked()), this, SLOT(findPrev()));
|
|
connect(ui.btnSearch, SIGNAL(clicked()), this, SLOT(search()));
|
|
|
|
/* Load the help topics from XML */
|
|
loadContentsFromXml(":/help/" + language() + "/contents.xml");
|
|
|
|
/* Show the first help topic in the tree */
|
|
ui.treeContents->setCurrentItem(ui.treeContents->topLevelItem(0));
|
|
ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
|
|
}
|
|
|
|
HelpBrowser::~HelpBrowser()
|
|
{
|
|
helpBrowser = NULL;
|
|
}
|
|
|
|
/** Returns the language in which help topics should appear, or English
|
|
* ("en") if no translated help files exist for the current GUI language. */
|
|
QString
|
|
HelpBrowser::language()
|
|
{
|
|
QString lang = RsApplication::language();
|
|
if (!QDir(":/help/" + lang).exists())
|
|
lang = "en";
|
|
return lang;
|
|
}
|
|
|
|
/** Load the contents of the help topics tree from the specified XML file. */
|
|
void
|
|
HelpBrowser::loadContentsFromXml(QString xmlFile)
|
|
{
|
|
QString errorString;
|
|
QFile file(xmlFile);
|
|
QDomDocument document;
|
|
|
|
/* Load the XML contents into the DOM document */
|
|
if (!document.setContent(&file, true, &errorString)) {
|
|
ui.txtBrowser->setPlainText(tr("Error Loading Help Contents:")+" "+errorString);
|
|
return;
|
|
}
|
|
/* Load the DOM document contents into the tree view */
|
|
if (!loadContents(&document, errorString)) {
|
|
ui.txtBrowser->setPlainText(tr("Error Loading Help Contents:")+" "+errorString);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/** Load the contents of the help topics tree from the given DOM document. */
|
|
bool
|
|
HelpBrowser::loadContents(const QDomDocument *document, QString &errorString)
|
|
{
|
|
/* Grab the root document element and make sure it's the right one */
|
|
QDomElement root = document->documentElement();
|
|
if (root.tagName() != ELEMENT_CONTENTS) {
|
|
errorString = tr("Supplied XML file is not a valid Contents document.");
|
|
return false;
|
|
}
|
|
_elementList << root;
|
|
|
|
/* Create the home item */
|
|
QTreeWidgetItem *home = createTopicTreeItem(root, 0);
|
|
ui.treeContents->addTopLevelItem(home);
|
|
|
|
/* Process all top-level help topics */
|
|
QDomElement child = root.firstChildElement(ELEMENT_TOPIC);
|
|
while (!child.isNull()) {
|
|
parseHelpTopic(child, home);
|
|
child = child.nextSiblingElement(ELEMENT_TOPIC);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Parse a Topic element and handle all its children recursively. */
|
|
void
|
|
HelpBrowser::parseHelpTopic(const QDomElement &topicElement,
|
|
QTreeWidgetItem *parent)
|
|
{
|
|
/* Check that we have a valid help topic */
|
|
if (isValidTopicElement(topicElement)) {
|
|
/* Save this element for later (used for searching) */
|
|
_elementList << topicElement;
|
|
|
|
/* Create and populate the new topic item in the tree */
|
|
QTreeWidgetItem *topic = createTopicTreeItem(topicElement, parent);
|
|
|
|
/* Process all its child elements */
|
|
QDomElement child = topicElement.firstChildElement(ELEMENT_TOPIC);
|
|
while (!child.isNull()) {
|
|
parseHelpTopic(child, topic);
|
|
child = child.nextSiblingElement(ELEMENT_TOPIC);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Returns true if the given Topic element has the necessary attributes. */
|
|
bool
|
|
HelpBrowser::isValidTopicElement(const QDomElement &topicElement)
|
|
{
|
|
return (topicElement.hasAttribute(ATTRIBUTE_TOPIC_ID) &&
|
|
topicElement.hasAttribute(ATTRIBUTE_TOPIC_NAME) &&
|
|
topicElement.hasAttribute(ATTRIBUTE_TOPIC_HTML));
|
|
}
|
|
|
|
/** Builds a resource path to an html file associated with the given help
|
|
* topic. If the help topic needs an achor, the anchor will be formatted and
|
|
* appended. */
|
|
QString
|
|
HelpBrowser::getResourcePath(const QDomElement &topicElement)
|
|
{
|
|
QString link = language() + "/" + topicElement.attribute(ATTRIBUTE_TOPIC_HTML);
|
|
if (topicElement.hasAttribute(ATTRIBUTE_TOPIC_SECTION)) {
|
|
link += "#" + topicElement.attribute(ATTRIBUTE_TOPIC_SECTION);
|
|
}
|
|
return link;
|
|
}
|
|
|
|
/** Creates a new element to be inserted into the topic tree. */
|
|
QTreeWidgetItem*
|
|
HelpBrowser::createTopicTreeItem(const QDomElement &topicElement,
|
|
QTreeWidgetItem *parent)
|
|
{
|
|
QTreeWidgetItem *topic = new QTreeWidgetItem(parent);
|
|
topic->setText(0, topicElement.attribute(ATTRIBUTE_TOPIC_NAME));
|
|
topic->setData(0, ROLE_TOPIC_ID, topicElement.attribute(ATTRIBUTE_TOPIC_ID));
|
|
topic->setData(0, ROLE_TOPIC_QRC_PATH, getResourcePath(topicElement));
|
|
return topic;
|
|
}
|
|
|
|
/** Called when the user selects a different item in the content topic tree */
|
|
void
|
|
HelpBrowser::contentsItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
|
|
{
|
|
QList<QTreeWidgetItem *> selected = ui.treeSearch->selectedItems();
|
|
/* Deselect the selection in the search tree */
|
|
if (!selected.isEmpty()) {
|
|
ui.treeSearch->setItemSelected(selected[0], false);
|
|
}
|
|
currentItemChanged(current, prev);
|
|
}
|
|
|
|
/** Called when the user selects a different item in the content topic tree */
|
|
void
|
|
HelpBrowser::searchItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
|
|
{
|
|
QList<QTreeWidgetItem *> selected = ui.treeContents->selectedItems();
|
|
/* Deselect the selection in the contents tree */
|
|
if (!selected.isEmpty()) {
|
|
ui.treeContents->setItemSelected(selected[0], false);
|
|
}
|
|
|
|
/* Change to selected page */
|
|
currentItemChanged(current, prev);
|
|
|
|
/* Highlight search phrase */
|
|
QTextCursor found;
|
|
QTextDocument::FindFlags flags = QTextDocument::FindWholeWords;
|
|
found = ui.txtBrowser->document()->find(_lastSearch, 0, flags);
|
|
if (!found.isNull()) {
|
|
ui.txtBrowser->setTextCursor(found);
|
|
}
|
|
}
|
|
|
|
/** Called when the user selects a different item in the tree. */
|
|
void
|
|
HelpBrowser::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
|
|
{
|
|
Q_UNUSED(prev);
|
|
if (current) {
|
|
ui.txtBrowser->setSource(QUrl(current->data(0,
|
|
ROLE_TOPIC_QRC_PATH).toString()));
|
|
}
|
|
_foundBefore = false;
|
|
}
|
|
|
|
/** Searches for a topic in the topic tree. Returns a pointer to that topics
|
|
* item in the topic tree if it is found, 0 otherwise. */
|
|
QTreeWidgetItem*
|
|
HelpBrowser::findTopicItem(QTreeWidgetItem *startItem, QString topic)
|
|
{
|
|
/* If startItem is null, then we don't know where to start searching. */
|
|
if (!startItem)
|
|
return 0;
|
|
|
|
/* Parse the first subtopic in the topic id. */
|
|
QString subtopic = topic.mid(0, topic.indexOf(".")).toLower();
|
|
|
|
/* Search through all children of startItem and look for a subtopic match */
|
|
for (int i = 0; i < startItem->childCount(); i++) {
|
|
QTreeWidgetItem *item = startItem->child(i);
|
|
|
|
if (subtopic == item->data(0, ROLE_TOPIC_ID).toString().toLower()) {
|
|
/* Found a subtopic match, so expand this item */
|
|
ui.treeContents->setItemExpanded(item, true);
|
|
if (!topic.contains(".")) {
|
|
/* Found the exact topic */
|
|
return item;
|
|
}
|
|
/* Search recursively for the next subtopic */
|
|
return findTopicItem(item, topic.mid(topic.indexOf(".")+1));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** Shows the help browser. If a sepcified topic was given, the search for
|
|
* that topic's ID (e.g., "log.basic") and display the appropriate page. */
|
|
void
|
|
HelpBrowser::showTopic(QString topic)
|
|
{
|
|
/* Search for the topic in the contents tree */
|
|
QTreeWidgetItem *item =
|
|
findTopicItem(ui.treeContents->topLevelItem(0), topic);
|
|
|
|
if (item) {
|
|
/* Item was found, so show its location in the hierarchy and select its
|
|
* tree item. */
|
|
QTreeWidgetItem* selected = ui.treeContents->selectedItems()[0];
|
|
if (selected) {
|
|
ui.treeContents->setItemSelected(selected, false);
|
|
}
|
|
ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
|
|
ui.treeContents->setItemSelected(item, true);
|
|
currentItemChanged(item, selected);
|
|
}
|
|
}
|
|
|
|
/** Called when the user clicks "Find Next". */
|
|
void
|
|
HelpBrowser::findNext()
|
|
{
|
|
find(true);
|
|
}
|
|
|
|
/** Called when the user clicks "Find Previous". */
|
|
void
|
|
HelpBrowser::findPrev()
|
|
{
|
|
find(false);
|
|
}
|
|
|
|
/** Searches the current page for the phrase in the Find box.
|
|
* Highlights the first instance found in the document
|
|
* \param forward true search forward if true, backward if false
|
|
**/
|
|
void
|
|
HelpBrowser::find(bool forward)
|
|
{
|
|
/* Don't bother searching if there is no search phrase */
|
|
if (ui.lineFind->text().isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QTextDocument::FindFlags flags = 0;
|
|
QTextCursor cursor = ui.txtBrowser->textCursor();
|
|
QString searchPhrase = ui.lineFind->text();
|
|
|
|
/* Clear status bar */
|
|
this->statusBar()->clearMessage();
|
|
|
|
/* Set search direction and other flags */
|
|
if (!forward) {
|
|
flags |= QTextDocument::FindBackward;
|
|
}
|
|
if (ui.chkbxMatchCase->isChecked()) {
|
|
flags |= QTextDocument::FindCaseSensitively;
|
|
}
|
|
if (ui.chkbxWholePhrase->isChecked()) {
|
|
flags |= QTextDocument::FindWholeWords;
|
|
}
|
|
|
|
/* Check if search phrase is the same as the previous */
|
|
if (searchPhrase != _lastFind) {
|
|
_foundBefore = false;
|
|
}
|
|
_lastFind = searchPhrase;
|
|
|
|
/* Set the cursor to the appropriate start location if necessary */
|
|
if (!cursor.hasSelection()) {
|
|
if (forward) {
|
|
cursor.movePosition(QTextCursor::Start);
|
|
} else {
|
|
cursor.movePosition(QTextCursor::End);
|
|
}
|
|
ui.txtBrowser->setTextCursor(cursor);
|
|
}
|
|
|
|
/* Search the page */
|
|
QTextCursor found;
|
|
found = ui.txtBrowser->document()->find(searchPhrase, cursor, flags);
|
|
|
|
/* If found, move the cursor to the location */
|
|
if (!found.isNull()) {
|
|
ui.txtBrowser->setTextCursor(found);
|
|
/* If not found, display appropriate error message */
|
|
} else {
|
|
if (_foundBefore) {
|
|
if (forward)
|
|
this->statusBar()->showMessage(tr("Search reached end of document"));
|
|
else
|
|
this->statusBar()->showMessage(tr("Search reached start of document"));
|
|
} else {
|
|
this->statusBar()->showMessage(tr("Text not found in document"));
|
|
}
|
|
}
|
|
|
|
/* Even if not found this time, may have been found previously */
|
|
_foundBefore |= !found.isNull();
|
|
}
|
|
|
|
/** Searches all help pages for the phrase the Search box.
|
|
* Fills treeSearch with documents containing matches and sets the
|
|
* status bar text appropriately.
|
|
*/
|
|
void
|
|
HelpBrowser::search()
|
|
{
|
|
/* Clear the list */
|
|
ui.treeSearch->clear();
|
|
|
|
/* Don't search if invalid document or blank search phrase */
|
|
if (ui.lineSearch->text().isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
HelpTextBrowser browser;
|
|
QTextCursor found;
|
|
QTextDocument::FindFlags flags = QTextDocument::FindWholeWords;
|
|
|
|
_lastSearch = ui.lineSearch->text();
|
|
|
|
/* Search through all the pages looking for the phrase */
|
|
for (int i=0; i < _elementList.size(); ++i) {
|
|
/* Load page data into browser */
|
|
browser.setSource(QUrl(getResourcePath(_elementList[i])));
|
|
|
|
/* Search current document */
|
|
found = browser.document()->find(ui.lineSearch->text(), 0, flags);
|
|
|
|
/* If found, add page to tree */
|
|
if (!found.isNull()) {
|
|
ui.treeSearch->addTopLevelItem(createTopicTreeItem(_elementList[i], 0));
|
|
}
|
|
}
|
|
|
|
/* Set the status bar text */
|
|
this->statusBar()->showMessage(tr("Found %1 results")
|
|
.arg(ui.treeSearch->topLevelItemCount()));
|
|
}
|
|
|
|
/** Overrides the default show method */
|
|
void
|
|
HelpBrowser::showWindow(const QString &topic)
|
|
{
|
|
/* Bring the window to the top */
|
|
if (helpBrowser == NULL) {
|
|
/*helpBrowser = */new HelpBrowser();
|
|
}
|
|
helpBrowser->show();
|
|
|
|
/* If a topic was specified, then go ahead and display it. */
|
|
if (!topic.isEmpty()) {
|
|
helpBrowser->showTopic(topic);
|
|
}
|
|
}
|
|
|