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

Generated on Wed Nov 26 21:02:42 2008 for Vidalia by  doxygen 1.5.7.1