diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 5ea5ea2d99..d9b00f6882 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -32,7 +32,6 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); horizontalHeader()->hide(); - setFrameShape(QFrame::NoFrame); setShowGrid(false); setMouseTracking(true); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -44,17 +43,17 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { setWhatsThis(R"( Binary View
- Shortcuts:
+ Shortcuts
Delete Signal: - x , - Backspace , - Delete
- Change endianness: e
- Change singedness: s
+  x , +  Backspace , +  Delete 
+ Change endianness:  e 
+ Change singedness:  s 
Open chart: - c , - p , - g
+  c , +  p , +  g  )"); } @@ -108,14 +107,14 @@ void BinaryView::addShortcuts() { QObject::connect(shortcut_plot_c, &QShortcut::activated, shortcut_plot, &QShortcut::activated); QObject::connect(shortcut_plot, &QShortcut::activated, [=]{ if (hovered_sig != nullptr) { - emit showChart(*model->msg_id, hovered_sig, true, false); + emit showChart(model->msg_id, hovered_sig, true, false); } }); } QSize BinaryView::minimumSizeHint() const { - return {(horizontalHeader()->minimumSectionSize() + 1) * 9 + VERTICAL_HEADER_WIDTH, - CELL_HEIGHT * std::min(model->rowCount(), 10)}; + return {(horizontalHeader()->minimumSectionSize() + 1) * 9 + VERTICAL_HEADER_WIDTH + 2, + CELL_HEIGHT * std::min(model->rowCount(), 10) + 2}; } void BinaryView::highlight(const Signal *sig) { @@ -127,6 +126,16 @@ void BinaryView::highlight(const Signal *sig) { emit model->dataChanged(index, index, {Qt::DisplayRole}); } } + + if (sig && underMouse()) { + QString tooltip = tr(R"(%1
+ Size:%2 LE:%3 SGD:%4 + )").arg(sig->name).arg(sig->size).arg(sig->is_little_endian ? "Y" : "N").arg(sig->is_signed ? "Y" : "N"); + QToolTip::showText(QCursor::pos(), tooltip, this, rect()); + } else { + QToolTip::showText(QCursor::pos(), "", this, rect()); + } + hovered_sig = sig; emit signalHovered(hovered_sig); } @@ -169,7 +178,6 @@ void BinaryView::highlightPosition(const QPoint &pos) { auto item = (BinaryViewModel::Item *)index.internalPointer(); const Signal *sig = item->sigs.isEmpty() ? nullptr : item->sigs.back(); highlight(sig); - QToolTip::showText(pos, sig ? sig->name : "", this, rect()); } } @@ -210,8 +218,6 @@ void BinaryView::setMessage(const MessageId &message_id) { } void BinaryView::refresh() { - if (!model->msg_id) return; - clearSelection(); anchor_index = QModelIndex(); resize_sig = nullptr; @@ -245,26 +251,26 @@ std::tuple BinaryView::getSelection(QModelIndex index) { void BinaryViewModel::refresh() { beginResetModel(); items.clear(); - if (auto dbc_msg = dbc()->msg(*msg_id)) { + if (auto dbc_msg = dbc()->msg(msg_id)) { row_count = dbc_msg->size; items.resize(row_count * column_count); - for (auto &sig : dbc_msg->sigs) { - auto [start, end] = getSignalRange(&sig); + for (auto sig : dbc_msg->getSignals()) { + auto [start, end] = getSignalRange(sig); for (int j = start; j <= end; ++j) { - int bit_index = sig.is_little_endian ? bigEndianBitIndex(j) : j; + int bit_index = sig->is_little_endian ? bigEndianBitIndex(j) : j; int idx = column_count * (bit_index / 8) + bit_index % 8; if (idx >= items.size()) { - qWarning() << "signal " << sig.name << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size; + qWarning() << "signal " << sig->name << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size; break; } - if (j == start) sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; - if (j == end) sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; - items[idx].bg_color = getColor(&sig); - items[idx].sigs.push_back(&sig); + if (j == start) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; + if (j == end) sig->is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; + items[idx].bg_color = getColor(sig); + items[idx].sigs.push_back(sig); } } } else { - row_count = can->lastMessage(*msg_id).dat.size(); + row_count = can->lastMessage(msg_id).dat.size(); items.resize(row_count * column_count); } endResetModel(); @@ -273,7 +279,7 @@ void BinaryViewModel::refresh() { void BinaryViewModel::updateState() { auto prev_items = items; - const auto &last_msg = can->lastMessage(*msg_id); + const auto &last_msg = can->lastMessage(msg_id); const auto &binary = last_msg.dat; // data size may changed. @@ -283,33 +289,29 @@ void BinaryViewModel::updateState() { items.resize(row_count * column_count); endInsertRows(); } + + double max_f = 255.0; + double factor = 0.25; + double scaler = max_f / log2(1.0 + factor); char hex[3] = {'\0'}; for (int i = 0; i < binary.size(); ++i) { for (int j = 0; j < 8; ++j) { - items[i * column_count + j].val = ((binary[i] >> (7 - j)) & 1) != 0 ? '1' : '0'; - + auto &item = items[i * column_count + j]; + item.val = ((binary[i] >> (7 - j)) & 1) != 0 ? '1' : '0'; // Bit update frequency based highlighting - bool has_signal = items[i * column_count + j].sigs.size() > 0; - double offset = has_signal ? 50 : 0; - - double min_f = last_msg.bit_change_counts[i][7 - j] == 0 ? offset : offset + 25; - double max_f = 255.0; - - double factor = 0.25; - double scaler = max_f / log2(1.0 + factor); - - double alpha = std::clamp(offset + log2(1.0 + factor * (double)last_msg.bit_change_counts[i][7 - j] / (double)last_msg.count) * scaler, min_f, max_f); - items[i * column_count + j].bg_color.setAlpha(alpha); + double offset = !item.sigs.empty() ? 50 : 0; + auto n = last_msg.bit_change_counts[i][7 - j]; + double min_f = n == 0 ? offset : offset + 25; + double alpha = std::clamp(offset + log2(1.0 + factor * (double)n / (double)last_msg.count) * scaler, min_f, max_f); + item.bg_color.setAlpha(alpha); } hex[0] = toHex(binary[i] >> 4); hex[1] = toHex(binary[i] & 0xf); items[i * column_count + 8].val = hex; items[i * column_count + 8].bg_color = last_msg.colors[i]; } - for (int i = binary.size(); i < row_count; ++i) { - for (int j = 0; j < column_count; ++j) { - items[i * column_count + j].val = "-"; - } + for (int i = binary.size() * column_count; i < items.size(); ++i) { + items[i].val = "-"; } for (int i = 0; i < items.size(); ++i) { diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 1d6d5d0b07..681ac1fbf3 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -50,7 +48,7 @@ public: }; std::vector items; - std::optional msg_id; + MessageId msg_id; int row_count = 0; const int column_count = 9; }; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 8515ffcfe7..e315f82398 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -11,15 +11,16 @@ #include #include #include -#include #include #include const int MAX_COLUMN_COUNT = 4; // ChartsWidget -ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { +ChartsWidget::ChartsWidget(QWidget *parent) : QFrame(parent) { + setFrameStyle(QFrame::StyledPanel | QFrame::Plain); QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); // toolbar QToolBar *toolbar = new QToolBar(tr("Charts"), this); @@ -43,6 +44,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { range_lb_action = toolbar->addWidget(range_lb = new QLabel(this)); range_slider = new QSlider(Qt::Horizontal, this); + range_slider->setMaximumWidth(200); range_slider->setToolTip(tr("Set the chart range")); range_slider->setRange(1, settings.max_cached_minutes * 60); range_slider->setSingleStep(1); @@ -67,6 +69,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { charts_main_layout->addStretch(0); QScrollArea *charts_scroll = new QScrollArea(this); + charts_scroll->setFrameStyle(QFrame::NoFrame); charts_scroll->setWidgetResizable(true); charts_scroll->setWidget(charts_container); charts_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -329,18 +332,12 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { move_icon = new QGraphicsPixmapItem(utils::icon("grip-horizontal"), chart); move_icon->setToolTip(tr("Drag and drop to combine charts")); - QToolButton *remove_btn = new QToolButton(); - remove_btn->setIcon(utils::icon("x")); - remove_btn->setAutoRaise(true); - remove_btn->setToolTip(tr("Remove Chart")); + QToolButton *remove_btn = toolButton("x", tr("Remove Chart")); close_btn_proxy = new QGraphicsProxyWidget(chart); close_btn_proxy->setWidget(remove_btn); close_btn_proxy->setZValue(chart->zValue() + 11); - QToolButton *manage_btn = new QToolButton(); - manage_btn->setToolButtonStyle(Qt::ToolButtonIconOnly); - manage_btn->setIcon(utils::icon("list")); - manage_btn->setAutoRaise(true); + QToolButton *manage_btn = toolButton("list", ""); QMenu *menu = new QMenu(this); line_series_action = menu->addAction(tr("Line"), [this]() { setSeriesType(QAbstractSeries::SeriesTypeLine); }); line_series_action->setCheckable(true); @@ -434,12 +431,12 @@ void ChartView::manageSeries() { } void ChartView::resizeEvent(QResizeEvent *event) { - QChartView::resizeEvent(event); updatePlotArea(align_to); int x = event->size().width() - close_btn_proxy->size().width() - 11; close_btn_proxy->setPos(x, 8); manage_btn_proxy->setPos(x - manage_btn_proxy->size().width() - 5, 8); move_icon->setPos(11, 8); + QChartView::resizeEvent(event); } void ChartView::updatePlotArea(int left) { @@ -448,7 +445,7 @@ void ChartView::updatePlotArea(int left) { align_to = left; background->setRect(r); chart()->legend()->setGeometry(QRect(r.left(), r.top(), r.width(), 45)); - chart()->setPlotArea(QRect(align_to, r.top() + 45, r.width() - align_to - 22, r.height() - 80)); + chart()->setPlotArea(QRect(align_to, r.top() + 45, r.width() - align_to - 36, r.height() - 80)); chart()->layout()->invalidate(); } } @@ -570,7 +567,7 @@ void ChartView::updateAxisY() { QFontMetrics fm(axis_y->labelsFont()); int n = qMax(int(-qFloor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1; - y_label_width = qMax(fm.width(QString::number(min_y, 'f', n)), fm.width(QString::number(max_y, 'f', n))) + 20; // left margin 20 + y_label_width = qMax(fm.width(QString::number(min_y, 'f', n)), fm.width(QString::number(max_y, 'f', n))) + 15; // left margin 15 emit axisYLabelWidthChanged(y_label_width); } } @@ -887,10 +884,10 @@ void SeriesSelector::updateAvailableList(int index) { available_list->clear(); MessageId msg_id = msgs_combo->itemData(index).value(); auto selected_items = seletedItems(); - for (auto &s : dbc()->msg(msg_id)->sigs) { - bool is_selected = std::any_of(selected_items.begin(), selected_items.end(), [=, sig=&s](auto it) { return it->msg_id == msg_id && it->sig == sig; }); + for (auto s : dbc()->msg(msg_id)->getSignals()) { + bool is_selected = std::any_of(selected_items.begin(), selected_items.end(), [=, sig=s](auto it) { return it->msg_id == msg_id && it->sig == sig; }); if (!is_selected) { - addItemToList(available_list, msg_id, &s); + addItemToList(available_list, msg_id, s); } } } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 7089b4eaee..a1dcf32513 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -92,7 +92,7 @@ private: friend class ChartsWidget; }; -class ChartsWidget : public QWidget { +class ChartsWidget : public QFrame { Q_OBJECT public: diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc index 70700b33b9..9b9724aada 100644 --- a/tools/cabana/commands.cc +++ b/tools/cabana/commands.cc @@ -38,8 +38,8 @@ RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) : void RemoveMsgCommand::undo() { if (!message.name.isEmpty()) { dbc()->updateMsg(id, message.name, message.size); - for (auto &s : message.sigs) - dbc()->addSignal(id, s); + for (auto s : message.getSignals()) + dbc()->addSignal(id, *s); } } diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbcmanager.cc index ae3a65a99b..32113c2f22 100644 --- a/tools/cabana/dbcmanager.cc +++ b/tools/cabana/dbcmanager.cc @@ -10,10 +10,6 @@ namespace dbcmanager { -void sortSignalsByAddress(QList &sigs) { - std::sort(sigs.begin(), sigs.end(), [](auto &a, auto &b) { return a.start_bit < b.start_bit; }); -} - bool DBCManager::open(const QString &dbc_file_name, QString *error) { QString opendbc_file_path = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, dbc_file_name); QFile file(opendbc_file_path); @@ -83,27 +79,27 @@ QString DBCManager::generateDBC() { QString dbc_string, signal_comment, val_desc; for (auto &[address, m] : msgs) { dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(address).arg(m.name).arg(m.size); - for (auto &sig : m.sigs) { + for (auto sig : m.getSignals()) { dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [%8|%9] \"%10\" XXX\n") - .arg(sig.name) - .arg(sig.start_bit) - .arg(sig.size) - .arg(sig.is_little_endian ? '1' : '0') - .arg(sig.is_signed ? '-' : '+') - .arg(sig.factor, 0, 'g', std::numeric_limits::digits10) - .arg(sig.offset, 0, 'g', std::numeric_limits::digits10) - .arg(sig.min) - .arg(sig.max) - .arg(sig.unit); - if (!sig.comment.isEmpty()) { - signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig.name).arg(sig.comment); + .arg(sig->name) + .arg(sig->start_bit) + .arg(sig->size) + .arg(sig->is_little_endian ? '1' : '0') + .arg(sig->is_signed ? '-' : '+') + .arg(sig->factor, 0, 'g', std::numeric_limits::digits10) + .arg(sig->offset, 0, 'g', std::numeric_limits::digits10) + .arg(sig->min) + .arg(sig->max) + .arg(sig->unit); + if (!sig->comment.isEmpty()) { + signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(sig->comment); } - if (!sig.val_desc.isEmpty()) { - QString text; - for (auto &[val, desc] : sig.val_desc) { - text += QString("%1 \"%2\"").arg(val, desc); + if (!sig->val_desc.isEmpty()) { + QStringList text; + for (auto &[val, desc] : sig->val_desc) { + text << QString("%1 \"%2\"").arg(val, desc); } - val_desc += QString("VAL_ %1 %2 %3;\n").arg(address).arg(sig.name).arg(text); + val_desc += QString("VAL_ %1 %2 %3;\n").arg(address).arg(sig->name).arg(text.join(" ")); } } dbc_string += "\n"; @@ -127,7 +123,6 @@ void DBCManager::addSignal(const MessageId &id, const Signal &sig) { if (auto m = const_cast(msg(id.address))) { m->sigs.push_back(sig); auto s = &m->sigs.last(); - sortSignalsByAddress(m->sigs); emit signalAdded(id.address, s); } } @@ -136,7 +131,6 @@ void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, cons if (auto m = const_cast(msg(id))) { if (auto s = (Signal *)m->sig(sig_name)) { *s = sig; - sortSignalsByAddress(m->sigs); emit signalUpdated(s); } } @@ -157,6 +151,16 @@ DBCManager *dbc() { return &dbc_manager; } +// Msg + +std::vector Msg::getSignals() const { + std::vector ret; + ret.reserve(sigs.size()); + for (auto &sig : sigs) ret.push_back(&sig); + std::sort(ret.begin(), ret.end(), [](auto l, auto r) { return l->start_bit < r->start_bit; }); + return ret; +} + // helper functions static QVector BIG_ENDIAN_START_BITS = []() { @@ -246,7 +250,6 @@ bool dbcmanager::DBCManager::open(const QString &name, const QString &content, Q sig.offset = s.offset; sig.is_little_endian = s.is_little_endian; } - sortSignalsByAddress(m.sigs); } parseExtraInfo(content); name_ = name; diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index 267157b31a..d877347796 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -52,12 +52,16 @@ struct Signal { struct Msg { QString name; uint32_t size; - QList sigs; + std::vector getSignals() const; const Signal *sig(const QString &sig_name) const { auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.name == sig_name; }); return it != sigs.end() ? &(*it) : nullptr; } + +private: + QList sigs; + friend class DBCManager; }; class DBCManager : public QObject { diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 882ea932c6..01fbccf7db 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -3,15 +3,13 @@ #include #include #include -#include #include "tools/cabana/commands.h" // DetailWidget DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { - QWidget *main_widget = new QWidget(this); - QVBoxLayout *main_layout = new QVBoxLayout(main_widget); + QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); // tabbar @@ -23,22 +21,22 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart main_layout->addWidget(tabbar); // message title - QToolBar *toolbar = new QToolBar(this); - toolbar->setIconSize({16, 16}); - toolbar->addWidget(new QLabel("time:")); + QHBoxLayout *title_layout = new QHBoxLayout(); + title_layout->setContentsMargins(0, 6, 0, 0); time_label = new QLabel(this); - time_label->setStyleSheet("font-weight:bold"); - toolbar->addWidget(time_label); + time_label->setToolTip(tr("Current time")); + time_label->setStyleSheet("QLabel{font-weight:bold;}"); + title_layout->addWidget(time_label); name_label = new ElidedLabel(this); - name_label->setContentsMargins(5, 0, 5, 0); - name_label->setStyleSheet("font-weight:bold;"); + name_label->setStyleSheet("QLabel{font-weight:bold;}"); name_label->setAlignment(Qt::AlignCenter); name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - toolbar->addWidget(name_label); - toolbar->addAction(utils::icon("pencil"), "", this, &DetailWidget::editMsg)->setToolTip(tr("Edit Message")); - remove_msg_act = toolbar->addAction(utils::icon("x-lg"), "", this, &DetailWidget::removeMsg); - remove_msg_act->setToolTip(tr("Remove Message")); - main_layout->addWidget(toolbar); + title_layout->addWidget(name_label); + auto edit_btn = toolButton("pencil", tr("Edit Message")); + title_layout->addWidget(edit_btn); + remove_btn = toolButton("x-lg", tr("Remove Message")); + title_layout->addWidget(remove_btn); + main_layout->addLayout(title_layout); // warning warning_widget = new QWidget(this); @@ -59,19 +57,18 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart splitter->setStretchFactor(1, 1); tab_widget = new QTabWidget(this); + tab_widget->setStyleSheet("QTabWidget::pane {border: none; margin-bottom: -2px;}"); tab_widget->setTabPosition(QTabWidget::South); tab_widget->addTab(splitter, utils::icon("file-earmark-ruled"), "&Msg"); tab_widget->addTab(history_log = new LogsWidget(this), utils::icon("stopwatch"), "&Logs"); main_layout->addWidget(tab_widget); - stacked_layout = new QStackedLayout(this); - stacked_layout->addWidget(new WelcomeWidget(this)); - stacked_layout->addWidget(main_widget); - + QObject::connect(edit_btn, &QToolButton::clicked, this, &DetailWidget::editMsg); + QObject::connect(remove_btn, &QToolButton::clicked, this, &DetailWidget::removeMsg); QObject::connect(binary_view, &BinaryView::resizeSignal, signal_view->model, &SignalModel::resizeSignal); QObject::connect(binary_view, &BinaryView::addSignal, signal_view->model, &SignalModel::addSignal); QObject::connect(binary_view, &BinaryView::signalHovered, signal_view, &SignalView::signalHovered); - QObject::connect(binary_view, &BinaryView::signalClicked, signal_view, &SignalView::expandSignal); + QObject::connect(binary_view, &BinaryView::signalClicked, [this](const Signal *s) { signal_view->selectSignal(s, true); }); QObject::connect(binary_view, &BinaryView::editSignal, signal_view->model, &SignalModel::saveSignal); QObject::connect(binary_view, &BinaryView::removeSignal, signal_view->model, &SignalModel::removeSignal); QObject::connect(binary_view, &BinaryView::showChart, charts, &ChartsWidget::showChart); @@ -87,9 +84,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart setMessage(tabbar->tabData(index).value()); } }); - QObject::connect(tabbar, &QTabBar::tabCloseRequested, [this](int index) { - tabbar->removeTab(index); - }); + QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab); QObject::connect(charts, &ChartsWidget::seriesChanged, signal_view, &SignalView::updateChartState); } @@ -108,18 +103,8 @@ void DetailWidget::showTabBarContextMenu(const QPoint &pt) { } } -void DetailWidget::removeAll() { - msg_id = std::nullopt; - tabbar->blockSignals(true); - while (tabbar->count() > 0) { - tabbar->removeTab(0); - } - tabbar->blockSignals(false); - stacked_layout->setCurrentIndex(0); -} - void DetailWidget::setMessage(const MessageId &message_id) { - msg_id = message_id; + if (std::exchange(msg_id, message_id) == message_id) return; tabbar->blockSignals(true); int index = tabbar->count() - 1; @@ -135,25 +120,18 @@ void DetailWidget::setMessage(const MessageId &message_id) { tabbar->blockSignals(false); setUpdatesEnabled(false); - - signal_view->setMessage(*msg_id); - binary_view->setMessage(*msg_id); - history_log->setMessage(*msg_id); - - stacked_layout->setCurrentIndex(1); + signal_view->setMessage(msg_id); + binary_view->setMessage(msg_id); + history_log->setMessage(msg_id); refresh(); - splitter->setSizes({1, 2}); - setUpdatesEnabled(true); } void DetailWidget::refresh() { - if (!msg_id) return; - QStringList warnings; - auto msg = dbc()->msg(*msg_id); + auto msg = dbc()->msg(msg_id); if (msg) { - if (msg->size != can->lastMessage(*msg_id).dat.size()) { + if (msg->size != can->lastMessage(msg_id).dat.size()) { warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size)); } for (auto s : binary_view->getOverlappingSignals()) { @@ -162,8 +140,8 @@ void DetailWidget::refresh() { } else { warnings.push_back(tr("Drag-Select in binary view to create new signal.")); } - remove_msg_act->setEnabled(msg != nullptr); - name_label->setText(msgName(*msg_id)); + remove_btn->setEnabled(msg != nullptr); + name_label->setText(msgName(msg_id)); if (!warnings.isEmpty()) { warning_label->setText(warnings.join('\n')); @@ -174,7 +152,7 @@ void DetailWidget::refresh() { void DetailWidget::updateState(const QHash *msgs) { time_label->setText(QString::number(can->currentSec(), 'f', 3)); - if (!msg_id || (msgs && !msgs->contains(*msg_id))) + if ((msgs && !msgs->contains(msg_id))) return; if (tab_widget->currentIndex() == 0) @@ -184,17 +162,16 @@ void DetailWidget::updateState(const QHash *msgs) { } void DetailWidget::editMsg() { - MessageId id = *msg_id; - auto msg = dbc()->msg(id); - int size = msg ? msg->size : can->lastMessage(id).dat.size(); - EditMessageDialog dlg(id, msgName(id), size, this); + auto msg = dbc()->msg(msg_id); + int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); + EditMessageDialog dlg(msg_id, msgName(msg_id), size, this); if (dlg.exec()) { - UndoStack::push(new EditMsgCommand(*msg_id, dlg.name_edit->text(), dlg.size_spin->value())); + UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text(), dlg.size_spin->value())); } } void DetailWidget::removeMsg() { - UndoStack::push(new RemoveMsgCommand(*msg_id)); + UndoStack::push(new RemoveMsgCommand(msg_id)); } // EditMessageDialog @@ -240,10 +217,34 @@ void EditMessageDialog::validateName(const QString &text) { btn_box->button(QDialogButtonBox::Ok)->setEnabled(valid); } -// WelcomeWidget +// CenterWidget -WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) { +CenterWidget::CenterWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->addWidget(welcome_widget = createWelcomeWidget()); +} + +void CenterWidget::setMessage(const MessageId &msg_id) { + if (!detail_widget) { + delete welcome_widget; + welcome_widget = nullptr; + layout()->addWidget(detail_widget = new DetailWidget(charts, this)); + } + detail_widget->setMessage(msg_id); +} + +void CenterWidget::clear() { + delete detail_widget; + detail_widget = nullptr; + if (!welcome_widget) { + layout()->addWidget(welcome_widget = createWelcomeWidget()); + } +} + +QWidget *CenterWidget::createWelcomeWidget() { + QWidget *w = new QWidget(this); + QVBoxLayout *main_layout = new QVBoxLayout(w); main_layout->addStretch(0); QLabel *logo = new QLabel("CABANA"); logo->setAlignment(Qt::AlignCenter); @@ -268,7 +269,8 @@ WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) { main_layout->addLayout(newShortcutRow("WhatsThis", "Shift+F1")); main_layout->addStretch(0); - setStyleSheet("QLabel{color:darkGray;}"); - setBackgroundRole(QPalette::Base); - setAutoFillBackground(true); + w->setStyleSheet("QLabel{color:darkGray;}"); + w->setBackgroundRole(QPalette::Base); + w->setAutoFillBackground(true); + return w; } diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index b3e353d539..0bfd8a74af 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -2,9 +2,7 @@ #include #include -#include #include -#include #include "selfdrive/ui/qt/widgets/controls.h" #include "tools/cabana/binaryview.h" @@ -24,11 +22,6 @@ public: QSpinBox *size_spin; }; -class WelcomeWidget : public QWidget { -public: - WelcomeWidget(QWidget *parent); -}; - class DetailWidget : public QWidget { Q_OBJECT @@ -36,7 +29,6 @@ public: DetailWidget(ChartsWidget *charts, QWidget *parent); void setMessage(const MessageId &message_id); void refresh(); - void removeAll(); QSize minimumSizeHint() const override { return binary_view->minimumSizeHint(); } private: @@ -45,17 +37,30 @@ private: void removeMsg(); void updateState(const QHash * msgs = nullptr); - std::optional msg_id; + MessageId msg_id; QLabel *time_label, *warning_icon, *warning_label; ElidedLabel *name_label; QWidget *warning_widget; QTabBar *tabbar; QTabWidget *tab_widget; - QAction *remove_msg_act; + QToolButton *remove_btn; LogsWidget *history_log; BinaryView *binary_view; SignalView *signal_view; ChartsWidget *charts; QSplitter *splitter; - QStackedLayout *stacked_layout; +}; + +class CenterWidget : public QWidget { + Q_OBJECT +public: + CenterWidget(ChartsWidget* charts, QWidget *parent); + void setMessage(const MessageId &msg_id); + void clear(); + +private: + QWidget *createWelcomeWidget(); + DetailWidget *detail_widget = nullptr; + QWidget *welcome_widget = nullptr; + ChartsWidget *charts; }; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index a1c671b68d..abf0b53af8 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -5,8 +5,6 @@ #include #include "tools/cabana/commands.h" -#include "tools/cabana/util.h" - // HistoryLogModel QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { @@ -27,17 +25,19 @@ void HistoryLogModel::setMessage(const MessageId &message_id) { msg_id = message_id; } -void HistoryLogModel::refresh() { +void HistoryLogModel::refresh(bool fetch_message) { beginResetModel(); sigs.clear(); - if (auto dbc_msg = dbc()->msg(*msg_id)) { - sigs = dbc_msg->sigs; + if (auto dbc_msg = dbc()->msg(msg_id)) { + sigs = dbc_msg->getSignals(); } last_fetch_time = 0; has_more_data = true; messages.clear(); hex_colors.clear(); - updateState(); + if (fetch_message) { + updateState(); + } endResetModel(); } @@ -48,9 +48,9 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i if (section == 0) { return "Time"; } - return show_signals ? sigs[section - 1].name : "Data"; + return show_signals ? sigs[section - 1]->name : "Data"; } else if (role == Qt::BackgroundRole && section > 0 && show_signals) { - return QBrush(getColor(&sigs[section - 1])); + return QBrush(getColor(sigs[section - 1])); } } return {}; @@ -79,17 +79,15 @@ void HistoryLogModel::setFilter(int sig_idx, const QString &value, std::function } void HistoryLogModel::updateState() { - if (msg_id) { - uint64_t current_time = (can->lastMessage(*msg_id).ts + can->routeStartTime()) * 1e9 + 1; - auto new_msgs = dynamic_mode ? fetchData(current_time, last_fetch_time) : fetchData(0); - if (!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())); - endInsertRows(); - } - has_more_data = new_msgs.size() >= batch_size; - last_fetch_time = current_time; + uint64_t current_time = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9 + 1; + auto new_msgs = dynamic_mode ? fetchData(current_time, last_fetch_time) : fetchData(0); + if (!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())); + endInsertRows(); } + has_more_data = new_msgs.size() >= batch_size; + last_fetch_time = current_time; } void HistoryLogModel::fetchMore(const QModelIndex &parent) { @@ -111,10 +109,10 @@ std::deque HistoryLogModel::fetchData(InputIt first, I for (auto it = first; it != last && (*it)->mono_time > min_time; ++it) { if ((*it)->which == cereal::Event::Which::CAN) { for (const auto &c : (*it)->event.getCan()) { - if (msg_id->address == c.getAddress() && msg_id->source == c.getSrc()) { + if (msg_id.address == c.getAddress() && msg_id.source == c.getSrc()) { const auto dat = c.getDat(); for (int i = 0; i < sigs.size(); ++i) { - values[i] = get_raw_value((uint8_t *)dat.begin(), dat.size(), sigs[i]); + values[i] = get_raw_value((uint8_t *)dat.begin(), dat.size(), *sigs[i]); } if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) { auto &m = msgs.emplace_back(); @@ -136,7 +134,7 @@ template std::deque HistoryLogModel::fetchData<>(std:: std::deque HistoryLogModel::fetchData(uint64_t from_time, uint64_t min_time) { auto events = can->events(); - const auto freq = can->lastMessage(*msg_id).freq; + const auto freq = can->lastMessage(msg_id).freq; const bool update_colors = !display_signals_mode || sigs.empty(); if (dynamic_mode) { @@ -189,7 +187,8 @@ void HeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalI // LogsWidget -LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) { +LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { + setFrameStyle(QFrame::StyledPanel | QFrame::Plain); QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setSpacing(0); @@ -209,7 +208,8 @@ LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) { h->addStretch(0); h->addWidget(dynamic_mode = new QCheckBox(tr("Dynamic")), 0, Qt::AlignRight); - display_type_cb->addItems({"Signal Value", "Hex Value"}); + display_type_cb->addItems({"Signal", "Hex"}); + display_type_cb->setToolTip(tr("Display signal value or raw hex value")); comp_box->addItems({">", "=", "!=", "<"}); value_edit->setClearButtonEnabled(true); value_edit->setValidator(new QDoubleValidator(-500000, 500000, 6, this)); @@ -219,10 +219,10 @@ LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) { main_layout->addWidget(toolbar); QFrame *line = new QFrame(this); line->setFrameStyle(QFrame::HLine | QFrame::Sunken); - main_layout->addWidget(line);; - + main_layout->addWidget(line); main_layout->addWidget(logs = new QTableView(this)); logs->setModel(model = new HistoryLogModel(this)); + delegate = new MessageBytesDelegate(this); logs->setItemDelegateForColumn(1, new MessageBytesDelegate(this)); logs->setHorizontalHeader(new HeaderView(Qt::Horizontal, this)); logs->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap); @@ -230,7 +230,10 @@ LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) { logs->verticalHeader()->setVisible(false); logs->setFrameShape(QFrame::NoFrame); - QObject::connect(display_type_cb, SIGNAL(activated(int)), model, SLOT(setDisplayType(int))); + QObject::connect(display_type_cb, qOverload(&QComboBox::activated), [this](int index) { + logs->setItemDelegateForColumn(1, index == 1 ? delegate : nullptr); + model->setDisplayType(index); + }); QObject::connect(dynamic_mode, &QCheckBox::stateChanged, model, &HistoryLogModel::setDynamicMode); QObject::connect(signals_cb, SIGNAL(activated(int)), this, SLOT(setFilter())); QObject::connect(comp_box, SIGNAL(activated(int)), this, SLOT(setFilter())); @@ -247,17 +250,16 @@ void LogsWidget::setMessage(const MessageId &message_id) { } void LogsWidget::refresh() { - if (!model->msg_id) return; - model->setFilter(0, "", nullptr); - model->refresh(); + model->refresh(isVisible()); bool has_signal = model->sigs.size(); if (has_signal) { signals_cb->clear(); - for (auto &s : model->sigs) { - signals_cb->addItem(s.name); + for (auto s : model->sigs) { + signals_cb->addItem(s->name); } } + logs->setItemDelegateForColumn(1, !has_signal || display_type_cb->currentIndex() == 1 ? delegate : nullptr); value_edit->clear(); comp_box->setCurrentIndex(0); filters_widget->setVisible(has_signal); @@ -276,3 +278,15 @@ void LogsWidget::setFilter() { model->setFilter(signals_cb->currentIndex(), value_edit->text(), cmp); model->refresh(); } + +void LogsWidget::updateState() { + if (isVisible() && dynamic_mode->isChecked()) { + model->updateState(); + } +} + +void LogsWidget::showEvent(QShowEvent *event) { + if (dynamic_mode->isChecked() || model->canFetchMore({}) && model->rowCount() == 0) { + model->refresh(); + } +} diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index a1b7bc0098..206d53bc8d 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -1,8 +1,6 @@ #pragma once #include -#include - #include #include #include @@ -11,6 +9,8 @@ #include "tools/cabana/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" +#include "tools/cabana/util.h" + using namespace dbcmanager; class HeaderView : public QHeaderView { @@ -36,7 +36,7 @@ public: int columnCount(const QModelIndex &parent = QModelIndex()) const override { return display_signals_mode && !sigs.empty() ? sigs.size() + 1 : 2; } - void refresh(); + void refresh(bool fetch_message = true); public slots: void setDisplayType(int type); @@ -55,7 +55,7 @@ public: std::deque fetchData(InputIt first, InputIt last, uint64_t min_time); std::deque fetchData(uint64_t from_time, uint64_t min_time = 0); - std::optional msg_id; + MessageId msg_id; ChangeTracker hex_colors; bool has_more_data = true; const int batch_size = 50; @@ -64,19 +64,19 @@ public: uint64_t last_fetch_time = 0; std::function filter_cmp = nullptr; std::deque messages; - QList sigs; + std::vector sigs; bool dynamic_mode = true; bool display_signals_mode = true; }; -class LogsWidget : public QWidget { +class LogsWidget : public QFrame { Q_OBJECT public: LogsWidget(QWidget *parent); void setMessage(const MessageId &message_id); - void updateState() {if (dynamic_mode->isChecked()) model->updateState(); } - void showEvent(QShowEvent *event) override { if (dynamic_mode->isChecked()) model->refresh(); } + void updateState(); + void showEvent(QShowEvent *event) override; private slots: void setFilter(); @@ -90,4 +90,5 @@ private: QComboBox *signals_cb, *comp_box, *display_type_cb; QLineEdit *value_edit; QWidget *filters_widget; + MessageBytesDelegate *delegate; }; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 47ad3a4a6d..bc090b2cc0 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -27,8 +27,8 @@ void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const MainWindow::MainWindow() : QMainWindow() { createDockWindows(); - detail_widget = new DetailWidget(charts_widget, this); - setCentralWidget(detail_widget); + center_widget = new CenterWidget(charts_widget, this); + setCentralWidget(center_widget); createActions(); createStatusBar(); createShortcuts(); @@ -60,7 +60,7 @@ MainWindow::MainWindow() : QMainWindow() { QObject::connect(this, &MainWindow::showMessage, statusBar(), &QStatusBar::showMessage); QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress); - QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage); + QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, center_widget, &CenterWidget::setMessage); QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); QObject::connect(can, &AbstractStream::streamStarted, this, &MainWindow::loadDBCFromFingerprint); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged); @@ -175,7 +175,7 @@ void MainWindow::createStatusBar() { progress_bar = new QProgressBar(); progress_bar->setRange(0, 100); progress_bar->setTextVisible(true); - progress_bar->setFixedSize({230, 16}); + progress_bar->setFixedSize({300, 16}); progress_bar->setVisible(false); statusBar()->addWidget(new QLabel(tr("For Help, Press F1"))); statusBar()->addPermanentWidget(progress_bar); @@ -220,7 +220,7 @@ void MainWindow::DBCFileChanged() { void MainWindow::openRoute() { OpenRouteDialog dlg(this); if (dlg.exec()) { - detail_widget->removeAll(); + center_widget->clear(); charts_widget->removeAll(); statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000); } else if (dlg.failedToLoad()) { diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 85d221a231..dd53b1f213 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -60,7 +60,7 @@ protected: VideoWidget *video_widget = nullptr; QDockWidget *video_dock; MessagesWidget *messages_widget; - DetailWidget *detail_widget; + CenterWidget *center_widget; ChartsWidget *charts_widget; QWidget *floating_window = nullptr; QVBoxLayout *charts_layout; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 42ac22865c..c880c61058 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -1,17 +1,17 @@ #include "tools/cabana/messageswidget.h" -#include -#include #include -#include #include #include MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0 ,0, 0, 0); // message filter filter = new QLineEdit(this); + QRegularExpression re("\\S+"); + filter->setValidator(new QRegularExpressionValidator(re, this)); filter->setClearButtonEnabled(true); filter->setPlaceholderText(tr("filter messages")); main_layout->addWidget(filter); @@ -44,11 +44,16 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::sortMessages); QObject::connect(dbc(), &DBCManager::msgUpdated, model, &MessageListModel::sortMessages); QObject::connect(dbc(), &DBCManager::msgRemoved, model, &MessageListModel::sortMessages); - QObject::connect(model, &MessageListModel::modelReset, [this]() { selectMessage(*current_msg_id); }); + QObject::connect(model, &MessageListModel::modelReset, [this]() { + if (current_msg_id) { + selectMessage(*current_msg_id); + } + }); QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { if (current.isValid() && current.row() < model->msgs.size()) { - if (model->msgs[current.row()] != *current_msg_id) { - current_msg_id = model->msgs[current.row()]; + auto &id = model->msgs[current.row()]; + if (!current_msg_id || id != *current_msg_id) { + current_msg_id = id; emit msgSelectionChanged(*current_msg_id); } } @@ -67,10 +72,10 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { setWhatsThis(tr(R"( Message View
- Byte color:
+ Byte color
constant changing
increasing
- decreasing
+ decreasing )")); } @@ -91,9 +96,10 @@ void MessagesWidget::updateSuppressedButtons() { } void MessagesWidget::reset() { + current_msg_id = std::nullopt; + table_widget->selectionModel()->clear(); model->reset(); filter->clear(); - current_msg_id = std::nullopt; updateSuppressedButtons(); } @@ -138,8 +144,8 @@ void MessageListModel::setFilterString(const QString &string) { if (id.toString().contains(txt, cs) || msgName(id).contains(txt, cs)) return true; // Search by signal name if (const auto msg = dbc()->msg(id)) { - for (auto &signal : msg->sigs) { - if (signal.name.contains(txt, cs)) return true; + for (auto s : msg->getSignals()) { + if (s->name.contains(txt, cs)) return true; } } return false; diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index d88def3acb..926d131c8d 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -1,12 +1,9 @@ #pragma once -#include - #include #include #include #include -#include #include #include "tools/cabana/dbcmanager.h" diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index d5099da6f4..e862439b68 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -6,7 +6,6 @@ #include #include #include -#include #include #include "tools/cabana/commands.h" @@ -48,9 +47,9 @@ void SignalModel::refresh() { beginResetModel(); root.reset(new SignalModel::Item); if (auto msg = dbc()->msg(msg_id)) { - for (auto &s : msg->sigs) { - if (filter_str.isEmpty() || s.name.contains(filter_str, Qt::CaseInsensitive)) { - insertItem(root.get(), root->children.size(), &s); + for (auto s : msg->getSignals()) { + if (filter_str.isEmpty() || s->name.contains(filter_str, Qt::CaseInsensitive)) { + insertItem(root.get(), root->children.size(), s); } } } @@ -58,7 +57,7 @@ void SignalModel::refresh() { } void SignalModel::updateState(const QHash *msgs) { - if (!msgs || (msgs->contains(msg_id))) { + if (!msgs || msgs->contains(msg_id)) { auto &dat = can->lastMessage(msg_id).dat; int row = 0; for (auto item : root->children) { @@ -93,9 +92,8 @@ Qt::ItemFlags SignalModel::flags(const QModelIndex &index) const { } int SignalModel::signalRow(const Signal *sig) const { - auto &children = root->children; - for (int i = 0; i < children.size(); ++i) { - if (children[i]->sig == sig) return i; + for (int i = 0; i < root->children.size(); ++i) { + if (root->children[i]->sig == sig) return i; } return -1; } @@ -129,11 +127,11 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const { case Item::Min: return item->sig->min; case Item::Max: return item->sig->max; case Item::Desc: { - QString val_desc; + QStringList val_desc; for (auto &[val, desc] : item->sig->val_desc) { - val_desc += QString("%1 \"%2\"").arg(val, desc); + val_desc << QString("%1 \"%2\"").arg(val, desc); } - return val_desc; + return val_desc.join(" "); } default: break; } @@ -280,16 +278,24 @@ void SignalModel::handleSignalRemoved(const Signal *sig) { // SignalItemDelegate -SignalItemDelegate::SignalItemDelegate(QObject *parent) { +SignalItemDelegate::SignalItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { name_validator = new NameValidator(this); double_validator = new QDoubleValidator(this); double_validator->setLocale(QLocale::C); // Match locale of QString::toDouble() instead of system small_font.setPointSize(8); } +QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { + QSize size = QStyledItemDelegate::sizeHint(option, index); + if (!index.parent().isValid() && index.column() == 0) { + size.rwidth() = std::min(((QWidget*)parent())->size().width() / 2, size.width() + color_label_width + 8); + } + return size; +} + void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto item = (SignalModel::Item *)index.internalPointer(); - if (item && !index.parent().isValid() && index.column() == 0) { + if (index.column() == 0 && item && item->type == SignalModel::Item::Sig) { painter->save(); painter->setRenderHint(QPainter::Antialiasing); if (option.state & QStyle::State_Selected) { @@ -298,7 +304,7 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op // color label auto bg_color = getColor(item->sig); - QRect rc{option.rect.left(), option.rect.top(), 18, option.rect.height()}; + QRect rc{option.rect.left(), option.rect.top(), color_label_width, option.rect.height()}; painter->setPen(Qt::NoPen); painter->setBrush(item->highlight ? bg_color.darker(125) : bg_color); painter->drawRoundedRect(rc.adjusted(0, 2, 0, -2), 3, 3); @@ -345,22 +351,22 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie // SignalView -SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { +SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QFrame(parent) { + setFrameStyle(QFrame::StyledPanel | QFrame::Plain); // title bar QWidget *title_bar = new QWidget(this); title_bar->setAutoFillBackground(true); QHBoxLayout *hl = new QHBoxLayout(title_bar); hl->addWidget(signal_count_lb = new QLabel()); filter_edit = new QLineEdit(this); + QRegularExpression re("\\S+"); + filter_edit->setValidator(new QRegularExpressionValidator(re, this)); filter_edit->setClearButtonEnabled(true); - filter_edit->setPlaceholderText(tr("filter signals by name")); + filter_edit->setPlaceholderText(tr("filter signals")); hl->addWidget(filter_edit); hl->addStretch(1); - auto collapse_btn = new QToolButton(); - collapse_btn->setIcon(utils::icon("dash-square")); + auto collapse_btn = toolButton("dash-square", tr("Collapse All")); collapse_btn->setIconSize({12, 12}); - collapse_btn->setAutoRaise(true); - collapse_btn->setToolTip(tr("Collapse All")); hl->addWidget(collapse_btn); // tree view @@ -371,7 +377,8 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), tree->setHeaderHidden(true); tree->setMouseTracking(true); tree->setExpandsOnDoubleClick(false); - tree->header()->setSectionResizeMode(QHeaderView::Stretch); + tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + tree->header()->setStretchLastSection(true); tree->setMinimumHeight(300); tree->setStyleSheet("QSpinBox{background-color:white;border:none;} QLineEdit{background-color:white;}"); @@ -389,7 +396,7 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QObject::connect(model, &QAbstractItemModel::modelReset, this, &SignalView::rowsChanged); QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &SignalView::rowsChanged); QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged); - QObject::connect(dbc(), &DBCManager::signalAdded, [this](uint32_t address, const Signal *sig) { expandSignal(sig); }); + QObject::connect(dbc(), &DBCManager::signalAdded, [this](uint32_t address, const Signal *sig) { selectSignal(sig); }); setWhatsThis(tr(R"( Signal view
@@ -404,16 +411,6 @@ void SignalView::setMessage(const MessageId &id) { } void SignalView::rowsChanged() { - auto create_btn = [](const QString &id, const QString &tooltip) { - auto btn = new QToolButton(); - btn->setIcon(utils::icon(id)); - btn->setToolTip(tooltip); - btn->setAutoRaise(true); - return btn; - }; - - signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount())); - for (int i = 0; i < model->rowCount(); ++i) { auto index = model->index(i, 1); if (!tree->indexWidget(index)) { @@ -422,8 +419,8 @@ void SignalView::rowsChanged() { h->setContentsMargins(0, 2, 0, 2); h->addStretch(1); - auto remove_btn = create_btn("x", tr("Remove signal")); - auto plot_btn = create_btn("graph-up", ""); + auto remove_btn = toolButton("x", tr("Remove signal")); + auto plot_btn = toolButton("graph-up", ""); plot_btn->setCheckable(true); h->addWidget(plot_btn); h->addWidget(remove_btn); @@ -436,6 +433,7 @@ void SignalView::rowsChanged() { }); } } + signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount())); updateChartState(); } @@ -449,23 +447,26 @@ void SignalView::rowClicked(const QModelIndex &index) { } } -void SignalView::expandSignal(const Signal *sig) { +void SignalView::selectSignal(const Signal *sig, bool expand) { if (int row = model->signalRow(sig); row != -1) { auto idx = model->index(row, 0); - bool expand = !tree->isExpanded(idx); - tree->setExpanded(idx, expand); + if (expand) { + tree->setExpanded(idx, !tree->isExpanded(idx)); + } tree->scrollTo(idx, QAbstractItemView::PositionAtTop); - if (expand) tree->setCurrentIndex(idx); + tree->setCurrentIndex(idx); } } void SignalView::updateChartState() { int i = 0; for (auto item : model->root->children) { - auto plot_btn = tree->indexWidget(model->index(i, 1))->findChildren()[0]; bool chart_opened = charts->hasSignal(msg_id, item->sig); - plot_btn->setChecked(chart_opened); - plot_btn->setToolTip(chart_opened ? tr("Close Plot") : tr("Show Plot\nSHIFT click to add to previous opened plot")); + auto buttons = tree->indexWidget(model->index(i, 1))->findChildren(); + if (buttons.size() > 0) { + buttons[0]->setChecked(chart_opened); + buttons[0]->setToolTip(chart_opened ? tr("Close Plot") : tr("Show Plot\nSHIFT click to add to previous opened plot")); + } ++i; } } diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index efdc653584..3cb193b62f 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -80,12 +80,14 @@ class SignalItemDelegate : public QStyledItemDelegate { public: SignalItemDelegate(QObject *parent); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QValidator *name_validator, *double_validator; QFont small_font; + const int color_label_width = 18; }; -class SignalView : public QWidget { +class SignalView : public QFrame { Q_OBJECT public: @@ -93,7 +95,7 @@ public: void setMessage(const MessageId &id); void signalHovered(const Signal *sig); void updateChartState(); - void expandSignal(const Signal *sig); + void selectSignal(const Signal *sig, bool expand = false); void rowClicked(const QModelIndex &index); SignalModel *model = nullptr; diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index 8fdfbd5c1b..3e631d3709 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -31,10 +31,12 @@ bool AbstractStream::updateEvent(const Event *event) { data.dat = QByteArray((char *)c.getDat().begin(), c.getDat().size()); data.count = ++counters[id]; data.freq = data.count / std::max(1.0, current_sec); - change_trackers[id].compute(data.dat, data.ts, data.freq); - data.colors = change_trackers[id].colors; - data.last_change_t = change_trackers[id].last_change_t; - data.bit_change_counts = change_trackers[id].bit_change_counts; + + auto &tracker = change_trackers[id]; + tracker.compute(data.dat, data.ts, data.freq); + data.colors = tracker.colors; + data.last_change_t = tracker.last_change_t; + data.bit_change_counts = tracker.bit_change_counts; } double ts = millis_since_boot(); diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index 9a8ab710bc..6f9f2d016b 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -23,9 +23,11 @@ TEST_CASE("DBCManager::generateDBC") { auto &new_m = new_msgs.at(address); REQUIRE(m.name == new_m.name); REQUIRE(m.size == new_m.size); - REQUIRE(m.sigs.size() == new_m.sigs.size()); - for (int i = 0; i < m.sigs.size(); ++i) { - REQUIRE(m.sigs[i] == new_m.sigs[i]); + REQUIRE(m.getSignals().size() == new_m.getSignals().size()); + auto sigs = m.getSignals(); + auto new_sigs = new_m.getSignals(); + for (int i = 0; i < sigs.size(); ++i) { + REQUIRE(*sigs[i] == *new_sigs[i]); } } } @@ -44,9 +46,9 @@ TEST_CASE("Parse can messages") { for (const auto &c : e->event.getCan()) { const auto msg = dbc.msg(c.getAddress()); if (c.getSrc() == 0 && msg) { - for (auto &sig : msg->sigs) { - double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), sig); - values_1[{c.getAddress(), sig.name}].push_back(val); + for (auto sig : msg->getSignals()) { + double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), *sig); + values_1[{c.getAddress(), sig->name}].push_back(val); } } } diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index 6415cc8e16..4f79f9a3ac 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -73,32 +74,23 @@ MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegat } void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - QStyleOptionViewItemV4 opt = option; - initStyleOption(&opt, index); - - auto byte_list = opt.text.split(" "); - if (byte_list.size() <= 1) { - QStyledItemDelegate::paint(painter, option, index); - return; - } - auto color_role = option.state & QStyle::State_Selected ? QPalette::HighlightedText: QPalette::Text; painter->setPen(option.palette.color(color_role)); 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; + int space = painter->boundingRect(option.rect, option.displayAlignment, " ").width(); + QRect pos = painter->boundingRect(option.rect, option.displayAlignment, "00").adjusted(0, 0, 2, 0); + pos.moveLeft(pos.x() + space); + int m = space / 2; const QMargins margins(m, m, m, m); auto colors = index.data(Qt::UserRole).value>(); + auto byte_list = index.data(Qt::DisplayRole).toString().split(" "); for (int i = 0; i < byte_list.size(); ++i) { - if (i < colors.size()) { + if (i < colors.size() && colors[i].alpha() > 0) { painter->fillRect(pos.marginsAdded(margins), colors[i]); } - painter->drawText(pos, opt.displayAlignment, byte_list[i]); - pos.moveLeft(pos.right() + space.width()); + painter->drawText(pos, Qt::AlignCenter, byte_list[i]); + pos.moveLeft(pos.right() + space); } } @@ -124,12 +116,25 @@ namespace utils { QPixmap icon(const QString &id) { static bool dark_theme = QApplication::style()->standardPalette().color(QPalette::WindowText).value() > QApplication::style()->standardPalette().color(QPalette::Background).value(); - QPixmap pm = bootstrapPixmap(id); - if (dark_theme) { - QPainter p(&pm); - p.setCompositionMode(QPainter::CompositionMode_SourceIn); - p.fillRect(pm.rect(), Qt::lightGray); + QPixmap pm; + QString key = "bootstrap_" % id % (dark_theme ? "1" : "0"); + if (!QPixmapCache::find(key, &pm)) { + pm = bootstrapPixmap(id); + if (dark_theme) { + QPainter p(&pm); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(pm.rect(), Qt::lightGray); + } + QPixmapCache::insert(key, pm); } return pm; } } // namespace utils + +QToolButton *toolButton(const QString &icon, const QString &tooltip) { + auto btn = new QToolButton(); + btn->setIcon(utils::icon(icon)); + btn->setToolTip(tooltip); + btn->setAutoRaise(true); + return btn; +}; diff --git a/tools/cabana/util.h b/tools/cabana/util.h index 2717451061..b72ddfcaa0 100644 --- a/tools/cabana/util.h +++ b/tools/cabana/util.h @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include #include #include "tools/cabana/dbcmanager.h" @@ -52,3 +54,5 @@ public: namespace utils { QPixmap icon(const QString &id); } + +QToolButton *toolButton(const QString &icon, const QString &tooltip); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 2b5374d85f..536ae7ba97 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -28,9 +28,9 @@ inline QString formatTime(int seconds) { VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); QFrame *frame = new QFrame(this); - frame->setFrameShape(QFrame::StyledPanel); - frame->setFrameShadow(QFrame::Sunken); + frame->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); main_layout->addWidget(frame); QVBoxLayout *frame_layout = new QVBoxLayout(frame);