helpbrowser.cpp

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

Generated on Mon Oct 23 20:08:16 2006 for Vidalia by  doxygen 1.5.0