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)
This commit is contained in:
Srivats P 2018-08-23 23:23:07 +05:30
parent 489099ca83
commit 509d777500
7 changed files with 168 additions and 15 deletions

View File

@ -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<LogLevel>(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));

View File

@ -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<Log> logs_;
LogLevel currentLevel_{kInfo};
};
#endif

View File

@ -20,26 +20,95 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "logswindow.h"
#include "logsmodel.h"
#include "modeltest.h"
#include <QDockWidget>
#include <QHeaderView>
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<QDockWidget*>(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()));
}
}

View File

@ -23,6 +23,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#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

View File

@ -41,6 +41,35 @@
<property name="statusTip">
<string>Select the desired logging level</string>
</property>
<item>
<property name="text">
<string>Info</string>
</property>
</item>
<item>
<property name="text">
<string>Warning</string>
</property>
</item>
<item>
<property name="text">
<string>Error</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="autoScroll">
<property name="text">
<string>Auto Scroll</string>
</property>
</widget>
</item>
<item>
@ -81,6 +110,9 @@
<property name="whatsThis">
<string>Ostinato application logs are displayed here</string>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
</layout>
@ -94,6 +126,7 @@
</customwidgets>
<tabstops>
<tabstop>level</tabstop>
<tabstop>autoScroll</tabstop>
<tabstop>clear</tabstop>
<tabstop>logs</tabstop>
</tabstops>

View File

@ -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);

View File

@ -22,6 +22,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QTableView>
#include <QClipboard>
#include <QKeyEvent>
#include <QPainter>
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