cabana: improve chart drag and drop (#27820)
* improve drag/drop * change alpha to 180 * cleanup * cleanup * draw drop indicator old-commit-hash: 23e420448b5713875bf2fd17fb9f0c1c804ec010
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include <QOpenGLWidget>
|
||||
#include <QPushButton>
|
||||
#include <QRubberBand>
|
||||
#include <QScrollBar>
|
||||
#include <QStylePainter>
|
||||
#include <QToolBar>
|
||||
#include <QToolTip>
|
||||
@@ -22,7 +23,7 @@ static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; }
|
||||
|
||||
// ChartsWidget
|
||||
|
||||
ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), QFrame(parent) {
|
||||
ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_timer(this), QFrame(parent) {
|
||||
setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -99,6 +100,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), QFrame(parent)
|
||||
|
||||
align_timer.setSingleShot(true);
|
||||
QObject::connect(&align_timer, &QTimer::timeout, this, &ChartsWidget::alignCharts);
|
||||
QObject::connect(&auto_scroll_timer, &QTimer::timeout, this, &ChartsWidget::doAutoScroll);
|
||||
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll);
|
||||
QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged);
|
||||
QObject::connect(can, &AbstractStream::updated, this, &ChartsWidget::updateState);
|
||||
@@ -270,6 +272,36 @@ void ChartsWidget::updateLayout() {
|
||||
}
|
||||
}
|
||||
|
||||
void ChartsWidget::startAutoScroll() {
|
||||
auto_scroll_timer.start(50);
|
||||
}
|
||||
|
||||
void ChartsWidget::stopAutoScroll() {
|
||||
auto_scroll_timer.stop();
|
||||
auto_scroll_count = 0;
|
||||
}
|
||||
|
||||
void ChartsWidget::doAutoScroll() {
|
||||
QScrollBar *scroll = charts_scroll->verticalScrollBar();
|
||||
if (auto_scroll_count < scroll->pageStep()) {
|
||||
++auto_scroll_count;
|
||||
}
|
||||
|
||||
int value = scroll->value();
|
||||
QPoint pos = charts_scroll->viewport()->mapFromGlobal(QCursor::pos());
|
||||
QRect area = charts_scroll->viewport()->rect();
|
||||
|
||||
if (pos.y() - area.top() < settings.chart_height / 2) {
|
||||
scroll->setValue(value - auto_scroll_count);
|
||||
} else if (area.bottom() - pos.y() < settings.chart_height / 2) {
|
||||
scroll->setValue(value + auto_scroll_count);
|
||||
}
|
||||
bool vertical_unchanged = value == scroll->value();
|
||||
if (vertical_unchanged) {
|
||||
stopAutoScroll();
|
||||
}
|
||||
}
|
||||
|
||||
void ChartsWidget::resizeEvent(QResizeEvent *event) {
|
||||
QWidget::resizeEvent(event);
|
||||
updateLayout();
|
||||
@@ -335,7 +367,6 @@ bool ChartsWidget::event(QEvent *event) {
|
||||
back_button = ev->button() == Qt::BackButton;
|
||||
break;
|
||||
}
|
||||
|
||||
case QEvent::NativeGesture: {
|
||||
QNativeGestureEvent *ev = static_cast<QNativeGestureEvent *>(event);
|
||||
back_button = (ev->value() == 180);
|
||||
@@ -361,7 +392,7 @@ bool ChartsWidget::event(QEvent *event) {
|
||||
|
||||
// ChartView
|
||||
|
||||
ChartView::ChartView(const std::pair<double, double> &x_range, QWidget *parent) : tip_label(this), QChartView(nullptr, parent) {
|
||||
ChartView::ChartView(const std::pair<double, double> &x_range, ChartsWidget *parent) : charts_widget(parent), tip_label(this), QChartView(nullptr, parent) {
|
||||
series_type = (SeriesType)settings.chart_series_type;
|
||||
QChart *chart = new QChart();
|
||||
chart->setBackgroundVisible(false);
|
||||
@@ -514,7 +545,7 @@ void ChartView::updatePlotArea(int left_pos, bool force) {
|
||||
qreal left, top, right, bottom;
|
||||
chart()->layout()->getContentsMargins(&left, &top, &right, &bottom);
|
||||
QSizeF x_label_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, QString::number(axis_x->max(), 'f', 2));
|
||||
x_label_size += QSizeF{5 * devicePixelRatioF(), 5 * devicePixelRatioF()};
|
||||
x_label_size += QSizeF{5, 5};
|
||||
int adjust_top = chart()->legend()->geometry().height() + style()->pixelMetric(QStyle::PM_LayoutTopMargin);
|
||||
chart()->setPlotArea(rect().adjusted(align_to + left, adjust_top + top, -x_label_size.width() / 2 - right, -x_label_size.height() - bottom));
|
||||
chart()->layout()->invalidate();
|
||||
@@ -704,11 +735,18 @@ void ChartView::mousePressEvent(QMouseEvent *event) {
|
||||
if (event->button() == Qt::LeftButton && move_icon->sceneBoundingRect().contains(event->pos())) {
|
||||
QMimeData *mimeData = new QMimeData;
|
||||
mimeData->setData(mime_type, QByteArray::number((qulonglong)this));
|
||||
QPixmap pm = grab();
|
||||
QPainter p(&pm);
|
||||
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||
p.fillRect(pm.rect(), QColor(0, 0, 0, 180));
|
||||
p.end();
|
||||
|
||||
QDrag *drag = new QDrag(this);
|
||||
drag->setMimeData(mimeData);
|
||||
drag->setPixmap(grab());
|
||||
drag->setPixmap(pm);
|
||||
drag->setHotSpot(event->pos());
|
||||
drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::MoveAction);
|
||||
charts_widget->stopAutoScroll();
|
||||
} else if (event->button() == Qt::LeftButton && QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) {
|
||||
if (!can->liveStreaming()) {
|
||||
// Save current playback state when scrubbing
|
||||
@@ -735,13 +773,11 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
|
||||
min = std::clamp(min, 0., can->totalSeconds());
|
||||
max = std::clamp(max, 0., can->totalSeconds());
|
||||
|
||||
double min_rounded = std::floor(min * 10.0) / 10.0;
|
||||
double max_rounded = std::floor(max * 10.0) / 10.0;
|
||||
if (rubber->width() <= 0) {
|
||||
// no rubber dragged, seek to mouse position
|
||||
can->seekTo(min);
|
||||
} else if (rubber->width() > 10) {
|
||||
emit zoomIn(min_rounded, max_rounded);
|
||||
emit zoomIn(min, max);
|
||||
} else {
|
||||
viewport()->update();
|
||||
}
|
||||
@@ -827,6 +863,17 @@ void ChartView::hideTip() {
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void ChartView::dragEnterEvent(QDragEnterEvent *event) {
|
||||
can_drop = event->source() != this;
|
||||
viewport()->update();
|
||||
QChartView::dragEnterEvent(event);
|
||||
}
|
||||
void ChartView::dragLeaveEvent(QDragLeaveEvent *event) {
|
||||
can_drop = false;
|
||||
viewport()->update();
|
||||
QChartView::dragLeaveEvent(event);
|
||||
}
|
||||
|
||||
void ChartView::dragMoveEvent(QDragMoveEvent *event) {
|
||||
if (event->mimeData()->hasFormat(mime_type)) {
|
||||
event->setDropAction(event->source() == this ? Qt::MoveAction : Qt::CopyAction);
|
||||
@@ -834,6 +881,7 @@ void ChartView::dragMoveEvent(QDragMoveEvent *event) {
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
charts_widget->startAutoScroll();
|
||||
}
|
||||
|
||||
void ChartView::dropEvent(QDropEvent *event) {
|
||||
@@ -844,11 +892,20 @@ void ChartView::dropEvent(QDropEvent *event) {
|
||||
} else {
|
||||
ChartView *source_chart = (ChartView *)event->source();
|
||||
for (auto &s : source_chart->sigs) {
|
||||
addSeries(s.msg_id, s.sig);
|
||||
source_chart->chart()->removeSeries(s.series);
|
||||
chart()->addSeries(s.series);
|
||||
s.series->attachAxis(axis_x);
|
||||
s.series->attachAxis(axis_y);
|
||||
}
|
||||
sigs.append(source_chart->sigs);
|
||||
updateAxisY();
|
||||
updateTitle();
|
||||
|
||||
source_chart->sigs.clear();
|
||||
emit source_chart->remove();
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
can_drop = false;
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
@@ -875,6 +932,10 @@ void ChartView::paintEvent(QPaintEvent *event) {
|
||||
QPainter painter(viewport());
|
||||
painter.setRenderHints(QPainter::Antialiasing);
|
||||
painter.drawPixmap(QPoint(), chart_pixmap);
|
||||
if (can_drop) {
|
||||
painter.setPen(QPen(palette().color(QPalette::Highlight), 4));
|
||||
painter.drawRect(viewport()->rect());
|
||||
}
|
||||
QRectF exposed_rect = mapToScene(event->region().boundingRect()).boundingRect();
|
||||
drawForeground(&painter, exposed_rect);
|
||||
} else {
|
||||
|
||||
@@ -33,11 +33,12 @@ public:
|
||||
void paintEvent(QPaintEvent *ev) override;
|
||||
};
|
||||
|
||||
class ChartsWidget;
|
||||
class ChartView : public QChartView {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ChartView(const std::pair<double, double> &x_range, QWidget *parent = nullptr);
|
||||
ChartView(const std::pair<double, double> &x_range, ChartsWidget *parent = nullptr);
|
||||
void addSeries(const MessageId &msg_id, const cabana::Signal *sig);
|
||||
bool hasSeries(const MessageId &msg_id, const cabana::Signal *sig) const;
|
||||
void updateSeries(const cabana::Signal *sig = nullptr);
|
||||
@@ -82,6 +83,8 @@ private:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *ev) override;
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dragLeaveEvent(QDragLeaveEvent *event) override;
|
||||
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
@@ -115,7 +118,9 @@ private:
|
||||
bool is_scrubbing = false;
|
||||
bool resume_after_scrub = false;
|
||||
QPixmap chart_pixmap;
|
||||
bool can_drop = false;
|
||||
double tooltip_x = -1;
|
||||
ChartsWidget *charts_widget;
|
||||
friend class ChartsWidget;
|
||||
};
|
||||
|
||||
@@ -148,6 +153,9 @@ private:
|
||||
void updateState();
|
||||
void zoomIn(double min, double max);
|
||||
void zoomReset();
|
||||
void startAutoScroll();
|
||||
void stopAutoScroll();
|
||||
void doAutoScroll();
|
||||
void updateToolBar();
|
||||
void setMaxChartRange(int value);
|
||||
void updateLayout();
|
||||
@@ -181,8 +189,11 @@ private:
|
||||
QAction *columns_action;
|
||||
int column_count = 1;
|
||||
int current_column_count = 0;
|
||||
int auto_scroll_count = 0;
|
||||
QTimer auto_scroll_timer;
|
||||
QTimer align_timer;
|
||||
friend class ZoomCommand;
|
||||
friend class ChartView;
|
||||
};
|
||||
|
||||
class ZoomCommand : public QUndoCommand {
|
||||
|
||||
Reference in New Issue
Block a user