Vidalia 0.2.10
|
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