00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
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
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
00036 #define ROLE_TOPIC_ID Qt::UserRole
00037 #define ROLE_TOPIC_QRC_PATH (Qt::UserRole+1)
00038
00039
00040
00041 HelpBrowser::HelpBrowser(QWidget *parent)
00042 : VidaliaWindow("HelpBrowser", parent)
00043 {
00044 VidaliaSettings settings;
00045
00046
00047 ui.setupUi(this);
00048 #if defined(Q_WS_MAC)
00049 ui.actionHome->setShortcut(QString("Shift+Ctrl+H"));
00050 #endif
00051
00052
00053 ui.actionClose->setShortcut(QString("Esc"));
00054 Vidalia::createShortcut("Ctrl+W", this, ui.actionClose, SLOT(trigger()));
00055
00056
00057 ui.frmFind->setHidden(true);
00058
00059
00060
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
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
00088 loadContentsFromXml(":/help/" + language() + "/contents.xml");
00089
00090
00091 ui.treeContents->setCurrentItem(ui.treeContents->topLevelItem(0));
00092 ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
00093 }
00094
00095
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
00108
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
00119 void
00120 HelpBrowser::loadContentsFromXml(QString xmlFile)
00121 {
00122 QString errorString;
00123 QFile file(xmlFile);
00124 QDomDocument document;
00125
00126
00127 if (!document.setContent(&file, true, &errorString)) {
00128 ui.txtBrowser->setPlainText(tr("Error Loading Help Contents: ")+errorString);
00129 return;
00130 }
00131
00132 if (!loadContents(&document, errorString)) {
00133 ui.txtBrowser->setPlainText(tr("Error Loading Help Contents: ")+errorString);
00134 return;
00135 }
00136 }
00137
00138
00139 bool
00140 HelpBrowser::loadContents(const QDomDocument *document, QString &errorString)
00141 {
00142
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
00151 QTreeWidgetItem *home = createTopicTreeItem(root, 0);
00152 ui.treeContents->addTopLevelItem(home);
00153
00154
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
00164 void
00165 HelpBrowser::parseHelpTopic(const QDomElement &topicElement,
00166 QTreeWidgetItem *parent)
00167 {
00168
00169 if (isValidTopicElement(topicElement)) {
00170
00171 _elementList << topicElement;
00172
00173
00174 QTreeWidgetItem *topic = createTopicTreeItem(topicElement, parent);
00175
00176
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
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
00195
00196
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
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
00224 void
00225 HelpBrowser::contentsItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
00226 {
00227 QList<QTreeWidgetItem *> selected = ui.treeSearch->selectedItems();
00228
00229 if (!selected.isEmpty()) {
00230 ui.treeSearch->setItemSelected(selected[0], false);
00231 }
00232 currentItemChanged(current, prev);
00233 }
00234
00235
00236 void
00237 HelpBrowser::searchItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
00238 {
00239 QList<QTreeWidgetItem *> selected = ui.treeContents->selectedItems();
00240
00241 if (!selected.isEmpty()) {
00242 ui.treeContents->setItemSelected(selected[0], false);
00243 }
00244
00245
00246 currentItemChanged(current, prev);
00247
00248
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
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
00270
00271 QTreeWidgetItem*
00272 HelpBrowser::findTopicItem(QTreeWidgetItem *startItem, QString topic)
00273 {
00274
00275 if (!startItem)
00276 return 0;
00277
00278
00279 QString subtopic = topic.mid(0, topic.indexOf(".")).toLower();
00280
00281
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
00287 ui.treeContents->setItemExpanded(item, true);
00288 if (!topic.contains(".")) {
00289
00290 return item;
00291 }
00292
00293 return findTopicItem(item, topic.mid(topic.indexOf(".")+1));
00294 }
00295 }
00296 return 0;
00297 }
00298
00299
00300
00301 void
00302 HelpBrowser::showTopic(QString topic)
00303 {
00304
00305 QTreeWidgetItem *item =
00306 findTopicItem(ui.treeContents->topLevelItem(0), topic);
00307 QTreeWidgetItem *selected = 0;
00308
00309 if (item) {
00310
00311
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
00324 void
00325 HelpBrowser::findNext()
00326 {
00327 find(true);
00328 }
00329
00330
00331 void
00332 HelpBrowser::findPrev()
00333 {
00334 find(false);
00335 }
00336
00337
00338
00339
00340
00341 void
00342 HelpBrowser::find(bool forward)
00343 {
00344
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
00354 this->statusBar()->clearMessage();
00355
00356
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
00368 if (searchPhrase != _lastFind) {
00369 _foundBefore = false;
00370 }
00371 _lastFind = searchPhrase;
00372
00373
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
00384 QTextCursor found;
00385 found = ui.txtBrowser->document()->find(searchPhrase, cursor, flags);
00386
00387
00388 if (!found.isNull()) {
00389 ui.txtBrowser->setTextCursor(found);
00390
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
00403 _foundBefore |= !found.isNull();
00404 }
00405
00406
00407
00408
00409
00410 void
00411 HelpBrowser::search()
00412 {
00413
00414 ui.treeSearch->clear();
00415
00416
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
00428 for (int i=0; i < _elementList.size(); ++i) {
00429
00430 browser.setSource(QUrl(getResourcePath(_elementList[i])));
00431
00432
00433 found = browser.document()->find(ui.lineSearch->text(), 0, flags);
00434
00435
00436 if (!found.isNull()) {
00437 ui.treeSearch->addTopLevelItem(createTopicTreeItem(_elementList[i], 0));
00438 }
00439 }
00440
00441
00442 this->statusBar()->showMessage(tr("Found %1 results")
00443 .arg(ui.treeSearch->topLevelItemCount()));
00444 }
00445
00446
00447 void
00448 HelpBrowser::showWindow(QString topic)
00449 {
00450
00451
00452 VidaliaWindow::showWindow();
00453
00454
00455 if (!topic.isEmpty()) {
00456 showTopic(topic);
00457 }
00458 }
00459