helpbrowser.cpp

Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to the
00008 **  terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file helpbrowser.cpp
00013 ** \version $Id: helpbrowser.cpp 3622 2009-03-16 19:45:20Z edmanm $
00014 ** \brief Displays a list of help topics and content
00015 */
00016 
00017 #include <QDomDocument>
00018 #include <QDir>
00019 #include <vidalia.h>
00020 
00021 #include "helpbrowser.h"
00022 
00023 
00024 #define LEFT_PANE_INDEX     0
00025 #define NO_STRETCH          0
00026 #define MINIMUM_PANE_SIZE   1
00027 
00028 /* Names of elements and attributes in the XML file */
00029 #define ELEMENT_CONTENTS        "Contents"
00030 #define ELEMENT_TOPIC           "Topic"
00031 #define ATTRIBUTE_TOPIC_ID      "id"
00032 #define ATTRIBUTE_TOPIC_HTML    "html"
00033 #define ATTRIBUTE_TOPIC_NAME    "name"
00034 #define ATTRIBUTE_TOPIC_SECTION "section"
00035 
00036 /* Define two roles used to store data associated with a topic item */
00037 #define ROLE_TOPIC_ID        Qt::UserRole
00038 #define ROLE_TOPIC_QRC_PATH (Qt::UserRole+1)
00039 
00040 
00041 /** Constuctor. This will probably do more later */
00042 HelpBrowser::HelpBrowser(QWidget *parent)
00043  : VidaliaWindow("HelpBrowser", parent)
00044 {
00045   VidaliaSettings settings;
00046 
00047   /* Invoke Qt Designer generated QObject setup routine */
00048   ui.setupUi(this);
00049 #if defined(Q_WS_MAC)
00050   ui.actionHome->setShortcut(QString("Shift+Ctrl+H"));
00051 #endif
00052 
00053   /* Pressing 'Esc' or 'Ctrl+W' will close the window */
00054   ui.actionClose->setShortcut(QString("Esc"));
00055   Vidalia::createShortcut("Ctrl+W", this, ui.actionClose, SLOT(trigger()));
00056 
00057   /* Hide Search frame */
00058   ui.frmFind->setHidden(true);
00059  
00060   /* Set the splitter pane sizes so that only the txtBrowser pane expands
00061    * and set to arbitrary sizes (the minimum sizes will take effect */
00062   QList<int> sizes;
00063   sizes.append(MINIMUM_PANE_SIZE); 
00064   sizes.append(MINIMUM_PANE_SIZE);
00065   ui.splitter->setSizes(sizes);
00066   ui.splitter->setStretchFactor(LEFT_PANE_INDEX, NO_STRETCH);
00067 
00068   connect(ui.treeContents,
00069           SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
00070           this, SLOT(contentsItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
00071 
00072   connect(ui.treeSearch,
00073           SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
00074           this, SLOT(searchItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
00075 
00076   /* Connect the navigation actions to their slots */
00077   connect(ui.actionHome, SIGNAL(triggered()), ui.txtBrowser, SLOT(home()));
00078   connect(ui.actionBack, SIGNAL(triggered()), ui.txtBrowser, SLOT(backward()));
00079   connect(ui.actionForward, SIGNAL(triggered()), ui.txtBrowser, SLOT(forward()));
00080   connect(ui.txtBrowser, SIGNAL(backwardAvailable(bool)), 
00081           ui.actionBack, SLOT(setEnabled(bool)));
00082   connect(ui.txtBrowser, SIGNAL(forwardAvailable(bool)),
00083           ui.actionForward, SLOT(setEnabled(bool)));
00084   connect(ui.btnFindNext, SIGNAL(clicked()), this, SLOT(findNext()));
00085   connect(ui.btnFindPrev, SIGNAL(clicked()), this, SLOT(findPrev()));
00086   connect(ui.btnSearch, SIGNAL(clicked()), this, SLOT(search()));
00087   
00088   /* Load the help topics from XML */
00089   loadContentsFromXml(":/help/" + language() + "/contents.xml");
00090 
00091   /* Show the first help topic in the tree */
00092   ui.treeContents->setCurrentItem(ui.treeContents->topLevelItem(0));
00093   ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
00094 }
00095 
00096 /** Returns the language in which help topics should appear, or English
00097  * ("en") if no translated help files exist for the current GUI language. */
00098 QString
00099 HelpBrowser::language()
00100 {
00101   QString lang = Vidalia::language();
00102   if (!QDir(":/help/" + lang).exists())
00103     lang = "en";
00104   return lang;
00105 }
00106 
00107 /** Load the contents of the help topics tree from the specified XML file. */
00108 void
00109 HelpBrowser::loadContentsFromXml(QString xmlFile)
00110 {
00111   QString errorString;
00112   QFile file(xmlFile);
00113   QDomDocument document;
00114   
00115   /* Load the XML contents into the DOM document */
00116   if (!document.setContent(&file, true, &errorString)) {
00117     ui.txtBrowser->setPlainText(tr("Error Loading Help Contents: ")+errorString);
00118     return;
00119   }
00120   /* Load the DOM document contents into the tree view */
00121   if (!loadContents(&document, errorString)) {
00122     ui.txtBrowser->setPlainText(tr("Error Loading Help Contents: ")+errorString);
00123     return;
00124   }
00125 }
00126 
00127 /** Load the contents of the help topics tree from the given DOM document. */
00128 bool
00129 HelpBrowser::loadContents(const QDomDocument *document, QString &errorString)
00130 {
00131   /* Grab the root document element and make sure it's the right one */
00132   QDomElement root = document->documentElement();
00133   if (root.tagName() != ELEMENT_CONTENTS) {
00134     errorString = tr("Supplied XML file is not a valid Contents document.");
00135     return false;
00136   }
00137   _elementList << root;
00138 
00139   /* Create the home item */
00140   QTreeWidgetItem *home = createTopicTreeItem(root, 0);
00141   ui.treeContents->addTopLevelItem(home);
00142   
00143   /* Process all top-level help topics */
00144   QDomElement child = root.firstChildElement(ELEMENT_TOPIC);
00145   while (!child.isNull()) {
00146     parseHelpTopic(child, home);
00147     child = child.nextSiblingElement(ELEMENT_TOPIC);
00148   }
00149   return true;
00150 }
00151 
00152 /** Parse a Topic element and handle all its children recursively. */
00153 void
00154 HelpBrowser::parseHelpTopic(const QDomElement &topicElement, 
00155                             QTreeWidgetItem *parent)
00156 {
00157   /* Check that we have a valid help topic */
00158   if (isValidTopicElement(topicElement)) {
00159     /* Save this element for later (used for searching) */
00160     _elementList << topicElement;
00161 
00162     /* Create and populate the new topic item in the tree */
00163     QTreeWidgetItem *topic = createTopicTreeItem(topicElement, parent);
00164 
00165     /* Process all its child elements */
00166     QDomElement child = topicElement.firstChildElement(ELEMENT_TOPIC);
00167     while (!child.isNull()) {
00168       parseHelpTopic(child, topic);
00169       child = child.nextSiblingElement(ELEMENT_TOPIC);
00170     }
00171   }
00172 }
00173 
00174 /** Returns true if the given Topic element has the necessary attributes. */
00175 bool
00176 HelpBrowser::isValidTopicElement(const QDomElement &topicElement)
00177 {
00178   return (topicElement.hasAttribute(ATTRIBUTE_TOPIC_ID) &&
00179           topicElement.hasAttribute(ATTRIBUTE_TOPIC_NAME) &&
00180           topicElement.hasAttribute(ATTRIBUTE_TOPIC_HTML));
00181 }
00182 
00183 /** Builds a resource path to an html file associated with the given help
00184  * topic. If the help topic needs an achor, the anchor will be formatted and
00185  * appended. */
00186 QString
00187 HelpBrowser::getResourcePath(const QDomElement &topicElement)
00188 {
00189   QString link = language() + "/" + topicElement.attribute(ATTRIBUTE_TOPIC_HTML);
00190   if (topicElement.hasAttribute(ATTRIBUTE_TOPIC_SECTION)) {
00191     link += "#" + topicElement.attribute(ATTRIBUTE_TOPIC_SECTION);
00192   }
00193   return link;
00194 }
00195 
00196 /** Creates a new element to be inserted into the topic tree. */
00197 QTreeWidgetItem*
00198 HelpBrowser::createTopicTreeItem(const QDomElement &topicElement, 
00199                                  QTreeWidgetItem *parent)
00200 {
00201   QTreeWidgetItem *topic = new QTreeWidgetItem(parent);
00202   topic->setText(0, topicElement.attribute(ATTRIBUTE_TOPIC_NAME));
00203   topic->setData(0, ROLE_TOPIC_ID, topicElement.attribute(ATTRIBUTE_TOPIC_ID));
00204   topic->setData(0, ROLE_TOPIC_QRC_PATH, getResourcePath(topicElement));
00205   return topic;
00206 }
00207 
00208 /** Called when the user selects a different item in the content topic tree */
00209 void
00210 HelpBrowser::contentsItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
00211 {
00212   QList<QTreeWidgetItem *> selected = ui.treeSearch->selectedItems();
00213   /* Deselect the selection in the search tree */
00214   if (!selected.isEmpty()) {
00215     ui.treeSearch->setItemSelected(selected[0], false);
00216   }
00217   currentItemChanged(current, prev);
00218 }
00219 
00220 /** Called when the user selects a different item in the content topic tree */
00221 void
00222 HelpBrowser::searchItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
00223 {
00224   QList<QTreeWidgetItem *> selected = ui.treeContents->selectedItems();
00225   /* Deselect the selection in the contents tree */
00226   if (!selected.isEmpty()) {
00227     ui.treeContents->setItemSelected(selected[0], false);
00228   }
00229 
00230   /* Change to selected page */
00231   currentItemChanged(current, prev);
00232 
00233   /* Highlight search phrase */
00234   QTextCursor found;
00235   QTextDocument::FindFlags flags = QTextDocument::FindWholeWords;
00236   found = ui.txtBrowser->document()->find(_lastSearch, 0, flags);
00237   if (!found.isNull()) {
00238     ui.txtBrowser->setTextCursor(found);
00239   }
00240 }
00241 
00242 /** Called when the user selects a different item in the tree. */
00243 void
00244 HelpBrowser::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
00245 {
00246   Q_UNUSED(prev);
00247   if (current) {
00248     ui.txtBrowser->setSource(QUrl(current->data(0, 
00249                                               ROLE_TOPIC_QRC_PATH).toString()));
00250   }
00251   _foundBefore = false;
00252 }
00253 
00254 /** Searches for a topic in the topic tree. Returns a pointer to that topics
00255  * item in the topic tree if it is found, 0 otherwise. */
00256 QTreeWidgetItem*
00257 HelpBrowser::findTopicItem(QTreeWidgetItem *startItem, QString topic)
00258 {
00259   /* If startItem is null, then we don't know where to start searching. */
00260   if (!startItem)
00261     return 0;
00262 
00263   /* Parse the first subtopic in the topic id. */
00264   QString subtopic = topic.mid(0, topic.indexOf(".")).toLower();
00265 
00266   /* Search through all children of startItem and look for a subtopic match */
00267   for (int i = 0; i < startItem->childCount(); i++) {
00268     QTreeWidgetItem *item = startItem->child(i);
00269     
00270     if (subtopic == item->data(0, ROLE_TOPIC_ID).toString().toLower()) {
00271       /* Found a subtopic match, so expand this item */
00272       ui.treeContents->setItemExpanded(item, true);
00273       if (!topic.contains(".")) {
00274         /* Found the exact topic */
00275         return item;
00276       }
00277       /* Search recursively for the next subtopic */
00278       return findTopicItem(item, topic.mid(topic.indexOf(".")+1));
00279     }
00280   }
00281   return 0;
00282 }
00283 
00284 /** Shows the help browser. If a sepcified topic was given, the search for
00285  * that topic's ID (e.g., "log.basic") and display the appropriate page. */
00286 void
00287 HelpBrowser::showTopic(QString topic)
00288 {
00289   /* Search for the topic in the contents tree */
00290   QTreeWidgetItem *item =
00291     findTopicItem(ui.treeContents->topLevelItem(0), topic);
00292   
00293   if (item) {
00294     /* Item was found, so show its location in the hierarchy and select its
00295      * tree item. */
00296     QTreeWidgetItem* selected = ui.treeContents->selectedItems()[0];
00297     if (selected) {
00298       ui.treeContents->setItemSelected(selected, false);
00299     }
00300     ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
00301     ui.treeContents->setItemSelected(item, true);
00302     currentItemChanged(item, selected);
00303   }
00304 }
00305 
00306 /** Called when the user clicks "Find Next". */
00307 void
00308 HelpBrowser::findNext()
00309 {
00310   find(true);
00311 }
00312 
00313 /** Called when the user clicks "Find Previous". */
00314 void
00315 HelpBrowser::findPrev()
00316 {
00317   find(false);
00318 }
00319 
00320 /** Searches the current page for the phrase in the Find box.
00321  *  Highlights the first instance found in the document
00322  *  \param forward true search forward if true, backward if false
00323  **/
00324 void
00325 HelpBrowser::find(bool forward)
00326 {
00327   /* Don't bother searching if there is no search phrase */
00328   if (ui.lineFind->text().isEmpty()) {
00329     return;
00330   }
00331   
00332   QTextDocument::FindFlags flags = 0;
00333   QTextCursor cursor = ui.txtBrowser->textCursor();
00334   QString searchPhrase = ui.lineFind->text();
00335   
00336   /* Clear status bar */
00337   this->statusBar()->clearMessage();
00338   
00339   /* Set search direction and other flags */
00340   if (!forward) {
00341     flags |= QTextDocument::FindBackward;
00342   }
00343   if (ui.chkbxMatchCase->isChecked()) {
00344     flags |= QTextDocument::FindCaseSensitively;
00345   }
00346   if (ui.chkbxWholePhrase->isChecked()) {
00347     flags |= QTextDocument::FindWholeWords;
00348   }
00349   
00350   /* Check if search phrase is the same as the previous */
00351   if (searchPhrase != _lastFind) {
00352     _foundBefore = false;
00353   }
00354   _lastFind = searchPhrase;
00355   
00356   /* Set the cursor to the appropriate start location if necessary */
00357   if (!cursor.hasSelection()) {
00358     if (forward) {
00359       cursor.movePosition(QTextCursor::Start);
00360     } else {
00361       cursor.movePosition(QTextCursor::End);
00362     }
00363     ui.txtBrowser->setTextCursor(cursor);
00364   }
00365 
00366   /* Search the page */
00367   QTextCursor found;
00368   found = ui.txtBrowser->document()->find(searchPhrase, cursor, flags);
00369   
00370   /* If found, move the cursor to the location */
00371   if (!found.isNull()) {
00372     ui.txtBrowser->setTextCursor(found);
00373   /* If not found, display appropriate error message */
00374   } else {
00375     if (_foundBefore) {
00376       if (forward) 
00377         this->statusBar()->showMessage(tr("Search reached end of document"));
00378       else 
00379         this->statusBar()->showMessage(tr("Search reached start of document"));
00380     } else {
00381       this->statusBar()->showMessage(tr("Text not found in document"));
00382     }
00383   }
00384   
00385   /* Even if not found this time, may have been found previously */
00386   _foundBefore |= !found.isNull();
00387 }
00388  
00389 /** Searches all help pages for the phrase the Search box.
00390  *  Fills treeSearch with documents containing matches and sets the
00391  *  status bar text appropriately.
00392  */
00393 void
00394 HelpBrowser::search()
00395 {
00396   /* Clear the list */
00397   ui.treeSearch->clear();
00398   
00399   /* Don't search if invalid document or blank search phrase */
00400   if (ui.lineSearch->text().isEmpty()) {
00401     return;
00402   }
00403     
00404   HelpTextBrowser browser;
00405   QTextCursor found;
00406   QTextDocument::FindFlags flags = QTextDocument::FindWholeWords;
00407 
00408   _lastSearch = ui.lineSearch->text();
00409 
00410   /* Search through all the pages looking for the phrase */
00411   for (int i=0; i < _elementList.size(); ++i) {
00412     /* Load page data into browser */
00413     browser.setSource(QUrl(getResourcePath(_elementList[i])));
00414       
00415     /* Search current document */
00416     found = browser.document()->find(ui.lineSearch->text(), 0, flags);
00417 
00418     /* If found, add page to tree */
00419     if (!found.isNull()) {
00420       ui.treeSearch->addTopLevelItem(createTopicTreeItem(_elementList[i], 0));
00421     }
00422   }
00423 
00424   /* Set the status bar text */
00425   this->statusBar()->showMessage(tr("Found %1 results")
00426                                 .arg(ui.treeSearch->topLevelItemCount()));
00427 }
00428 
00429 /** Overrides the default show method */
00430 void
00431 HelpBrowser::showWindow(QString topic)
00432 {
00433   
00434   /* Bring the window to the top */
00435   VidaliaWindow::showWindow();
00436 
00437   /* If a topic was specified, then go ahead and display it. */
00438   if (!topic.isEmpty()) {
00439     showTopic(topic);
00440   }
00441 }
00442 

Generated on 22 Feb 2010 for Vidalia by  doxygen 1.6.1