Vidalia  0.2.21
MessageLog.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 /*
12 ** \file MessageLog.cpp
13 ** \brief Displays log messages and message log settings
14 */
15 
16 #include "MessageLog.h"
17 #include "StatusEventItem.h"
18 #include "Vidalia.h"
19 #include "VMessageBox.h"
20 
21 #include "html.h"
22 
23 #include <QMessageBox>
24 #include <QFileDialog>
25 #include <QInputDialog>
26 #include <QMessageBox>
27 #include <QClipboard>
28 
29 /* Message log settings */
30 #define SETTING_MSG_FILTER "MessageFilter"
31 #define SETTING_MAX_MSG_COUNT "MaxMsgCount"
32 #define SETTING_ENABLE_LOGFILE "EnableLogFile"
33 #define SETTING_LOGFILE "LogFile"
34 #define DEFAULT_MSG_FILTER \
35  (tc::ErrorSeverity|tc::WarnSeverity|tc::NoticeSeverity)
36 #define DEFAULT_MAX_MSG_COUNT 50
37 #define DEFAULT_ENABLE_LOGFILE false
38 #if defined(Q_OS_WIN32)
39 
40 /** Default location of the log file to which log messages will be written. */
41 #define DEFAULT_LOGFILE \
42  (win32_program_files_folder()+"\\Tor\\tor-log.txt")
43 #else
44 #define DEFAULT_LOGFILE ("/var/log/tor/tor.log")
45 #endif
46 
47 #define ADD_TO_FILTER(f,v,b) (f = ((b) ? ((f) | (v)) : ((f) & ~(v))))
48 
49 
50 /** Constructor. The constructor will load the message log's settings from
51  * VidaliSettings and register for log events according to the most recently
52  * set severity filter.
53  * \param torControl A TorControl object used to register for log events.
54  * \param parent The parent widget of this MessageLog object.
55  * \param flags Any desired window creation flags.
56  */
57 MessageLog::MessageLog(QWidget *parent, Qt::WFlags flags)
58 : VidaliaWindow("MessageLog", parent, flags)
59 {
60  /* Invoke Qt Designer generated QObject setup routine */
61  ui.setupUi(this);
62 
63  /* Create necessary Message Log QObjects */
65  connect(_torControl, SIGNAL(logMessage(tc::Severity, QString)),
66  this, SLOT(log(tc::Severity, QString)));
67 
68  /* Bind events to actions */
69  createActions();
70 
71  /* Set tooltips for necessary widgets */
72  setToolTips();
73 
74  /* Load the message log's stored settings */
75  loadSettings();
76 
77  /* Sort in ascending chronological order */
78  ui.listMessages->sortItems(LogTreeWidget::TimeColumn,
79  Qt::AscendingOrder);
80  ui.listNotifications->sortItems(0, Qt::AscendingOrder);
81 }
82 
83 /** Default Destructor. Simply frees up any memory allocated for member
84  * variables. */
86 {
87  _logFile.close();
88 }
89 
90 /** Binds events (signals) to actions (slots). */
91 void
93 {
94  connect(ui.actionSave_Selected, SIGNAL(triggered()),
95  this, SLOT(saveSelected()));
96 
97  connect(ui.actionSave_All, SIGNAL(triggered()),
98  this, SLOT(saveAll()));
99 
100  connect(ui.actionSelect_All, SIGNAL(triggered()),
101  this, SLOT(selectAll()));
102 
103  connect(ui.actionCopy, SIGNAL(triggered()),
104  this, SLOT(copy()));
105 
106  connect(ui.actionFind, SIGNAL(triggered()),
107  this, SLOT(find()));
108 
109  connect(ui.actionClear, SIGNAL(triggered()),
110  this, SLOT(clear()));
111 
112  connect(ui.actionHelp, SIGNAL(triggered()),
113  this, SLOT(help()));
114 
115  connect(ui.btnSaveSettings, SIGNAL(clicked()),
116  this, SLOT(saveSettings()));
117 
118  connect(ui.btnCancelSettings, SIGNAL(clicked()),
119  this, SLOT(cancelChanges()));
120 
121  connect(ui.btnBrowse, SIGNAL(clicked()),
122  this, SLOT(browse()));
123 
124 #if defined(Q_WS_MAC)
125  ui.actionHelp->setShortcut(QString("Ctrl+?"));
126 #endif
127  ui.actionClose->setShortcut(QString("Esc"));
128  Vidalia::createShortcut("Ctrl+W", this, ui.actionClose, SLOT(trigger()));
129 }
130 
131 /** Set tooltips for Message Filter checkboxes in code because they are long
132  * and Designer wouldn't let us insert newlines into the text. */
133 void
135 {
136  ui.chkTorErr->setToolTip(tr("Messages that appear when something has \n"
137  "gone very wrong and Tor cannot proceed."));
138  ui.chkTorWarn->setToolTip(tr("Messages that only appear when \n"
139  "something has gone wrong with Tor."));
140  ui.chkTorNote->setToolTip(tr("Messages that appear infrequently \n"
141  "during normal Tor operation and are \n"
142  "not considered errors, but you may \n"
143  "care about."));
144  ui.chkTorInfo->setToolTip(tr("Messages that appear frequently \n"
145  "during normal Tor operation."));
146  ui.chkTorDebug->setToolTip(tr("Hyper-verbose messages primarily of \n"
147  "interest to Tor developers."));
148 }
149 
150 /** Called when the user changes the UI translation. */
151 void
153 {
154  ui.retranslateUi(this);
155  setToolTips();
156 }
157 
158 /** Loads the saved Message Log settings */
159 void
161 {
162  /* Set Max Count widget */
163  uint maxMsgCount = getSetting(SETTING_MAX_MSG_COUNT,
164  DEFAULT_MAX_MSG_COUNT).toUInt();
165  ui.spnbxMaxCount->setValue(maxMsgCount);
166  ui.listMessages->setMaximumMessageCount(maxMsgCount);
167  ui.listNotifications->setMaximumItemCount(maxMsgCount);
168 
169  /* Set whether or not logging to file is enabled */
171  DEFAULT_ENABLE_LOGFILE).toBool();
172  QString logfile = getSetting(SETTING_LOGFILE,
173  DEFAULT_LOGFILE).toString();
174  ui.lineFile->setText(QDir::convertSeparators(logfile));
175  rotateLogFile(logfile);
176  ui.chkEnableLogFile->setChecked(_logFile.isOpen());
177 
178  /* Set the checkboxes accordingly */
180  ui.chkTorErr->setChecked(_filter & tc::ErrorSeverity);
181  ui.chkTorWarn->setChecked(_filter & tc::WarnSeverity);
182  ui.chkTorNote->setChecked(_filter & tc::NoticeSeverity);
183  ui.chkTorInfo->setChecked(_filter & tc::InfoSeverity);
184  ui.chkTorDebug->setChecked(_filter & tc::DebugSeverity);
186 
187  /* Filter the message log */
188  QApplication::setOverrideCursor(Qt::WaitCursor);
189  ui.listMessages->filter(_filter);
190  QApplication::restoreOverrideCursor();
191 }
192 
193 /** Attempts to register the selected message filter with Tor and displays an
194  * error if setting the events fails. */
195 void
197 {
200  _filter & tc::DebugSeverity, false);
202  _filter & tc::InfoSeverity, false);
204  _filter & tc::NoticeSeverity, false);
206  _filter & tc::WarnSeverity, false);
208  _filter & tc::ErrorSeverity, false);
209 
210  QString errmsg;
211  if (_torControl->isConnected() && !_torControl->setEvents(&errmsg)) {
212  VMessageBox::warning(this, tr("Error Setting Filter"),
213  p(tr("Vidalia was unable to register for Tor's log events.")) + p(errmsg),
215  }
216 }
217 
218 /** Opens a log file if necessary, or closes it if logging is disabled. If a
219  * log file is already opened and a new filename is specified, then the log
220  * file will be rotated to the new filename. In the case that the new filename
221  * can not be openend, the old file will remain open and writable. */
222 bool
223 MessageLog::rotateLogFile(const QString &filename)
224 {
225  QString errmsg;
226  if (_enableLogging) {
227  if (!_logFile.open(filename, &errmsg)) {
228  VMessageBox::warning(this, tr("Error Opening Log File"),
229  p(tr("Vidalia was unable to open the specified log file."))+p(errmsg),
231  return false;
232  }
233  } else {
234  /* Close the log file. */
235  _logFile.close();
236  }
237  return true;
238 }
239 
240 /** Saves the Message Log settings, adjusts the message list if required, and
241  * then hides the settings frame. */
242 void
244 {
245  /* Update the logging status */
246  _enableLogging = ui.chkEnableLogFile->isChecked();
247  if (_enableLogging && ui.lineFile->text().isEmpty()) {
248  /* The user chose to enable logging messages to a file, but didn't specify
249  * a log filename. */
250  VMessageBox::warning(this, tr("Log Filename Required"),
251  p(tr("You must enter a filename to be able to save log "
252  "messages to a file.")), VMessageBox::Ok);
253  return;
254  }
255  if (rotateLogFile(ui.lineFile->text())) {
256  saveSetting(SETTING_LOGFILE, ui.lineFile->text());
258  }
259  ui.lineFile->setText(QDir::convertSeparators(ui.lineFile->text()));
260  ui.chkEnableLogFile->setChecked(_logFile.isOpen());
261 
262  /* Update the maximum displayed item count */
263  saveSetting(SETTING_MAX_MSG_COUNT, ui.spnbxMaxCount->value());
264  ui.listMessages->setMaximumMessageCount(ui.spnbxMaxCount->value());
265  ui.listNotifications->setMaximumItemCount(ui.spnbxMaxCount->value());
266 
267  /* Save message filter and refilter the list */
268  uint filter = 0;
269  ADD_TO_FILTER(filter, tc::ErrorSeverity, ui.chkTorErr->isChecked());
270  ADD_TO_FILTER(filter, tc::WarnSeverity, ui.chkTorWarn->isChecked());
271  ADD_TO_FILTER(filter, tc::NoticeSeverity, ui.chkTorNote->isChecked());
272  ADD_TO_FILTER(filter, tc::InfoSeverity, ui.chkTorInfo->isChecked());
273  ADD_TO_FILTER(filter, tc::DebugSeverity, ui.chkTorDebug->isChecked());
276 
277  /* Filter the message log */
278  QApplication::setOverrideCursor(Qt::WaitCursor);
279  ui.listMessages->filter(_filter);
280  QApplication::restoreOverrideCursor();
281 
282  /* Hide the settings frame and reset toggle button*/
283  ui.actionSettings->toggle();
284 }
285 
286 /** Simply restores the previously saved settings and hides the settings
287  * frame. */
288 void
290 {
291  /* Hide the settings frame and reset toggle button */
292  ui.actionSettings->toggle();
293  /* Reload the settings */
294  loadSettings();
295 }
296 
297 /** Called when the user clicks "Browse" to select a new log file. */
298 void
300 {
301  /* Strangely, QFileDialog returns a non seperator converted path. */
302  QString filename = QDir::convertSeparators(
303  QFileDialog::getSaveFileName(this,
304  tr("Select Log File"), "tor-log.txt"));
305  if (!filename.isEmpty()) {
306  ui.lineFile->setText(filename);
307  }
308 }
309 
310 /** Saves the given list of items to a file.
311  * \param items A list of log message items to save.
312  */
313 void
314 MessageLog::save(const QStringList &messages)
315 {
316  if (!messages.size()) {
317  return;
318  }
319 
320  QString fileName = QFileDialog::getSaveFileName(this,
321  tr("Save Log Messages"),
322  "VidaliaLog-" +
323  QDateTime::currentDateTime().toString("MM.dd.yyyy")
324  + ".txt", tr("Text Files (*.txt)"));
325 
326  /* If the choose to save */
327  if (!fileName.isEmpty()) {
328  LogFile logFile;
329  QString errmsg;
330 
331  /* If can't write to file, show error message */
332  if (!logFile.open(fileName, &errmsg)) {
333  VMessageBox::warning(this, tr("Vidalia"),
334  p(tr("Cannot write file %1\n\n%2."))
335  .arg(fileName)
336  .arg(errmsg),
338  return;
339  }
340 
341  /* Write out the message log to the file */
342  QApplication::setOverrideCursor(Qt::WaitCursor);
343  foreach (QString msg, messages) {
344  logFile << msg << "\n";
345  }
346  QApplication::restoreOverrideCursor();
347  }
348 }
349 
350 /** Saves currently selected messages to a file. */
351 void
353 {
354  if (ui.tabWidget->currentIndex() == 0)
355  save(ui.listNotifications->selectedEvents());
356  else
357  save(ui.listMessages->selectedMessages());
358 }
359 
360 /** Saves all shown messages to a file. */
361 void
363 {
364  if (ui.tabWidget->currentIndex() == 0)
365  save(ui.listNotifications->allEvents());
366  else
367  save(ui.listMessages->allMessages());
368 }
369 
370 void
372 {
373  if (ui.tabWidget->currentIndex() == 0)
374  ui.listNotifications->selectAll();
375  else
376  ui.listMessages->selectAll();
377 }
378 
379 /** Copies contents of currently selected messages to the 'clipboard'. */
380 void
382 {
383  QString contents;
384 
385  if (ui.tabWidget->currentIndex() == 0)
386  contents = ui.listNotifications->selectedEvents().join("\n");
387  else
388  contents = ui.listMessages->selectedMessages().join("\n");
389 
390  if (!contents.isEmpty()) {
391  /* Copy the selected messages to the clipboard */
392  QApplication::clipboard()->setText(contents);
393  }
394 }
395 
396 /** Clears all log messages or status notifications, depending on which tab
397  * is currently visible. */
398 void
400 {
401  if (ui.tabWidget->currentIndex() == 0)
402  ui.listNotifications->clear();
403  else
404  ui.listMessages->clearMessages();
405 }
406 
407 /** Prompts the user for a search string. If the search string is not found in
408  * any of the currently displayed log entires, then a message will be
409  * displayed for the user informing them that no matches were found.
410  * \sa search()
411  */
412 void
414 {
415  bool ok;
416  QString text = QInputDialog::getText(this, tr("Find in Message Log"),
417  tr("Find:"), QLineEdit::Normal, QString(), &ok);
418 
419  if (ok && !text.isEmpty()) {
420  QTreeWidget *tree;
421  QTreeWidgetItem *firstItem = 0;
422 
423  /* Pick the right tree widget to search based on the current tab */
424  if (ui.tabWidget->currentIndex() == 0) {
425  QList<StatusEventItem *> results = ui.listNotifications->find(text, true);
426  if (results.size() > 0) {
427  tree = ui.listNotifications;
428  firstItem = dynamic_cast<QTreeWidgetItem *>(results.at(0));
429  }
430  } else {
431  QList<LogTreeItem *> results = ui.listMessages->find(text, true);
432  if (results.size() > 0) {
433  tree = ui.listMessages;
434  firstItem = dynamic_cast<QTreeWidgetItem *>(results.at(0));
435  }
436  }
437 
438  if (! firstItem) {
439  VMessageBox::information(this, tr("Not Found"),
440  p(tr("Search found 0 matches.")),
442  } else {
443  tree->scrollToItem(firstItem);
444  }
445  }
446 }
447 
448 /** Writes a message to the Message History and tags it with
449  * the proper date, time and type.
450  * \param type The message's severity type.
451  * \param message The log message to be added.
452  */
453 void
454 MessageLog::log(tc::Severity type, const QString &message)
455 {
456  setUpdatesEnabled(false);
457  /* Only add the message if it's not being filtered out */
458  if (_filter & (uint)type) {
459  /* Add the message to the list and scroll to it if necessary. */
460  LogTreeItem *item = ui.listMessages->log(type, message);
461 
462  /* This is a workaround to force Qt to update the statusbar text (if any
463  * is currently displayed) to reflect the new message added. */
464  QString currStatusTip = ui.statusbar->currentMessage();
465  if (!currStatusTip.isEmpty()) {
466  currStatusTip = ui.listMessages->statusTip();
467  ui.statusbar->showMessage(currStatusTip);
468  }
469 
470  /* If we're saving log messages to a file, go ahead and do that now */
471  if (_enableLogging) {
472  _logFile << item->toString() << "\n";
473  }
474  }
475  setUpdatesEnabled(true);
476 }
477 
478 /** Displays help information about the message log. */
479 void
481 {
482  emit helpRequested("log");
483 }
484