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:
parent
489099ca83
commit
509d777500
@ -36,17 +36,13 @@ static QStringList columnTitles = QStringList()
|
|||||||
<< "Message";
|
<< "Message";
|
||||||
|
|
||||||
static QStringList levelTitles = QStringList()
|
static QStringList levelTitles = QStringList()
|
||||||
<< "Error"
|
<< "Info"
|
||||||
<< "Warning"
|
<< "Warning"
|
||||||
<< "Info";
|
<< "Error";
|
||||||
|
|
||||||
LogsModel::LogsModel(QObject *parent)
|
LogsModel::LogsModel(QObject *parent)
|
||||||
: QAbstractTableModel(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
|
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
|
int LogsModel::columnCount(const QModelIndex &/*parent*/) const
|
||||||
{
|
{
|
||||||
if (!logs_.size())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return kFieldCount;
|
return kFieldCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +74,9 @@ QVariant LogsModel::headerData(
|
|||||||
|
|
||||||
QVariant LogsModel::data(const QModelIndex &index, int role) const
|
QVariant LogsModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
|
if (!index.isValid() || (index.row() >= logs_.size()))
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
if (role == Qt::ForegroundRole) {
|
if (role == Qt::ForegroundRole) {
|
||||||
switch(logs_.at(index.row()).logLevel) {
|
switch(logs_.at(index.row()).logLevel) {
|
||||||
case kError:
|
case kError:
|
||||||
@ -98,7 +94,7 @@ QVariant LogsModel::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
case kTimeStamp:
|
case kTimeStamp:
|
||||||
return logs_.at(index.row()).timeStamp.toString();
|
return logs_.at(index.row()).timeStamp.toString("hh:mm:ss.zzz");
|
||||||
case kLogLevel:
|
case kLogLevel:
|
||||||
return levelTitles.at(logs_.at(index.row()).logLevel);
|
return levelTitles.at(logs_.at(index.row()).logLevel);
|
||||||
case kPort:
|
case kPort:
|
||||||
@ -122,8 +118,19 @@ void LogsModel::clear()
|
|||||||
endResetModel();
|
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)
|
void LogsModel::log(int logLevel,QString port, QString message)
|
||||||
{
|
{
|
||||||
|
if (logLevel < currentLevel_)
|
||||||
|
return;
|
||||||
|
|
||||||
// TODO: discard logs older than some threshold
|
// TODO: discard logs older than some threshold
|
||||||
|
|
||||||
//qDebug("adding log %u %s", logs_.size(), qPrintable(message));
|
//qDebug("adding log %u %s", logs_.size(), qPrintable(message));
|
||||||
|
@ -28,9 +28,10 @@ class LogsModel: public QAbstractTableModel
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum LogLevel { // FIXME: use enum class?
|
enum LogLevel { // FIXME: use enum class?
|
||||||
kError,
|
kInfo,
|
||||||
kWarning,
|
kWarning,
|
||||||
kInfo
|
kError,
|
||||||
|
kLevelCount
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -45,6 +46,7 @@ public:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void clear();
|
void clear();
|
||||||
|
void setLogLevel(int level);
|
||||||
void log(int logLevel,QString port, QString message);
|
void log(int logLevel,QString port, QString message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -55,6 +57,7 @@ private:
|
|||||||
QString message;
|
QString message;
|
||||||
};
|
};
|
||||||
QVector<Log> logs_;
|
QVector<Log> logs_;
|
||||||
|
LogLevel currentLevel_{kInfo};
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -20,26 +20,95 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
#include "logswindow.h"
|
#include "logswindow.h"
|
||||||
|
|
||||||
#include "logsmodel.h"
|
#include "logsmodel.h"
|
||||||
|
#include "modeltest.h"
|
||||||
|
|
||||||
|
#include <QDockWidget>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
|
||||||
LogsWindow::LogsWindow(LogsModel *model, QWidget *parent)
|
LogsWindow::LogsWindow(LogsModel *model, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
setupUi(this);
|
setupUi(this);
|
||||||
|
logs->setModel(model);
|
||||||
|
autoScroll->setChecked(true);
|
||||||
|
|
||||||
logs->verticalHeader()->setHighlightSections(false);
|
logs->verticalHeader()->setHighlightSections(false);
|
||||||
logs->verticalHeader()->setDefaultSectionSize(
|
logs->verticalHeader()->setDefaultSectionSize(
|
||||||
logs->verticalHeader()->minimumSectionSize());
|
logs->verticalHeader()->minimumSectionSize());
|
||||||
logs->setShowGrid(false);
|
logs->setShowGrid(false);
|
||||||
logs->setAlternatingRowColors(true);
|
logs->setAlternatingRowColors(true);
|
||||||
logs->setModel(model);
|
|
||||||
logs->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch);
|
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()
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,6 +23,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
#include "ui_logswindow.h"
|
#include "ui_logswindow.h"
|
||||||
|
|
||||||
class LogsModel;
|
class LogsModel;
|
||||||
|
class QDockWidget;
|
||||||
|
class QShowEvent;
|
||||||
|
|
||||||
class LogsWindow: public QWidget, private Ui::LogsWindow
|
class LogsWindow: public QWidget, private Ui::LogsWindow
|
||||||
{
|
{
|
||||||
@ -30,6 +32,18 @@ class LogsWindow: public QWidget, private Ui::LogsWindow
|
|||||||
public:
|
public:
|
||||||
LogsWindow(LogsModel *model, QWidget *parent = 0);
|
LogsWindow(LogsModel *model, QWidget *parent = 0);
|
||||||
~LogsWindow();
|
~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
|
#endif
|
||||||
|
@ -41,6 +41,35 @@
|
|||||||
<property name="statusTip">
|
<property name="statusTip">
|
||||||
<string>Select the desired logging level</string>
|
<string>Select the desired logging level</string>
|
||||||
</property>
|
</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>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -81,6 +110,9 @@
|
|||||||
<property name="whatsThis">
|
<property name="whatsThis">
|
||||||
<string>Ostinato application logs are displayed here</string>
|
<string>Ostinato application logs are displayed here</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@ -94,6 +126,7 @@
|
|||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>level</tabstop>
|
<tabstop>level</tabstop>
|
||||||
|
<tabstop>autoScroll</tabstop>
|
||||||
<tabstop>clear</tabstop>
|
<tabstop>clear</tabstop>
|
||||||
<tabstop>logs</tabstop>
|
<tabstop>logs</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
|
@ -96,7 +96,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
|
|
||||||
portsWindow = new PortsWindow(pgl, this);
|
portsWindow = new PortsWindow(pgl, this);
|
||||||
statsWindow = new PortStatsWindow(pgl, this);
|
statsWindow = new PortStatsWindow(pgl, this);
|
||||||
logsWindow_ = new LogsWindow(appLogs, this);
|
|
||||||
|
|
||||||
portsDock = new QDockWidget(tr("Ports and Streams"), this);
|
portsDock = new QDockWidget(tr("Ports and Streams"), this);
|
||||||
portsDock->setObjectName("portsDock");
|
portsDock->setObjectName("portsDock");
|
||||||
@ -112,6 +111,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
logsDock_->setObjectName("logsDock");
|
logsDock_->setObjectName("logsDock");
|
||||||
logsDock_->setFeatures(
|
logsDock_->setFeatures(
|
||||||
logsDock_->features() & ~QDockWidget::DockWidgetClosable);
|
logsDock_->features() & ~QDockWidget::DockWidgetClosable);
|
||||||
|
logsWindow_ = new LogsWindow(appLogs, logsDock_);
|
||||||
|
|
||||||
setupUi(this);
|
setupUi(this);
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
|
|||||||
|
|
||||||
#include <QTableView>
|
#include <QTableView>
|
||||||
|
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QKeyEvent>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
class XTableView : public QTableView
|
class XTableView : public QTableView
|
||||||
@ -42,6 +44,31 @@ protected:
|
|||||||
else
|
else
|
||||||
QTableView::paintEvent(event);
|
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
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user