mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-04 13:02:09 +08:00
cabana: add a slider to set sparkline time range (#27727)
* add slider to set sparkline time range * rename signaleedit to signalview * default is 15s * util::formatSeconds * update() is faster than invalidate * draw points * fix scatter width old-commit-hash: d6961152b824518f584d2ffad831312447d79d5f
This commit is contained in:
@@ -28,7 +28,7 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset
|
||||
|
||||
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',
|
||||
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', 'signalview.cc',
|
||||
'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
|
||||
'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <QToolTip>
|
||||
|
||||
#include "tools/cabana/commands.h"
|
||||
#include "tools/cabana/signaledit.h"
|
||||
#include "tools/cabana/signalview.h"
|
||||
|
||||
// BinaryView
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ void ChartsWidget::setMaxChartRange(int value) {
|
||||
void ChartsWidget::updateToolBar() {
|
||||
title_label->setText(tr("Charts: %1").arg(charts.size()));
|
||||
columns_action->setText(tr("Column: %1").arg(column_count));
|
||||
range_lb->setText(QString("Range: %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0')));
|
||||
range_lb->setText(QString("Range: %1 ").arg(utils::formatSeconds(max_chart_range)));
|
||||
range_lb_action->setVisible(!is_zoomed);
|
||||
range_slider_action->setVisible(!is_zoomed);
|
||||
undo_zoom_action->setVisible(is_zoomed);
|
||||
@@ -528,7 +528,7 @@ void ChartView::updatePlot(double cur, double min, double max) {
|
||||
updateAxisY();
|
||||
updateSeriesPoints();
|
||||
}
|
||||
scene()->invalidate({}, QGraphicsScene::ForegroundLayer);
|
||||
update();
|
||||
}
|
||||
|
||||
void ChartView::updateSeriesPoints() {
|
||||
@@ -536,14 +536,16 @@ void ChartView::updateSeriesPoints() {
|
||||
for (auto &s : sigs) {
|
||||
auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan);
|
||||
auto end = std::lower_bound(begin, s.vals.end(), axis_x->max(), xLessThan);
|
||||
if (begin != end) {
|
||||
int num_points = std::max<int>((end - begin), 1);
|
||||
QPointF right_pt = end == s.vals.end() ? s.vals.back() : *end;
|
||||
double pixels_per_point = (chart()->mapToPosition(right_pt).x() - chart()->mapToPosition(*begin).x()) / num_points;
|
||||
|
||||
int num_points = std::max<int>(end - begin, 1);
|
||||
int pixels_per_point = width() / num_points;
|
||||
|
||||
if (series_type == SeriesType::Scatter) {
|
||||
((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 2, 8) * devicePixelRatioF());
|
||||
} else {
|
||||
s.series->setPointsVisible(pixels_per_point > 20);
|
||||
if (series_type == SeriesType::Scatter) {
|
||||
((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 2.0, 2.0, 8.0) * devicePixelRatioF());
|
||||
} else {
|
||||
s.series->setPointsVisible(pixels_per_point > 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -715,7 +717,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
|
||||
// zoom in if selected range is greater than 0.5s
|
||||
emit zoomIn(min_rounded, max_rounded);
|
||||
} else {
|
||||
scene()->invalidate({}, QGraphicsScene::ForegroundLayer);
|
||||
update();
|
||||
}
|
||||
event->accept();
|
||||
} else if (!can->liveStreaming() && event->button() == Qt::RightButton) {
|
||||
@@ -773,7 +775,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
|
||||
text_list.push_front(QString::number(chart()->mapToValue({x, 0}).x(), 'f', 3));
|
||||
QPointF tooltip_pt(x + 12, plot_area.top() - 20);
|
||||
QToolTip::showText(mapToGlobal(tooltip_pt.toPoint()), text_list.join("<br />"), this, plot_area.toRect());
|
||||
scene()->invalidate({}, QGraphicsScene::ForegroundLayer);
|
||||
update();
|
||||
} else {
|
||||
QToolTip::hideText();
|
||||
}
|
||||
@@ -786,7 +788,7 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) {
|
||||
if (rubber_rect != rubber->geometry()) {
|
||||
rubber->setGeometry(rubber_rect);
|
||||
}
|
||||
scene()->invalidate({}, QGraphicsScene::ForegroundLayer);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -847,8 +849,8 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) {
|
||||
if (s.series->useOpenGL() && s.series->isVisible() && s.series->pointsVisible()) {
|
||||
auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan);
|
||||
auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), xLessThan);
|
||||
painter->setBrush(s.series->color());
|
||||
for (auto it = first; it != last; ++it) {
|
||||
painter->setBrush(s.series->color());
|
||||
painter->drawEllipse(chart()->mapToPosition(*it), 4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "tools/cabana/binaryview.h"
|
||||
#include "tools/cabana/chartswidget.h"
|
||||
#include "tools/cabana/historylog.h"
|
||||
#include "tools/cabana/signaledit.h"
|
||||
#include "tools/cabana/signalview.h"
|
||||
|
||||
class EditMessageDialog : public QDialog {
|
||||
public:
|
||||
|
||||
@@ -27,6 +27,7 @@ void Settings::save() {
|
||||
s.setValue("recent_files", recent_files);
|
||||
s.setValue("message_header_state", message_header_state);
|
||||
s.setValue("chart_series_type", chart_series_type);
|
||||
s.setValue("sparkline_range", sparkline_range);
|
||||
}
|
||||
|
||||
void Settings::load() {
|
||||
@@ -44,6 +45,7 @@ void Settings::load() {
|
||||
recent_files = s.value("recent_files").toStringList();
|
||||
message_header_state = s.value("message_header_state").toByteArray();
|
||||
chart_series_type = s.value("chart_series_type", 0).toInt();
|
||||
sparkline_range = s.value("sparkline_range", 15).toInt();
|
||||
}
|
||||
|
||||
// SettingsDlg
|
||||
|
||||
@@ -17,8 +17,9 @@ public:
|
||||
int max_cached_minutes = 30;
|
||||
int chart_height = 200;
|
||||
int chart_column_count = 1;
|
||||
int chart_range = 3 * 60; // e minutes
|
||||
int chart_range = 3 * 60; // 3 minutes
|
||||
int chart_series_type = 0;
|
||||
int sparkline_range = 15; // 15 seconds
|
||||
QString last_dir;
|
||||
QString last_route_dir;
|
||||
QByteArray geometry;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "tools/cabana/signaledit.h"
|
||||
#include "tools/cabana/signalview.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCompleter>
|
||||
@@ -366,17 +366,15 @@ void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
|
||||
|
||||
void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
static std::vector<QPointF> points;
|
||||
// TODO: get seconds from settings.
|
||||
const uint64_t chart_seconds = 15; // seconds
|
||||
const auto &msg_id = ((SignalView *)parent())->msg_id;
|
||||
const auto &msgs = can->events().at(msg_id);
|
||||
uint64_t ts = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9;
|
||||
auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = (uint64_t)std::max<int64_t>(ts - chart_seconds * 1e9, 0)});
|
||||
if (first != msgs.cend()) {
|
||||
auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = (uint64_t)std::max<int64_t>(ts - settings.sparkline_range * 1e9, 0)});
|
||||
auto last = std::upper_bound(first, msgs.cend(), CanEvent{.mono_time = ts});
|
||||
if (first != last) {
|
||||
double min = std::numeric_limits<double>::max();
|
||||
double max = std::numeric_limits<double>::lowest();
|
||||
const auto sig = ((SignalModel::Item *)index.internalPointer())->sig;
|
||||
auto last = std::lower_bound(first, msgs.cend(), CanEvent{.mono_time = ts});
|
||||
points.clear();
|
||||
for (auto it = first; it != last; ++it) {
|
||||
double value = get_raw_value(it->dat, it->size, *sig);
|
||||
@@ -391,7 +389,7 @@ void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionView
|
||||
|
||||
int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin);
|
||||
int v_margin = std::max(option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin) + 2, 4);
|
||||
const double xscale = (option.rect.width() - 175.0 * option.widget->devicePixelRatioF() - h_margin * 2) / chart_seconds;
|
||||
const double xscale = (option.rect.width() - 175.0 * option.widget->devicePixelRatioF() - h_margin * 2) / settings.sparkline_range;
|
||||
const double yscale = (option.rect.height() - v_margin * 2) / (max - min);
|
||||
const int left = option.rect.left();
|
||||
const int top = option.rect.top() + v_margin;
|
||||
@@ -401,6 +399,13 @@ void SignalItemDelegate::drawSparkline(QPainter *painter, const QStyleOptionView
|
||||
}
|
||||
painter->setPen(getColor(sig));
|
||||
painter->drawPolyline(points.data(), points.size());
|
||||
if ((points.back().x() - points.front().x()) / points.size() > 10) {
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(getColor(sig));
|
||||
for (const auto &pt : points) {
|
||||
painter->drawEllipse(pt, 2, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,6 +456,17 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
|
||||
filter_edit->setPlaceholderText(tr("filter signals"));
|
||||
hl->addWidget(filter_edit);
|
||||
hl->addStretch(1);
|
||||
|
||||
// WARNING: increasing the maximum range can result in severe performance degradation.
|
||||
// 30s is a reasonable value at present.
|
||||
const int max_range = 30; // 30s
|
||||
settings.sparkline_range = std::clamp(settings.sparkline_range, 1, max_range);
|
||||
hl->addWidget(sparkline_label = new QLabel());
|
||||
hl->addWidget(sparkline_range_slider = new QSlider(Qt::Horizontal, this));
|
||||
sparkline_range_slider->setRange(1, max_range);
|
||||
sparkline_range_slider->setValue(settings.sparkline_range);
|
||||
sparkline_range_slider->setToolTip(tr("Sparkline time range"));
|
||||
|
||||
auto collapse_btn = toolButton("dash-square", tr("Collapse All"));
|
||||
collapse_btn->setIconSize({12, 12});
|
||||
hl->addWidget(collapse_btn);
|
||||
@@ -473,8 +489,10 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
|
||||
main_layout->setSpacing(0);
|
||||
main_layout->addWidget(title_bar);
|
||||
main_layout->addWidget(tree);
|
||||
updateToolBar();
|
||||
|
||||
QObject::connect(filter_edit, &QLineEdit::textEdited, model, &SignalModel::setFilter);
|
||||
QObject::connect(sparkline_range_slider, &QSlider::valueChanged, this, &SignalView::setSparklineRange);
|
||||
QObject::connect(collapse_btn, &QPushButton::clicked, tree, &QTreeView::collapseAll);
|
||||
QObject::connect(tree, &QAbstractItemView::clicked, this, &SignalView::rowClicked);
|
||||
QObject::connect(tree, &QTreeView::viewportEntered, [this]() { emit highlight(nullptr); });
|
||||
@@ -521,7 +539,7 @@ void SignalView::rowsChanged() {
|
||||
});
|
||||
}
|
||||
}
|
||||
signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount()));
|
||||
updateToolBar();
|
||||
updateChartState();
|
||||
}
|
||||
|
||||
@@ -569,6 +587,17 @@ void SignalView::signalHovered(const cabana::Signal *sig) {
|
||||
}
|
||||
}
|
||||
|
||||
void SignalView::updateToolBar() {
|
||||
signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount()));
|
||||
sparkline_label->setText(QString("Range: %1 ").arg(utils::formatSeconds(settings.sparkline_range)));
|
||||
}
|
||||
|
||||
void SignalView::setSparklineRange(int value) {
|
||||
settings.sparkline_range = value;
|
||||
updateToolBar();
|
||||
model->updateState(nullptr);
|
||||
}
|
||||
|
||||
void SignalView::leaveEvent(QEvent *event) {
|
||||
emit highlight(nullptr);
|
||||
QWidget::leaveEvent(event);
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QAbstractItemModel>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QSlider>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QTableWidget>
|
||||
#include <QTreeView>
|
||||
@@ -109,6 +110,8 @@ signals:
|
||||
private:
|
||||
void rowsChanged();
|
||||
void leaveEvent(QEvent *event);
|
||||
void updateToolBar();
|
||||
void setSparklineRange(int value);
|
||||
|
||||
struct TreeView : public QTreeView {
|
||||
TreeView(QWidget *parent) : QTreeView(parent) {}
|
||||
@@ -120,6 +123,8 @@ private:
|
||||
};
|
||||
|
||||
TreeView *tree;
|
||||
QLabel *sparkline_label;
|
||||
QSlider *sparkline_range_slider;
|
||||
QLineEdit *filter_edit;
|
||||
ChartsWidget *charts;
|
||||
QLabel *signal_count_lb;
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cmath>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QRegExpValidator>
|
||||
@@ -97,6 +98,9 @@ public:
|
||||
|
||||
namespace utils {
|
||||
QPixmap icon(const QString &id);
|
||||
inline QString formatSeconds(int seconds) {
|
||||
return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss");
|
||||
}
|
||||
}
|
||||
|
||||
QToolButton *toolButton(const QString &icon, const QString &tooltip);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "tools/cabana/videowidget.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QDateTime>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionSlider>
|
||||
@@ -20,10 +19,6 @@ static const QColor timeline_colors[] = {
|
||||
[(int)TimelineType::AlertCritical] = QColor(199, 0, 57),
|
||||
};
|
||||
|
||||
static inline QString formatTime(int seconds) {
|
||||
return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss");
|
||||
}
|
||||
|
||||
VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
|
||||
setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
|
||||
auto main_layout = new QVBoxLayout(this);
|
||||
@@ -101,11 +96,11 @@ QWidget *VideoWidget::createCameraWidget() {
|
||||
slider_layout->addWidget(end_time_label);
|
||||
l->addLayout(slider_layout);
|
||||
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); });
|
||||
QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); });
|
||||
QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(value / 1000)); });
|
||||
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
|
||||
QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState);
|
||||
QObject::connect(can, &AbstractStream::streamStarted, [this]() {
|
||||
end_time_label->setText(formatTime(can->totalSeconds()));
|
||||
end_time_label->setText(utils::formatSeconds(can->totalSeconds()));
|
||||
slider->setRange(0, can->totalSeconds() * 1000);
|
||||
});
|
||||
return w;
|
||||
@@ -116,7 +111,7 @@ void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) {
|
||||
min = 0;
|
||||
max = can->totalSeconds();
|
||||
}
|
||||
end_time_label->setText(formatTime(max));
|
||||
end_time_label->setText(utils::formatSeconds(max));
|
||||
slider->setRange(min * 1000, max * 1000);
|
||||
}
|
||||
|
||||
@@ -230,7 +225,7 @@ void Slider::mouseMoveEvent(QMouseEvent *e) {
|
||||
}
|
||||
int x = std::clamp(e->pos().x() - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN);
|
||||
int y = -thumb.height() - THUMBNAIL_MARGIN - style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing);
|
||||
thumbnail_label.showPixmap(mapToGlobal({x, y}), formatTime(seconds), thumb);
|
||||
thumbnail_label.showPixmap(mapToGlobal({x, y}), utils::formatSeconds(seconds), thumb);
|
||||
QSlider::mouseMoveEvent(e);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user