/** * @file LogView/logview.cpp * @brief Implementation of the LogView module * @author Enar Vaikene * * Copyright (c) 2011-2019 Enar Vaikene * * This file is part of the eVaf C++ cross-platform application development framework. * * This file can be used under the terms of the GNU General Public License * version 3.0 as published by the Free Software Foundation and appearing in * the file LICENSE included in the packaging of this file. Please review the * the following information to ensure the GNU General Public License version * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. * * Alternatively, this file may be used in accordance with the Commercial License * Agreement provided with the Software. */ #include "logview.h" #include "version.h" #include #include #include #include #include #include #include using namespace eVaf; using namespace eVaf::LogView::Internal; //------------------------------------------------------------------- int const Model::MaxLines = 1000; char const * const Model::SeverityText[Common::iLogger::Count] = { QT_TR_NOOP("[NONE] "), QT_TR_NOOP("[FATAL] "), QT_TR_NOOP("[ERROR] "), QT_TR_NOOP("[WARNING]"), QT_TR_NOOP("[INFO] "), QT_TR_NOOP("[DEBUG] ") }; Model::Model(QObject * parent) : QAbstractListModel(parent) { } QVariant Model::data(QModelIndex const & index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= mData.size() || index.column() != 0) return QVariant(); switch (role) { // Return the message for the display role case Qt::DisplayRole: { return mData.at(index.row()).simplified; } // Change color for different message types case Qt::ForegroundRole: { Common::iLogger::Severity s = mData.at(index.row()).severity; switch (s) { case Common::iLogger::Info: return QBrush(QColor(Qt::blue)); case Common::iLogger::Warning: return QBrush(QColor(Qt::black)); case Common::iLogger::Error: case Common::iLogger::Fatal: return QBrush(QColor(Qt::red)); default: return QVariant(); } } } // switch (role) return QVariant(); } void Model::addMessage(Common::iLogger::Severity severity, QString const & text, QString const & where) { // Add the message to the end of the queue beginInsertRows(QModelIndex(), mData.size(), mData.size()); mData.enqueue(Message(severity, text, where)); endInsertRows(); // Remove oldest messages if the list is full if (mData.size() > MaxLines) { beginRemoveRows(QModelIndex(), 0, 0); mData.dequeue(); endRemoveRows(); } emit messageAdded(index(mData.size() - 1, 0)); } QString Model::details(QModelIndex const & index) const { Message const & m = mData.at(index.row()); return tr("%1 %2 %3 : %4\nOccurred in %5") .arg(m.dt.date().toString(Qt::DefaultLocaleShortDate)) .arg(m.dt.time().toString("HH:mm:ss.zzz")) .arg(tr(severityText(m.severity))) .arg(m.text) .arg(m.where); } bool Model::copyToClipboard(QModelIndex const & index) { mErrorString.clear(); QClipboard * cb = QApplication::clipboard(); if (cb) { cb->setText(details(index)); return true; } else { mErrorString = tr("The global clipboard is not available"); return false; } } bool Model::saveToFile(QString const & fileName) { mErrorString.clear(); QFile f(fileName); if (!f.open(QFile::WriteOnly)) { mErrorString = tr("Failed to open the file '%1' for writing : %2").arg(fileName).arg(f.errorString()); return false; } QTextStream out(&f); for (int i = 0; i < mData.size(); ++i) { Message const & m = mData.at(i); out << tr("%1 %2 %3 : %4 (occurred in %5)\n") .arg(m.dt.date().toString(Qt::DefaultLocaleShortDate)) .arg(m.dt.time().toString("HH:mm:ss.zzz")) .arg(tr(severityText(m.severity))) .arg(m.text) .arg(m.where) << endl; } return true; } char const * Model::severityText(Common::iLogger::Severity s) const { if (s >= Common::iLogger::None && s < Common::iLogger::Count) return SeverityText[s]; else return SeverityText[Common::iLogger::None]; } //------------------------------------------------------------------- Widget::Widget(QString const & source, QWidget * parent) : QWidget(parent) , mSource(source) , mAutoScroll(true) { QVBoxLayout * w = new QVBoxLayout; setLayout(w); mModel = new Model(this); connect(mModel, SIGNAL(messageAdded(QModelIndex)), this, SLOT(messageAdded(QModelIndex))); wList = new QListView; wList->setModel(mModel); wList->setUniformItemSizes(true); connect(wList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(currentChanged(QModelIndex,QModelIndex))); w->addWidget(wList); wList->setContextMenuPolicy(Qt::ActionsContextMenu); QAction * a = new QAction(tr("&Copy", VER_MODULE_NAME_STR), this); a->setStatusTip(tr("Copies the selected message to the clipboard for pasting into other applications", VER_MODULE_NAME_STR)); connect(a, SIGNAL(triggered()), this, SLOT(copyToClipboard())); wList->addAction(a); a = new QAction(tr("&Save to ...", VER_MODULE_NAME_STR), this); a->setStatusTip(tr("Saves all the messages to a file", VER_MODULE_NAME_STR)); connect(a, SIGNAL(triggered()), this, SLOT(saveToFile())); wList->addAction(a); wDetails = new QLabel; wDetails->setWordWrap(true); w->addWidget(wDetails); } void Widget::messageAdded(QModelIndex const & index) { if (mAutoScroll) wList->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); } void Widget::currentChanged(QModelIndex const & current, QModelIndex const & previous) { Q_UNUSED(previous); if (!current.isValid() || current.row() < 0 || current.row() > mModel->rowCount()) { wDetails->clear(); return; } mAutoScroll = current.row() == (mModel->rowCount() - 1); wDetails->setText(mModel->details(current)); } void Widget::copyToClipboard() { QModelIndex idx = wList->selectionModel()->currentIndex(); if (idx.isValid()) mModel->copyToClipboard(idx); } void Widget::saveToFile() { QString fileName = QFileDialog::getSaveFileName(this, tr("Save to file", VER_MODULE_NAME_STR), Common::iApp::instance()->dataRootDir() + QString("%1_%2.txt").arg(mSource).arg(QDate::currentDate().toString("yyyyMMdd")), tr("Text files (*.txt);;All files (*)", VER_MODULE_NAME_STR)); if (fileName.isEmpty()) return; if (!mModel->saveToFile(fileName)) QMessageBox::critical(this, tr("Error", VER_MODULE_NAME_STR), mModel->errorString()); } //------------------------------------------------------------------- Window::Window(QString const & args, QWidget * parent, Qt::WindowFlags flags) : Gui::Panel(parent, flags) { setObjectName(QString("%1-Window").arg(VER_MODULE_NAME_STR)); SdiWindow::iSdiWindow * win = evafQueryInterface("iSdiWindow"); win->addPanel(getPanelName(args), this); setWindowTitle(tr("Messages")); Common::iLogger * logger = Common::iLogger::instance(); EVAF_TEST_X(logger, "No iLogger interface"); mDefSource = logger->defaultSource(); if (mDefSource.isEmpty()) mDefSource = "Common"; QVBoxLayout * w = new QVBoxLayout; w->setMargin(0); setLayout(w); wTabs = new QTabWidget; w->addWidget(wTabs); // Add the default source Widget * s = new Widget(mDefSource); mLogViews.insert(s->source(), s); wTabs->addTab(s, s->source()); wStatusBar = new QStatusBar; w->addWidget(wStatusBar); QAction * a = new QAction(this); a->setShortcut(Qt::Key_Escape); connect(a, SIGNAL(triggered()), this, SLOT(close())); addAction(a); restoreSettings(); connect(logger, SIGNAL(loggerEvent(Common::iLogger::Severity,QString,QString,QString)), this, SLOT(loggerEvent(Common::iLogger::Severity,QString,QString,QString))); show(); EVAF_INFO("%s created", qPrintable(objectName())); } Window::~Window() { mLogViews.clear(); saveSettings(); EVAF_INFO("%s destroyed", qPrintable(objectName())); } QString Window::getPanelName(QString const & args) const { QString panelName = "LogView"; QXmlStreamReader xml(args); while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement() && xml.name() == "attributes") { if (xml.attributes().hasAttribute("panelName")) { QString s = xml.attributes().value("panelName").toString(); if (!s.isEmpty()) panelName = s; } } } return panelName; } bool Window::event(QEvent * e) { if (e->type() == QEvent::StatusTip) { QStatusTipEvent * event = static_cast(e); wStatusBar->showMessage(event->tip()); return true; } return QWidget::event(e); } void Window::saveSettings() { static int ver[4] = {VER_FILE_VERSION}; QSettings settings(VER_COMPANY_NAME_STR, Common::iApp::instance()->name()); settings.setValue(QString("%1/version/major").arg(objectName()), ver[0]); settings.setValue(QString("%1/version/minor").arg(objectName()), ver[1]); settings.setValue(QString("%1/geometry").arg(objectName()), saveGeometry()); } void Window::restoreSettings() { static int ver[4] = {VER_FILE_VERSION}; QSettings settings(VER_COMPANY_NAME_STR, Common::iApp::instance()->name()); // Ignore saved settings if the version number is not the same // More intelligent checks can be implemented to allow upgrading from previous versions QVariant v = settings.value(QString("%1/version/major").arg(objectName())); if (!v.isValid() || v.toInt() != ver[0]) return; v = settings.value(QString("%1/version/minor").arg(objectName())); if (!v.isValid() || v.toInt() != ver[1]) return; // Restore the geometry restoreGeometry(settings.value(QString("%1/geometry").arg(objectName())).toByteArray()); } void Window::loggerEvent(Common::iLogger::Severity severity, QString const & text, QString const & source, QString const & where) { // Ignore messages with >=DEBUG severity level if (severity >= Common::iLogger::Debug) return; // Find or create the log view widget for this source Widget * w = 0; QString s = source.isEmpty() ? mDefSource : source; QHash::const_iterator it = mLogViews.constFind(s); if (it == mLogViews.constEnd()) { w = new Widget(s); mLogViews.insert(w->source(), w); wTabs->addTab(w, w->source()); } else w = *it; w->addMessage(severity, text, where); } //------------------------------------------------------------------- Module::Module() : Plugins::iPlugin() , wWindow(0) { setObjectName(QString("%1-Module").arg(VER_MODULE_NAME_STR)); EVAF_INFO("%s created", qPrintable(objectName())); } Module::~Module() { EVAF_INFO("%s destroyed", qPrintable(objectName())); } bool Module::init(QString const & args) { wWindow = new Window(args); EVAF_INFO("%s initialized", qPrintable(objectName())); return true; } void Module::done() { if (wWindow) { delete wWindow; wWindow = 0; } EVAF_INFO("%s finalized", qPrintable(objectName())); }