00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
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
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
00038 #define ROLE_TOPIC_ID Qt::UserRole
00039 #define ROLE_TOPIC_QRC_PATH (Qt::UserRole+1)
00040
00041
00042
00043 HelpBrowser::HelpBrowser(QWidget *parent)
00044 : VidaliaWindow("HelpBrowser", parent)
00045 {
00046 VidaliaSettings settings;
00047
00048
00049 ui.setupUi(this);
00050 #if defined(Q_WS_MAC)
00051 ui.actionHome->setShortcut(QString("Shift+Ctrl+H"));
00052 #endif
00053
00054
00055 ui.actionClose->setShortcut(QString("Esc"));
00056 Vidalia::createShortcut("Ctrl+W", this, ui.actionClose, SLOT(trigger()));
00057
00058
00059 ui.frmFind->setHidden(true);
00060
00061
00062
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
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
00090 loadContentsFromXml(":/help/" + language() + "/contents.xml");
00091
00092
00093 ui.treeContents->setCurrentItem(ui.treeContents->topLevelItem(0));
00094 ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
00095 }
00096
00097
00098
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
00109 void
00110 HelpBrowser::loadContentsFromXml(QString xmlFile)
00111 {
00112 QString errorString;
00113 QFile file(xmlFile);
00114 QDomDocument document;
00115
00116
00117 if (!document.setContent(&file, true, &errorString)) {
00118 ui.txtBrowser->setPlainText(tr("Error Loading Help Contents: ")+errorString);
00119 return;
00120 }
00121
00122 if (!loadContents(&document, errorString)) {
00123 ui.txtBrowser->setPlainText(tr("Error Loading Help Contents: ")+errorString);
00124 return;
00125 }
00126 }
00127
00128
00129 bool
00130 HelpBrowser::loadContents(const QDomDocument *document, QString &errorString)
00131 {
00132
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
00141 QTreeWidgetItem *home = createTopicTreeItem(root, 0);
00142 ui.treeContents->addTopLevelItem(home);
00143
00144
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
00154 void
00155 HelpBrowser::parseHelpTopic(const QDomElement &topicElement,
00156 QTreeWidgetItem *parent)
00157 {
00158
00159 if (isValidTopicElement(topicElement)) {
00160
00161 _elementList << topicElement;
00162
00163
00164 QTreeWidgetItem *topic = createTopicTreeItem(topicElement, parent);
00165
00166
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
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
00185
00186
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
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
00210 void
00211 HelpBrowser::contentsItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
00212 {
00213 QList<QTreeWidgetItem *> selected = ui.treeSearch->selectedItems();
00214
00215 if (!selected.isEmpty()) {
00216 ui.treeSearch->setItemSelected(selected[0], false);
00217 }
00218 currentItemChanged(current, prev);
00219 }
00220
00221
00222 void
00223 HelpBrowser::searchItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
00224 {
00225 QList<QTreeWidgetItem *> selected = ui.treeContents->selectedItems();
00226
00227 if (!selected.isEmpty()) {
00228 ui.treeContents->setItemSelected(selected[0], false);
00229 }
00230
00231
00232 currentItemChanged(current, prev);
00233
00234
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
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
00256
00257 QTreeWidgetItem*
00258 HelpBrowser::findTopicItem(QTreeWidgetItem *startItem, QString topic)
00259 {
00260
00261 if (!startItem)
00262 return 0;
00263
00264
00265 QString subtopic = topic.mid(0, topic.indexOf(".")).toLower();
00266
00267
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
00273 ui.treeContents->setItemExpanded(item, true);
00274 if (!topic.contains(".")) {
00275
00276 return item;
00277 }
00278
00279 return findTopicItem(item, topic.mid(topic.indexOf(".")+1));
00280 }
00281 }
00282 return 0;
00283 }
00284
00285
00286
00287 void
00288 HelpBrowser::showTopic(QString topic)
00289 {
00290
00291 QTreeWidgetItem *item =
00292 findTopicItem(ui.treeContents->topLevelItem(0), topic);
00293
00294 if (item) {
00295
00296
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
00308 void
00309 HelpBrowser::findNext()
00310 {
00311 find(true);
00312 }
00313
00314
00315 void
00316 HelpBrowser::findPrev()
00317 {
00318 find(false);
00319 }
00320
00321
00322
00323
00324
00325 void
00326 HelpBrowser::find(bool forward)
00327 {
00328
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
00338 this->statusBar()->clearMessage();
00339
00340
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
00352 if (searchPhrase != _lastFind) {
00353 _foundBefore = false;
00354 }
00355 _lastFind = searchPhrase;
00356
00357
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
00368 QTextCursor found;
00369 found = ui.txtBrowser->document()->find(searchPhrase, cursor, flags);
00370
00371
00372 if (!found.isNull()) {
00373 ui.txtBrowser->setTextCursor(found);
00374
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
00387 _foundBefore |= !found.isNull();
00388 }
00389
00390
00391
00392
00393
00394 void
00395 HelpBrowser::search()
00396 {
00397
00398 ui.treeSearch->clear();
00399
00400
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
00412 for (int i=0; i < _elementList.size(); ++i) {
00413
00414 browser.setSource(QUrl(getResourcePath(_elementList[i])));
00415
00416
00417 found = browser.document()->find(ui.lineSearch->text(), 0, flags);
00418
00419
00420 if (!found.isNull()) {
00421 ui.treeSearch->addTopLevelItem(createTopicTreeItem(_elementList[i], 0));
00422 }
00423 }
00424
00425
00426 this->statusBar()->showMessage(tr("Found %1 results")
00427 .arg(ui.treeSearch->topLevelItemCount()));
00428 }
00429
00430
00431 void
00432 HelpBrowser::showWindow(QString topic)
00433 {
00434
00435
00436 VidaliaWindow::showWindow();
00437
00438
00439 if (!topic.isEmpty()) {
00440 showTopic(topic);
00441 }
00442 }
00443