+ // Add separator if needed
+ if (menu->actions().length() > 0)
+ menu->addSeparator();
+ }
+
+ selected_row_ = nullptr;
+ const DecodeTraceRow* r = get_row_at_point(click_pos);
+ if (r)
+ selected_row_ = r->decode_row;
+
+ const View *const view = owner_->view();
+ assert(view);
+ QPoint pos = view->viewport()->mapFrom(parent, click_pos);
+
+ // Default sample range is "from here"
+ const pair<uint64_t, uint64_t> sample_range = get_view_sample_range(pos.x(), pos.x() + 1);
+ selected_sample_range_ = make_pair(sample_range.first, numeric_limits<uint64_t>::max());
+
+ if (decode_signal_->is_paused()) {
+ QAction *const resume =
+ new QAction(tr("Resume decoding"), this);
+ resume->setIcon(QIcon::fromTheme("media-playback-start",
+ QIcon(":/icons/media-playback-start.png")));
+ connect(resume, SIGNAL(triggered()), this, SLOT(on_pause_decode()));
+ menu->addAction(resume);
+ } else {
+ QAction *const pause =
+ new QAction(tr("Pause decoding"), this);
+ pause->setIcon(QIcon::fromTheme("media-playback-pause",
+ QIcon(":/icons/media-playback-pause.png")));
+ connect(pause, SIGNAL(triggered()), this, SLOT(on_pause_decode()));
+ menu->addAction(pause);
+ }
+
+ QAction *const copy_annotation_to_clipboard =
+ new QAction(tr("Copy annotation text to clipboard"), this);
+ copy_annotation_to_clipboard->setIcon(QIcon::fromTheme("edit-paste",
+ QIcon(":/icons/edit-paste.svg")));
+ connect(copy_annotation_to_clipboard, SIGNAL(triggered()), this, SLOT(on_copy_annotation_to_clipboard()));
+ menu->addAction(copy_annotation_to_clipboard);
+
+ menu->addSeparator();
+
+ QAction *const export_all_rows =
+ new QAction(tr("Export all annotations"), this);
+ export_all_rows->setIcon(QIcon::fromTheme("document-save-as",
+ QIcon(":/icons/document-save-as.png")));
+ connect(export_all_rows, SIGNAL(triggered()), this, SLOT(on_export_all_rows()));
+ menu->addAction(export_all_rows);
+
+ QAction *const export_row =
+ new QAction(tr("Export all annotations for this row"), this);
+ export_row->setIcon(QIcon::fromTheme("document-save-as",
+ QIcon(":/icons/document-save-as.png")));
+ connect(export_row, SIGNAL(triggered()), this, SLOT(on_export_row()));
+ menu->addAction(export_row);
+
+ menu->addSeparator();
+
+ QAction *const export_all_rows_from_here =
+ new QAction(tr("Export all annotations, starting here"), this);
+ export_all_rows_from_here->setIcon(QIcon::fromTheme("document-save-as",
+ QIcon(":/icons/document-save-as.png")));
+ connect(export_all_rows_from_here, SIGNAL(triggered()), this, SLOT(on_export_all_rows_from_here()));
+ menu->addAction(export_all_rows_from_here);
+
+ QAction *const export_row_from_here =
+ new QAction(tr("Export annotations for this row, starting here"), this);
+ export_row_from_here->setIcon(QIcon::fromTheme("document-save-as",
+ QIcon(":/icons/document-save-as.png")));
+ connect(export_row_from_here, SIGNAL(triggered()), this, SLOT(on_export_row_from_here()));
+ menu->addAction(export_row_from_here);
+
+ menu->addSeparator();
+
+ QAction *const export_all_rows_with_cursor =
+ new QAction(tr("Export all annotations within cursor range"), this);
+ export_all_rows_with_cursor->setIcon(QIcon::fromTheme("document-save-as",
+ QIcon(":/icons/document-save-as.png")));
+ connect(export_all_rows_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_all_rows_with_cursor()));
+ menu->addAction(export_all_rows_with_cursor);
+
+ QAction *const export_row_with_cursor =
+ new QAction(tr("Export annotations for this row within cursor range"), this);
+ export_row_with_cursor->setIcon(QIcon::fromTheme("document-save-as",
+ QIcon(":/icons/document-save-as.png")));
+ connect(export_row_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_row_with_cursor()));
+ menu->addAction(export_row_with_cursor);
+
+ if (!view->cursors()->enabled()) {
+ export_all_rows_with_cursor->setEnabled(false);
+ export_row_with_cursor->setEnabled(false);
+ }
+
+ return menu;
+}
+
+void DecodeTrace::delete_pressed()
+{
+ on_delete();
+}
+
+void DecodeTrace::hover_point_changed(const QPoint &hp)
+{
+ Trace::hover_point_changed(hp);
+
+ assert(owner_);
+
+ DecodeTraceRow* hover_row = get_row_at_point(hp);
+
+ // Row expansion marker handling
+ for (DecodeTraceRow& r : rows_)
+ r.expand_marker_highlighted = false;
+
+ if (hover_row) {
+ int row_y = get_row_y(hover_row);
+ if ((hp.x() > 0) && (hp.x() < (int)(ArrowSize + 3 + hover_row->title_width)) &&
+ (hp.y() > (int)(row_y - ArrowSize)) && (hp.y() < (int)(row_y + ArrowSize))) {
+
+ hover_row->expand_marker_highlighted = true;
+ show_hidden_rows_ = true;
+ delayed_hidden_row_hider_.start();
+ }
+ }
+
+ // Tooltip handling
+ if (hp.x() > 0) {
+ QString ann = get_annotation_at_point(hp);
+
+ if (!ann.isEmpty()) {
+ QFontMetrics m(QToolTip::font());
+ const QRect text_size = m.boundingRect(QRect(), 0, ann);
+
+ // This is OS-specific and unfortunately we can't query it, so
+ // use an approximation to at least try to minimize the error.
+ const int padding = default_row_height_ + 8;
+
+ // Make sure the tool tip doesn't overlap with the mouse cursor.
+ // If it did, the tool tip would constantly hide and re-appear.
+ // We also push it up by one row so that it appears above the
+ // decode trace, not below.
+ QPoint p = hp;
+ p.setX(hp.x() - (text_size.width() / 2) - padding);
+
+ p.setY(get_row_y(hover_row) - default_row_height_ -
+ text_size.height() - padding);
+
+ const View *const view = owner_->view();
+ assert(view);
+ QToolTip::showText(view->viewport()->mapToGlobal(p), ann);
+
+ } else
+ QToolTip::hideText();
+
+ } else
+ QToolTip::hideText();
+}
+
+void DecodeTrace::mouse_left_press_event(const QMouseEvent* event)
+{
+ // Update container widths which depend on the scrollarea's current width
+ update_expanded_rows();
+
+ // Handle row expansion marker
+ for (DecodeTraceRow& r : rows_) {
+ if (!r.expand_marker_highlighted)
+ continue;
+
+ unsigned int y = get_row_y(&r);
+ if ((event->x() > 0) && (event->x() <= (int)(ArrowSize + 3 + r.title_width)) &&
+ (event->y() > (int)(y - (default_row_height_ / 2))) &&
+ (event->y() <= (int)(y + (default_row_height_ / 2)))) {
+
+ if (r.expanded) {
+ r.collapsing = true;
+ r.expanded = false;
+ r.anim_shape = ArrowSize;
+ } else {
+ r.expanding = true;
+ r.anim_shape = 0;
+
+ // Force geometry update of the widget container to get
+ // an up-to-date height (which also depends on the width)
+ forceUpdate(r.container);
+
+ r.container->setVisible(true);
+ r.expanded_height = 2 * default_row_height_ + r.container->sizeHint().height();
+ }
+
+ r.animation_step = 0;
+ r.anim_height = r.height;
+
+ animation_timer_.start();
+ }
+ }
+}
+
+void DecodeTrace::draw_annotations(deque<const Annotation*>& annotations,
+ QPainter &p, const ViewItemPaintParams &pp, int y, const DecodeTraceRow& row)
+{
+ Annotation::Class block_class = 0;
+ bool block_class_uniform = true;
+ qreal block_start = 0;
+ int block_ann_count = 0;
+
+ const Annotation* prev_ann;
+ qreal prev_end = INT_MIN;
+
+ qreal a_end;