Vidalia
0.2.17
|
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