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