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