Vidalia 0.3.1
HelpBrowser.cpp
Go to the documentation of this file.
1/*
2** This file is part of Vidalia, and is subject to the license terms in the
3** LICENSE file, found in the top level directory of this distribution. If you
4** did not receive the LICENSE file with this file, you may obtain it from the
5** Vidalia source package distributed by the Vidalia Project at
6** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7** including this file, may be copied, modified, propagated, or distributed
8** except according to the terms described in the LICENSE file.
9*/
10
11/*
12** \file HelpBrowser.cpp
13** \brief Displays a list of help topics and content
14*/
15
16#include "HelpBrowser.h"
17#include "Vidalia.h"
18
19#include <QDomDocument>
20#include <QDir>
21
22#define LEFT_PANE_INDEX 0
23#define NO_STRETCH 0
24#define MINIMUM_PANE_SIZE 1
25
26/* Names of elements and attributes in the XML file */
27#define ELEMENT_CONTENTS "Contents"
28#define ELEMENT_TOPIC "Topic"
29#define ATTRIBUTE_TOPIC_ID "id"
30#define ATTRIBUTE_TOPIC_HTML "html"
31#define ATTRIBUTE_TOPIC_NAME "name"
32#define ATTRIBUTE_TOPIC_SECTION "section"
33
34/* Define two roles used to store data associated with a topic item */
35#define ROLE_TOPIC_ID Qt::UserRole
36#define ROLE_TOPIC_QRC_PATH (Qt::UserRole+1)
37
38
39/** Constuctor. This will probably do more later */
41 : VidaliaWindow("HelpBrowser", parent)
42{
43 VidaliaSettings settings;
44
45 /* Invoke Qt Designer generated QObject setup routine */
46 ui.setupUi(this);
47#if defined(Q_WS_MAC)
48 ui.actionHome->setShortcut(QString("Shift+Ctrl+H"));
49#endif
50
51 /* Pressing 'Esc' or 'Ctrl+W' will close the window */
52 ui.actionClose->setShortcut(QString("Esc"));
53 Vidalia::createShortcut("Ctrl+W", this, ui.actionClose, SLOT(trigger()));
54
55 /* Hide Search frame */
56 ui.frmFind->setHidden(true);
57
58 /* Set the splitter pane sizes so that only the txtBrowser pane expands
59 * and set to arbitrary sizes (the minimum sizes will take effect */
60 QList<int> sizes;
61 sizes.append(MINIMUM_PANE_SIZE);
62 sizes.append(MINIMUM_PANE_SIZE);
63 ui.splitter->setSizes(sizes);
64 ui.splitter->setStretchFactor(LEFT_PANE_INDEX, NO_STRETCH);
65
66 connect(ui.treeContents,
67 SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
68 this, SLOT(contentsItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
69
70 connect(ui.treeSearch,
71 SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
72 this, SLOT(searchItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
73
74 /* Connect the navigation actions to their slots */
75 connect(ui.actionHome, SIGNAL(triggered()), ui.txtBrowser, SLOT(home()));
76 connect(ui.actionBack, SIGNAL(triggered()), ui.txtBrowser, SLOT(backward()));
77 connect(ui.actionForward, SIGNAL(triggered()), ui.txtBrowser, SLOT(forward()));
78 connect(ui.txtBrowser, SIGNAL(backwardAvailable(bool)),
79 ui.actionBack, SLOT(setEnabled(bool)));
80 connect(ui.txtBrowser, SIGNAL(forwardAvailable(bool)),
81 ui.actionForward, SLOT(setEnabled(bool)));
82 connect(ui.btnFindNext, SIGNAL(clicked()), this, SLOT(findNext()));
83 connect(ui.btnFindPrev, SIGNAL(clicked()), this, SLOT(findPrev()));
84 connect(ui.btnSearch, SIGNAL(clicked()), this, SLOT(search()));
85
86 /* Load the help topics from XML */
87 loadContentsFromXml(":/help/" + language() + "/contents.xml");
88
89 /* Show the first help topic in the tree */
90 ui.treeContents->setCurrentItem(ui.treeContents->topLevelItem(0));
91 ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
92}
93
94/** Called when the user changes the UI translation. */
95void
97{
98 ui.retranslateUi(this);
99 ui.treeContents->clear();
100 loadContentsFromXml(":/help/" + language() + "/contents.xml");
101 ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
102 ui.treeContents->setCurrentItem(ui.treeContents->topLevelItem(0));
103 ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
104}
105
106/** Returns the language in which help topics should appear, or English
107 * ("en") if no translated help files exist for the current GUI language. */
108QString
110{
111 QString lang = Vidalia::language();
112 if (!QDir(":/help/" + lang).exists())
113 lang = "en";
114 return lang;
115}
116
117/** Load the contents of the help topics tree from the specified XML file. */
118void
120{
121 QString errorString;
122 QFile file(xmlFile);
123 QDomDocument document;
124
125 /* Load the XML contents into the DOM document */
126 if (!document.setContent(&file, true, &errorString)) {
127 ui.txtBrowser->setPlainText(tr("Error Loading Help Contents: ")+errorString);
128 return;
129 }
130 /* Load the DOM document contents into the tree view */
131 if (!loadContents(&document, errorString)) {
132 ui.txtBrowser->setPlainText(tr("Error Loading Help Contents: ")+errorString);
133 return;
134 }
135}
136
137/** Load the contents of the help topics tree from the given DOM document. */
138bool
139HelpBrowser::loadContents(const QDomDocument *document, QString &errorString)
140{
141 /* Grab the root document element and make sure it's the right one */
142 QDomElement root = document->documentElement();
143 if (root.tagName() != ELEMENT_CONTENTS) {
144 errorString = tr("Supplied XML file is not a valid Contents document.");
145 return false;
146 }
147 _elementList << root;
148
149 /* Create the home item */
150 QTreeWidgetItem *home = createTopicTreeItem(root, 0);
151 ui.treeContents->addTopLevelItem(home);
152
153 /* Process all top-level help topics */
154 QDomElement child = root.firstChildElement(ELEMENT_TOPIC);
155 while (!child.isNull()) {
156 parseHelpTopic(child, home);
157 child = child.nextSiblingElement(ELEMENT_TOPIC);
158 }
159 return true;
160}
161
162/** Parse a Topic element and handle all its children recursively. */
163void
164HelpBrowser::parseHelpTopic(const QDomElement &topicElement,
165 QTreeWidgetItem *parent)
166{
167 /* Check that we have a valid help topic */
168 if (isValidTopicElement(topicElement)) {
169 /* Save this element for later (used for searching) */
170 _elementList << topicElement;
171
172 /* Create and populate the new topic item in the tree */
173 QTreeWidgetItem *topic = createTopicTreeItem(topicElement, parent);
174
175 /* Process all its child elements */
176 QDomElement child = topicElement.firstChildElement(ELEMENT_TOPIC);
177 while (!child.isNull()) {
178 parseHelpTopic(child, topic);
179 child = child.nextSiblingElement(ELEMENT_TOPIC);
180 }
181 }
182}
183
184/** Returns true if the given Topic element has the necessary attributes. */
185bool
186HelpBrowser::isValidTopicElement(const QDomElement &topicElement)
187{
188 return (topicElement.hasAttribute(ATTRIBUTE_TOPIC_ID) &&
189 topicElement.hasAttribute(ATTRIBUTE_TOPIC_NAME) &&
190 topicElement.hasAttribute(ATTRIBUTE_TOPIC_HTML));
191}
192
193/** Builds a resource path to an html file associated with the given help
194 * topic. If the help topic needs an achor, the anchor will be formatted and
195 * appended. */
196QString
197HelpBrowser::getResourcePath(const QDomElement &topicElement)
198{
199 QString link = language() + "/" + topicElement.attribute(ATTRIBUTE_TOPIC_HTML);
200 if (topicElement.hasAttribute(ATTRIBUTE_TOPIC_SECTION)) {
201 link += "#" + topicElement.attribute(ATTRIBUTE_TOPIC_SECTION);
202 }
203 return link;
204}
205
206/** Creates a new element to be inserted into the topic tree. */
207QTreeWidgetItem*
208HelpBrowser::createTopicTreeItem(const QDomElement &topicElement,
209 QTreeWidgetItem *parent)
210{
211 QTreeWidgetItem *topic = new QTreeWidgetItem(parent);
212 QString label = topicElement.attribute(ATTRIBUTE_TOPIC_NAME);
213
214 topic->setText(0, label);
215 topic->setToolTip(0, label);
216 topic->setData(0, ROLE_TOPIC_ID, topicElement.attribute(ATTRIBUTE_TOPIC_ID));
217 topic->setData(0, ROLE_TOPIC_QRC_PATH, getResourcePath(topicElement));
218
219 return topic;
220}
221
222/** Called when the user selects a different item in the content topic tree */
223void
224HelpBrowser::contentsItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
225{
226 QList<QTreeWidgetItem *> selected = ui.treeSearch->selectedItems();
227 /* Deselect the selection in the search tree */
228 if (!selected.isEmpty()) {
229 ui.treeSearch->setItemSelected(selected[0], false);
230 }
231 currentItemChanged(current, prev);
232}
233
234/** Called when the user selects a different item in the content topic tree */
235void
236HelpBrowser::searchItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
237{
238 QList<QTreeWidgetItem *> selected = ui.treeContents->selectedItems();
239 /* Deselect the selection in the contents tree */
240 if (!selected.isEmpty()) {
241 ui.treeContents->setItemSelected(selected[0], false);
242 }
243
244 /* Change to selected page */
245 currentItemChanged(current, prev);
246
247 /* Highlight search phrase */
248 QTextCursor found;
249 QTextDocument::FindFlags flags = QTextDocument::FindWholeWords;
250 found = ui.txtBrowser->document()->find(_lastSearch, 0, flags);
251 if (!found.isNull()) {
252 ui.txtBrowser->setTextCursor(found);
253 }
254}
255
256/** Called when the user selects a different item in the tree. */
257void
258HelpBrowser::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
259{
260 Q_UNUSED(prev);
261 if (current) {
262 ui.txtBrowser->setSource(QUrl(current->data(0,
263 ROLE_TOPIC_QRC_PATH).toString()));
264 }
265 _foundBefore = false;
266}
267
268/** Searches for a topic in the topic tree. Returns a pointer to that topics
269 * item in the topic tree if it is found, 0 otherwise. */
270QTreeWidgetItem*
271HelpBrowser::findTopicItem(QTreeWidgetItem *startItem, QString topic)
272{
273 /* If startItem is null, then we don't know where to start searching. */
274 if (!startItem)
275 return 0;
276
277 /* Parse the first subtopic in the topic id. */
278 QString subtopic = topic.mid(0, topic.indexOf(".")).toLower();
279
280 /* Search through all children of startItem and look for a subtopic match */
281 for (int i = 0; i < startItem->childCount(); i++) {
282 QTreeWidgetItem *item = startItem->child(i);
283
284 if (subtopic == item->data(0, ROLE_TOPIC_ID).toString().toLower()) {
285 /* Found a subtopic match, so expand this item */
286 ui.treeContents->setItemExpanded(item, true);
287 if (!topic.contains(".")) {
288 /* Found the exact topic */
289 return item;
290 }
291 /* Search recursively for the next subtopic */
292 return findTopicItem(item, topic.mid(topic.indexOf(".")+1));
293 }
294 }
295 return 0;
296}
297
298/** Shows the help browser. If a sepcified topic was given, then search for
299 * that topic's ID (e.g., "log.basic") and display the appropriate page. */
300void
302{
303 /* Search for the topic in the contents tree */
304 QTreeWidgetItem *item =
305 findTopicItem(ui.treeContents->topLevelItem(0), topic);
306 QTreeWidgetItem *selected = 0;
307
308 if (item) {
309 /* Item was found, so show its location in the hierarchy and select its
310 * tree item. */
311 if (ui.treeContents->selectedItems().size()) {
312 selected = ui.treeContents->selectedItems()[0];
313 if (selected)
314 ui.treeContents->setItemSelected(selected, false);
315 }
316 ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
317 ui.treeContents->setItemSelected(item, true);
318 currentItemChanged(item, selected);
319 }
320}
321
322/** Called when the user clicks "Find Next". */
323void
325{
326 find(true);
327}
328
329/** Called when the user clicks "Find Previous". */
330void
332{
333 find(false);
334}
335
336/** Searches the current page for the phrase in the Find box.
337 * Highlights the first instance found in the document
338 * \param forward true search forward if true, backward if false
339 **/
340void
341HelpBrowser::find(bool forward)
342{
343 /* Don't bother searching if there is no search phrase */
344 if (ui.lineFind->text().isEmpty()) {
345 return;
346 }
347
348 QTextDocument::FindFlags flags = 0;
349 QTextCursor cursor = ui.txtBrowser->textCursor();
350 QString searchPhrase = ui.lineFind->text();
351
352 /* Clear status bar */
353 this->statusBar()->clearMessage();
354
355 /* Set search direction and other flags */
356 if (!forward) {
357 flags |= QTextDocument::FindBackward;
358 }
359 if (ui.chkbxMatchCase->isChecked()) {
360 flags |= QTextDocument::FindCaseSensitively;
361 }
362 if (ui.chkbxWholePhrase->isChecked()) {
363 flags |= QTextDocument::FindWholeWords;
364 }
365
366 /* Check if search phrase is the same as the previous */
367 if (searchPhrase != _lastFind) {
368 _foundBefore = false;
369 }
370 _lastFind = searchPhrase;
371
372 /* Set the cursor to the appropriate start location if necessary */
373 if (!cursor.hasSelection()) {
374 if (forward) {
375 cursor.movePosition(QTextCursor::Start);
376 } else {
377 cursor.movePosition(QTextCursor::End);
378 }
379 ui.txtBrowser->setTextCursor(cursor);
380 }
381
382 /* Search the page */
383 QTextCursor found;
384 found = ui.txtBrowser->document()->find(searchPhrase, cursor, flags);
385
386 /* If found, move the cursor to the location */
387 if (!found.isNull()) {
388 ui.txtBrowser->setTextCursor(found);
389 /* If not found, display appropriate error message */
390 } else {
391 if (_foundBefore) {
392 if (forward)
393 this->statusBar()->showMessage(tr("Search reached end of document"));
394 else
395 this->statusBar()->showMessage(tr("Search reached start of document"));
396 } else {
397 this->statusBar()->showMessage(tr("Text not found in document"));
398 }
399 }
400
401 /* Even if not found this time, may have been found previously */
402 _foundBefore |= !found.isNull();
403}
404
405/** Searches all help pages for the phrase the Search box.
406 * Fills treeSearch with documents containing matches and sets the
407 * status bar text appropriately.
408 */
409void
411{
412 /* Clear the list */
413 ui.treeSearch->clear();
414
415 /* Don't search if invalid document or blank search phrase */
416 if (ui.lineSearch->text().isEmpty()) {
417 return;
418 }
419
420 HelpTextBrowser browser;
421 QTextCursor found;
422 QTextDocument::FindFlags flags = QTextDocument::FindWholeWords;
423
424 _lastSearch = ui.lineSearch->text();
425
426 /* Search through all the pages looking for the phrase */
427 for (int i=0; i < _elementList.size(); ++i) {
428 /* Load page data into browser */
429 browser.setSource(QUrl(getResourcePath(_elementList[i])));
430
431 /* Search current document */
432 found = browser.document()->find(ui.lineSearch->text(), 0, flags);
433
434 /* If found, add page to tree */
435 if (!found.isNull()) {
436 ui.treeSearch->addTopLevelItem(createTopicTreeItem(_elementList[i], 0));
437 }
438 }
439
440 /* Set the status bar text */
441 this->statusBar()->showMessage(tr("Found %1 results")
442 .arg(ui.treeSearch->topLevelItemCount()));
443}
444
445/** Overrides the default show method */
446void
448{
449
450 /* Bring the window to the top */
452
453 /* If a topic was specified, then go ahead and display it. */
454 if (!topic.isEmpty()) {
455 showTopic(topic);
456 }
457}
458
#define ATTRIBUTE_TOPIC_HTML
Definition: HelpBrowser.cpp:30
#define ELEMENT_CONTENTS
Definition: HelpBrowser.cpp:27
#define ATTRIBUTE_TOPIC_NAME
Definition: HelpBrowser.cpp:31
#define ATTRIBUTE_TOPIC_SECTION
Definition: HelpBrowser.cpp:32
#define ROLE_TOPIC_QRC_PATH
Definition: HelpBrowser.cpp:36
#define ATTRIBUTE_TOPIC_ID
Definition: HelpBrowser.cpp:29
#define MINIMUM_PANE_SIZE
Definition: HelpBrowser.cpp:24
#define NO_STRETCH
Definition: HelpBrowser.cpp:23
#define ELEMENT_TOPIC
Definition: HelpBrowser.cpp:28
#define LEFT_PANE_INDEX
Definition: HelpBrowser.cpp:22
#define ROLE_TOPIC_ID
Definition: HelpBrowser.cpp:35
stop errmsg connect(const QHostAddress &address, quint16 port)
QList< QDomElement > _elementList
Definition: HelpBrowser.h:87
bool _foundBefore
Definition: HelpBrowser.h:93
void contentsItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
QString _lastFind
Definition: HelpBrowser.h:89
void findNext()
void showTopic(QString topic)
Ui::HelpBrowser ui
Definition: HelpBrowser.h:96
virtual void retranslateUi()
Definition: HelpBrowser.cpp:96
QString getResourcePath(const QDomElement &topicElement)
QString _lastSearch
Definition: HelpBrowser.h:91
QTreeWidgetItem * createTopicTreeItem(const QDomElement &topicElement, QTreeWidgetItem *parent)
void loadContentsFromXml(QString xmlFile)
QTreeWidgetItem * findTopicItem(QTreeWidgetItem *startItem, QString topic)
void findPrev()
HelpBrowser(QWidget *parent=0)
Definition: HelpBrowser.cpp:40
void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
bool loadContents(const QDomDocument *document, QString &errorString)
void find(bool forward)
void parseHelpTopic(const QDomElement &element, QTreeWidgetItem *parent)
QString language()
bool isValidTopicElement(const QDomElement &topicElement)
void searchItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
virtual void setSource(const QUrl &url)
static void createShortcut(const QKeySequence &key, QWidget *sender, QObject *receiver, const char *slot)
Definition: Vidalia.cpp:402
static QString language()
Definition: Vidalia.h:69
virtual void showWindow()
Definition: VidaliaWindow.h:64
QString i(QString str)
Definition: html.cpp:32