mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-06-28 01:52:06 +08:00
cabana: colorful logs based on activity (#27008)
* color logs * remove space * update in updateColors old-commit-hash: b2675cef9a9d4993859b0cd924a1ca74281024f9
This commit is contained in:
@@ -21,7 +21,7 @@ cabana_env = qt_env.Clone()
|
||||
prev_moc_path = cabana_env['QT_MOCHPREFIX']
|
||||
cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_'
|
||||
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc',
|
||||
'commands.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
'commands.cc', 'messageswidget.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, asset_obj], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
|
||||
if GetOption('test'):
|
||||
|
||||
@@ -16,16 +16,16 @@ void HistoryLogModel::setDisplayType(HistoryLogModel::DisplayType type) {
|
||||
|
||||
QVariant HistoryLogModel::data(const QModelIndex &index, int role) const {
|
||||
const bool display_signals = display_type == HistoryLogModel::Signals;
|
||||
const auto &m = messages[index.row()];
|
||||
if (role == Qt::DisplayRole) {
|
||||
const auto &m = messages[index.row()];
|
||||
if (index.column() == 0) {
|
||||
return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 2);
|
||||
}
|
||||
return display_signals ? QString::number(m.sig_values[index.column() - 1]) : m.data;
|
||||
} else if (role == Qt::FontRole && index.column() == 1 && !display_signals) {
|
||||
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
return display_signals ? QString::number(m.sig_values[index.column() - 1]) : toHex(m.data);
|
||||
} else if (role == Qt::ToolTipRole && index.column() > 0 && display_signals) {
|
||||
return tr("double click to open the chart");
|
||||
} else if (role == Qt::UserRole && index.column() == 1 && !display_signals) {
|
||||
return HexColors::toVariantList(m.colors);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -45,6 +45,7 @@ void HistoryLogModel::refresh() {
|
||||
beginResetModel();
|
||||
last_fetch_time = 0;
|
||||
messages.clear();
|
||||
hex_colors.clear();
|
||||
updateState();
|
||||
endResetModel();
|
||||
}
|
||||
@@ -91,6 +92,7 @@ void HistoryLogModel::updateState() {
|
||||
if ((has_more_data = !new_msgs.empty())) {
|
||||
beginInsertRows({}, 0, new_msgs.size() - 1);
|
||||
messages.insert(messages.begin(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end()));
|
||||
updateColors();
|
||||
endInsertRows();
|
||||
}
|
||||
last_fetch_time = current_time;
|
||||
@@ -103,11 +105,29 @@ void HistoryLogModel::fetchMore(const QModelIndex &parent) {
|
||||
if ((has_more_data = !new_msgs.empty())) {
|
||||
beginInsertRows({}, messages.size(), messages.size() + new_msgs.size() - 1);
|
||||
messages.insert(messages.end(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end()));
|
||||
if (!dynamic_mode) {
|
||||
updateColors();
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryLogModel::updateColors() {
|
||||
if (display_type == HistoryLogModel::Hex) {
|
||||
const auto freq = can->lastMessage(msg_id).freq;
|
||||
if (dynamic_mode) {
|
||||
for (auto it = messages.rbegin(); it != messages.rend(); ++it) {
|
||||
it->colors = hex_colors.compute(it->data, it->mono_time / (double)1e9, freq);
|
||||
}
|
||||
} else {
|
||||
for (auto it = messages.begin(); it != messages.end(); ++it) {
|
||||
it->colors = hex_colors.compute(it->data, it->mono_time / (double)1e9, freq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class InputIt>
|
||||
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) {
|
||||
std::deque<HistoryLogModel::Message> msgs;
|
||||
@@ -124,7 +144,7 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, I
|
||||
if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) {
|
||||
auto &m = msgs.emplace_back();
|
||||
m.mono_time = (*it)->mono_time;
|
||||
m.data = toHex(QByteArray((char *)dat.begin(), dat.size()));
|
||||
m.data = QByteArray((char *)dat.begin(), dat.size());
|
||||
m.sig_values = values;
|
||||
if (msgs.size() >= batch_size && min_time == 0)
|
||||
return msgs;
|
||||
@@ -135,6 +155,7 @@ std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, I
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
|
||||
template std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData<>(std::vector<const Event*>::iterator first, std::vector<const Event*>::iterator last, uint64_t min_time);
|
||||
template std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData<>(std::vector<const Event*>::reverse_iterator first, std::vector<const Event*>::reverse_iterator last, uint64_t min_time);
|
||||
|
||||
@@ -183,6 +204,7 @@ HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) {
|
||||
horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap);
|
||||
horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
verticalHeader()->setVisible(false);
|
||||
setItemDelegateForColumn(1, new MessageBytesDelegate(this));
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
@@ -225,8 +247,8 @@ LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) {
|
||||
QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::refresh);
|
||||
QObject::connect(can, &AbstractStream::eventsMerged, model, &HistoryLogModel::segmentsMerged);
|
||||
|
||||
dynamic_mode->setChecked(true);
|
||||
if (can->liveStreaming()) {
|
||||
dynamic_mode->setChecked(true);
|
||||
dynamic_mode->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,12 +41,14 @@ public:
|
||||
}
|
||||
void setDynamicMode(int state);
|
||||
void segmentsMerged();
|
||||
void updateColors();
|
||||
void refresh();
|
||||
|
||||
struct Message {
|
||||
uint64_t mono_time = 0;
|
||||
QVector<double> sig_values;
|
||||
QString data;
|
||||
QByteArray data;
|
||||
QVector<QColor> colors;
|
||||
};
|
||||
|
||||
template <class InputIt>
|
||||
@@ -54,6 +56,7 @@ public:
|
||||
std::deque<Message> fetchData(uint64_t from_time, uint64_t min_time = 0);
|
||||
|
||||
QString msg_id;
|
||||
HexColors hex_colors;
|
||||
bool has_more_data = true;
|
||||
const int batch_size = 50;
|
||||
int filter_sig_idx = -1;
|
||||
@@ -62,7 +65,7 @@ public:
|
||||
std::function<bool(double, double)> filter_cmp = nullptr;
|
||||
std::deque<Message> messages;
|
||||
std::vector<const Signal*> sigs;
|
||||
bool dynamic_mode = false;
|
||||
bool dynamic_mode = true;
|
||||
DisplayType display_type = HistoryLogModel::Signals;
|
||||
};
|
||||
|
||||
|
||||
@@ -79,15 +79,8 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
|
||||
case 3: return can_data.count;
|
||||
case 4: return toHex(can_data.dat);
|
||||
}
|
||||
} else if (role == Qt::FontRole && index.column() == columnCount() - 1) {
|
||||
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
} else if (role == Qt::UserRole && index.column() == 4) {
|
||||
|
||||
QList<QVariant> colors;
|
||||
for (int i = 0; i < can_data.dat.size(); i++){
|
||||
colors.append(can_data.colors[i]);
|
||||
}
|
||||
return colors;
|
||||
return HexColors::toVariantList(can_data.colors);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -174,40 +167,3 @@ void MessageListModel::sort(int column, Qt::SortOrder order) {
|
||||
sortMessages();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegate(parent) {
|
||||
}
|
||||
|
||||
void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
QList<QVariant> colors = index.data(Qt::UserRole).toList();
|
||||
|
||||
QStyleOptionViewItemV4 opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
|
||||
const QFont font = index.data(Qt::FontRole).value<QFont>();
|
||||
painter->setFont(font);
|
||||
|
||||
QRect rect = opt.rect;
|
||||
QString bytes = QString(opt.text);
|
||||
|
||||
QRect pos = rect;
|
||||
QRect space = painter->boundingRect(pos, opt.displayAlignment, " ");
|
||||
pos.setX(pos.x() + space.width());
|
||||
|
||||
if ((option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active)) {
|
||||
painter->setPen(option.palette.color(QPalette::HighlightedText));
|
||||
} else {
|
||||
painter->setPen(option.palette.color(QPalette::Text));
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (auto &byte : bytes.split(" ")) {
|
||||
QRect sz = painter->boundingRect(pos, opt.displayAlignment, byte);
|
||||
const int m = space.width() / 2;
|
||||
painter->fillRect(sz.marginsAdded(QMargins(m + 1, m, m, m)), colors[i].value<QColor>());
|
||||
painter->drawText(pos, opt.displayAlignment, byte);
|
||||
pos.setX(pos.x() + sz.width() + space.width());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,3 @@ protected:
|
||||
QString current_msg_id;
|
||||
MessageListModel *model;
|
||||
};
|
||||
|
||||
class MessageBytesDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MessageBytesDelegate(QObject *parent);
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
};
|
||||
|
||||
@@ -17,15 +17,9 @@ void AbstractStream::process(QHash<QString, CanData> *messages) {
|
||||
processing = false;
|
||||
}
|
||||
|
||||
static QColor blend(QColor a, QColor b) {
|
||||
return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2);
|
||||
}
|
||||
|
||||
bool AbstractStream::updateEvent(const Event *event) {
|
||||
static std::unique_ptr new_msgs = std::make_unique<QHash<QString, CanData>>();
|
||||
static QHash<QString, QByteArray> prev_dat;
|
||||
static QHash<QString, QList<QColor>> colors;
|
||||
static QHash<QString, QList<double>> last_change_t;
|
||||
static QHash<QString, HexColors> hex_colors;
|
||||
static double prev_update_ts = 0;
|
||||
|
||||
if (event->which == cereal::Event::Which::CAN) {
|
||||
@@ -48,59 +42,7 @@ bool AbstractStream::updateEvent(const Event *event) {
|
||||
if (double delta = (current_sec - counters_begin_sec); delta > 0) {
|
||||
data.freq = data.count / delta;
|
||||
}
|
||||
|
||||
// Init colors
|
||||
if (colors[id].size() != data.dat.size()) {
|
||||
colors[id].clear();
|
||||
for (int i = 0; i < data.dat.size(); i++){
|
||||
colors[id].append(QColor(0, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Init last_change_t
|
||||
if (last_change_t[id].size() != data.dat.size()) {
|
||||
last_change_t[id].clear();
|
||||
for (int i = 0; i < data.dat.size(); i++){
|
||||
last_change_t[id].append(data.ts);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute background color for the bytes based on changes
|
||||
if (prev_dat[id].size() == data.dat.size()) {
|
||||
for (int i = 0; i < data.dat.size(); i++){
|
||||
uint8_t last = prev_dat[id][i];
|
||||
uint8_t cur = data.dat[i];
|
||||
|
||||
const int periodic_threshold = 10;
|
||||
const int start_alpha = 128;
|
||||
const float fade_time = 2.0;
|
||||
|
||||
if (last != cur) {
|
||||
double delta_t = data.ts - last_change_t[id][i];
|
||||
|
||||
if (delta_t * data.freq > periodic_threshold) {
|
||||
// Last change was while ago, choose color based on delta up or down
|
||||
if (cur > last) {
|
||||
colors[id][i] = QColor(0, 187, 255, start_alpha); // Cyan
|
||||
} else {
|
||||
colors[id][i] = QColor(255, 0, 0, start_alpha); // Red
|
||||
}
|
||||
} else {
|
||||
// Periodic changes
|
||||
colors[id][i] = blend(colors[id][i], QColor(102, 86, 169, start_alpha / 2)); // Greyish/Blue
|
||||
}
|
||||
|
||||
last_change_t[id][i] = data.ts;
|
||||
} else {
|
||||
// Fade out
|
||||
float alpha_delta = 1.0 / (data.freq + 1) / fade_time;
|
||||
colors[id][i].setAlphaF(std::max(0.0, colors[id][i].alphaF() - alpha_delta));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.colors = colors[id];
|
||||
prev_dat[id] = data.dat;
|
||||
data.colors = hex_colors[id].compute(data.dat, data.ts, data.freq);
|
||||
}
|
||||
|
||||
double ts = millis_since_boot();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QHash>
|
||||
|
||||
#include "tools/cabana/settings.h"
|
||||
#include "tools/cabana/util.h"
|
||||
#include "tools/replay/replay.h"
|
||||
|
||||
struct CanData {
|
||||
@@ -15,7 +16,7 @@ struct CanData {
|
||||
uint32_t count = 0;
|
||||
uint32_t freq = 0;
|
||||
QByteArray dat;
|
||||
QList<QColor> colors;
|
||||
QVector<QColor> colors;
|
||||
};
|
||||
|
||||
class AbstractStream : public QObject {
|
||||
@@ -64,13 +65,5 @@ protected:
|
||||
QHash<QString, uint32_t> counters;
|
||||
};
|
||||
|
||||
inline QString toHex(const QByteArray &dat) { return dat.toHex(' ').toUpper(); }
|
||||
inline char toHex(uint value) { return "0123456789ABCDEF"[value & 0xF]; }
|
||||
inline const QString &getColor(int i) {
|
||||
// TODO: add more colors
|
||||
static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"};
|
||||
return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)];
|
||||
}
|
||||
|
||||
// A global pointer referring to the unique AbstractStream object
|
||||
extern AbstractStream *can;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
#include "tools/cabana/util.h"
|
||||
|
||||
#include <QFontDatabase>
|
||||
#include <QPainter>
|
||||
|
||||
static QColor blend(QColor a, QColor b) {
|
||||
return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2);
|
||||
}
|
||||
|
||||
const QVector<QColor> &HexColors::compute(const QByteArray &dat, double ts, uint32_t freq) {
|
||||
if (prev_dat.size() != dat.size()) {
|
||||
colors.resize(dat.size());
|
||||
last_change_t.resize(dat.size());
|
||||
std::fill(colors.begin(), colors.end(), QColor(0, 0, 0, 0));
|
||||
std::fill(last_change_t.begin(), last_change_t.end(), ts);
|
||||
} else {
|
||||
for (int i = 0; i < dat.size(); ++i) {
|
||||
const uint8_t last = prev_dat[i];
|
||||
const uint8_t cur = dat[i];
|
||||
|
||||
if (last != cur) {
|
||||
double delta_t = ts - last_change_t[i];
|
||||
if (delta_t * freq > periodic_threshold) {
|
||||
// Last change was while ago, choose color based on delta up or down
|
||||
if (cur > last) {
|
||||
colors[i] = QColor(0, 187, 255, start_alpha); // Cyan
|
||||
} else {
|
||||
colors[i] = QColor(255, 0, 0, start_alpha); // Red
|
||||
}
|
||||
} else {
|
||||
// Periodic changes
|
||||
colors[i] = blend(colors[i], QColor(102, 86, 169, start_alpha / 2)); // Greyish/Blue
|
||||
}
|
||||
|
||||
last_change_t[i] = ts;
|
||||
} else {
|
||||
// Fade out
|
||||
float alpha_delta = 1.0 / (freq + 1) / fade_time;
|
||||
colors[i].setAlphaF(std::max(0.0, colors[i].alphaF() - alpha_delta));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prev_dat = dat;
|
||||
return colors;
|
||||
}
|
||||
|
||||
void HexColors::clear() {
|
||||
prev_dat.clear();
|
||||
last_change_t.clear();
|
||||
colors.clear();
|
||||
}
|
||||
|
||||
QList<QVariant> HexColors::toVariantList(const QVector<QColor> &colors) {
|
||||
QList<QVariant> ret;
|
||||
ret.reserve(colors.size());
|
||||
for (auto &c : colors) ret.append(c);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// MessageBytesDelegate
|
||||
|
||||
MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegate(parent) {
|
||||
fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
}
|
||||
|
||||
void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
QList<QVariant> colors = index.data(Qt::UserRole).toList();
|
||||
if (colors.empty()) {
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
return;
|
||||
}
|
||||
QStyleOptionViewItemV4 opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
|
||||
if ((option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active)) {
|
||||
painter->setPen(option.palette.color(QPalette::HighlightedText));
|
||||
} else {
|
||||
painter->setPen(option.palette.color(QPalette::Text));
|
||||
}
|
||||
|
||||
painter->setFont(fixed_font);
|
||||
QRect space = painter->boundingRect(opt.rect, opt.displayAlignment, " ");
|
||||
QRect pos = painter->boundingRect(opt.rect, opt.displayAlignment, "00");
|
||||
pos.moveLeft(pos.x() + space.width());
|
||||
|
||||
int m = space.width() / 2;
|
||||
const QMargins margins(m + 1, m, m, m);
|
||||
|
||||
int i = 0;
|
||||
for (auto &byte : opt.text.split(" ")) {
|
||||
if (i < colors.size()) {
|
||||
painter->fillRect(pos.marginsAdded(margins), colors[i].value<QColor>());
|
||||
}
|
||||
painter->drawText(pos, opt.displayAlignment, byte);
|
||||
pos.moveLeft(pos.right() + space.width());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QVector>
|
||||
|
||||
class HexColors {
|
||||
public:
|
||||
const QVector<QColor> &compute(const QByteArray &dat, double ts, uint32_t freq);
|
||||
static QList<QVariant> toVariantList(const QVector<QColor> &colors);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
const int periodic_threshold = 10;
|
||||
const int start_alpha = 128;
|
||||
const float fade_time = 2.0;
|
||||
QByteArray prev_dat;
|
||||
QVector<double> last_change_t;
|
||||
QVector<QColor> colors;
|
||||
};
|
||||
|
||||
class MessageBytesDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MessageBytesDelegate(QObject *parent);
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
QFont fixed_font;
|
||||
};
|
||||
|
||||
inline QString toHex(const QByteArray &dat) { return dat.toHex(' ').toUpper(); }
|
||||
inline char toHex(uint value) { return "0123456789ABCDEF"[value & 0xF]; }
|
||||
inline const QString &getColor(int i) {
|
||||
// TODO: add more colors
|
||||
static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"};
|
||||
return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)];
|
||||
}
|
||||
Reference in New Issue
Block a user