mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-02-19 13:33:59 +08:00
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
This commit is contained in:
@@ -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::setX(double x) {
|
||||
x_pos = x;
|
||||
update();
|
||||
}
|
||||
|
||||
void LineMarker::paintEvent(QPaintEvent *event) {
|
||||
auto axis_x = dynamic_cast<QValueAxis *>(chart->axisX());
|
||||
if (axis_x->max() <= axis_x->min()) return;
|
||||
|
||||
double x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min());
|
||||
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 updateState();
|
||||
void removeAll();
|
||||
inline bool hasChart(const QString &id, const QString &sig_name) {
|
||||
return charts.find(id + sig_name) != charts.end();
|
||||
}
|
||||
|
||||
protected:
|
||||
QVBoxLayout *main_layout;
|
||||
signals:
|
||||
void dock(bool floating);
|
||||
|
||||
private:
|
||||
void updateState();
|
||||
void updateDockButton();
|
||||
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user