]> vaikene.ee Git - evaf/blob - src/plugins/LogView/logview.cpp
Implemented the LogView module that shows messages output using the Common::iLogger...
[evaf] / src / plugins / LogView / logview.cpp
1 /**
2 * @file LogView/logview.cpp
3 * @brief Implementation of the LogView module
4 * @author Enar Vaikene
5 *
6 * Copyright (c) 2011 Enar Vaikene
7 *
8 * This file is part of the eVaf C++ cross-platform application development framework.
9 *
10 * This file can be used under the terms of the GNU General Public License
11 * version 3.0 as published by the Free Software Foundation and appearing in
12 * the file LICENSE included in the packaging of this file. Please review the
13 * the following information to ensure the GNU General Public License version
14 * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
15 *
16 * Alternatively, this file may be used in accordance with the Commercial License
17 * Agreement provided with the Software.
18 */
19
20 #include "logview.h"
21 #include "version.h"
22
23 #include <Common/Globals>
24 #include <Common/iLogger>
25 #include <Common/iApp>
26
27 #include <QtGui>
28
29
30 using namespace eVaf;
31 using namespace eVaf::LogView::Internal;
32
33
34 //-------------------------------------------------------------------
35
36 int const Model::MaxLines = 1000;
37
38 char const * const Model::SeverityText[Common::iLogger::Count] = {
39 QT_TR_NOOP("[NONE] "),
40 QT_TR_NOOP("[FATAL] "),
41 QT_TR_NOOP("[ERROR] "),
42 QT_TR_NOOP("[WARNING]"),
43 QT_TR_NOOP("[INFO] "),
44 QT_TR_NOOP("[DEBUG] ")
45 };
46
47 Model::Model(QObject * parent)
48 : QAbstractListModel(parent)
49 {
50 }
51
52 QVariant Model::data(QModelIndex const & index, int role) const
53 {
54 if (!index.isValid() || index.row() < 0 || index.row() >= mData.size() || index.column() != 0)
55 return QVariant();
56
57 switch (role) {
58
59 // Return the message for the display role
60 case Qt::DisplayRole: {
61 return mData.at(index.row()).simplified;
62 break;
63 }
64
65 // Change color for different message types
66 case Qt::ForegroundRole: {
67 Common::iLogger::Severity s = mData.at(index.row()).severity;
68 switch (s) {
69 case Common::iLogger::Info:
70 return QBrush(QColor(Qt::blue));
71 break;
72 case Common::iLogger::Warning:
73 return QBrush(QColor(Qt::black));
74 break;
75 case Common::iLogger::Error:
76 case Common::iLogger::Fatal:
77 return QBrush(QColor(Qt::red));
78 break;
79 default:
80 return QVariant();
81 }
82 break;
83 }
84 } // switch (role)
85
86 return QVariant();
87 }
88
89 void Model::addMessage(Common::iLogger::Severity severity, QString const & text, QString const & where)
90 {
91 // Add the message to the end of the queue
92 beginInsertRows(QModelIndex(), mData.size(), mData.size());
93 mData.enqueue(Message(severity, text, where));
94 endInsertRows();
95
96 // Remove oldest messages if the list is full
97 if (mData.size() > MaxLines) {
98 beginRemoveRows(QModelIndex(), 0, 0);
99 mData.dequeue();
100 endRemoveRows();
101 }
102
103 emit messageAdded(index(mData.size() - 1, 0));
104 }
105
106 QString Model::details(QModelIndex const & index) const
107 {
108 Message const & m = mData.at(index.row());
109 return tr("%1 %2 %3 : %4\nOccurred in %5")
110 .arg(m.dt.date().toString(Qt::DefaultLocaleShortDate))
111 .arg(m.dt.time().toString("HH:mm:ss.zzz"))
112 .arg(tr(severityText(m.severity)))
113 .arg(m.text)
114 .arg(m.where);
115 }
116
117 bool Model::copyToClipboard(QModelIndex const & index)
118 {
119 mErrorString.clear();
120
121 QClipboard * cb = QApplication::clipboard();
122 if (cb) {
123 cb->setText(details(index));
124 return true;
125 }
126 else {
127 mErrorString = tr("The global clipboard is not available");
128 return false;
129 }
130 }
131
132 bool Model::saveToFile(QString const & fileName)
133 {
134 mErrorString.clear();
135
136 QFile f(fileName);
137 if (!f.open(QFile::WriteOnly)) {
138 mErrorString = tr("Failed to open the file '%1' for writing : %2").arg(fileName).arg(f.errorString());
139 return false;
140 }
141
142 QTextStream out(&f);
143
144 for (int i = 0; i < mData.size(); ++i) {
145 Message const & m = mData.at(i);
146 out << tr("%1 %2 %3 : %4 (occurred in %5)\n")
147 .arg(m.dt.date().toString(Qt::DefaultLocaleShortDate))
148 .arg(m.dt.time().toString("HH:mm:ss.zzz"))
149 .arg(tr(severityText(m.severity)))
150 .arg(m.text)
151 .arg(m.where)
152 << endl;
153 }
154
155 return true;
156 }
157
158 char const * const Model::severityText(Common::iLogger::Severity s) const
159 {
160 if (s >= Common::iLogger::None && s < Common::iLogger::Count)
161 return SeverityText[s];
162 else
163 return SeverityText[Common::iLogger::None];
164 }
165
166
167 //-------------------------------------------------------------------
168
169 Widget::Widget(QString const & source, QWidget * parent)
170 : QWidget(parent)
171 , mSource(source)
172 , mAutoScroll(true)
173 {
174 QVBoxLayout * w = new QVBoxLayout;
175 setLayout(w);
176
177 mModel = new Model(this);
178 connect(mModel, SIGNAL(messageAdded(QModelIndex)), this, SLOT(messageAdded(QModelIndex)));
179
180 wList = new QListView;
181 wList->setModel(mModel);
182 wList->setUniformItemSizes(true);
183 connect(wList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(currentChanged(QModelIndex,QModelIndex)));
184 w->addWidget(wList);
185
186 wList->setContextMenuPolicy(Qt::ActionsContextMenu);
187
188 QAction * a = new QAction(tr("&Copy", VER_MODULE_NAME_STR), this);
189 a->setStatusTip(tr("Copies the selected message to the clipboard for pasting into other applications", VER_MODULE_NAME_STR));
190 connect(a, SIGNAL(triggered()), this, SLOT(copyToClipboard()));
191 wList->addAction(a);
192
193 a = new QAction(tr("&Save to ...", VER_MODULE_NAME_STR), this);
194 a->setStatusTip(tr("Saves all the messages to a file", VER_MODULE_NAME_STR));
195 connect(a, SIGNAL(triggered()), this, SLOT(saveToFile()));
196 wList->addAction(a);
197
198 wDetails = new QLabel;
199 wDetails->setWordWrap(true);
200 w->addWidget(wDetails);
201 }
202
203 void Widget::messageAdded(QModelIndex const & index)
204 {
205 if (mAutoScroll)
206 wList->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
207 }
208
209 void Widget::currentChanged(QModelIndex const & current, QModelIndex const & previous)
210 {
211 Q_UNUSED(previous);
212
213 if (!current.isValid() || current.row() < 0 || current.row() > mModel->rowCount()) {
214 wDetails->clear();
215 return;
216 }
217
218 mAutoScroll = current.row() == (mModel->rowCount() - 1);
219
220 wDetails->setText(mModel->details(current));
221 }
222
223 void Widget::copyToClipboard()
224 {
225 QModelIndex idx = wList->selectionModel()->currentIndex();
226 if (idx.isValid())
227 mModel->copyToClipboard(idx);
228 }
229
230 void Widget::saveToFile()
231 {
232 QString fileName = QFileDialog::getSaveFileName(this,
233 tr("Save to file", VER_MODULE_NAME_STR),
234 Common::iApp::instance()->dataRootDir() + QString("%1_%2.txt").arg(mSource).arg(QDate::currentDate().toString("yyyyMMdd")),
235 tr("Text files (*.txt);;All files (*)", VER_MODULE_NAME_STR));
236 if (fileName.isEmpty())
237 return;
238
239 if (!mModel->saveToFile(fileName))
240 QMessageBox::critical(this, tr("Error", VER_MODULE_NAME_STR), mModel->errorString());
241 }
242
243
244 //-------------------------------------------------------------------
245
246 Window::Window(QWidget * parent, Qt::WindowFlags flags)
247 : QWidget(parent, flags)
248 {
249 setObjectName(QString("%1-Window").arg(VER_MODULE_NAME_STR));
250
251 setWindowTitle(tr("Messages"));
252
253 Common::iLogger * logger = Common::iLogger::instance();
254 EVAF_TEST_X(logger, "No iLogger interface");
255
256 mDefSource = logger->defaultSource();
257 if (mDefSource.isEmpty())
258 mDefSource = "Common";
259
260 QVBoxLayout * w = new QVBoxLayout;
261 w->setMargin(0);
262 setLayout(w);
263
264 wTabs = new QTabWidget;
265 w->addWidget(wTabs);
266
267 // Add the default source
268 Widget * s = new Widget(mDefSource);
269 mLogViews.insert(s->source(), s);
270 wTabs->addTab(s, s->source());
271
272 wStatusBar = new QStatusBar;
273 w->addWidget(wStatusBar);
274
275 QAction * a = new QAction(this);
276 a->setShortcut(Qt::Key_Escape);
277 connect(a, SIGNAL(triggered()), this, SLOT(close()));
278 addAction(a);
279
280 restoreSettings();
281
282 connect(logger, SIGNAL(loggerEvent(Common::iLogger::Severity,QString,QString,QString)), this, SLOT(loggerEvent(Common::iLogger::Severity,QString,QString,QString)));
283
284 show();
285
286 EVAF_INFO("%s created", qPrintable(objectName()));
287 }
288
289 Window::~Window()
290 {
291 mLogViews.clear();
292
293 saveSettings();
294
295 EVAF_INFO("%s destroyed", qPrintable(objectName()));
296 }
297
298 bool Window::event(QEvent * e)
299 {
300 if (e->type() == QEvent::StatusTip) {
301 QStatusTipEvent * event = static_cast<QStatusTipEvent *>(e);
302 wStatusBar->showMessage(event->tip());
303 return true;
304 }
305 return QWidget::event(e);
306 }
307
308 void Window::saveSettings()
309 {
310 static int ver[4] = {VER_FILE_VERSION};
311 QSettings settings(VER_COMPANY_NAME_STR, Common::iApp::instance()->name());
312 settings.setValue(QString("%1/version/major").arg(objectName()), ver[0]);
313 settings.setValue(QString("%1/version/minor").arg(objectName()), ver[1]);
314 settings.setValue(QString("%1/geometry").arg(objectName()), saveGeometry());
315 }
316
317 void Window::restoreSettings()
318 {
319 static int ver[4] = {VER_FILE_VERSION};
320 QSettings settings(VER_COMPANY_NAME_STR, Common::iApp::instance()->name());
321
322 // Ignore saved settings if the version number is not the same
323 // More intelligent checks can be implemented to allow upgrading from previous versions
324 QVariant v = settings.value(QString("%1/version/major").arg(objectName()));
325 if (!v.isValid() || v.toInt() != ver[0])
326 return;
327 v = settings.value(QString("%1/version/minor").arg(objectName()));
328 if (!v.isValid() || v.toInt() != ver[1])
329 return;
330
331 // Restore the geometry
332 restoreGeometry(settings.value(QString("%1/geometry").arg(objectName())).toByteArray());
333 }
334
335 void Window::loggerEvent(Common::iLogger::Severity severity, QString const & text, QString const & source, QString const & where)
336 {
337 // Ignore messages with >=DEBUG severity level
338 if (severity >= Common::iLogger::Debug)
339 return;
340
341 // Find or create the log view widget for this source
342 Widget * w = 0;
343 QString s = source.isEmpty() ? mDefSource : source;
344 QHash<QString, Widget *>::const_iterator it = mLogViews.constFind(s);
345 if (it == mLogViews.constEnd()) {
346 w = new Widget(s);
347 mLogViews.insert(w->source(), w);
348 wTabs->addTab(w, w->source());
349 }
350 else
351 w = *it;
352
353 w->addMessage(severity, text, where);
354 }
355
356
357 //-------------------------------------------------------------------
358
359 Module::Module()
360 : Plugins::iPlugin()
361 , wWindow(0)
362 {
363 setObjectName(QString("%1-Module").arg(VER_MODULE_NAME_STR));
364 EVAF_INFO("%s created", qPrintable(objectName()));
365 }
366
367 Module::~Module()
368 {
369 EVAF_INFO("%s destroyed", qPrintable(objectName()));
370 }
371
372 bool Module::init(QString const & args)
373 {
374 Q_UNUSED(args);
375
376 wWindow = new Window();
377
378 EVAF_INFO("%s initialized", qPrintable(objectName()));
379
380 return true;
381 }
382
383 void Module::done()
384 {
385 if (wWindow) {
386 delete wWindow;
387 wWindow = 0;
388 }
389
390 EVAF_INFO("%s finalized", qPrintable(objectName()));
391 }