TAMSVIZ
Visualization and annotation tool for ROS
timeline.cpp
1 // TAMSVIZ
2 // (c) 2020 Philipp Ruppel
3 
4 #include "timeline.h"
5 
6 #include "../annotations/image.h"
7 #include "../core/bagplayer.h"
8 #include "../core/log.h"
9 #include "../core/topic.h"
10 #include "../core/tracks.h"
11 #include "../core/workspace.h"
12 #include "mainwindow.h"
13 
14 #include <fstream>
15 #include <rosbag/bag.h>
16 #include <rosbag/view.h>
17 #include <sensor_msgs/Image.h>
18 #include <std_msgs/Float64.h>
19 
20 static const double track_height = 42;
21 static const double track_label_width = 200;
22 static const double track_padding_right = 100;
23 
24 template <class FNC>
25 static void updateEnabled(QLayout *layout, const FNC &callback) {
26  for (size_t i = 0; i < layout->count(); i++) {
27  if (auto *widget = layout->itemAt(i)->widget()) {
28  callback(widget);
29  }
30  if (auto *l = layout->itemAt(i)->layout()) {
31  updateEnabled(l, callback);
32  }
33  }
34 }
35 
36 static size_t trackCount(const std::shared_ptr<Workspace> &ws) {
37  return (ws->document() && ws->document()->timeline())
38  ? ws->document()->timeline()->tracks().size()
39  : 0;
40 }
41 
42 static double timelineDuration(const std::shared_ptr<Workspace> &ws) {
43  return std::max(0.001, ws->player ? ws->player->duration() : 1.0) + 1.0;
44 }
45 
46 static std::shared_ptr<TrackBase> trackAt(const std::shared_ptr<Workspace> &ws,
47  size_t index) {
48  if (ws->document() && ws->document()->timeline() &&
49  index < ws->document()->timeline()->tracks().size()) {
50  return ws->document()->timeline()->tracks()[index];
51  } else {
52  return nullptr;
53  }
54 }
55 
56 class ItemBase : public QObject {
57  bool ok = true;
58 
59 public:
60  virtual void sync(const std::shared_ptr<Workspace> &ws) {}
61  ItemBase() {
62  LockScope()->modified.connect(this, [this]() {
63  if (!ok) {
64  throw std::runtime_error("timeline item already destroyed");
65  }
66  sync(LockScope().ws());
67  });
68  }
69  ~ItemBase() { ok = false; }
70 };
71 
72 class EditableText : public QGraphicsRectItem, public QObject {
73  QString _text;
74  QGraphicsProxyWidget *_proxy = nullptr;
75  QLineEdit *_edit = nullptr;
76  QMargins margins() const {
77  int m = std::round(std::min(rect().width(), rect().height()) * 0.25);
78  if (_alignment & Qt::AlignHCenter) {
79  m = 1;
80  }
81  return QMargins(m, 0, m, 0);
82  }
83  QPen _textPen = QPen(QApplication::palette().text(), 1);
84  Qt::Alignment _alignment = (Qt::AlignLeft | Qt::AlignVCenter);
85  QFont _font = QApplication::font();
86 
87 public:
88  EditableText(QGraphicsItem *parent = nullptr) : QGraphicsRectItem(parent) {
89  setPen(QPen(Qt::NoPen));
90  }
91  void setFont(const QFont &font) { _font = font; }
93  std::string text() const { return _text.toStdString(); }
94  void setText(const std::string &t) {
95  _text = t.c_str();
96  update();
97  }
98  void setTextAlignment(Qt::Alignment a) {
99  _alignment = a;
100  update();
101  }
102  void setTextPen(const QPen &pen) {
103  _textPen = pen;
104  update();
105  }
106  virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
107  QWidget *widget) override {
108  if (_proxy) {
109  return;
110  }
111  painter->save();
112  QGraphicsRectItem::paint(painter, option, widget);
113  painter->setFont(_font);
114  painter->setPen(_textPen);
115  painter->drawText(rect().marginsRemoved(margins()), _text,
116  QTextOption(_alignment));
117  painter->restore();
118  }
119  virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override {
120  if (!_edit) {
121  scene()->views().front()->setFocusPolicy(Qt::StrongFocus);
122  scene()->views().front()->setFocus();
123  _edit = new QLineEdit();
124  _edit->setFixedSize(rect().width(), rect().height());
125  _edit->setAlignment(_alignment);
126  _edit->setTextMargins(margins());
127  _edit->setText(_text);
128  connect(_edit, &QLineEdit::editingFinished, this, [this]() {
129  if (_text != _edit->text()) {
130  _text = _edit->text();
131  LOG_DEBUG("text changed " << text());
132  changed(text());
133  } else {
134  LOG_DEBUG("text unchanged");
135  }
136  scene()->views().front()->setFocusPolicy(Qt::NoFocus);
137  _edit->clearFocus();
138  _proxy->clearFocus();
139  _edit->deleteLater();
140  _edit = nullptr;
141  _proxy->deleteLater();
142  _proxy = nullptr;
143  scene()->clearFocus();
144  scene()->views().front()->window()->setFocus();
145  });
146  _proxy = new QGraphicsProxyWidget(this);
147  _proxy->setWidget(_edit);
148  _proxy->setPos(rect().x(), rect().y());
149  _edit->setFocus(Qt::MouseFocusReason);
150  _edit->selectAll();
151  }
152  }
153 };
154 
156 
157  class ResizeHandle : public QGraphicsRectItem {
158  AnnotationSpanItem *_parent = nullptr;
159  int _side = 0;
160  double _drag_offset = 0.0;
161  QRectF _drag_start_rect;
162 
163  public:
164  double timeToPosition(double t) {
165  LockScope ws;
166  double bag_duration = _parent->bag_duration;
167  double track_length = _parent->track_length;
168  return t * track_length / bag_duration;
169  }
170  double positionToTime(double p) {
171  LockScope ws;
172  double bag_duration = _parent->bag_duration;
173  double track_length = _parent->track_length;
174  return p / track_length * bag_duration;
175  }
176  ResizeHandle(AnnotationSpanItem *parent, int side)
177  : _parent(parent), _side(side) {
178  setCursor(Qt::SizeHorCursor);
179  setParentItem(parent);
180  setBrush(QBrush(Qt::transparent));
181  setPen(QPen(Qt::NoPen));
182  }
183  void update() {
184  auto rect = _parent->rect();
185  double w = std::min(5.0, rect.width() * 0.25);
186  if (_side < 0) {
187  rect.setRight(rect.left() + w);
188  }
189  if (_side > 0) {
190  rect.setLeft(rect.right() - w);
191  }
192  setRect(rect);
193  setCursor(Qt::SizeHorCursor);
194  }
195  virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
196  if (event->button() == Qt::LeftButton) {
197  event->accept();
198  LOG_DEBUG("begin resize span");
199  _drag_start_rect = rect();
200  if (_side < 0) {
201  _drag_offset = event->scenePos().x() - _parent->rect().left();
202  }
203  if (_side > 0) {
204  _drag_offset = event->scenePos().x() - _parent->rect().right();
205  }
206  }
207  }
208  virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
209  if (event->button() == Qt::LeftButton) {
210  if (_drag_start_rect != rect()) {
211  if (rect().width() < 2.5) {
212  ActionScope ws("Delete annotation");
213  auto &spans = _parent->_track->branch(ws(), true)->spans();
214  spans.erase(
215  std::remove(spans.begin(), spans.end(), _parent->_annotation),
216  spans.end());
217  ws->modified();
218  } else {
219  ActionScope ws("Resize Annotation Span");
220  _parent->commit();
221  ws->modified();
222  }
223  }
224  }
225  }
226  virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
227  if (event->buttons() == Qt::LeftButton) {
228  event->accept();
229  LOG_DEBUG("resize span");
230  auto rect = _parent->rect();
231  LockScope ws;
232  if (_side < 0) {
233  double x = event->scenePos().x() - _drag_offset;
234  _parent->snap(x);
235  if (auto branch = _parent->_track->branch(ws(), false)) {
236  for (auto &span : branch->spans()) {
237  if (timeToPosition(span->start() + span->duration()) <
238  _drag_start_rect.center().x()) {
239  x = std::max(x,
240  timeToPosition(span->start() + span->duration()));
241  }
242  }
243  }
244  rect.setLeft(std::max(0.0, std::min(rect.right(), x)));
245  }
246  if (_side > 0) {
247  double x = event->scenePos().x() - _drag_offset;
248  _parent->snap(x);
249  if (auto branch = _parent->_track->branch(ws(), false)) {
250  for (auto &span : branch->spans()) {
251  if (timeToPosition(span->start()) >
252  _drag_start_rect.center().x()) {
253  x = std::min(x, timeToPosition(span->start()));
254  }
255  }
256  }
257  rect.setRight(std::min(scene()->sceneRect().width() -
258  track_label_width - track_padding_right,
259  std::max(rect.left(), x)));
260  }
261  _parent->setItemRect(rect.x(), rect.y(), rect.width());
262  update();
263  }
264  }
265  };
266 
267  std::shared_ptr<AnnotationTrack> _track;
268  std::shared_ptr<AnnotationSpan> _annotation;
269  double track_length = 0;
270  double bag_duration = 0.0;
271  double timeToPosition(double t) { return t * track_length / bag_duration; }
272  double positionToTime(double p) { return p / track_length * bag_duration; }
273  QRectF _drag_start_rect;
274  ResizeHandle *left_handle = new ResizeHandle(this, -1);
275  ResizeHandle *right_handle = new ResizeHandle(this, +1);
276  bool _dragged = false;
277  std::unordered_set<std::shared_ptr<AnnotationSpan>> _dragged_spans;
278  bool _selected = false;
279  double _track_color = 0;
280 
281  bool snap(double &x) {
282  snap(x, [this](const std::shared_ptr<AnnotationSpan> &span) {
283  return span != _annotation;
284  });
285  }
286  template <class F> bool snap(double &x, const F &filter) {
287  double snap_position = 0;
288  bool snapped = false;
289  LockScope ws;
290  if (ws->player) {
291  double l = timeToPosition(ws->player->time());
292  if (!snapped || std::abs(x - l) < std::abs(x - snap_position)) {
293  snap_position = l;
294  snapped = true;
295  }
296  }
297  for (size_t itrack = 0; itrack < trackCount(ws()); itrack++) {
298  if (auto track = std::dynamic_pointer_cast<AnnotationTrack>(
299  trackAt(ws(), itrack))) {
300  if (auto branch = track->branch(ws(), false)) {
301  for (auto &other_span : branch->spans()) {
302  if (!filter(other_span)) {
303  continue;
304  }
305  double l = timeToPosition(other_span->start());
306  double r =
307  timeToPosition(other_span->start() + other_span->duration());
308  if (!snapped || std::abs(x - l) < std::abs(x - snap_position)) {
309  snap_position = l;
310  snapped = true;
311  }
312  if (!snapped || std::abs(x - r) < std::abs(x - snap_position)) {
313  snap_position = r;
314  snapped = true;
315  }
316  }
317  }
318  }
319  }
320  if (std::abs(snap_position - x) < 6.1) {
321  x = snap_position;
322  return true;
323  } else {
324  return false;
325  }
326  }
327 
328  static std::unordered_set<AnnotationSpanItem *> &instances() {
329  static std::unordered_set<AnnotationSpanItem *> instances;
330  return instances;
331  }
332 
333  QPointF &last_clicked_point = *[]() {
334  static QPointF s;
335  return &s;
336  }();
337 
338 public:
340 
341  setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
342 
343  setTextPen(QPen(QBrush(QColor(0, 0, 0)), 1));
344 
345  changed.connect([this](const std::string &label) {
346  ActionScope ws("Change annotation span label");
347  if (label == _track->label()) {
348  _annotation->label().clear();
349  } else {
350  _annotation->label() = label;
351  }
352  ws->modified();
353  });
354 
355  instances().insert(this);
356  }
357  ~AnnotationSpanItem() { instances().erase(this); }
358 
359  void setItemRect(double x, double y, double width) {
360  setRect(x, y, width, track_height);
361  left_handle->update();
362  right_handle->update();
363 
364  {
365  LockScope ws;
366  size_t itrack = std::max(0.0, std::round(y / track_height) - 1.0);
367  if (auto track = trackAt(ws(), itrack)) {
368 
369  setTextPen(QPen(QBrush(QColor(0, 0, 0)), 1));
370  QFont font = QApplication::font();
371  if (_annotation->label().empty()) {
372  setText(track->label());
373  font.setItalic(true);
374  } else {
375  setText(_annotation->label());
376  }
377  _track_color = track->color();
378  if (_selected = ws->selection().contains(_annotation)) {
379  setBrush(QBrush(QColor::fromHsvF(track->color(), 0.8, 0.6)));
380  setTextPen(QPen(QBrush(QColor(Qt::white)), 1));
381  } else {
382  setBrush(QBrush(QColor::fromHsvF(track->color(), 0.2, 1.0)));
383  setTextPen(QPen(QBrush(QColor(Qt::black)), 1));
384  }
385  setPen(QPen(QBrush(Qt::black), 1));
386  setFont(font);
387  }
388  }
389  }
390  virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
391  QWidget *widget) override {
392  EditableText::paint(painter, option, widget);
393  painter->setBrush(QBrush(Qt::transparent));
394  painter->setPen(QPen(QBrush(QColor(255, 255, 255, 200)), 1, Qt::SolidLine,
395  Qt::SquareCap, Qt::MiterJoin));
396  painter->drawRect(rect().marginsRemoved(QMarginsF(1, 1, 1, 1)));
397  }
398  void update(const std::shared_ptr<Workspace> &ws, size_t track_index,
399  const std::shared_ptr<AnnotationTrack> &track,
400  const std::shared_ptr<AnnotationSpan> &annotation,
401  double track_length, double bag_duration) {
402  _track = track;
403  _annotation = annotation;
404  this->track_length = track_length;
405  this->bag_duration = bag_duration;
406  setItemRect(track_length * annotation->start() / bag_duration,
407  (track_index + 1) * track_height,
408  track_length * annotation->duration() / bag_duration);
409  }
410  virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
411  QGraphicsRectItem::mousePressEvent(event);
412  LockScope ws;
413  if (event->button() == Qt::LeftButton) {
414  LOG_DEBUG("begin dragging annotation span");
415  _dragged = false;
416  event->accept();
417  {
418  _dragged_spans.clear();
419  LockScope ws;
420  LOG_DEBUG("select " << _annotation);
421  ws->currentAnnotationTrack() = _track;
422  if (event->modifiers() & Qt::ShiftModifier) {
423  if (!last_clicked_point.isNull()) {
424  double left =
425  std::min(last_clicked_point.x(), event->scenePos().x());
426  double right =
427  std::max(last_clicked_point.x(), event->scenePos().x());
428  double top =
429  std::min(std::floor(last_clicked_point.y() / track_height) *
430  track_height,
431  rect().top());
432  double bottom = std::max(
433  std::ceil(last_clicked_point.y() / track_height) * track_height,
434  rect().bottom());
435  for (auto *instance : instances()) {
436  if (instance->rect().right() - 1e-6 > left &&
437  instance->rect().left() + 1e-6 < right &&
438  instance->rect().center().y() >= top &&
439  instance->rect().center().y() <= bottom) {
440  ws->selection().add(instance->_annotation);
441  }
442  }
443  ws->selection().add(_annotation);
444  } else {
445  ws->selection().toggle(_annotation);
446  if (ws->selection().contains(_annotation)) {
447  last_clicked_point = event->scenePos();
448  } else {
449  last_clicked_point = QPointF();
450  }
451  }
452  } else if (event->modifiers() & Qt::ControlModifier) {
453  ws->selection().toggle(_annotation);
454  if (ws->selection().contains(_annotation)) {
455  last_clicked_point = event->scenePos();
456  } else {
457  last_clicked_point = QPointF();
458  }
459  } else {
460  if (!ws->selection().contains(_annotation)) {
461  ws->selection() = _annotation;
462  last_clicked_point = event->scenePos();
463  }
464  for (auto object : ws->selection().resolve(ws())) {
465  if (auto span = std::dynamic_pointer_cast<AnnotationSpan>(object)) {
466  _dragged_spans.insert(span);
467  }
468  }
469  for (auto &view : instances()) {
470  if (_dragged_spans.find(view->_annotation) !=
471  _dragged_spans.end()) {
472  view->_drag_start_rect = view->rect();
473  }
474  }
475  }
476  left_handle->unsetCursor();
477  right_handle->unsetCursor();
478  ws->modified();
479  }
480  }
481  }
482  virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
483  QGraphicsRectItem::mouseMoveEvent(event);
484  if (event->buttons() == Qt::LeftButton) {
485  if (!_dragged_spans.empty()) {
486  LockScope ws;
487  _dragged = true;
488  double timeline_duration = timelineDuration(ws());
489  double max_x = timeToPosition(timeline_duration);
490  size_t track_count = trackCount(ws());
491  double dx = event->scenePos().x() -
492  event->buttonDownScenePos(Qt::LeftButton).x();
493  double di = std::round((event->scenePos().y() -
494  event->buttonDownScenePos(Qt::LeftButton).y()) /
495  track_height);
496  for (auto *view : instances()) {
497  if (_dragged_spans.find(view->_annotation) != _dragged_spans.end()) {
498  double vi =
499  std::round(view->_drag_start_rect.y() / track_height) - 1;
500  di = std::min(track_count - 1.0, std::max(0.0, di + vi)) - vi;
501  }
502  }
503  {
504  bool snapped = false;
505  double best_snap = 0;
506  for (auto *view : instances()) {
507  if (_dragged_spans.find(view->_annotation) !=
508  _dragged_spans.end()) {
509  for (double offset : {0.0, view->rect().width()}) {
510  double side = view->_drag_start_rect.x() + offset + dx;
511  double x = side;
512  if (snap(x, [this](
513  const std::shared_ptr<AnnotationSpan> &span) {
514  return _dragged_spans.find(span) == _dragged_spans.end();
515  })) {
516  x -= side;
517  if (!snapped || std::abs(x) < std::abs(best_snap)) {
518  snapped = true;
519  best_snap = x;
520  }
521  }
522  }
523  }
524  }
525  dx += best_snap;
526  }
527  for (auto *view : instances()) {
528  if (_dragged_spans.find(view->_annotation) != _dragged_spans.end()) {
529  dx = std::max(dx, -view->_drag_start_rect.x());
530  dx = std::min(max_x - view->_drag_start_rect.right(), dx);
531  }
532  }
533  for (auto *view : instances()) {
534  if (_dragged_spans.find(view->_annotation) != _dragged_spans.end()) {
535  for (auto *other : instances()) {
536  if (_dragged_spans.find(other->_annotation) ==
537  _dragged_spans.end()) {
538  if ((std::round(view->_drag_start_rect.y() / track_height) +
539  di) == std::round(other->rect().y() / track_height)) {
540  double min_distance =
541  (view->rect().width() + other->rect().width()) * 0.5;
542  double distance = (view->_drag_start_rect.center().x() + dx) -
543  other->rect().center().x();
544  if (std::abs(distance) < min_distance) {
545  dx += (std::signbit(distance) ? -1.0 : 1.0) * min_distance -
546  distance;
547  }
548  }
549  }
550  }
551  }
552  }
553  for (auto *view : instances()) {
554  if (_dragged_spans.find(view->_annotation) != _dragged_spans.end()) {
555  for (auto *other : instances()) {
556  if (_dragged_spans.find(other->_annotation) ==
557  _dragged_spans.end()) {
558  if ((std::round(view->_drag_start_rect.y() / track_height) +
559  di) == std::round(other->rect().y() / track_height)) {
560  double min_distance =
561  (view->rect().width() + other->rect().width()) * 0.5;
562  double distance = (view->_drag_start_rect.center().x() + dx) -
563  other->rect().center().x();
564  if (std::abs(distance) < min_distance - 1e-6) {
565  return;
566  }
567  }
568  }
569  }
570  }
571  }
572  for (auto *view : instances()) {
573  if (_dragged_spans.find(view->_annotation) != _dragged_spans.end()) {
574  if (view->_drag_start_rect.x() + dx < -1e-6) {
575  return;
576  }
577  if (view->_drag_start_rect.right() + dx > max_x + 1e-6) {
578  return;
579  }
580  }
581  }
582  for (auto *view : instances()) {
583  if (_dragged_spans.find(view->_annotation) != _dragged_spans.end()) {
584  auto rect = view->rect();
585  view->setItemRect(view->_drag_start_rect.x() + dx,
586  view->_drag_start_rect.y() + di * track_height,
587  rect.width());
588  }
589  }
590  }
591  }
592  }
593  virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
594  QGraphicsRectItem::mouseReleaseEvent(event);
595  if (instances().find(this) == instances().end()) {
596  throw std::runtime_error("already deleted");
597  }
598  LOG_DEBUG("mouse release event " << this);
599  if (event->button() == Qt::LeftButton) {
600  if (!_dragged_spans.empty() && _dragged) {
601  LOG_DEBUG("finished dragging annotation span");
602  event->accept();
603  {
604  ActionScope ws("Drag Annotation Spans");
605  std::vector<QPointer<AnnotationSpanItem>> insts;
606  for (auto &view : instances()) {
607  insts.emplace_back(view);
608  }
609  LOG_DEBUG("count " << insts.size());
610  for (auto &view : insts) {
611  if (view) {
612  if (_dragged_spans.find(view->_annotation) !=
613  _dragged_spans.end()) {
614  LOG_DEBUG("commit");
615  view->commit();
616  }
617  } else {
618  LOG_DEBUG("view gone");
619  }
620  }
621  QTimer::singleShot(0, []() {
622  LockScope ws;
623  ws->modified();
624  });
625  }
626  _dragged_spans.clear();
627  } else {
628  if (event->modifiers() == 0) {
629  LockScope ws;
630  ws->selection() = _annotation;
631  ws->modified();
632  last_clicked_point = event->buttonDownPos(Qt::LeftButton);
633  }
634  }
635  }
636  }
637 
638  void commit() {
639  LockScope ws;
640  if (auto annotation = _annotation) {
641  double start = positionToTime(rect().x());
642  double duration = positionToTime(rect().width());
643  ssize_t itrack =
644  std::max(0.0, std::round(rect().y() / track_height) - 1.0);
645  if (annotation->start() != start || annotation->duration() != duration ||
646  trackAt(ws(), itrack) != _track) {
647  if (itrack >= 0 && itrack < trackCount(ws())) {
648  // ActionScope ws("Move annotation");
649  if (trackAt(ws(), itrack) != _track) {
650  auto &spans = _track->branch(ws(), true)->spans();
651  spans.erase(std::remove(spans.begin(), spans.end(), _annotation),
652  spans.end());
653  if (auto track = std::dynamic_pointer_cast<AnnotationTrack>(
654  trackAt(ws(), itrack))) {
655  track->branch(ws(), true)->spans().push_back(_annotation);
656  }
657  }
658  annotation->start() = start;
659  annotation->duration() = duration;
660  } else {
661  LOG_ERROR("failed to move annotation");
662  }
663  } else {
664  LOG_DEBUG("annotation not moved");
665  }
666  }
667  }
668 };
669 
670 class TrackViewBase : public QGraphicsRectItem, public QObject {
671 
672  class LabelItem : public EditableText {
673  TrackViewBase *_parent = nullptr;
674  QGraphicsRectItem *_reorder_marker = new QGraphicsRectItem(this);
675 
676  protected:
677  virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
678  static std::weak_ptr<TrackBase> last_clicked_track;
679  EditableText::mousePressEvent(event);
680  if (event->button() == Qt::LeftButton) {
681  event->accept();
682  LockScope ws;
683  LOG_DEBUG("select " << _parent->_track->label());
684  if (event->modifiers() & Qt::ControlModifier) {
685  ws->selection().toggle(_parent->_track);
686  } else if (event->modifiers() & Qt::ShiftModifier) {
687  if (auto last_clicked = last_clicked_track.lock()) {
688  size_t track_count = trackCount(ws());
689  bool selection_flag = false;
690  for (size_t i = 0; i < track_count; i++) {
691  auto track = trackAt(ws(), i);
692  bool range_boundary =
693  (track == last_clicked || track == _parent->_track);
694  if (selection_flag || range_boundary) {
695  ws->selection().add(track);
696  }
697  if (range_boundary) {
698  selection_flag = !selection_flag;
699  }
700  }
701  } else {
702  ws->selection().toggle(_parent->_track);
703  }
704  } else {
705  ws->selection() = _parent->_track;
706  }
707  if (auto annotation_track =
708  std::dynamic_pointer_cast<AnnotationTrack>(_parent->_track)) {
709  if (ws->selection().contains(annotation_track)) {
710  ws->currentAnnotationTrack() = annotation_track;
711  last_clicked_track = annotation_track;
712  }
713  }
714  ws->modified();
715  //_reorder_marker->setBrush(
716  // QBrush(QColor::fromHsvF(_parent->_track->color(), 1.0, 0.5)));
717  _reorder_marker->setBrush(QBrush(QColor(100, 100, 100)));
718  _reorder_marker->setPen(QPen(Qt::NoPen));
719  }
720  }
721  size_t y2i(double y) {
722  LockScope ws;
723  return std::max((int64_t)0,
724  std::min((int64_t)trackCount(ws()),
725  (int64_t)std::round(y / track_height) - 1));
726  }
727  virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
728  EditableText::mouseMoveEvent(event);
729  event->accept();
730  if (event->buttons() == Qt::LeftButton) {
731  LOG_DEBUG("drag track");
732  double thickness = 6;
733  double y =
734  (y2i(event->scenePos().y()) + 1) * track_height - thickness * 0.5;
735  y = std::max(y, track_height);
736  _reorder_marker->setRect(rect().x(), y, rect().width(), thickness);
737  _reorder_marker->show();
738  }
739  }
740  virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
741  EditableText::mouseReleaseEvent(event);
742  event->accept();
743  _reorder_marker->hide();
744  if (event->button() == Qt::LeftButton &&
745  event->buttonDownScenePos(Qt::LeftButton).y() !=
746  event->scenePos().y()) {
747  LockScope ws;
748  size_t new_index = y2i(event->scenePos().y());
749  LOG_DEBUG("dragged from " << _parent->_track_index << " to "
750  << new_index);
751  if (new_index != _parent->_track_index) {
752  ActionScope ws("Reorder Tracks");
753  auto &tracks = ws->document()->timeline()->tracks();
754  auto new_position_it =
755  tracks.insert(tracks.begin() + new_index, _parent->_track);
756  for (auto it = tracks.begin(); it < tracks.end(); it++) {
757  if (*it == _parent->_track && it != new_position_it) {
758  tracks.erase(it);
759  break;
760  }
761  }
762  QTimer::singleShot(0, []() {
763  LockScope ws;
764  ws->modified();
765  });
766  }
767  }
768  }
769 
770  public:
771  LabelItem(TrackViewBase *parent) : EditableText(parent), _parent(parent) {}
772  virtual void paint(QPainter *painter,
773  const QStyleOptionGraphicsItem *option,
774  QWidget *widget) override {
775  EditableText::paint(painter, option, widget);
776  painter->setBrush(QBrush(Qt::transparent));
777  painter->setPen(QPen(QBrush(QColor(255, 255, 255, 200)), 1, Qt::SolidLine,
778  Qt::SquareCap, Qt::MiterJoin));
779  painter->drawRect(rect().marginsRemoved(QMarginsF(1, 1, 1, 1)));
780  }
781  };
782  LabelItem *_text = nullptr;
783  std::shared_ptr<TrackBase> _track;
784  bool _selected = false;
785 
786 private:
787  double scrollPositionX() const {
788  auto *view = scene()->views().first();
789  QPointF p = view->mapToScene(view->viewport()->rect().topLeft());
790  return p.x();
791  }
792 
793 protected:
794  const size_t _track_index = 0;
795 
796 public:
797  std::shared_ptr<TrackBase> track() const { return _track; }
798  TrackViewBase(size_t track_index, std::shared_ptr<TrackBase> track)
799  : _track(track), _track_index(track_index) {
800 
801  setBrush(QBrush(Qt::transparent));
802  setPen(QPen(Qt::NoPen));
803 
804  _text = new LabelItem(this);
805  _text->changed.connect(this, [track](const std::string &str) {
806  ActionScope ws("Change annotation track label");
807  track->label() = str;
808  ws->modified();
809  });
810  _text->setZValue(100000);
811  }
812  virtual void update(const std::shared_ptr<Workspace> &ws, double width) {
813  QRectF rect = scene()->sceneRect();
814  double y = track_height * (_track_index + 1);
815  setRect(QRectF(scrollPositionX() + track_label_width, y,
816  rect.width() - track_padding_right - track_label_width,
817  track_height));
818  _text->setRect(scrollPositionX() - 1, y, track_label_width + 1,
819  track_height);
820  setZValue(ws->selection().contains(_track) ? 2 : 1);
821  _text->setText(_track->label().c_str());
822  if (_selected = ws->selection().contains(_track)) {
823 
824  _text->setZValue(100002);
825  _text->setBrush(QBrush(QColor::fromHsvF(_track->color(), 0.8, 0.6)));
826  _text->setPen(QPen(QApplication::palette().brush(QPalette::Mid), 1));
827  _text->setTextPen(QPen(QBrush(Qt::white), 1));
828 
829  } else {
830 
831  _text->setBrush(QBrush(QColor::fromHsvF(_track->color(), 0.2, 1)));
832  _text->setTextPen(QPen(QBrush(Qt::black), 1));
833  _text->setPen(QPen(QApplication::palette().brush(QPalette::Mid), 1));
834  _text->setZValue(100001);
835  }
836  {
837  QFont font = QApplication::font();
838  if (_track->id() == ws->currentAnnotationTrack().id()) {
839  font.setBold(true);
840  }
841  _text->setFont(font);
842  }
843  }
844 };
845 
846 class GraphTree {
847  struct Item {
848  double xmin = 0, xmax = 0, ymin = 0, ymax = 0;
849  void merge(const Item &other) {
850  xmin = std::min(xmin, other.xmin);
851  ymin = std::min(ymin, other.ymin);
852  xmax = std::max(xmax, other.xmax);
853  ymax = std::max(ymax, other.ymax);
854  }
855  };
856  struct Level {
857  std::vector<Item> items;
858  };
859  struct Data {
860  std::deque<Level> levels;
861  };
862  std::shared_ptr<Data> _data;
863 
864 public:
865  struct Sample {
866  double x = 0;
867  double ymin = 0;
868  double ymax = 0;
869  };
870 
871 private:
872  void sample(size_t ilevel, size_t iitem, double xmin, double xmax,
873  double xstep, std::vector<Sample> &samples) {
874  auto &item = _data->levels[ilevel].items[iitem];
875  double item_end = item.xmax;
876  if (iitem + 1 < _data->levels[ilevel].items.size()) {
877  auto &item2 = _data->levels[ilevel].items[iitem + 1];
878  item_end = item2.xmin;
879  }
880  double prev_x = item.xmin;
881  if (iitem > 0) {
882  prev_x = _data->levels[ilevel].items[iitem - 1].xmax;
883  }
884  if (item_end < xmin || prev_x > xmax) {
885  return;
886  }
887  if (ilevel >= 1 && (item_end - item.xmin) > xstep) {
888  sample(ilevel - 1, iitem * 2, xmin, xmax, xstep, samples);
889  if (iitem * 2 + 1 < _data->levels[ilevel - 1].items.size()) {
890  sample(ilevel - 1, iitem * 2 + 1, xmin, xmax, xstep, samples);
891  }
892  } else {
893  Sample sample;
894  sample.x = (item.xmin + item.xmax) / 2;
895  sample.ymin = item.ymin;
896  sample.ymax = item.ymax;
897  samples.push_back(sample);
898  }
899  }
900 
901 public:
902  void clear() { _data.reset(); }
903  void push(double x, double y) {
904  // LOG_DEBUG("push " << x << " " << y);
905  if (!_data) {
906  _data = std::make_shared<Data>();
907  }
908  if (_data.use_count() > 1) {
909  _data = std::make_shared<Data>(*_data);
910  }
911  {
912  Item item;
913  item.xmin = item.xmax = x;
914  item.ymin = item.ymax = y;
915  if (_data->levels.empty()) {
916  _data->levels.emplace_back();
917  }
918  _data->levels.front().items.push_back(item);
919  }
920  for (size_t ilevel = 1;; ilevel++) {
921  if (_data->levels[ilevel - 1].items.size() <= 1) {
922  break;
923  }
924  if (_data->levels.size() <= ilevel) {
925  _data->levels.emplace_back();
926  }
927  size_t iitem = (_data->levels[ilevel - 1].items.size() - 1) / 2;
928  _data->levels[ilevel].items.resize(iitem + 1);
929  _data->levels[ilevel].items[iitem] =
930  _data->levels[ilevel - 1].items[iitem * 2];
931  if (iitem * 2 + 1 < _data->levels[ilevel - 1].items.size()) {
932  _data->levels[ilevel].items[iitem].merge(
933  _data->levels[ilevel - 1].items[iitem * 2 + 1]);
934  }
935  // LOG_DEBUG("level " << ilevel << " "
936  // << _data->levels[ilevel].items.size());
937  if (ilevel > 100) {
938  throw std::runtime_error("graph tree overflow");
939  }
940  }
941  }
942  void sample(double xmin, double xmax, double xstep,
943  std::vector<Sample> &samples) {
944  if (_data && !_data->levels.empty()) {
945  sample(_data->levels.size() - 1, 0, xmin, xmax, xstep, samples);
946  }
947  }
948  void range(double &ymin, double &ymax) {
949  if (_data && !_data->levels.empty()) {
950  ymin = _data->levels.back().items.front().ymin;
951  ymax = _data->levels.back().items.front().ymax;
952  }
953  }
954 };
955 
957  std::shared_ptr<GraphTrack> _track;
958  size_t _track_height = 0;
959  double _track_length = 0;
960  double _bag_duration = 0.0;
961  QPen _pen;
962  double timeToPosition(double t) { return t * _track_length / _bag_duration; }
963  double positionToTime(double p) { return p / _track_length * _bag_duration; }
964  volatile bool _exit_flag = false;
965  std::mutex _load_mutex;
966  GraphTree _load_data;
967  std::string _load_query, _load_topic;
968  double _load_duration = 0;
969  std::condition_variable _load_condition;
970  std::shared_ptr<BagPlayer> _load_player;
971  Watcher _watcher;
972  bool _load_flag = false;
973  void _update() {
974  QObject o;
975  QObject::connect(&o, &QObject::destroyed, this,
976  [this](QObject *o) { LockScope()->modified(); });
977  }
978  std::thread _load_thread{[this]() {
979  while (true) {
980  std::string topic;
981  MessageQuery query;
982  MessageParser parser;
983  std::shared_ptr<BagPlayer> player;
984  double duration = 0;
985  {
986  std::unique_lock<std::mutex> lock(_load_mutex);
987  while (true) {
988  if (_exit_flag) {
989  LOG_DEBUG("exit");
990  return;
991  }
992  if (_load_flag) {
993  _load_flag = false;
994  topic = _load_topic;
995  duration = _load_duration;
996  query = MessageQuery(_load_query);
997  player = _load_player;
998  _load_data.clear();
999  _update();
1000  if (topic.empty() || query.str().empty() || player == nullptr) {
1001  LOG_DEBUG("empty");
1002  continue;
1003  } else {
1004  break;
1005  }
1006  }
1007  LOG_DEBUG("wait");
1008  _load_condition.wait(lock);
1009  }
1010  }
1011  LOG_DEBUG("load graph track data");
1012  {
1013  LOG_DEBUG("begin loading graph track data");
1014  GraphTree data;
1015  auto t0 = std::chrono::steady_clock::now();
1016  player->readMessageSamples(
1017  topic, 0, duration, [&](const std::shared_ptr<const Message> &msg) {
1018  // LOG_DEBUG("a");
1019  parser.parse(msg);
1020  double x = (msg->time() - player->startTime()).toSec() / duration;
1021  double y = query(parser).toDouble();
1022  data.push(x, y);
1023  if (std::chrono::steady_clock::now() >
1024  t0 + std::chrono::milliseconds(250)) {
1025  {
1026  std::unique_lock<std::mutex> lock(_load_mutex);
1027  _load_data = data;
1028  }
1029  _update();
1030  t0 = std::chrono::steady_clock::now();
1031  }
1032  return !_exit_flag && !_load_flag;
1033  });
1034  // LOG_DEBUG("b");
1035  {
1036  {
1037  std::unique_lock<std::mutex> lock(_load_mutex);
1038  _load_data = data;
1039  }
1040  _update();
1041  }
1042  LOG_DEBUG("graph track data loaded");
1043  }
1044  }
1045  }};
1046 
1047 public:
1048  GraphTrackView(size_t track_index, std::shared_ptr<GraphTrack> track)
1049  : TrackViewBase(track_index, track), _track_height(track_index),
1050  _track(track) {}
1051  ~GraphTrackView() {
1052  LOG_DEBUG("stop");
1053  {
1054  std::unique_lock<std::mutex> lock(_load_mutex);
1055  _exit_flag = true;
1056  _load_condition.notify_all();
1057  }
1058  LOG_DEBUG("join");
1059  _load_thread.join();
1060  LOG_DEBUG("ready");
1061  }
1062  virtual void update(const std::shared_ptr<Workspace> &ws,
1063  double width) override {
1064  TrackViewBase::update(ws, width);
1065  _track_length = width;
1066  _bag_duration = timelineDuration(ws);
1067  _pen = QPen(QBrush(QColor::fromHsvF(_track->color(), 1.0, 0.5)), 1.5,
1068  Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin);
1069  if (_watcher.changed(ws->player, _bag_duration, _track->query().str(),
1070  _track->query().str())) {
1071  LOG_DEBUG("load graph track");
1072  std::unique_lock<std::mutex> lock(_load_mutex);
1073  _load_flag = true;
1074  _load_player = ws->player;
1075  _load_duration = _bag_duration;
1076  _load_query = _track->query().str();
1077  _load_topic = _track->topic().topic();
1078  _load_condition.notify_all();
1079  }
1080  // LOG_DEBUG(__LINE__);
1081  }
1082  virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
1083  QWidget *widget) override {
1084  QPolygonF polygon;
1085  {
1086  GraphTree data;
1087  {
1088  std::unique_lock<std::mutex> lock(_load_mutex);
1089  data = _load_data;
1090  }
1091  std::vector<GraphTree::Sample> samples;
1092  auto *view = scene()->views().first();
1093  auto view_top_left = view->mapToScene(view->viewport()->rect().topLeft());
1094  auto view_top_right =
1095  view->mapToScene(view->viewport()->rect().topRight());
1096  data.sample(view_top_left.x() / _track_length,
1097  view_top_right.x() / _track_length, 1.5 / _track_length,
1098  samples);
1099  LOG_DEBUG("graph track samples " << samples.size());
1100  if (!samples.empty()) {
1101  QVector<QPointF> points;
1102  /*
1103  double lo = samples.front().ymin;
1104  double hi = samples.front().ymax;
1105  for (auto &sample : samples) {
1106  lo = std::min(lo, sample.ymin);
1107  hi = std::max(hi, sample.ymax);
1108  }
1109  */
1110  double hi = 0, lo = 0;
1111  data.range(lo, hi);
1112  for (size_t isample = 0; isample < samples.size(); isample++) {
1113  auto &sample = samples[isample];
1114  points.push_back(QPointF(
1115  sample.x * _track_length,
1116  (0.9 - (sample.ymax - lo) / (hi - lo) * 0.8) * track_height +
1117  track_height + _track_index * track_height));
1118  }
1119  for (ssize_t isample = ssize_t(samples.size()) - 1; isample >= 0;
1120  isample--) {
1121  auto &sample = samples[isample];
1122  points.push_back(QPointF(
1123  sample.x * _track_length,
1124  (0.9 - (sample.ymin - lo) / (hi - lo) * 0.8) * track_height +
1125  track_height + _track_index * track_height));
1126  }
1127  polygon = QPolygonF(points);
1128  }
1129  }
1130  painter->save();
1131  painter->setRenderHints(QPainter::Antialiasing);
1132  painter->setPen(_pen);
1133  // painter->setBrush(QBrush(Qt::transparent));
1134  painter->setBrush(_pen.brush());
1135  // painter->drawPolyline(polygon);
1136  painter->drawPolygon(polygon);
1137  painter->restore();
1138  }
1139  /*
1140  virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
1141  QWidget *widget) override {
1142  QPolygonF polygon;
1143  {
1144  std::unique_lock<std::mutex> lock(_load_mutex);
1145  if (!_points.isEmpty()) {
1146  LOG_DEBUG("draw graph track " << _points.size());
1147  QVector<QPointF> points2;
1148  double lo = _points[0].y();
1149  double hi = _points[0].y();
1150  for (auto &p : _points) {
1151  lo = std::min(lo, p.y());
1152  hi = std::max(hi, p.y());
1153  }
1154  LOG_DEBUG("yrange " << lo << " " << hi);
1155  for (auto p : _points) {
1156  p.setX(p.x() * _track_length);
1157  p.setY((0.9 - (p.y() - lo) / (hi - lo) * 0.8) * track_height +
1158  track_height + _track_index * track_height);
1159  points2.push_back(p);
1160  }
1161  polygon = QPolygonF(points2);
1162  }
1163  }
1164  painter->save();
1165  painter->setRenderHints(QPainter::Antialiasing);
1166  painter->setPen(_pen);
1167  painter->setBrush(QBrush(Qt::transparent));
1168  painter->drawPolyline(polygon);
1169  painter->restore();
1170  }
1171  */
1172 };
1173 
1175  const std::shared_ptr<AnnotationTrack> _track;
1176  QGraphicsRectItem *_item_prototype = nullptr;
1177  double _new_item_start = 0;
1178  std::vector<AnnotationSpanItem *> _span_items;
1179  QGraphicsRectItem *_clip_rect = new QGraphicsRectItem(this);
1180 
1181 private:
1182  void updateItemPrototype(QGraphicsSceneMouseEvent *event) {
1183  LockScope ws;
1184  double track_length =
1185  scene()->sceneRect().width() - track_label_width - track_padding_right;
1186  double x = std::max(0.0, std::min(track_length, event->scenePos().x()));
1187  double wmin = 3;
1188  if (x == _new_item_start) {
1189  x += wmin;
1190  }
1191  x = std::min(x, track_length);
1192  if (auto branch = _track->branch(ws(), false)) {
1193  for (auto &span : branch->spans()) {
1194  double left = span->start() * track_length / timelineDuration(ws());
1195  double right = (span->start() + span->duration()) * track_length /
1196  timelineDuration(ws());
1197  if (_new_item_start > right) {
1198  x = std::max(x, right);
1199  }
1200  if (_new_item_start < left) {
1201  x = std::min(x, left);
1202  }
1203  }
1204  }
1205  double x0 = std::min(x, _new_item_start);
1206  double x1 = std::max(x, _new_item_start);
1207  _item_prototype->setRect(x0, rect().y(), x1 - x0, rect().height());
1208  }
1209 
1210 protected:
1211  virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
1212  QGraphicsItem::mousePressEvent(event);
1213  LockScope ws;
1214  double track_length =
1215  scene()->sceneRect().width() - track_label_width - track_padding_right;
1216  if (event->button() == Qt::LeftButton) {
1217  event->accept();
1218  _new_item_start =
1219  std::max(0.0, std::min(scene()->sceneRect().width() -
1220  track_padding_right - track_label_width,
1221  event->scenePos().x()));
1222  {
1223  double t = _new_item_start * timelineDuration(ws()) / track_length;
1224  if (auto branch = _track->branch(ws(), false)) {
1225  for (auto &span : branch->spans()) {
1226  if (t > span->start() && t < span->start() + span->duration()) {
1227  return;
1228  }
1229  }
1230  }
1231  }
1232  updateItemPrototype(event);
1233  _item_prototype->setBrush(
1234  QBrush(QColor::fromHsvF(_track->color(), 1.0, 0.5)));
1235  _item_prototype->show();
1236  }
1237  if (!ws->selection().empty()) {
1238  ws->selection().clear();
1239  ws->modified();
1240  }
1241  }
1242  virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
1243  QGraphicsItem::mouseMoveEvent(event);
1244  LockScope ws;
1245  if (event->buttons() & Qt::LeftButton) {
1246  event->accept();
1247  updateItemPrototype(event);
1248  }
1249  }
1250  virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
1251  QGraphicsItem::mouseReleaseEvent(event);
1252  LockScope ws;
1253  if (event->button() == Qt::LeftButton) {
1254  event->accept();
1255  if (_item_prototype->isVisible()) {
1256  if (_item_prototype->rect().width() > 3.2) {
1257  ActionScope ws("Add annotation");
1258  double track_length = scene()->sceneRect().width() -
1259  track_padding_right - track_label_width;
1260  auto span = std::make_shared<AnnotationSpan>();
1261  span->start() = _item_prototype->rect().x() * timelineDuration(ws()) /
1262  track_length;
1263  span->duration() = _item_prototype->rect().width() *
1264  timelineDuration(ws()) / track_length;
1265  _track->branch(ws(), true)->spans().push_back(span);
1266  _item_prototype->hide();
1267  ws->selection() = span;
1268  ws->currentAnnotationTrack() = _track;
1269  ws->modified();
1270  }
1271  _item_prototype->hide();
1272  }
1273  }
1274  }
1275 
1276 public:
1277  AnnotationTrackView(size_t track_index,
1278  std::shared_ptr<AnnotationTrack> track)
1279  : TrackViewBase(track_index, track), _track(track) {
1280 
1281  // setBrush(QApplication::palette().brush(QPalette::Dark));
1282  setBrush(QBrush(Qt::transparent));
1283  setPen(QPen(Qt::NoPen));
1284 
1285  _item_prototype = new QGraphicsRectItem();
1286  _item_prototype->setParentItem(this);
1287  _item_prototype->setBrush(QBrush(Qt::blue));
1288  _item_prototype->setPen(QPen(Qt::NoPen));
1289  _item_prototype->hide();
1290 
1291  _clip_rect->setFlag(ItemClipsChildrenToShape, true);
1292  _clip_rect->setBrush(QBrush(Qt::transparent));
1293  _clip_rect->setPen(QPen(Qt::NoPen));
1294  }
1295  virtual void update(const std::shared_ptr<Workspace> &ws,
1296  double width) override {
1297  TrackViewBase::update(ws, width);
1298 
1299  auto *view = scene()->views().first();
1300  auto view_top_left = view->mapToScene(view->viewport()->rect().topLeft());
1301  auto view_top_right = view->mapToScene(view->viewport()->rect().topRight());
1302  _clip_rect->setRect(
1303  QRect(view_top_left.x() + track_label_width, scene()->sceneRect().y(),
1304  view_top_right.x() - view_top_left.x() - track_label_width,
1305  scene()->sceneRect().height() + track_height));
1306 
1307  QRectF rect = scene()->sceneRect();
1308  double y = track_height * (_track_index + 1);
1309  if (auto branch = _track->branch(ws, false)) {
1310  for (size_t i = 0; i < branch->spans().size(); i++) {
1311  if (_span_items.size() <= i) {
1312  auto *item = new AnnotationSpanItem();
1313  item->setParentItem(_clip_rect);
1314  _span_items.push_back(item);
1315  }
1316  auto *item = _span_items[i];
1317  auto &annotation = branch->spans()[i];
1318  item->update(ws, _track_index, _track, annotation, width,
1319  timelineDuration(ws));
1320  }
1321  while (_span_items.size() > branch->spans().size()) {
1322  delete _span_items.back();
1323  _span_items.pop_back();
1324  }
1325  } else {
1326  while (_span_items.size() > 0) {
1327  delete _span_items.back();
1328  _span_items.pop_back();
1329  }
1330  }
1331  }
1332 };
1333 
1334 class Scene : public QGraphicsScene, public ItemBase {
1335  double _width = 300;
1336  bool _enabled = false;
1337  std::vector<TrackViewBase *> _tracks;
1338 
1339 private:
1340  TrackViewBase *createTrackView(size_t index,
1341  const std::shared_ptr<TrackBase> &track) {
1342  if (auto t = std::dynamic_pointer_cast<AnnotationTrack>(track)) {
1343  return new AnnotationTrackView(index, t);
1344  }
1345  if (auto t = std::dynamic_pointer_cast<GraphTrack>(track)) {
1346  return new GraphTrackView(index, t);
1347  }
1348  throw std::runtime_error("unknown track type");
1349  }
1350 
1351 public:
1352  virtual void sync(const std::shared_ptr<Workspace> &ws) override {
1353  //_enabled = (ws->player != nullptr);
1354  _enabled = true;
1355  {
1356  size_t track_count = trackCount(ws);
1357  LOG_DEBUG("annotation tracks " << track_count);
1358  while (_tracks.size() > track_count) {
1359  delete _tracks.back();
1360  _tracks.pop_back();
1361  }
1362  while (_tracks.size() < track_count) {
1363  auto track =
1364  createTrackView(_tracks.size(), trackAt(ws, _tracks.size()));
1365  addItem(track);
1366  _tracks.push_back(track);
1367  }
1368  }
1369  updateSceneRect();
1370  for (size_t i = 0; i < _tracks.size(); i++) {
1371  if (_tracks[i]->track() != trackAt(ws, i)) {
1372  delete _tracks[i];
1373  _tracks[i] = nullptr;
1374  _tracks[i] = createTrackView(i, trackAt(ws, i));
1375  addItem(_tracks[i]);
1376  }
1377  _tracks[i]->update(ws, _width);
1378  }
1379  }
1380  void updateSceneRect() {
1381  LockScope ws;
1382  double height = track_height * (trackCount(ws()) + 1);
1383  setSceneRect(-track_label_width, 0,
1384  track_label_width + _width + track_padding_right, height);
1385  }
1386  Scene(QWidget *parent) : QGraphicsScene(parent) {
1387  updateSceneRect();
1388  // setBackgroundBrush(QApplication::palette().brush(QPalette::Mid));
1389  setBackgroundBrush(QApplication::palette().brush(QPalette::Window));
1390  }
1391  void wheelEvent(QGraphicsSceneWheelEvent *wheel) {
1392  // QGraphicsScene::wheelEvent(wheel);
1393  // double posx = wheel->scenePos().x();
1394  // LOG_DEBUG("posx " << posx);
1395 
1396  wheel->accept();
1397 
1398  auto *view = views().first();
1399 
1400  QPointF center = view->mapToScene(view->viewport()->rect().center());
1401  double center2mouse = wheel->scenePos().x() - center.x();
1402  LOG_DEBUG("zoom " << wheel->scenePos().x() << " " << center.x() << " "
1403  << center2mouse);
1404  center.setX(center.x() + center2mouse);
1405  center.setX(center.x() / _width);
1406 
1407  double degrees = wheel->delta() * (1.0 / 8);
1408  double exponent = degrees / 90;
1409  double factor = std::pow(0.5, -exponent);
1410  _width *= factor;
1411  LOG_DEBUG("scene " << -track_label_width << " " << track_label_width << " "
1412  << _width << " " << track_label_width + _width);
1413  updateSceneRect();
1414  LockScope()->modified();
1415 
1416  center.setX(center.x() * _width);
1417  center.setX(center.x() - center2mouse);
1418  view->centerOn(center);
1419 
1420  LockScope()->modified();
1421  }
1422  void handleSeek(QGraphicsSceneMouseEvent *event) {
1423  LOG_DEBUG("seek");
1424  LockScope ws;
1425  if (ws->player) {
1426  event->accept();
1427  ws->player->seek(std::max(
1428  0.0,
1429  std::min(timelineDuration(ws()),
1430  event->scenePos().x() * timelineDuration(ws()) / _width)));
1431  }
1432  }
1433  virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
1434  QGraphicsScene::mousePressEvent(event);
1435  if (event->isAccepted()) {
1436  return;
1437  }
1438  if (event->button() == Qt::MiddleButton) {
1439  event->accept();
1440  }
1441  if (event->button() == Qt::LeftButton &&
1442  event->scenePos().y() < track_height) {
1443  handleSeek(event);
1444  }
1445  }
1446  virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
1447  QGraphicsScene::mouseMoveEvent(event);
1448  if (event->isAccepted()) {
1449  return;
1450  }
1451  if (event->buttons() == Qt::MiddleButton) {
1452  event->accept();
1453  static int recursion_depth = 0;
1454  if (recursion_depth == 0) {
1455  recursion_depth++;
1456  if (auto *view = views().first()) {
1457  double dx = event->scenePos().x() - event->lastScenePos().x();
1458  LOG_DEBUG("dx " << dx);
1459  view->translate(dx, 0);
1460  LockScope()->modified();
1461  }
1462  recursion_depth--;
1463  }
1464  }
1465  if (event->buttons() & Qt::LeftButton &&
1466  event->buttonDownScenePos(Qt::LeftButton).y() < track_height) {
1467  handleSeek(event);
1468  }
1469  }
1470  virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
1471  QGraphicsScene::mouseReleaseEvent(event);
1472  }
1473 
1474  void render(QPainter *painter, const QRectF &target, const QRectF &source,
1475  Qt::AspectRatioMode aspectRatioMode) {
1476  QGraphicsScene::render(painter, target, source, aspectRatioMode);
1477  }
1478 
1479  void drawBackground(QPainter *painter, const QRectF &rect) {
1480  QGraphicsScene::drawBackground(painter, rect);
1481  painter->save();
1482  painter->fillRect(rect, QApplication::palette().brush(QPalette::Base));
1483  painter->setPen(QPen(QApplication::palette().brush(
1484  _enabled ? QPalette::Normal : QPalette::Disabled,
1485  QPalette::ButtonText),
1486  1.0));
1487  for (double y = track_height; y < sceneRect().height() + track_height * 0.5;
1488  y += track_height) {
1489  painter->drawLine(rect.x(), y, rect.x() + rect.width(), y);
1490  if (y == track_height) {
1491  auto pen = painter->pen();
1492  auto brush = pen.brush();
1493  auto color = brush.color();
1494  color.setAlpha(color.alpha() / 4);
1495  brush.setColor(color);
1496  pen.setBrush(brush);
1497  painter->setPen(pen);
1498  }
1499  }
1500  painter->restore();
1501  }
1502 };
1503 
1504 class TimeBar : public QGraphicsRectItem, public ItemBase {
1505 public:
1506  TimeBar() {
1507  setBrush(QBrush(Qt::transparent));
1508  setPen(QPen(Qt::NoPen));
1509  setZValue(10);
1510  }
1511  virtual void sync(const std::shared_ptr<Workspace> &ws) override {
1512  QRectF rect = scene()->sceneRect();
1513  setRect(QRectF(0, 0, rect.width() - track_label_width - track_padding_right,
1514  track_height - 1));
1515  }
1516 };
1517 
1518 class ScaleItem : public QGraphicsItem, public ItemBase {
1519  double _duration = 0.001;
1520 
1521 public:
1522  ScaleItem() {}
1523  virtual void sync(const std::shared_ptr<Workspace> &ws) override {
1524  _duration = timelineDuration(ws);
1525  }
1526  virtual QRectF boundingRect() const override {
1527  auto rect = scene()->sceneRect();
1528  auto *graphics_view = scene()->views().first();
1529  if (graphics_view) {
1530  auto clip =
1531  graphics_view->mapToScene(graphics_view->viewport()->geometry())
1532  .boundingRect();
1533  rect.setHeight(clip.y() + clip.height());
1534  }
1535  return rect;
1536  }
1537  double timeToPosition(double t) {
1538  auto r = parentItem()->boundingRect();
1539  return r.x() + r.width() * t / _duration;
1540  }
1541  double positionToTime(double p) {
1542  auto r = parentItem()->boundingRect();
1543  return (p - r.x()) * _duration / r.width();
1544  }
1545  void drawTime(QPainter *painter, double t) {
1546  double p = timeToPosition(t);
1547  painter->drawLine(p, 0.0, p, track_height * 0.15);
1548  QTime tq(0, 0);
1549  tq = tq.addMSecs(std::round(t * 1000));
1550  QString tstr = tq.toString("hh:mm:ss.zzz");
1551  painter->drawText(QRectF(p - 100, 0, 200, track_height),
1552  Qt::AlignCenter | Qt::TextSingleLine, tstr);
1553  }
1554  void drawTick(QPainter *painter, double t) {
1555  double p = timeToPosition(t);
1556  painter->drawLine(p, track_height * 0.85, p, track_height);
1557  }
1558  virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
1559  QWidget *widget) override {
1560  painter->save();
1561 
1562  {
1563  QColor color = QApplication::palette().color(
1564  isEnabled() ? QPalette::Normal : QPalette::Disabled,
1565  QPalette::ButtonText);
1566  color.setAlpha(color.alpha() / 4);
1567  painter->setPen(QPen(QBrush(color), 1.0));
1568  }
1569 
1570  auto *graphics_view = scene()->views().first();
1571  auto clip = graphics_view->mapToScene(graphics_view->viewport()->geometry())
1572  .boundingRect();
1573 
1574  {
1575  auto rect = clip;
1576  rect.setY(0);
1577  rect.setHeight(track_height);
1578  rect.setX(rect.x() - 1);
1579  rect.setWidth(rect.width() + 2);
1580  painter->fillRect(rect, QApplication::palette().brush(QPalette::Button));
1581  {
1582  painter->save();
1583  painter->setPen(
1584  QPen(QApplication::palette().brush(isEnabled() ? QPalette::Normal
1585  : QPalette::Disabled,
1586  QPalette::ButtonText),
1587  1.0));
1588  painter->drawLine(rect.x(), track_height, rect.x() + rect.width(),
1589  track_height);
1590  painter->restore();
1591  }
1592  painter->drawLine(clip.x() + track_label_width - 1, track_height,
1593  clip.x() + track_label_width - 1,
1594  clip.y() + clip.height());
1595  }
1596 
1597  auto r = parentItem()->boundingRect();
1598  painter->drawLine(r.x() + r.width(), clip.y(), r.x() + r.width(),
1599  clip.height());
1600 
1601  painter->setPen(
1602  QPen(QApplication::palette().brush(isEnabled() ? QPalette::Normal
1603  : QPalette::Disabled,
1604  QPalette::ButtonText),
1605  1.0));
1606 
1607  double min_width = QFontMetrics(painter->font()).width(" 00:00:00.000");
1608  double desired_time_step = min_width * _duration / r.width();
1609  auto step_sizes = {
1610  0.0001,
1611  0.0005,
1612  0.001,
1613  0.005,
1614  0.01,
1615  0.05,
1616  0.1,
1617  0.5,
1618  1.0,
1619  10.0,
1620  30.0,
1621  60.0,
1622  5.0 * 60,
1623  10.0 * 60,
1624  30.0 * 60,
1625  60.0 * 60,
1626  3.0 * 60 * 60,
1627  6.0 * 60 * 60,
1628  12.0 * 60 * 60,
1629  24.0 * 60 * 60,
1630  };
1631  double time_step = *step_sizes.begin();
1632  double tick_step = *step_sizes.begin();
1633  for (auto &s : step_sizes) {
1634  tick_step = time_step;
1635  time_step = s;
1636  if (s > desired_time_step) {
1637  break;
1638  }
1639  }
1640 
1641  double t0 = std::max(0.0, std::round(positionToTime(clip.x()) / time_step) *
1642  time_step -
1643  time_step);
1644  double tn =
1645  std::min(_duration, std::round(positionToTime(clip.x() + clip.width()) /
1646  time_step) *
1647  time_step +
1648  time_step);
1649  for (double t = t0; t == t0 || t < tn; t += tick_step) {
1650  drawTick(painter, t);
1651  }
1652  for (double t = t0; t == t0 || t < tn - desired_time_step; t += time_step) {
1653  double p = timeToPosition(t);
1654  drawTime(painter, t);
1655  }
1656  drawTick(painter, _duration);
1657  drawTime(painter, _duration);
1658  painter->restore();
1659  }
1660 };
1661 
1662 class SeekHead : public QGraphicsRectItem, public ItemBase {
1663  bool _dragged = false;
1664  double _drag_offset = 0.0;
1665  QTimer *timer = new QTimer((ItemBase *)this);
1666 
1667 public:
1668  SeekHead() {
1669  setRect(0, -1, 11, track_height);
1670  auto brush = QBrush(QColor(255, 0, 0));
1671  setBrush(brush);
1672  setPen(QPen(QColor(130, 0, 0), 1.0));
1673  auto *seek_line = new QGraphicsLineItem(5, rect().height() * 0.5, 5, 1000);
1674  seek_line->setPen(QPen(brush, 3.0));
1675  seek_line->setParentItem(this);
1676  timer->setInterval(10);
1677  QObject::connect(timer, &QTimer::timeout, (ItemBase *)this,
1678  [this]() { sync(LockScope().ws()); });
1679  auto *fx = new QGraphicsDropShadowEffect();
1680  fx->setBlurRadius(4);
1681  fx->setOffset(1, 2);
1682  setGraphicsEffect(fx);
1683  setZValue(1000000);
1684  seek_line->setZValue(1000000);
1685  setCursor(Qt::OpenHandCursor);
1686  }
1687  virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
1688  if (event->button() == Qt::LeftButton) {
1689  event->accept();
1690  _dragged = true;
1691  setCursor(Qt::ClosedHandCursor);
1692  _drag_offset = pos().x() + rect().width() * 0.5 - event->scenePos().x();
1693  sync(LockScope().ws());
1694  }
1695  }
1696  virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
1697  auto parent_rect = parentItem()->sceneBoundingRect();
1698  double x = _drag_offset + event->scenePos().x();
1699  x = std::max(parent_rect.x(),
1700  std::min(parent_rect.x() + parent_rect.width(), x));
1701  setX(x - rect().width() * 0.5);
1702  {
1703  LockScope ws;
1704  if (auto player = ws->player) {
1705  player->seek((x - parent_rect.x()) * timelineDuration(ws.ws()) /
1706  parent_rect.width());
1707  }
1708  }
1709  }
1710  virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
1711  if (event->button() == Qt::LeftButton) {
1712  _dragged = false;
1713  setCursor(Qt::OpenHandCursor);
1714  }
1715  }
1716  virtual void sync(const std::shared_ptr<Workspace> &ws) override {
1717  setZValue(1000000);
1718  if (_dragged) {
1719  return;
1720  }
1721  if (ws->player) {
1722  auto r = parentItem()->sceneBoundingRect();
1723  auto px = ws->player->time() * r.width() / timelineDuration(ws) + r.x() -
1724  rect().width() * 0.5;
1725  if (x() != px) {
1726  setX(px);
1727  update();
1728  }
1729  if (!isVisible()) {
1730  show();
1731  }
1732  if (!timer->isActive()) {
1733  timer->start();
1734  }
1735  } else {
1736  if (isVisible()) {
1737  hide();
1738  }
1739  if (timer->isActive()) {
1740  timer->stop();
1741  }
1742  }
1743  }
1744 };
1745 
1746 TimelineWidget::TimelineWidget() : QDockWidget("Timeline") {
1747 
1748  LockScope ws;
1749 
1750  QWidget *content_widget = new QWidget();
1751 
1752  auto *main_layout = new QVBoxLayout(content_widget);
1753  main_layout->setSpacing(0);
1754  main_layout->setContentsMargins(0, 0, 0, 0);
1755 
1756  auto *playback_bar = new QHBoxLayout(content_widget);
1757  playback_bar->setSpacing(0);
1758  playback_bar->setContentsMargins(3, 3, 3, 3);
1759 
1760  QWidget *playback_bar_widget = new QWidget();
1761  playback_bar_widget->setAutoFillBackground(true);
1762  playback_bar_widget->setLayout(playback_bar);
1763  setTitleBarWidget(playback_bar_widget);
1764 
1765  connect(this, &QDockWidget::dockLocationChanged, this,
1766  [this](Qt::DockWidgetArea area) {
1767  LOG_DEBUG("timeline dock location changed");
1768  updateGeometry();
1769  update();
1770  });
1771 
1772  auto *playback_bar_left = new QHBoxLayout(content_widget);
1773  playback_bar_left->setSpacing(0);
1774  playback_bar->addLayout(playback_bar_left, 4);
1775 
1776  std::unordered_set<QWidget *> always_active_widgets;
1777 
1778  if (0) {
1779  auto *title = new QLabel(tr("Timeline"));
1780  playback_bar_left->addWidget(title);
1781  always_active_widgets.insert(title);
1782  }
1783 
1784  {
1785  auto *button = new FlatButton("Create");
1786  QMenu *menu = new QMenu(this);
1787  auto types = Type::find<TrackBase>()->list();
1788  for (auto &type : types) {
1789  if (!type->constructable()) {
1790  continue;
1791  }
1792  QString label = type->name().c_str();
1793  connect(menu->addAction(label), &QAction::triggered, this,
1794  [type, this](bool checked) {
1795  ActionScope ws("Create Timeline Track");
1796  auto track = type->instantiate<TrackBase>();
1797  for (size_t i = 1;; i++) {
1798  auto label = std::string() + type->name() + std::to_string(i);
1799  bool label_taken = false;
1800  for (auto &t : ws->document()->timeline()->tracks()) {
1801  if (t->label() == label) {
1802  label_taken = true;
1803  break;
1804  }
1805  }
1806  if (label_taken) {
1807  continue;
1808  } else {
1809  track->label() = label;
1810  break;
1811  }
1812  }
1813  {
1814  std::vector<double> colors;
1815  for (auto &t : ws->document()->timeline()->tracks()) {
1816  colors.push_back(t->color());
1817  }
1818  if (colors.size() == 1) {
1819  double color = colors.at(0) + 0.5;
1820  color -= std::floor(color);
1821  track->color() = color;
1822  }
1823  if (colors.size() >= 2) {
1824  std::sort(colors.begin(), colors.end());
1825  double best_color = 0;
1826  double largest_distance = 0;
1827  for (size_t i = 0; i < colors.size(); i++) {
1828  size_t j = (i + 1) % colors.size();
1829  double distance = colors[j] - colors[i];
1830  distance -= std::floor(distance);
1831  if (distance > largest_distance) {
1832  largest_distance = distance;
1833  best_color = colors[i] + distance * 0.5;
1834  }
1835  }
1836  track->color() = best_color - std::floor(best_color);
1837  }
1838  }
1839  ws->document()->timeline()->tracks().push_back(track);
1840  ws->modified();
1841  });
1842  }
1843  button->setMenu(menu);
1844  always_active_widgets.insert(button);
1845  playback_bar_left->addWidget(button);
1846  }
1847 
1848  if (0) {
1849  auto *open_button = new FlatButton("Open");
1850  always_active_widgets.insert(open_button);
1851  connect(open_button, &QPushButton::clicked, this, [this]() {
1852  QString path = QFileDialog::getOpenFileName(
1853  this, tr("Open Bag"), QString(),
1854  tr("Bag files (*.bag);;All files (*.*)"));
1855  if (path.isNull()) {
1856  return;
1857  }
1858  MainWindow::instance()->openBag(path);
1859  });
1860  playback_bar_left->addWidget(open_button);
1861  }
1862 
1863  if (1) {
1864  auto *open_button = new FlatButton("Data");
1865  QMenu *menu = new QMenu(this);
1866  connect(menu, &QMenu::aboutToShow, [menu, this]() {
1867  LOG_DEBUG("update timeline bag menu");
1868  menu->clear();
1869  connect(menu->addAction("Open Bag"), &QAction::triggered, this,
1870  [this](bool checked) {
1871  QString path = QFileDialog::getOpenFileName(
1872  this, tr("Open Bag"), QString(),
1873  tr("Bag files (*.bag);;All files (*.*)"));
1874  if (path.isNull()) {
1875  return;
1876  }
1877  MainWindow::instance()->openBag(path);
1878  });
1879  LockScope ws;
1880  {
1881  auto action = menu->addAction("Close Bag");
1882  connect(action, &QAction::triggered, this,
1883  [this](bool checked) { MainWindow::instance()->closeBag(); });
1884  action->setEnabled(ws->player != nullptr);
1885  }
1886  std::set<std::string> branches;
1887  for (auto &track : ws->document()->timeline()->tracks()) {
1888  LOG_DEBUG("track " << track->label());
1889  if (auto annotation_track =
1890  std::dynamic_pointer_cast<AnnotationTrack>(track)) {
1891  for (auto &branch : annotation_track->branches()) {
1892  branches.insert(branch->name());
1893  }
1894  }
1895  }
1896  // if (ws->player) {
1897  // branches.insert(ws->player->fileName());
1898  //}
1899  if (!branches.empty()) {
1900  menu->addSeparator();
1901  for (auto &branch : branches) {
1902  auto *action = menu->addAction(QString::fromStdString(branch));
1903  connect(action, &QAction::triggered, this,
1904  [this, branch](bool checked) {
1905  if (checked) {
1906  MainWindow::instance()->findAndOpenBag(branch);
1907  } else {
1908  MainWindow::instance()->closeBag();
1909  }
1910  });
1911  action->setCheckable(true);
1912  if (auto player = ws->player) {
1913  action->setChecked(branch == ws->player->fileName());
1914  }
1915  }
1916  }
1917  /*
1918  std::map<QString, QString> files;
1919  for (auto &track : ws->document()->timeline()->tracks()) {
1920  LOG_DEBUG("track " << track->label());
1921  if (auto annotation_track =
1922  std::dynamic_pointer_cast<AnnotationTrack>(track)) {
1923  LOG_DEBUG("annotation track " << track->label());
1924  for (auto &branch : annotation_track->branches()) {
1925  {
1926  QFileInfo f(
1927  QFileInfo(QString::fromStdString(ws->document()->path)).dir(),
1928  QString::fromStdString(branch->name()));
1929  LOG_DEBUG("df " << f.absoluteFilePath().toStdString());
1930  if (f.exists()) {
1931  files[f.fileName()] = f.absoluteFilePath();
1932  }
1933  }
1934  if (auto player = ws->player) {
1935  QFileInfo f(
1936  QFileInfo(QString::fromStdString(player->path())).dir(),
1937  QString::fromStdString(branch->name()));
1938  LOG_DEBUG("pf " << f.absoluteFilePath().toStdString());
1939  if (f.exists()) {
1940  files[f.fileName()] = f.absoluteFilePath();
1941  }
1942  }
1943  }
1944  }
1945  }
1946  if (!files.empty()) {
1947  menu->addSeparator();
1948  for (auto f : files) {
1949  auto *action = menu->addAction(f.first);
1950  connect(action, &QAction::triggered, this, [this, f](bool checked) {
1951  if (checked) {
1952  MainWindow::instance()->openBag(f.second);
1953  } else {
1954  MainWindow::instance()->closeBag();
1955  }
1956  });
1957  action->setCheckable(true);
1958  if (auto player = ws->player) {
1959  action->setChecked(f.first.toStdString() == ws->player->fileName());
1960  }
1961  }
1962  }
1963  */
1964  });
1965  open_button->setMenu(menu);
1966  always_active_widgets.insert(open_button);
1967  playback_bar_left->addWidget(open_button);
1968  }
1969 
1970  {
1971  auto *button = new FlatButton("Export");
1972  QMenu *menu = new QMenu(this);
1973  connect(
1974  menu->addAction("Annotated Bag"), &QAction::triggered, this,
1975  [this](bool checked) {
1976  try {
1977  LockScope ws;
1978  auto player = ws->player;
1979  if (!player) {
1980  throw std::runtime_error("No bag player");
1981  }
1982  std::string src_path(player->path());
1983  std::string dst_path(src_path + ".annotated.bag");
1984  QFile::remove(dst_path.c_str());
1985  if (!QFile::copy(src_path.c_str(), dst_path.c_str())) {
1986  throw std::runtime_error("Failed to copy bag file");
1987  }
1988  rosbag::Bag src_bag(src_path, rosbag::bagmode::Read);
1989  rosbag::Bag dst_bag(dst_path, rosbag::bagmode::Append);
1990  rosbag::View view(src_bag);
1991 
1992  for (auto &message : view) {
1993  double t = (message.getTime() - view.getBeginTime()).toSec();
1994  if (message.getDataType() == "sensor_msgs/Image") {
1995  if (auto image_message =
1996  message.instantiate<sensor_msgs::Image>()) {
1997  QImage annotation_image(image_message->width,
1998  image_message->height,
1999  QImage::Format_Grayscale8);
2000  annotation_image.fill(Qt::black);
2001  QPainter annotation_painter(&annotation_image);
2002  for (auto &track : ws->document()->timeline()->tracks()) {
2003  if (auto annotation_track =
2004  std::dynamic_pointer_cast<AnnotationTrack>(track)) {
2005  for (auto &branch : annotation_track->branches()) {
2006  if (branch->name() == player->fileName()) {
2007  for (auto &span : branch->spans()) {
2008  for (auto &annotation : span->annotations()) {
2009  if (auto image_annotation =
2010  std::dynamic_pointer_cast<
2011  ImageAnnotationBase>(annotation)) {
2012  if (image_annotation->topic() ==
2013  message.getTopic()) {
2014  if (span->start() <= t + 1e-6 &&
2015  span->start() + span->duration() >
2016  t + 1e-6) {
2017  auto label = span->label().empty()
2018  ? track->label()
2019  : span->label();
2020  LOG_DEBUG("annotation span "
2021  << label << " "
2022  << image_annotation->topic()
2023  << " " << span->start() << " "
2024  << span->duration());
2025  image_annotation->render();
2026  if (auto &shape = image_annotation->shape) {
2027  annotation_painter.setPen(Qt::NoPen);
2028  annotation_painter.setBrush(Qt::white);
2029  annotation_painter.drawPath(*shape);
2030  }
2031  }
2032  }
2033  }
2034  }
2035  }
2036  }
2037  }
2038  }
2039  }
2040  annotation_painter.end();
2041  sensor_msgs::Image annotation_message;
2042  annotation_message.header = image_message->header;
2043  annotation_message.width = annotation_image.width();
2044  annotation_message.height = annotation_image.height();
2045  annotation_message.step = annotation_message.width;
2046  annotation_message.encoding = "mono8";
2047  for (size_t y = 0; y < annotation_message.height; y++) {
2048  auto *line = annotation_image.constScanLine(y);
2049  for (size_t x = 0; x < annotation_message.width; x++) {
2050  annotation_message.data.push_back(line[x]);
2051  }
2052  }
2053  std::string annotation_topic = message.getTopic();
2054  if (!annotation_topic.empty() && annotation_topic[0] != '/') {
2055  annotation_topic = "/" + annotation_topic;
2056  }
2057  annotation_topic = "/annotations" + annotation_topic;
2058  LOG_DEBUG("annotation_topic " << annotation_topic);
2059  dst_bag.write(annotation_topic, message.getTime(),
2060  annotation_message);
2061  }
2062  }
2063  }
2064  src_bag.close();
2065  dst_bag.close();
2066  } catch (const std::exception &ex) {
2067  QMessageBox::critical(nullptr, "Error", ex.what());
2068  }
2069  });
2070  connect(menu->addAction("Time Spans / CSV"), &QAction::triggered, this,
2071  [this](bool checked) {
2072  try {
2073  LockScope ws;
2074  auto player = ws->player;
2075  if (!player) {
2076  throw std::runtime_error("No bag player");
2077  }
2078  std::string dst_path(player->path() + ".annotations.csv");
2079  LOG_INFO("writing csv " << dst_path);
2080  QFile::remove(dst_path.c_str());
2081  std::ofstream stream(dst_path);
2082  stream << "start,duration,label" << std::endl;
2083  for (auto &track : ws->document()->timeline()->tracks()) {
2084  if (auto annotation_track =
2085  std::dynamic_pointer_cast<AnnotationTrack>(track)) {
2086  for (auto &branch : annotation_track->branches()) {
2087  if (branch->name() == player->fileName()) {
2088  for (auto &span : branch->spans()) {
2089  auto label = span->label().empty() ? track->label()
2090  : span->label();
2091  std::string label_escaped;
2092  for (auto &c : label) {
2093  if (c == '"') {
2094  label_escaped.append("\"\"");
2095  } else {
2096  label_escaped.push_back(c);
2097  }
2098  }
2099  stream << span->start() << "," << span->duration()
2100  << ",\"" << label_escaped << "\"" << std::endl;
2101  }
2102  }
2103  }
2104  }
2105  }
2106  LOG_SUCCESS("csv written " << dst_path);
2107  } catch (const std::exception &ex) {
2108  QMessageBox::critical(nullptr, "Error", ex.what());
2109  }
2110  });
2111  button->setMenu(menu);
2112  playback_bar_left->addWidget(button);
2113  }
2114 
2115  if (0) {
2116  auto *close_button = new FlatButton("Close");
2117  connect(close_button, &QPushButton::clicked, this,
2118  [this]() { MainWindow::instance()->closeBag(); });
2119  playback_bar_left->addWidget(close_button);
2120  }
2121 
2122  playback_bar_left->addStretch(1);
2123 
2124  {
2125  auto *button = new FlatButton(MATERIAL_ICON("fast_rewind", -0.13));
2126  connect(button, &QPushButton::clicked, this, [this]() {
2127  LockScope ws;
2128  if (ws->player) {
2129  ws->player->rewind();
2130  }
2131  });
2132  playback_bar->addWidget(button);
2133  }
2134 
2135  {
2136  auto *button = new FlatButton(MATERIAL_ICON("skip_previous", -0.1));
2137  connect(button, &QPushButton::clicked, this, [this]() {
2138  LockScope ws;
2139  if (ws->player) {
2140  double current_time = ws->player->time();
2141  double seek_time = -std::numeric_limits<double>::max();
2142  for (size_t i = 0; i < trackCount(ws()); i++) {
2143  if (auto track = std::dynamic_pointer_cast<AnnotationTrack>(
2144  trackAt(ws(), i))) {
2145  if (auto branch = track->branch(ws(), false)) {
2146  for (auto &span : branch->spans()) {
2147  for (double span_time : {
2148  span->start(),
2149  span->start() + span->duration(),
2150  }) {
2151  if (span_time < current_time - 1e-6 &&
2152  std::abs(current_time - span_time) <
2153  std::abs(current_time - seek_time)) {
2154  seek_time = span_time;
2155  }
2156  }
2157  }
2158  }
2159  }
2160  }
2161  ws->player->seek(
2162  std::min(timelineDuration(ws()), std::max(0.0, seek_time)));
2163  }
2164  });
2165  playback_bar->addWidget(button);
2166  }
2167 
2168  {
2169  auto *button = new FlatButton(MATERIAL_ICON("stop", -0.1));
2170  connect(button, &QPushButton::clicked, this, [this]() {
2171  LockScope ws;
2172  if (ws->player) {
2173  ws->player->stop();
2174  }
2175  });
2176  playback_bar->addWidget(button);
2177  }
2178  {
2179  auto *button = new FlatButton(MATERIAL_ICON("play_arrow", -0.12));
2180  connect(button, &QPushButton::clicked, this, [this]() {
2181  LockScope ws;
2182  if (ws->player && ws->document()->timeline()) {
2183  std::vector<double> annotation_times;
2184  for (auto &track : ws->document()->timeline()->tracks()) {
2185  if (auto annotation_track =
2186  std::dynamic_pointer_cast<AnnotationTrack>(track)) {
2187  if (auto branch = annotation_track->branch(ws(), false)) {
2188  for (auto &span : branch->spans()) {
2189  annotation_times.push_back(span->start());
2190  annotation_times.push_back(span->start() + span->duration());
2191  }
2192  }
2193  }
2194  }
2195  std::sort(annotation_times.begin(), annotation_times.end());
2196  annotation_times.erase(
2197  std::unique(annotation_times.begin(), annotation_times.end()),
2198  annotation_times.end());
2199  ws->player->play(annotation_times);
2200  }
2201  });
2202  playback_bar->addWidget(button);
2203  }
2204 
2205  {
2206  auto *button = new FlatButton(MATERIAL_ICON("skip_next", -0.1));
2207  connect(button, &QPushButton::clicked, this, [this]() {
2208  LockScope ws;
2209  if (ws->player) {
2210  double current_time = ws->player->time();
2211  double seek_time = std::numeric_limits<double>::max();
2212  for (size_t i = 0; i < trackCount(ws()); i++) {
2213  if (auto track = std::dynamic_pointer_cast<AnnotationTrack>(
2214  trackAt(ws(), i))) {
2215  if (auto branch = track->branch(ws(), false)) {
2216  for (auto &span : branch->spans()) {
2217  for (double span_time : {
2218  span->start(),
2219  span->start() + span->duration(),
2220  }) {
2221  if (span_time > current_time + 1e-6 &&
2222  std::abs(current_time - span_time) <
2223  std::abs(current_time - seek_time)) {
2224  seek_time = span_time;
2225  }
2226  }
2227  }
2228  }
2229  }
2230  }
2231  ws->player->seek(
2232  std::min(timelineDuration(ws()), std::max(0.0, seek_time)));
2233  }
2234  });
2235  playback_bar->addWidget(button);
2236  }
2237 
2238  auto *playback_bar_right = new QHBoxLayout(content_widget);
2239  playback_bar_right->setSpacing(0);
2240  playback_bar->addLayout(playback_bar_right, 4);
2241 
2242  playback_bar_right->addStretch(1);
2243 
2244  {
2245  auto *button =
2246  new FlatButton(style()->standardIcon(QStyle::SP_TitleBarCloseButton));
2247  connect(button, &QPushButton::clicked, this, [this]() {
2248  LOG_DEBUG("close");
2249  hide();
2250  });
2251  playback_bar->addWidget(button);
2252  always_active_widgets.insert(button);
2253  }
2254 
2255  Scene *scene = new Scene(this);
2256 
2257  auto *time_bar = new TimeBar();
2258  scene->addItem(time_bar);
2259 
2260  auto *scale_item = new ScaleItem();
2261  scale_item->setParentItem(time_bar);
2262  scene->addItem(scale_item);
2263 
2264  auto *seek_head = new SeekHead();
2265  seek_head->setParentItem(time_bar);
2266  scene->addItem(seek_head);
2267 
2268  class GraphicsView : public QGraphicsView {
2269 
2270  protected:
2271  virtual void mouseMoveEvent(QMouseEvent *event) override {
2272  QGraphicsView::mouseMoveEvent(event);
2273  }
2274  virtual void mousePressEvent(QMouseEvent *event) override {
2275  QGraphicsView::mousePressEvent(event);
2276  }
2277  virtual void mouseReleaseEvent(QMouseEvent *event) override {
2278  QGraphicsView::mouseReleaseEvent(event);
2279  }
2280  virtual void scrollContentsBy(int dx, int dy) override {
2281  QGraphicsView::scrollContentsBy(dx, dy);
2282  LockScope()->modified();
2283  }
2284 
2285  public:
2286  GraphicsView() { setMouseTracking(true); }
2287  };
2288 
2289  auto *view = new GraphicsView();
2290  view->setParent(this);
2291  view->setViewport(new QOpenGLWidget(this));
2292  view->setScene(scene);
2293  view->setCacheMode(QGraphicsView::CacheNone);
2294  view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
2295  view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
2296  view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
2297  view->setAlignment(Qt::AlignLeft | Qt::AlignTop);
2298  view->setFocusPolicy(Qt::NoFocus);
2299  view->setTransformationAnchor(QGraphicsView::NoAnchor);
2300 
2301  main_layout->addWidget(view, true);
2302 
2303  auto sync = [=]() {
2304  bool enabled = false;
2305  {
2306  LockScope ws;
2307  enabled = (ws->player != nullptr);
2308  }
2309 
2310  updateEnabled(playback_bar, [enabled, always_active_widgets](QWidget *w) {
2311  if (always_active_widgets.find(w) == always_active_widgets.end()) {
2312  w->setEnabled(enabled);
2313  }
2314  });
2315  };
2316  sync();
2317  ws->modified.connect(playback_bar, sync);
2318 
2319  setWidget(content_widget);
2320 }
2321 
2322 TimelineWidget::~TimelineWidget() {}
Definition: watcher.h:8