00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028 #include <QDomDocument>
00029 #include <vidalia.h>
00030 #include <gui/mainwindow.h>
00031
00032 #include "helpbrowser.h"
00033
00034
00035 #define LEFT_PANE_INDEX 0
00036 #define NO_STRETCH 0
00037 #define MINIMUM_PANE_SIZE 1
00038
00039
00040 #define ELEMENT_CONTENTS "Contents"
00041 #define ELEMENT_TOPIC "Topic"
00042 #define ATTRIBUTE_TOPIC_ID "id"
00043 #define ATTRIBUTE_TOPIC_HTML "html"
00044 #define ATTRIBUTE_TOPIC_NAME "name"
00045 #define ATTRIBUTE_TOPIC_SECTION "section"
00046
00047
00048 #define ROLE_TOPIC_ID Qt::UserRole
00049 #define ROLE_TOPIC_QRC_PATH (Qt::UserRole+1)
00050
00051
00052
00053 HelpBrowser::HelpBrowser(QWidget *parent)
00054 : VidaliaWindow("HelpBrowser", parent)
00055 {
00056 VidaliaSettings settings;
00057
00058
00059 ui.setupUi(this);
00060 #if defined(Q_WS_MAC)
00061 ui.actionHome->setShortcut(QString("Shift+Ctrl+H"));
00062 ui.actionClose->setShortcut(QString("Ctrl+W"));
00063 #endif
00064
00065
00066 ui.frmFind->setHidden(true);
00067
00068
00069
00070 QList<int> sizes;
00071 sizes.append(MINIMUM_PANE_SIZE);
00072 sizes.append(MINIMUM_PANE_SIZE);
00073 ui.splitter->setSizes(sizes);
00074 ui.splitter->setStretchFactor(LEFT_PANE_INDEX, NO_STRETCH);
00075
00076 connect(ui.treeContents,
00077 SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
00078 this, SLOT(contentsItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
00079
00080 connect(ui.treeSearch,
00081 SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
00082 this, SLOT(searchItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
00083
00084
00085 connect(ui.actionHome, SIGNAL(triggered()), ui.txtBrowser, SLOT(home()));
00086 connect(ui.actionBack, SIGNAL(triggered()), ui.txtBrowser, SLOT(backward()));
00087 connect(ui.actionForward, SIGNAL(triggered()), ui.txtBrowser, SLOT(forward()));
00088 connect(ui.txtBrowser, SIGNAL(backwardAvailable(bool)),
00089 ui.actionBack, SLOT(setEnabled(bool)));
00090 connect(ui.txtBrowser, SIGNAL(forwardAvailable(bool)),
00091 ui.actionForward, SLOT(setEnabled(bool)));
00092 connect(ui.btnFindNext, SIGNAL(clicked()), this, SLOT(findNext()));
00093 connect(ui.btnFindPrev, SIGNAL(clicked()), this, SLOT(findPrev()));
00094 connect(ui.btnSearch, SIGNAL(clicked()), this, SLOT(search()));
00095
00096
00097 loadContentsFromXml(":/help/" + Vidalia::language() + "/contents.xml");
00098
00099
00100 ui.treeContents->setCurrentItem(ui.treeContents->topLevelItem(0));
00101 ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
00102 }
00103
00104
00105 void
00106 HelpBrowser::loadContentsFromXml(QString xmlFile)
00107 {
00108 QString errorString;
00109 QFile file(xmlFile);
00110 QDomDocument document;
00111
00112
00113 if (!document.setContent(&file, true, &errorString)) {
00114 ui.txtBrowser->setPlainText(tr("Error Loading Help Contents: ")+errorString);
00115 return;
00116 }
00117
00118 if (!loadContents(&document, errorString)) {
00119 ui.txtBrowser->setPlainText(tr("Error Loading Help Contents: ")+errorString);
00120 return;
00121 }
00122 }
00123
00124
00125 bool
00126 HelpBrowser::loadContents(const QDomDocument *document, QString &errorString)
00127 {
00128
00129 QDomElement root = document->documentElement();
00130 if (root.tagName() != ELEMENT_CONTENTS) {
00131 errorString = tr("Supplied XML file is not a valid Contents document.");
00132 return false;
00133 }
00134 _elementList << root;
00135
00136
00137 QTreeWidgetItem *home = createTopicTreeItem(root, 0);
00138 ui.treeContents->addTopLevelItem(home);
00139
00140
00141 QDomElement child = root.firstChildElement(ELEMENT_TOPIC);
00142 while (!child.isNull()) {
00143 parseHelpTopic(child, home);
00144 child = child.nextSiblingElement(ELEMENT_TOPIC);
00145 }
00146 return true;
00147 }
00148
00149
00150 void
00151 HelpBrowser::parseHelpTopic(const QDomElement &topicElement,
00152 QTreeWidgetItem *parent)
00153 {
00154
00155 if (isValidTopicElement(topicElement)) {
00156
00157 _elementList << topicElement;
00158
00159
00160 QTreeWidgetItem *topic = createTopicTreeItem(topicElement, parent);
00161
00162
00163 QDomElement child = topicElement.firstChildElement(ELEMENT_TOPIC);
00164 while (!child.isNull()) {
00165 parseHelpTopic(child, topic);
00166 child = child.nextSiblingElement(ELEMENT_TOPIC);
00167 }
00168 }
00169 }
00170
00171
00172 bool
00173 HelpBrowser::isValidTopicElement(const QDomElement &topicElement)
00174 {
00175 return (topicElement.hasAttribute(ATTRIBUTE_TOPIC_ID) &&
00176 topicElement.hasAttribute(ATTRIBUTE_TOPIC_NAME) &&
00177 topicElement.hasAttribute(ATTRIBUTE_TOPIC_HTML));
00178 }
00179
00180
00181
00182
00183 QString
00184 HelpBrowser::getResourcePath(const QDomElement &topicElement)
00185 {
00186 QString link = Vidalia::language() + "/" + topicElement.attribute(ATTRIBUTE_TOPIC_HTML);
00187 if (topicElement.hasAttribute(ATTRIBUTE_TOPIC_SECTION)) {
00188 link += "#" + topicElement.attribute(ATTRIBUTE_TOPIC_SECTION);
00189 }
00190 return link;
00191 }
00192
00193
00194 QTreeWidgetItem*
00195 HelpBrowser::createTopicTreeItem(const QDomElement &topicElement,
00196 QTreeWidgetItem *parent)
00197 {
00198 QTreeWidgetItem *topic = new QTreeWidgetItem(parent);
00199 topic->setText(0, topicElement.attribute(ATTRIBUTE_TOPIC_NAME));
00200 topic->setData(0, ROLE_TOPIC_ID, topicElement.attribute(ATTRIBUTE_TOPIC_ID));
00201 topic->setData(0, ROLE_TOPIC_QRC_PATH, getResourcePath(topicElement));
00202 return topic;
00203 }
00204
00205
00206 void
00207 HelpBrowser::contentsItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
00208 {
00209 QList<QTreeWidgetItem *> selected = ui.treeSearch->selectedItems();
00210
00211 if (!selected.isEmpty()) {
00212 ui.treeSearch->setItemSelected(selected[0], false);
00213 }
00214 currentItemChanged(current, prev);
00215 }
00216
00217
00218 void
00219 HelpBrowser::searchItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
00220 {
00221 QList<QTreeWidgetItem *> selected = ui.treeContents->selectedItems();
00222
00223 if (!selected.isEmpty()) {
00224 ui.treeContents->setItemSelected(selected[0], false);
00225 }
00226
00227
00228 currentItemChanged(current, prev);
00229
00230
00231 QTextCursor found;
00232 QTextDocument::FindFlags flags = QTextDocument::FindWholeWords;
00233 found = ui.txtBrowser->document()->find(_lastSearch, 0, flags);
00234 if (!found.isNull()) {
00235 ui.txtBrowser->setTextCursor(found);
00236 }
00237 }
00238
00239
00240 void
00241 HelpBrowser::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev)
00242 {
00243 Q_UNUSED(prev);
00244 if (current) {
00245 ui.txtBrowser->setSource(QUrl(current->data(0,
00246 ROLE_TOPIC_QRC_PATH).toString()));
00247 }
00248 _foundBefore = false;
00249 }
00250
00251
00252
00253 QTreeWidgetItem*
00254 HelpBrowser::findTopicItem(QTreeWidgetItem *startItem, QString topic)
00255 {
00256
00257 QString subtopic = topic.mid(0, topic.indexOf(".")).toLower();
00258
00259
00260 for (int i = 0; i < startItem->childCount(); i++) {
00261 QTreeWidgetItem *item = startItem->child(i);
00262
00263 if (subtopic == item->data(0, ROLE_TOPIC_ID).toString().toLower()) {
00264
00265 ui.treeContents->setItemExpanded(item, true);
00266 if (!topic.contains(".")) {
00267
00268 return item;
00269 }
00270
00271 return findTopicItem(item, topic.mid(topic.indexOf(".")+1));
00272 }
00273 }
00274 return 0;
00275 }
00276
00277
00278
00279 void
00280 HelpBrowser::showTopic(QString topic)
00281 {
00282
00283 QTreeWidgetItem *item =
00284 findTopicItem(ui.treeContents->topLevelItem(0), topic);
00285
00286 if (item) {
00287
00288
00289 QTreeWidgetItem* selected = ui.treeContents->selectedItems()[0];
00290 if (selected) {
00291 ui.treeContents->setItemSelected(selected, false);
00292 }
00293 ui.treeContents->setItemExpanded(ui.treeContents->topLevelItem(0), true);
00294 ui.treeContents->setItemSelected(item, true);
00295 currentItemChanged(item, selected);
00296 }
00297 }
00298
00299
00300 void
00301 HelpBrowser::findNext()
00302 {
00303 find(true);
00304 }
00305
00306
00307 void
00308 HelpBrowser::findPrev()
00309 {
00310 find(false);
00311 }
00312
00313
00314
00315
00316
00317 void
00318 HelpBrowser::find(bool forward)
00319 {
00320
00321 if (ui.lineFind->text().isEmpty()) {
00322 return;
00323 }
00324
00325 QTextDocument::FindFlags flags = 0;
00326 QTextCursor cursor = ui.txtBrowser->textCursor();
00327 QString searchPhrase = ui.lineFind->text();
00328
00329
00330 this->statusBar()->clearMessage();
00331
00332
00333 if (!forward) {
00334 flags |= QTextDocument::FindBackward;
00335 }
00336 if (ui.chkbxMatchCase->isChecked()) {
00337 flags |= QTextDocument::FindCaseSensitively;
00338 }
00339 if (ui.chkbxWholePhrase->isChecked()) {
00340 flags |= QTextDocument::FindWholeWords;
00341 }
00342
00343
00344 if (searchPhrase != _lastFind) {
00345 _foundBefore = false;
00346 }
00347 _lastFind = searchPhrase;
00348
00349
00350 if (!cursor.hasSelection()) {
00351 if (forward) {
00352 cursor.movePosition(QTextCursor::Start);
00353 } else {
00354 cursor.movePosition(QTextCursor::End);
00355 }
00356 ui.txtBrowser->setTextCursor(cursor);
00357 }
00358
00359
00360 QTextCursor found;
00361 found = ui.txtBrowser->document()->find(searchPhrase, cursor, flags);
00362
00363
00364 if (!found.isNull()) {
00365 ui.txtBrowser->setTextCursor(found);
00366
00367 } else {
00368 if (_foundBefore) {
00369 if (forward)
00370 this->statusBar()->showMessage(tr("Search reached end of document"));
00371 else
00372 this->statusBar()->showMessage(tr("Search reached start of document"));
00373 } else {
00374 this->statusBar()->showMessage(tr("Text not found in document"));
00375 }
00376 }
00377
00378
00379 _foundBefore |= !found.isNull();
00380 }
00381
00382
00383
00384
00385
00386 void
00387 HelpBrowser::search()
00388 {
00389
00390 ui.treeSearch->clear();
00391
00392
00393 if (ui.lineSearch->text().isEmpty()) {
00394 return;
00395 }
00396
00397 HelpTextBrowser browser;
00398 QTextCursor found;
00399 QTextDocument::FindFlags flags = QTextDocument::FindWholeWords;
00400
00401 _lastSearch = ui.lineSearch->text();
00402
00403
00404 for (int i=0; i < _elementList.size(); ++i) {
00405
00406 browser.setSource(QUrl(getResourcePath(_elementList[i])));
00407
00408
00409 found = browser.document()->find(ui.lineSearch->text(), 0, flags);
00410
00411
00412 if (!found.isNull()) {
00413 ui.treeSearch->addTopLevelItem(createTopicTreeItem(_elementList[i], 0));
00414 }
00415 }
00416
00417
00418 this->statusBar()->showMessage(tr("Found %1 results")
00419 .arg(ui.treeSearch->topLevelItemCount()));
00420 }
00421
00422
00423 void
00424 HelpBrowser::show(QString topic)
00425 {
00426
00427
00428 VidaliaWindow::show();
00429
00430
00431 if (!topic.isEmpty()) {
00432 showTopic(topic);
00433 }
00434 }
00435