From 509d777500b89247dd68cf9cdeaf1349f687d2b7 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Thu, 23 Aug 2018 23:23:07 +0530 Subject: [PATCH] LogsWindow: Improve UX * Timestamp at millisec resolution * Log level selection - Info by default * Auto scroll control - enable(default)/disable * Support copy (selected) logs to clipboard * Support clear logs * Annotate dock window title, if not on top (aka visible) --- client/logsmodel.cpp | 27 ++++++++++------ client/logsmodel.h | 7 +++-- client/logswindow.cpp | 73 +++++++++++++++++++++++++++++++++++++++++-- client/logswindow.h | 14 +++++++++ client/logswindow.ui | 33 +++++++++++++++++++ client/mainwindow.cpp | 2 +- client/xtableview.h | 27 ++++++++++++++++ 7 files changed, 168 insertions(+), 15 deletions(-) diff --git a/client/logsmodel.cpp b/client/logsmodel.cpp index 85dff09..e749db9 100644 --- a/client/logsmodel.cpp +++ b/client/logsmodel.cpp @@ -36,17 +36,13 @@ static QStringList columnTitles = QStringList() << "Message"; static QStringList levelTitles = QStringList() - << "Error" + << "Info" << "Warning" - << "Info"; + << "Error"; LogsModel::LogsModel(QObject *parent) : QAbstractTableModel(parent) { - // FIXME: test only - log(kInfo, QString("--"), QString("Welcome to Ostinato!")); - log(kWarning, QString("--"), QString("This is a sample warning!")); - log(kError, QString("--"), QString("This is a sample error!")); } int LogsModel::rowCount(const QModelIndex &/*parent*/) const @@ -56,9 +52,6 @@ int LogsModel::rowCount(const QModelIndex &/*parent*/) const int LogsModel::columnCount(const QModelIndex &/*parent*/) const { - if (!logs_.size()) - return 0; - return kFieldCount; } @@ -81,6 +74,9 @@ QVariant LogsModel::headerData( QVariant LogsModel::data(const QModelIndex &index, int role) const { + if (!index.isValid() || (index.row() >= logs_.size())) + return QVariant(); + if (role == Qt::ForegroundRole) { switch(logs_.at(index.row()).logLevel) { case kError: @@ -98,7 +94,7 @@ QVariant LogsModel::data(const QModelIndex &index, int role) const switch (index.column()) { case kTimeStamp: - return logs_.at(index.row()).timeStamp.toString(); + return logs_.at(index.row()).timeStamp.toString("hh:mm:ss.zzz"); case kLogLevel: return levelTitles.at(logs_.at(index.row()).logLevel); case kPort: @@ -122,8 +118,19 @@ void LogsModel::clear() endResetModel(); } +void LogsModel::setLogLevel(int level) +{ + currentLevel_ = static_cast(level % kLevelCount); + log(currentLevel_, QString("--"), + QString("Log level changed to %1 or higher") + .arg(levelTitles.at(currentLevel_))); +} + void LogsModel::log(int logLevel,QString port, QString message) { + if (logLevel < currentLevel_) + return; + // TODO: discard logs older than some threshold //qDebug("adding log %u %s", logs_.size(), qPrintable(message)); diff --git a/client/logsmodel.h b/client/logsmodel.h index 1699d16..a7edb17 100644 --- a/client/logsmodel.h +++ b/client/logsmodel.h @@ -28,9 +28,10 @@ class LogsModel: public QAbstractTableModel Q_OBJECT public: enum LogLevel { // FIXME: use enum class? - kError, + kInfo, kWarning, - kInfo + kError, + kLevelCount }; public: @@ -45,6 +46,7 @@ public: public slots: void clear(); + void setLogLevel(int level); void log(int logLevel,QString port, QString message); private: @@ -55,6 +57,7 @@ private: QString message; }; QVector logs_; + LogLevel currentLevel_{kInfo}; }; #endif diff --git a/client/logswindow.cpp b/client/logswindow.cpp index a6b2d21..608fc31 100644 --- a/client/logswindow.cpp +++ b/client/logswindow.cpp @@ -20,26 +20,95 @@ along with this program. If not, see #include "logswindow.h" #include "logsmodel.h" +#include "modeltest.h" +#include #include LogsWindow::LogsWindow(LogsModel *model, QWidget *parent) : QWidget(parent) { setupUi(this); + logs->setModel(model); + autoScroll->setChecked(true); logs->verticalHeader()->setHighlightSections(false); logs->verticalHeader()->setDefaultSectionSize( logs->verticalHeader()->minimumSectionSize()); logs->setShowGrid(false); logs->setAlternatingRowColors(true); - logs->setModel(model); logs->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); - //FIXME: connect(clear, SIGNAL(clicked()), model, SLOT(clear())); + parentDock_ = qobject_cast(parent); + windowTitle_ = parentDock_->windowTitle(); + + connect(level, SIGNAL(currentIndexChanged(int)), + model, SLOT(setLogLevel(int))); + connect(clear, SIGNAL(clicked()), model, SLOT(clear())); + + connect(parentDock_, SIGNAL(visibilityChanged(bool)), + SLOT(when_visibilityChanged(bool))); + connect(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)), + SLOT(when_rowsInserted(const QModelIndex&, int, int))); + +#if defined(QT_NO_DEBUG) || QT_VERSION < 0x050700 + logsModelTest_ = nullptr; +#else + logsModelTest_ = new ModelTest(model); +#endif } LogsWindow::~LogsWindow() { + delete logsModelTest_; } +void LogsWindow::when_visibilityChanged(bool visible) +{ + if (visible) { + parentDock_->setWindowTitle(windowTitle_); + annotation_.clear(); + } + + isVisible_ = visible; + qDebug("isVisible = %u", isVisible_); +} + +void LogsWindow::when_rowsInserted(const QModelIndex &parent, + int first, int last) +{ + if (isVisible_) + return; + + if (annotation_.contains("Error")) + return; + + for (int i = first; i <= last; i++) { + // FIXME: use a user-role instead, so we don't need to know column and + // have to compare strings? + QString level = logs->model()->data(logs->model()->index(i, 1, parent)) + .toString(); + if (level == "Error") { + annotation_ = QString(" - Error(s)"); + break; // Highest level - no need to look further + } + else if (level == "Warning") { + annotation_ = QString(" - Warning(s)"); + } + } + parentDock_>setWindowTitle(windowTitle_+annotation_); +} + +void LogsWindow::on_autoScroll_toggled(bool checked) +{ + if (checked) { + connect(logs->model(), + SIGNAL(rowsInserted(const QModelIndex&, int, int)), + logs, SLOT(scrollToBottom())); + } + else { + disconnect(logs->model(), + SIGNAL(rowsInserted(const QModelIndex&, int, int)), + logs, SLOT(scrollToBottom())); + } +} diff --git a/client/logswindow.h b/client/logswindow.h index 2818c4c..ee3d59f 100644 --- a/client/logswindow.h +++ b/client/logswindow.h @@ -23,6 +23,8 @@ along with this program. If not, see #include "ui_logswindow.h" class LogsModel; +class QDockWidget; +class QShowEvent; class LogsWindow: public QWidget, private Ui::LogsWindow { @@ -30,6 +32,18 @@ class LogsWindow: public QWidget, private Ui::LogsWindow public: LogsWindow(LogsModel *model, QWidget *parent = 0); ~LogsWindow(); + +private slots: + void when_visibilityChanged(bool visible); + void when_rowsInserted(const QModelIndex &parent, int first, int last); + void on_autoScroll_toggled(bool checked); + +private: + QDockWidget *parentDock_; + QString windowTitle_; + QString annotation_; + bool isVisible_{false}; + QObject *logsModelTest_; }; #endif diff --git a/client/logswindow.ui b/client/logswindow.ui index d20b87a..cabff41 100644 --- a/client/logswindow.ui +++ b/client/logswindow.ui @@ -41,6 +41,35 @@ Select the desired logging level + + + Info + + + + + Warning + + + + + Error + + + + + + + + Qt::Vertical + + + + + + + Auto Scroll + @@ -81,6 +110,9 @@ Ostinato application logs are displayed here + + QAbstractItemView::SelectRows + @@ -94,6 +126,7 @@ level + autoScroll clear logs diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index c036a5e..b76100b 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -96,7 +96,6 @@ MainWindow::MainWindow(QWidget *parent) portsWindow = new PortsWindow(pgl, this); statsWindow = new PortStatsWindow(pgl, this); - logsWindow_ = new LogsWindow(appLogs, this); portsDock = new QDockWidget(tr("Ports and Streams"), this); portsDock->setObjectName("portsDock"); @@ -112,6 +111,7 @@ MainWindow::MainWindow(QWidget *parent) logsDock_->setObjectName("logsDock"); logsDock_->setFeatures( logsDock_->features() & ~QDockWidget::DockWidgetClosable); + logsWindow_ = new LogsWindow(appLogs, logsDock_); setupUi(this); diff --git a/client/xtableview.h b/client/xtableview.h index 296ec54..3de3353 100644 --- a/client/xtableview.h +++ b/client/xtableview.h @@ -22,6 +22,8 @@ along with this program. If not, see #include +#include +#include #include class XTableView : public QTableView @@ -42,6 +44,31 @@ protected: else QTableView::paintEvent(event); } + + virtual void keyPressEvent(QKeyEvent *event) + { + // Copy selection to clipboard (base class copies only current item) + if (event->matches(QKeySequence::Copy) + && selectionBehavior() == SelectRows) { + QString text; + int lastRow = -1; + QModelIndexList selected = selectionModel()->selectedIndexes(); + qSort(selected); + foreach(QModelIndex index, selected) { + if (index.row() != lastRow) { + if (!text.isEmpty()) + text.append("\n"); + } + else + text.append("\t"); + text.append(model()->data(index).toString()); + lastRow = index.row(); + } + qApp->clipboard()->setText(text); + } + else + QTableView::keyPressEvent(event); + } }; #endif