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