/******************************************************************************* * retroshare-gui/src/gui/help/browser/helpbrowser.cpp * * * * Copyright (c) 2008, defnax * * 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 . * * * *******************************************************************************/ /* ** \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 #include #include #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 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 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 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); } }