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