Vidalia  0.2.17
MessageLog.cpp
Go to the documentation of this file.
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 MessageLog.cpp
00013 ** \brief Displays log messages and message log settings
00014 */
00015 
00016 #include "MessageLog.h"
00017 #include "StatusEventItem.h"
00018 #include "Vidalia.h"
00019 #include "VMessageBox.h"
00020 
00021 #include "html.h"
00022 
00023 #include <QMessageBox>
00024 #include <QFileDialog>
00025 #include <QInputDialog>
00026 #include <QMessageBox>
00027 #include <QClipboard>
00028 
00029 /* Message log settings */
00030 #define SETTING_MSG_FILTER          "MessageFilter"
00031 #define SETTING_MAX_MSG_COUNT       "MaxMsgCount"
00032 #define SETTING_ENABLE_LOGFILE      "EnableLogFile"
00033 #define SETTING_LOGFILE             "LogFile"
00034 #define DEFAULT_MSG_FILTER \
00035   (tc::ErrorSeverity|tc::WarnSeverity|tc::NoticeSeverity)
00036 #define DEFAULT_MAX_MSG_COUNT       50
00037 #define DEFAULT_ENABLE_LOGFILE      false
00038 #if defined(Q_OS_WIN32)
00039 
00040 /** Default location of the log file to which log messages will be written. */
00041 #define DEFAULT_LOGFILE \
00042   (win32_program_files_folder()+"\\Tor\\tor-log.txt")
00043 #else
00044 #define DEFAULT_LOGFILE       (QDir::homePath() + "/.tor/tor-log.txt")
00045 #endif
00046 
00047 #define ADD_TO_FILTER(f,v,b)  (f = ((b) ? ((f) | (v)) : ((f) & ~(v))))
00048 
00049 
00050 /** Constructor. The constructor will load the message log's settings from
00051  * VidaliSettings and register for log events according to the most recently
00052  * set severity filter.
00053  * \param torControl A TorControl object used to register for log events.
00054  * \param parent The parent widget of this MessageLog object.
00055  * \param flags Any desired window creation flags.
00056  */
00057 MessageLog::MessageLog(QWidget *parent, Qt::WFlags flags)
00058 : VidaliaWindow("MessageLog", parent, flags)
00059 {
00060   /* Invoke Qt Designer generated QObject setup routine */
00061   ui.setupUi(this);
00062 
00063   /* Create necessary Message Log QObjects */
00064   _torControl = Vidalia::torControl();
00065   connect(_torControl, SIGNAL(logMessage(tc::Severity, QString)),
00066           this, SLOT(log(tc::Severity, QString)));
00067 
00068   /* Bind events to actions */
00069   createActions();
00070 
00071   /* Set tooltips for necessary widgets */
00072   setToolTips();
00073 
00074   /* Load the message log's stored settings */
00075   loadSettings();
00076 
00077   /* Sort in ascending chronological order */
00078   ui.listMessages->sortItems(LogTreeWidget::TimeColumn,
00079                              Qt::AscendingOrder);
00080   ui.listNotifications->sortItems(0, Qt::AscendingOrder);
00081 }
00082 
00083 /** Default Destructor. Simply frees up any memory allocated for member
00084  * variables. */
00085 MessageLog::~MessageLog()
00086 {
00087   _logFile.close();
00088 }
00089 
00090 /** Binds events (signals) to actions (slots). */
00091 void
00092 MessageLog::createActions()
00093 {
00094   connect(ui.actionSave_Selected, SIGNAL(triggered()),
00095           this, SLOT(saveSelected()));
00096 
00097   connect(ui.actionSave_All, SIGNAL(triggered()),
00098           this, SLOT(saveAll()));
00099 
00100   connect(ui.actionSelect_All, SIGNAL(triggered()),
00101           this, SLOT(selectAll()));
00102 
00103   connect(ui.actionCopy, SIGNAL(triggered()),
00104           this, SLOT(copy()));
00105 
00106   connect(ui.actionFind, SIGNAL(triggered()),
00107           this, SLOT(find()));
00108 
00109   connect(ui.actionClear, SIGNAL(triggered()),
00110           this, SLOT(clear()));
00111 
00112   connect(ui.actionHelp, SIGNAL(triggered()),
00113           this, SLOT(help()));
00114 
00115   connect(ui.btnSaveSettings, SIGNAL(clicked()),
00116           this, SLOT(saveSettings()));
00117 
00118   connect(ui.btnCancelSettings, SIGNAL(clicked()),
00119           this, SLOT(cancelChanges()));
00120 
00121   connect(ui.btnBrowse, SIGNAL(clicked()),
00122           this, SLOT(browse()));
00123 
00124 #if defined(Q_WS_MAC)
00125   ui.actionHelp->setShortcut(QString("Ctrl+?"));
00126 #endif
00127   ui.actionClose->setShortcut(QString("Esc"));
00128   Vidalia::createShortcut("Ctrl+W", this, ui.actionClose, SLOT(trigger()));
00129 }
00130 
00131 /** Set tooltips for Message Filter checkboxes in code because they are long
00132  * and Designer wouldn't let us insert newlines into the text. */
00133 void
00134 MessageLog::setToolTips()
00135 {
00136   ui.chkTorErr->setToolTip(tr("Messages that appear when something has \n"
00137                               "gone very wrong and Tor cannot proceed."));
00138   ui.chkTorWarn->setToolTip(tr("Messages that only appear when \n"
00139                                "something has gone wrong with Tor."));
00140   ui.chkTorNote->setToolTip(tr("Messages that appear infrequently \n"
00141                                "during normal Tor operation and are \n"
00142                                "not considered errors, but you may \n"
00143                                "care about."));
00144   ui.chkTorInfo->setToolTip(tr("Messages that appear frequently \n"
00145                                "during normal Tor operation."));
00146   ui.chkTorDebug->setToolTip(tr("Hyper-verbose messages primarily of \n"
00147                                 "interest to Tor developers."));
00148 }
00149 
00150 /** Called when the user changes the UI translation. */
00151 void
00152 MessageLog::retranslateUi()
00153 {
00154   ui.retranslateUi(this);
00155   setToolTips();
00156 }
00157 
00158 /** Loads the saved Message Log settings */
00159 void
00160 MessageLog::loadSettings()
00161 {
00162   /* Set Max Count widget */
00163   uint maxMsgCount = getSetting(SETTING_MAX_MSG_COUNT,
00164                                 DEFAULT_MAX_MSG_COUNT).toUInt();
00165   ui.spnbxMaxCount->setValue(maxMsgCount);
00166   ui.listMessages->setMaximumMessageCount(maxMsgCount);
00167   ui.listNotifications->setMaximumItemCount(maxMsgCount);
00168 
00169   /* Set whether or not logging to file is enabled */
00170   _enableLogging = getSetting(SETTING_ENABLE_LOGFILE,
00171                               DEFAULT_ENABLE_LOGFILE).toBool();
00172   QString logfile = getSetting(SETTING_LOGFILE,
00173                                DEFAULT_LOGFILE).toString();
00174   ui.lineFile->setText(QDir::convertSeparators(logfile));
00175   rotateLogFile(logfile);
00176   ui.chkEnableLogFile->setChecked(_logFile.isOpen());
00177 
00178   /* Set the checkboxes accordingly */
00179   _filter = getSetting(SETTING_MSG_FILTER, DEFAULT_MSG_FILTER).toUInt();
00180   ui.chkTorErr->setChecked(_filter & tc::ErrorSeverity);
00181   ui.chkTorWarn->setChecked(_filter & tc::WarnSeverity);
00182   ui.chkTorNote->setChecked(_filter & tc::NoticeSeverity);
00183   ui.chkTorInfo->setChecked(_filter & tc::InfoSeverity);
00184   ui.chkTorDebug->setChecked(_filter & tc::DebugSeverity);
00185   registerLogEvents();
00186 
00187   /* Filter the message log */
00188   QApplication::setOverrideCursor(Qt::WaitCursor);
00189   ui.listMessages->filter(_filter);
00190   QApplication::restoreOverrideCursor();
00191 }
00192 
00193 /** Attempts to register the selected message filter with Tor and displays an
00194  * error if setting the events fails. */
00195 void
00196 MessageLog::registerLogEvents()
00197 {
00198   _filter = getSetting(SETTING_MSG_FILTER, DEFAULT_MSG_FILTER).toUInt();
00199   _torControl->setEvent(TorEvents::LogDebug,
00200                         _filter & tc::DebugSeverity, false);
00201   _torControl->setEvent(TorEvents::LogInfo,
00202                         _filter & tc::InfoSeverity, false);
00203   _torControl->setEvent(TorEvents::LogNotice,
00204                         _filter & tc::NoticeSeverity, false);
00205   _torControl->setEvent(TorEvents::LogWarn,
00206                         _filter & tc::WarnSeverity, false);
00207   _torControl->setEvent(TorEvents::LogError,
00208                         _filter & tc::ErrorSeverity, false);
00209 
00210   QString errmsg;
00211   if (_torControl->isConnected() && !_torControl->setEvents(&errmsg)) {
00212     VMessageBox::warning(this, tr("Error Setting Filter"),
00213       p(tr("Vidalia was unable to register for Tor's log events.")) + p(errmsg),
00214       VMessageBox::Ok);
00215   }
00216 }
00217 
00218 /** Opens a log file if necessary, or closes it if logging is disabled. If a
00219  * log file is already opened and a new filename is specified, then the log
00220  * file will be rotated to the new filename. In the case that the new filename
00221  * can not be openend, the old file will remain open and writable. */
00222 bool
00223 MessageLog::rotateLogFile(const QString &filename)
00224 {
00225   QString errmsg;
00226   if (_enableLogging) {
00227     if (!_logFile.open(filename, &errmsg)) {
00228       VMessageBox::warning(this, tr("Error Opening Log File"),
00229         p(tr("Vidalia was unable to open the specified log file."))+p(errmsg),
00230         VMessageBox::Ok);
00231       return false;
00232     }
00233   } else {
00234     /* Close the log file. */
00235     _logFile.close();
00236   }
00237   return true;
00238 }
00239 
00240 /** Saves the Message Log settings, adjusts the message list if required, and
00241  * then hides the settings frame. */
00242 void
00243 MessageLog::saveSettings()
00244 {
00245   /* Update the logging status */
00246   _enableLogging = ui.chkEnableLogFile->isChecked();
00247   if (_enableLogging && ui.lineFile->text().isEmpty()) {
00248     /* The user chose to enable logging messages to a file, but didn't specify
00249      * a log filename. */
00250     VMessageBox::warning(this, tr("Log Filename Required"),
00251       p(tr("You must enter a filename to be able to save log "
00252            "messages to a file.")), VMessageBox::Ok);
00253     return;
00254   }
00255   if (rotateLogFile(ui.lineFile->text())) {
00256     saveSetting(SETTING_LOGFILE, ui.lineFile->text());
00257     saveSetting(SETTING_ENABLE_LOGFILE, _logFile.isOpen());
00258   }
00259   ui.lineFile->setText(QDir::convertSeparators(ui.lineFile->text()));
00260   ui.chkEnableLogFile->setChecked(_logFile.isOpen());
00261 
00262   /* Update the maximum displayed item count */
00263   saveSetting(SETTING_MAX_MSG_COUNT, ui.spnbxMaxCount->value());
00264   ui.listMessages->setMaximumMessageCount(ui.spnbxMaxCount->value());
00265   ui.listNotifications->setMaximumItemCount(ui.spnbxMaxCount->value());
00266 
00267   /* Save message filter and refilter the list */
00268   uint filter = 0;
00269   ADD_TO_FILTER(filter, tc::ErrorSeverity, ui.chkTorErr->isChecked());
00270   ADD_TO_FILTER(filter, tc::WarnSeverity, ui.chkTorWarn->isChecked());
00271   ADD_TO_FILTER(filter, tc::NoticeSeverity, ui.chkTorNote->isChecked());
00272   ADD_TO_FILTER(filter, tc::InfoSeverity, ui.chkTorInfo->isChecked());
00273   ADD_TO_FILTER(filter, tc::DebugSeverity, ui.chkTorDebug->isChecked());
00274   saveSetting(SETTING_MSG_FILTER, filter);
00275   registerLogEvents();
00276 
00277   /* Filter the message log */
00278   QApplication::setOverrideCursor(Qt::WaitCursor);
00279   ui.listMessages->filter(_filter);
00280   QApplication::restoreOverrideCursor();
00281 
00282   /* Hide the settings frame and reset toggle button*/
00283   ui.actionSettings->toggle();
00284 }
00285 
00286 /** Simply restores the previously saved settings and hides the settings
00287  * frame. */
00288 void
00289 MessageLog::cancelChanges()
00290 {
00291   /* Hide the settings frame and reset toggle button */
00292   ui.actionSettings->toggle();
00293   /* Reload the settings */
00294   loadSettings();
00295 }
00296 
00297 /** Called when the user clicks "Browse" to select a new log file. */
00298 void
00299 MessageLog::browse()
00300 {
00301   /* Strangely, QFileDialog returns a non seperator converted path. */
00302   QString filename = QDir::convertSeparators(
00303                           QFileDialog::getSaveFileName(this,
00304                               tr("Select Log File"), "tor-log.txt"));
00305   if (!filename.isEmpty()) {
00306     ui.lineFile->setText(filename);
00307   }
00308 }
00309 
00310 /** Saves the given list of items to a file.
00311  * \param items A list of log message items to save.
00312  */
00313 void
00314 MessageLog::save(const QStringList &messages)
00315 {
00316   if (!messages.size()) {
00317     return;
00318   }
00319 
00320   QString fileName = QFileDialog::getSaveFileName(this,
00321                           tr("Save Log Messages"),
00322                           "VidaliaLog-" +
00323                           QDateTime::currentDateTime().toString("MM.dd.yyyy")
00324                           + ".txt", tr("Text Files (*.txt)"));
00325 
00326   /* If the choose to save */
00327   if (!fileName.isEmpty()) {
00328     LogFile logFile;
00329     QString errmsg;
00330 
00331     /* If can't write to file, show error message */
00332     if (!logFile.open(fileName, &errmsg)) {
00333       VMessageBox::warning(this, tr("Vidalia"),
00334                            p(tr("Cannot write file %1\n\n%2."))
00335                                                 .arg(fileName)
00336                                                 .arg(errmsg),
00337                            VMessageBox::Ok);
00338       return;
00339     }
00340 
00341     /* Write out the message log to the file */
00342     QApplication::setOverrideCursor(Qt::WaitCursor);
00343     foreach (QString msg, messages) {
00344       logFile << msg << "\n";
00345     }
00346     QApplication::restoreOverrideCursor();
00347   }
00348 }
00349 
00350 /** Saves currently selected messages to a file. */
00351 void
00352 MessageLog::saveSelected()
00353 {
00354   if (ui.tabWidget->currentIndex() == 0)
00355     save(ui.listNotifications->selectedEvents());
00356   else
00357     save(ui.listMessages->selectedMessages());
00358 }
00359 
00360 /** Saves all shown messages to a file. */
00361 void
00362 MessageLog::saveAll()
00363 {
00364   if (ui.tabWidget->currentIndex() == 0)
00365     save(ui.listNotifications->allEvents());
00366   else
00367     save(ui.listMessages->allMessages());
00368 }
00369 
00370 void
00371 MessageLog::selectAll()
00372 {
00373   if (ui.tabWidget->currentIndex() == 0)
00374     ui.listNotifications->selectAll();
00375   else
00376     ui.listMessages->selectAll();
00377 }
00378 
00379 /** Copies contents of currently selected messages to the 'clipboard'. */
00380 void
00381 MessageLog::copy()
00382 {
00383   QString contents;
00384 
00385   if (ui.tabWidget->currentIndex() == 0)
00386     contents = ui.listNotifications->selectedEvents().join("\n");
00387   else
00388     contents = ui.listMessages->selectedMessages().join("\n");
00389 
00390   if (!contents.isEmpty()) {
00391     /* Copy the selected messages to the clipboard */
00392     QApplication::clipboard()->setText(contents);
00393   }
00394 }
00395 
00396 /** Clears all log messages or status notifications, depending on which tab
00397  * is currently visible. */
00398 void
00399 MessageLog::clear()
00400 {
00401   if (ui.tabWidget->currentIndex() == 0)
00402     ui.listNotifications->clear();
00403   else
00404     ui.listMessages->clearMessages();
00405 }
00406 
00407 /** Prompts the user for a search string. If the search string is not found in
00408  * any of the currently displayed log entires, then a message will be
00409  * displayed for the user informing them that no matches were found.
00410  * \sa search()
00411  */
00412 void
00413 MessageLog::find()
00414 {
00415   bool ok;
00416   QString text = QInputDialog::getText(this, tr("Find in Message Log"),
00417                   tr("Find:"), QLineEdit::Normal, QString(), &ok);
00418 
00419   if (ok && !text.isEmpty()) {
00420     QTreeWidget *tree;
00421     QTreeWidgetItem *firstItem = 0;
00422 
00423     /* Pick the right tree widget to search based on the current tab */
00424     if (ui.tabWidget->currentIndex() == 0) {
00425       QList<StatusEventItem *> results = ui.listNotifications->find(text, true);
00426       if (results.size() > 0) {
00427         tree = ui.listNotifications;
00428         firstItem = dynamic_cast<QTreeWidgetItem *>(results.at(0));
00429       }
00430     } else {
00431       QList<LogTreeItem *> results = ui.listMessages->find(text, true);
00432       if (results.size() > 0) {
00433         tree = ui.listMessages;
00434         firstItem = dynamic_cast<QTreeWidgetItem *>(results.at(0));
00435       }
00436     }
00437 
00438     if (! firstItem) {
00439       VMessageBox::information(this, tr("Not Found"),
00440                                p(tr("Search found 0 matches.")),
00441                                VMessageBox::Ok);
00442     } else {
00443       tree->scrollToItem(firstItem);
00444     }
00445   }
00446 }
00447 
00448 /** Writes a message to the Message History and tags it with
00449  * the proper date, time and type.
00450  * \param type The message's severity type.
00451  * \param message The log message to be added.
00452  */
00453 void
00454 MessageLog::log(tc::Severity type, const QString &message)
00455 {
00456   setUpdatesEnabled(false);  
00457   /* Only add the message if it's not being filtered out */
00458   if (_filter & (uint)type) {
00459     /* Add the message to the list and scroll to it if necessary. */
00460     LogTreeItem *item = ui.listMessages->log(type, message);
00461 
00462     /* This is a workaround to force Qt to update the statusbar text (if any
00463      * is currently displayed) to reflect the new message added. */
00464     QString currStatusTip = ui.statusbar->currentMessage();
00465     if (!currStatusTip.isEmpty()) {
00466       currStatusTip = ui.listMessages->statusTip();
00467       ui.statusbar->showMessage(currStatusTip);
00468     }
00469 
00470     /* If we're saving log messages to a file, go ahead and do that now */
00471     if (_enableLogging) {
00472       _logFile << item->toString() << "\n";
00473     }
00474   }
00475   setUpdatesEnabled(true);  
00476 }
00477 
00478 /** Displays help information about the message log. */
00479 void
00480 MessageLog::help()
00481 {
00482   emit helpRequested("log");
00483 }
00484