cabana: Docking and undocking charts (#25983)

* floating dock charts

* more button

* setMinimumSize

* move reset zoom button to title bar

* show chart count

* cleanup

* reduce flicker

* dont update linemarker if pos not changed

* cleanup

* remove blank line

* always show dock/undock button
old-commit-hash: 9ec262bbfd
taco
Dean Lee 3 years ago committed by GitHub
parent 7176feffdc
commit a5e61f27cf
  1. 125
      tools/cabana/chartswidget.cc
  2. 34
      tools/cabana/chartswidget.h
  3. 40
      tools/cabana/mainwin.cc
  4. 7
      tools/cabana/mainwin.h
  5. 2
      tools/cabana/signaledit.cc
  6. 1
      tools/cabana/videowidget.cc

@ -2,7 +2,6 @@
#include <QGraphicsLayout>
#include <QLabel>
#include <QPushButton>
#include <QRubberBand>
#include <QStackedLayout>
#include <QtCharts/QLineSeries>
@ -27,30 +26,105 @@ int64_t get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) {
return ret;
}
// ChartsWidget
ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
main_layout = new QVBoxLayout(this);
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
connect(parser, &Parser::showPlot, this, &ChartsWidget::addChart);
connect(parser, &Parser::hidePlot, this, &ChartsWidget::removeChart);
connect(parser, &Parser::signalRemoved, this, &ChartsWidget::removeChart);
// title bar
title_bar = new QWidget(this);
QHBoxLayout *title_layout = new QHBoxLayout(title_bar);
title_label = new QLabel(tr("Charts"));
title_layout->addWidget(title_label);
title_layout->addStretch();
reset_zoom_btn = new QPushButton("", this);
reset_zoom_btn->setVisible(false);
reset_zoom_btn->setFixedSize(30, 30);
reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)"));
title_layout->addWidget(reset_zoom_btn);
remove_all_btn = new QPushButton(tr(""));
remove_all_btn->setVisible(false);
remove_all_btn->setToolTip(tr("Remove all charts"));
remove_all_btn->setFixedSize(30, 30);
title_layout->addWidget(remove_all_btn);
dock_btn = new QPushButton();
dock_btn->setFixedSize(30, 30);
updateDockButton();
title_layout->addWidget(dock_btn);
main_layout->addWidget(title_bar, 0, Qt::AlignTop);
// charts
QWidget *charts_container = new QWidget(this);
QVBoxLayout *charts_main = new QVBoxLayout(charts_container);
charts_layout = new QVBoxLayout();
charts_main->addLayout(charts_layout);
charts_main->addStretch();
QScrollArea *charts_scroll = new QScrollArea(this);
charts_scroll->setWidgetResizable(true);
charts_scroll->setWidget(charts_container);
charts_scroll->setFrameShape(QFrame::NoFrame);
charts_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
main_layout->addWidget(charts_scroll);
QObject::connect(parser, &Parser::showPlot, this, &ChartsWidget::addChart);
QObject::connect(parser, &Parser::hidePlot, this, &ChartsWidget::removeChart);
QObject::connect(parser, &Parser::signalRemoved, this, &ChartsWidget::removeChart);
QObject::connect(reset_zoom_btn, &QPushButton::clicked, parser, &Parser::resetRange);
QObject::connect(remove_all_btn, &QPushButton::clicked, this, &ChartsWidget::removeAll);
QObject::connect(dock_btn, &QPushButton::clicked, [=]() {
emit dock(!docking);
docking = !docking;
updateDockButton();
});
}
void ChartsWidget::updateDockButton() {
dock_btn->setText(docking ? "" : "");
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts"));
}
void ChartsWidget::addChart(const QString &id, const QString &sig_name) {
const QString char_name = id + sig_name;
if (charts.find(char_name) == charts.end()) {
auto chart = new ChartWidget(id, sig_name, this);
main_layout->insertWidget(0, chart);
charts_layout->insertWidget(0, chart);
charts[char_name] = chart;
}
remove_all_btn->setVisible(true);
reset_zoom_btn->setVisible(true);
title_label->setText(tr("Charts (%1)").arg(charts.size()));
}
void ChartsWidget::removeChart(const QString &id, const QString &sig_name) {
if (auto it = charts.find(id + sig_name); it != charts.end()) {
it->second->deleteLater();
charts.erase(it);
if (charts.empty()) {
remove_all_btn->setVisible(false);
reset_zoom_btn->setVisible(false);
}
}
title_label->setText(tr("Charts (%1)").arg(charts.size()));
}
void ChartsWidget::removeAll() {
for (auto [_, chart] : charts)
chart->deleteLater();
charts.clear();
remove_all_btn->setVisible(false);
reset_zoom_btn->setVisible(false);
}
// ChartWidget
ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *parent) : id(id), sig_name(sig_name), QWidget(parent) {
QStackedLayout *stacked = new QStackedLayout(this);
stacked->setStackingMode(QStackedLayout::StackAll);
@ -64,17 +138,13 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa
header->setStyleSheet("background-color:white");
QHBoxLayout *header_layout = new QHBoxLayout(header);
header_layout->setContentsMargins(11, 11, 11, 0);
auto title = new QLabel(tr("%1 %2").arg(parser->getMsg(id)->name.c_str()).arg(id));
QLabel *title = new QLabel(tr("%1 %2").arg(parser->getMsg(id)->name.c_str()).arg(id));
header_layout->addWidget(title);
header_layout->addStretch();
zoom_label = new QLabel("", this);
header_layout->addWidget(zoom_label);
QPushButton *zoom_in = new QPushButton("", this);
zoom_in->setToolTip(tr("reset zoom"));
QObject::connect(zoom_in, &QPushButton::clicked, []() { parser->resetRange(); });
header_layout->addWidget(zoom_in);
QPushButton *remove_btn = new QPushButton("", this);
remove_btn->setFixedSize(30, 30);
remove_btn->setToolTip(tr("Remove chart"));
QObject::connect(remove_btn, &QPushButton::clicked, [=]() {
emit parser->hidePlot(id, sig_name);
});
@ -108,7 +178,7 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa
chart_layout->addStretch();
stacked->addWidget(chart_widget);
line_marker = new LineMarker(chart, this);
line_marker = new LineMarker(this);
stacked->addWidget(line_marker);
line_marker->setAttribute(Qt::WA_TransparentForMouseEvents, true);
line_marker->raise();
@ -122,7 +192,15 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa
}
void ChartWidget::updateState() {
line_marker->update();
auto chart = chart_view->chart();
auto axis_x = dynamic_cast<QValueAxis *>(chart->axisX());
if (axis_x->max() <= axis_x->min()) return;
int x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min());
if (line_marker_x != x) {
line_marker->setX(x);
line_marker_x = x;
}
}
void ChartWidget::updateSeries() {
@ -182,16 +260,15 @@ void ChartWidget::rangeChanged(qreal min, qreal max) {
chart_view->chart()->axisY()->setRange(min_y * 0.95, max_y * 1.05);
}
LineMarker::LineMarker(QChart *chart, QWidget *parent) : chart(chart), QWidget(parent) {}
// LineMarker
void LineMarker::paintEvent(QPaintEvent *event) {
auto axis_x = dynamic_cast<QValueAxis *>(chart->axisX());
if (axis_x->max() <= axis_x->min()) return;
void LineMarker::setX(double x) {
x_pos = x;
update();
}
double x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min());
void LineMarker::paintEvent(QPaintEvent *event) {
QPainter p(this);
QPen pen = QPen(Qt::black);
pen.setWidth(2);
p.setPen(pen);
p.drawLine(QPointF{x, 50.}, QPointF{x, (qreal)height() - 11});
p.setPen(QPen(Qt::black, 2));
p.drawLine(QPointF{x_pos, 50.}, QPointF{x_pos, (qreal)height() - 11});
}

@ -3,6 +3,7 @@
#include <map>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QtCharts/QChartView>
@ -16,11 +17,12 @@ class LineMarker : public QWidget {
Q_OBJECT
public:
LineMarker(QChart *chart, QWidget *parent);
void paintEvent(QPaintEvent *event) override;
LineMarker(QWidget *parent) : QWidget(parent) {}
void setX(double x);
private:
QChart *chart;
void paintEvent(QPaintEvent *event) override;
double x_pos = 0.0;
};
class ChartWidget : public QWidget {
@ -30,7 +32,7 @@ public:
ChartWidget(const QString &id, const QString &sig_name, QWidget *parent);
inline QChart *chart() const { return chart_view->chart(); }
protected:
private:
void updateState();
void addData(const CanData &can_data, const Signal &sig);
void updateSeries();
@ -38,9 +40,9 @@ protected:
QString id;
QString sig_name;
QLabel *zoom_label;
QChartView *chart_view = nullptr;
LineMarker *line_marker = nullptr;
double line_marker_x = 0.0;
QList<QPointF> vals;
};
@ -49,14 +51,26 @@ class ChartsWidget : public QWidget {
public:
ChartsWidget(QWidget *parent = nullptr);
inline bool hasChart(const QString &id, const QString &sig_name) {
return charts.find(id+sig_name) != charts.end();
}
void addChart(const QString &id, const QString &sig_name);
void removeChart(const QString &id, const QString &sig_name);
void removeAll();
inline bool hasChart(const QString &id, const QString &sig_name) {
return charts.find(id + sig_name) != charts.end();
}
signals:
void dock(bool floating);
private:
void updateState();
void updateDockButton();
protected:
QVBoxLayout *main_layout;
QWidget *title_bar;
QLabel *title_label;
bool docking = true;
QPushButton *dock_btn;
QPushButton *reset_zoom_btn;
QPushButton *remove_all_btn;
QVBoxLayout *charts_layout;
std::map<QString, ChartWidget *> charts;
};

@ -1,6 +1,7 @@
#include "tools/cabana/mainwin.h"
#include <QHBoxLayout>
#include <QScreen>
#include <QVBoxLayout>
MainWindow::MainWindow() : QWidget() {
@ -16,23 +17,42 @@ MainWindow::MainWindow() : QWidget() {
detail_widget->setFixedWidth(600);
h_layout->addWidget(detail_widget);
// right widget
// right widgets
QWidget *right_container = new QWidget(this);
right_container->setFixedWidth(640);
QVBoxLayout *r_layout = new QVBoxLayout(right_container);
r_layout = new QVBoxLayout(right_container);
video_widget = new VideoWidget(this);
r_layout->addWidget(video_widget);
r_layout->addWidget(video_widget, 0, Qt::AlignTop);
charts_widget = new ChartsWidget(this);
QScrollArea *scroll = new QScrollArea(this);
scroll->setWidget(charts_widget);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
r_layout->addWidget(scroll);
r_layout->addWidget(charts_widget);
h_layout->addWidget(right_container);
QObject::connect(messages_widget, &MessagesWidget::msgChanged, detail_widget, &DetailWidget::setMsg);
QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts);
}
void MainWindow::dockCharts(bool dock) {
charts_widget->setUpdatesEnabled(false);
if (dock && floating_window) {
r_layout->addWidget(charts_widget);
floating_window->deleteLater();
floating_window = nullptr;
} else if (!dock && !floating_window) {
floating_window = new QWidget(nullptr);
floating_window->setLayout(new QVBoxLayout());
floating_window->layout()->addWidget(charts_widget);
floating_window->setWindowFlags(Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint);
floating_window->setMinimumSize(QGuiApplication::primaryScreen()->size() / 2);
floating_window->showMaximized();
}
charts_widget->setUpdatesEnabled(true);
}
void MainWindow::closeEvent(QCloseEvent *event) {
if (floating_window)
floating_window->deleteLater();
QWidget::closeEvent(event);
}

@ -1,7 +1,5 @@
#pragma once
#include <QWidget>
#include "tools/cabana/chartswidget.h"
#include "tools/cabana/detailwidget.h"
#include "tools/cabana/messageswidget.h"
@ -13,10 +11,15 @@ class MainWindow : public QWidget {
public:
MainWindow();
void dockCharts(bool dock);
protected:
void closeEvent(QCloseEvent *event) override;
VideoWidget *video_widget;
MessagesWidget *messages_widget;
DetailWidget *detail_widget;
ChartsWidget *charts_widget;
QWidget *floating_window = nullptr;
QVBoxLayout *r_layout;
};

@ -91,9 +91,7 @@ SignalEdit::SignalEdit(const QString &id, const Signal &sig, const QString &colo
title_layout->addStretch();
plot_btn = new QPushButton("📈");
plot_btn->setStyleSheet("font-size:16px");
plot_btn->setToolTip(tr("Show Plot"));
plot_btn->setContentsMargins(5, 5, 5, 5);
plot_btn->setFixedSize(30, 30);
QObject::connect(plot_btn, &QPushButton::clicked, [=]() { emit parser->showPlot(id, name_); });
title_layout->addWidget(plot_btn);

@ -64,6 +64,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
}
main_layout->addLayout(control_layout);
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QObject::connect(parser, &Parser::rangeChanged, this, &VideoWidget::rangeChanged);
QObject::connect(parser, &Parser::updated, this, &VideoWidget::updateState);

Loading…
Cancel
Save