Implement expansion marker animation and its infrastructure
[pulseview.git] / pv / views / trace / decodetrace.cpp
1 /*
2  * This file is part of the PulseView project.
3  *
4  * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 extern "C" {
21 #include <libsigrokdecode/libsigrokdecode.h>
22 }
23
24 #include <limits>
25 #include <mutex>
26 #include <tuple>
27
28 #include <extdef.h>
29
30 #include <boost/functional/hash.hpp>
31
32 #include <QAction>
33 #include <QApplication>
34 #include <QClipboard>
35 #include <QComboBox>
36 #include <QFileDialog>
37 #include <QFormLayout>
38 #include <QLabel>
39 #include <QMenu>
40 #include <QMessageBox>
41 #include <QPushButton>
42 #include <QTextStream>
43 #include <QToolTip>
44
45 #include "decodetrace.hpp"
46 #include "view.hpp"
47 #include "viewport.hpp"
48
49 #include <pv/globalsettings.hpp>
50 #include <pv/session.hpp>
51 #include <pv/strnatcmp.hpp>
52 #include <pv/data/decodesignal.hpp>
53 #include <pv/data/decode/annotation.hpp>
54 #include <pv/data/decode/decoder.hpp>
55 #include <pv/data/logic.hpp>
56 #include <pv/data/logicsegment.hpp>
57 #include <pv/widgets/decodergroupbox.hpp>
58 #include <pv/widgets/decodermenu.hpp>
59
60 using std::abs;
61 using std::find_if;
62 using std::lock_guard;
63 using std::make_pair;
64 using std::max;
65 using std::min;
66 using std::numeric_limits;
67 using std::out_of_range;
68 using std::pair;
69 using std::shared_ptr;
70 using std::tie;
71 using std::vector;
72
73 using pv::data::decode::Annotation;
74 using pv::data::decode::Row;
75 using pv::data::decode::DecodeChannel;
76 using pv::data::DecodeSignal;
77
78 namespace pv {
79 namespace views {
80 namespace trace {
81
82
83 #define DECODETRACE_COLOR_SATURATION (180) /* 0-255 */
84 #define DECODETRACE_COLOR_VALUE (170) /* 0-255 */
85
86 const QColor DecodeTrace::ErrorBgColor = QColor(0xEF, 0x29, 0x29);
87 const QColor DecodeTrace::NoDecodeColor = QColor(0x88, 0x8A, 0x85);
88
89 const int DecodeTrace::ArrowSize = 6;
90 const double DecodeTrace::EndCapWidth = 5;
91 const int DecodeTrace::RowTitleMargin = 7;
92 const int DecodeTrace::DrawPadding = 100;
93
94 const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz
95 const unsigned int DecodeTrace::AnimationDurationInTicks = 7;
96
97 DecodeTrace::DecodeTrace(pv::Session &session,
98         shared_ptr<data::SignalBase> signalbase, int index) :
99         Trace(signalbase),
100         session_(session),
101         max_visible_rows_(0),
102         delete_mapper_(this),
103         show_hide_mapper_(this)
104 {
105         decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
106
107         GlobalSettings settings;
108         always_show_all_rows_ = settings.value(GlobalSettings::Key_Dec_AlwaysShowAllRows).toBool();
109
110         GlobalSettings::add_change_handler(this);
111
112         // Determine shortest string we want to see displayed in full
113         QFontMetrics m(QApplication::font());
114         min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
115
116         default_row_height_ = (ViewItemPaintParams::text_height() * 6) / 4;
117         annotation_height_ = (ViewItemPaintParams::text_height() * 5) / 4;
118
119         // For the base color, we want to start at a very different color for
120         // every decoder stack, so multiply the index with a number that is
121         // rather close to 180 degrees of the color circle but not a dividend of 360
122         // Note: The offset equals the color of the first annotation
123         QColor color;
124         const int h = (120 + 160 * index) % 360;
125         const int s = DECODETRACE_COLOR_SATURATION;
126         const int v = DECODETRACE_COLOR_VALUE;
127         color.setHsv(h, s, v);
128         base_->set_color(color);
129
130         connect(decode_signal_.get(), SIGNAL(new_annotations()),
131                 this, SLOT(on_new_annotations()));
132         connect(decode_signal_.get(), SIGNAL(decode_reset()),
133                 this, SLOT(on_decode_reset()));
134         connect(decode_signal_.get(), SIGNAL(decode_finished()),
135                 this, SLOT(on_decode_finished()));
136         connect(decode_signal_.get(), SIGNAL(channels_updated()),
137                 this, SLOT(on_channels_updated()));
138
139         connect(&delete_mapper_, SIGNAL(mapped(int)),
140                 this, SLOT(on_delete_decoder(int)));
141         connect(&show_hide_mapper_, SIGNAL(mapped(int)),
142                 this, SLOT(on_show_hide_decoder(int)));
143
144         connect(&delayed_trace_updater_, SIGNAL(timeout()),
145                 this, SLOT(on_delayed_trace_update()));
146         delayed_trace_updater_.setSingleShot(true);
147         delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
148
149         connect(&animation_timer_, SIGNAL(timeout()),
150                 this, SLOT(on_animation_timer()));
151         animation_timer_.setInterval(1000 / 50);
152
153         default_marker_shape_ << QPoint(0,         -ArrowSize);
154         default_marker_shape_ << QPoint(ArrowSize,  0);
155         default_marker_shape_ << QPoint(0,          ArrowSize);
156 }
157
158 DecodeTrace::~DecodeTrace()
159 {
160         GlobalSettings::remove_change_handler(this);
161 }
162
163 bool DecodeTrace::enabled() const
164 {
165         return true;
166 }
167
168 shared_ptr<data::SignalBase> DecodeTrace::base() const
169 {
170         return base_;
171 }
172
173 pair<int, int> DecodeTrace::v_extents() const
174 {
175         // Make an empty decode trace appear symmetrical
176         if (max_visible_rows_ == 0)
177                 return make_pair(-default_row_height_, default_row_height_);
178
179         unsigned int height = 0;
180         for (const RowData& r : rows_)
181                 if (r.currently_visible)
182                         height += r.height;
183
184         return make_pair(-default_row_height_, height);
185 }
186
187 void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
188 {
189         Trace::paint_back(p, pp);
190         paint_axis(p, pp, get_visual_y());
191 }
192
193 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
194 {
195         lock_guard<mutex> lock(row_modification_mutex_);
196
197         // Set default pen to allow for text width calculation
198         p.setPen(Qt::black);
199
200         pair<uint64_t, uint64_t> sample_range = get_view_sample_range(pp.left(), pp.right());
201
202         // Just because the view says we see a certain sample range it
203         // doesn't mean we have this many decoded samples, too, so crop
204         // the range to what has been decoded already
205         sample_range.second = min((int64_t)sample_range.second,
206                 decode_signal_->get_decoded_sample_count(current_segment_, false));
207
208         for (RowData& r : rows_)
209                 r.currently_visible = false;
210         visible_rows_ = 0;
211         int y = get_visual_y();
212
213         for (RowData& r : rows_) {
214                 // If an entire decoder is hidden, we don't want to fetch annotations
215                 if (!r.decode_row.decoder()->shown()) {
216                         r.currently_visible = false;
217                         continue;
218                 }
219
220                 vector<Annotation> annotations;
221                 decode_signal_->get_annotation_subset(annotations, r.decode_row,
222                         current_segment_, sample_range.first, sample_range.second);
223
224                 // Show row if there are visible annotations or when user wants to see
225                 // all rows that have annotations somewhere and this one is one of them
226                 size_t ann_count = decode_signal_->get_annotation_count(r.decode_row, current_segment_);
227                 r.currently_visible = !annotations.empty() || (always_show_all_rows_ && (ann_count > 0));
228
229                 if (r.currently_visible) {
230                         draw_annotations(annotations, p, annotation_height_, pp, y,
231                                 get_row_color(r.decode_row.index()), r.title_width);
232                         y += r.height;
233                         visible_rows_++;
234                 }
235         }
236
237         draw_unresolved_period(p, annotation_height_, pp.left(), pp.right());
238
239         if (visible_rows_ > max_visible_rows_) {
240                 max_visible_rows_ = visible_rows_;
241
242                 // Call order is important, otherwise the lazy event handler won't work
243                 owner_->extents_changed(false, true);
244                 owner_->row_item_appearance_changed(false, true);
245         }
246
247         const QString err = decode_signal_->error_message();
248         if (!err.isEmpty())
249                 draw_error(p, err, pp);
250 }
251
252 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
253 {
254         unsigned int y = get_visual_y();
255
256         for (const RowData& r : rows_) {
257                 if (!r.currently_visible)
258                         continue;
259
260                 p.setPen(QPen(Qt::NoPen));
261
262                 if (r.expand_marker_highlighted)
263                         p.setBrush(QApplication::palette().brush(QPalette::Highlight));
264                 else
265                         p.setBrush(QApplication::palette().brush(QPalette::WindowText));
266
267                 // Draw expansion marker
268                 QPolygon marker(r.expand_marker_shape);
269                 marker.translate(pp.left(), y);
270                 p.drawPolygon(marker);
271
272                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
273
274                 const QRect text_rect(pp.left() + ArrowSize * 2, y - r.height / 2,
275                         pp.right() - pp.left(), r.height);
276                 const QString h(r.decode_row.title());
277                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
278                         Qt::TextDontClip;
279
280                 // Draw the outline
281                 p.setPen(QApplication::palette().color(QPalette::Base));
282                 for (int dx = -1; dx <= 1; dx++)
283                         for (int dy = -1; dy <= 1; dy++)
284                                 if (dx != 0 && dy != 0)
285                                         p.drawText(text_rect.translated(dx, dy), f, h);
286
287                 // Draw the text
288                 p.setPen(QApplication::palette().color(QPalette::WindowText));
289                 p.drawText(text_rect, f, h);
290
291                 y += r.height;
292         }
293
294         if (show_hover_marker_)
295                 paint_hover_marker(p);
296 }
297
298 void DecodeTrace::update_stack_button()
299 {
300         const vector< shared_ptr<data::decode::Decoder> > &stack = decode_signal_->decoder_stack();
301
302         // Only show decoders in the menu that can be stacked onto the last one in the stack
303         if (!stack.empty()) {
304                 const srd_decoder* d = stack.back()->decoder();
305
306                 if (d->outputs) {
307                         pv::widgets::DecoderMenu *const decoder_menu =
308                                 new pv::widgets::DecoderMenu(stack_button_, (const char*)(d->outputs->data));
309                         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
310                                 this, SLOT(on_stack_decoder(srd_decoder*)));
311
312                         decoder_menu->setStyleSheet("QMenu { menu-scrollable: 1; }");
313
314                         stack_button_->setMenu(decoder_menu);
315                         stack_button_->show();
316                         return;
317                 }
318         }
319
320         // No decoders available for stacking
321         stack_button_->setMenu(nullptr);
322         stack_button_->hide();
323 }
324
325 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
326 {
327         using pv::data::decode::Decoder;
328
329         assert(form);
330
331         // Add the standard options
332         Trace::populate_popup_form(parent, form);
333
334         // Add the decoder options
335         bindings_.clear();
336         channel_id_map_.clear();
337         init_state_map_.clear();
338         decoder_forms_.clear();
339
340         const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_stack();
341
342         if (stack.empty()) {
343                 QLabel *const l = new QLabel(
344                         tr("<p><i>No decoders in the stack</i></p>"));
345                 l->setAlignment(Qt::AlignCenter);
346                 form->addRow(l);
347         } else {
348                 auto iter = stack.cbegin();
349                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
350                         shared_ptr<Decoder> dec(*iter);
351                         create_decoder_form(i, dec, parent, form);
352                 }
353
354                 form->addRow(new QLabel(
355                         tr("<i>* Required channels</i>"), parent));
356         }
357
358         // Add stacking button
359         stack_button_ = new QPushButton(tr("Stack Decoder"), parent);
360         stack_button_->setToolTip(tr("Stack a higher-level decoder on top of this one"));
361         update_stack_button();
362
363         QHBoxLayout *stack_button_box = new QHBoxLayout;
364         stack_button_box->addWidget(stack_button_, 0, Qt::AlignRight);
365         form->addRow(stack_button_box);
366 }
367
368 QMenu* DecodeTrace::create_header_context_menu(QWidget *parent)
369 {
370         QMenu *const menu = Trace::create_header_context_menu(parent);
371
372         menu->addSeparator();
373
374         QAction *const del = new QAction(tr("Delete"), this);
375         del->setShortcuts(QKeySequence::Delete);
376         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
377         menu->addAction(del);
378
379         return menu;
380 }
381
382 QMenu* DecodeTrace::create_view_context_menu(QWidget *parent, QPoint &click_pos)
383 {
384         // Get entries from default menu before adding our own
385         QMenu *const menu = new QMenu(parent);
386
387         QMenu* default_menu = Trace::create_view_context_menu(parent, click_pos);
388         if (default_menu) {
389                 for (QAction *action : default_menu->actions()) {  // clazy:exclude=range-loop
390                         menu->addAction(action);
391                         if (action->parent() == default_menu)
392                                 action->setParent(menu);
393                 }
394                 delete default_menu;
395
396                 // Add separator if needed
397                 if (menu->actions().length() > 0)
398                         menu->addSeparator();
399         }
400
401         selected_row_ = nullptr;
402         const RowData* r = get_row_at_point(click_pos);
403         if (r)
404                 selected_row_ = &(r->decode_row);
405
406         const View *const view = owner_->view();
407         assert(view);
408         QPoint pos = view->viewport()->mapFrom(parent, click_pos);
409
410         // Default sample range is "from here"
411         const pair<uint64_t, uint64_t> sample_range = get_view_sample_range(pos.x(), pos.x() + 1);
412         selected_sample_range_ = make_pair(sample_range.first, numeric_limits<uint64_t>::max());
413
414         if (decode_signal_->is_paused()) {
415                 QAction *const resume =
416                         new QAction(tr("Resume decoding"), this);
417                 resume->setIcon(QIcon::fromTheme("media-playback-start",
418                         QIcon(":/icons/media-playback-start.png")));
419                 connect(resume, SIGNAL(triggered()), this, SLOT(on_pause_decode()));
420                 menu->addAction(resume);
421         } else {
422                 QAction *const pause =
423                         new QAction(tr("Pause decoding"), this);
424                 pause->setIcon(QIcon::fromTheme("media-playback-pause",
425                         QIcon(":/icons/media-playback-pause.png")));
426                 connect(pause, SIGNAL(triggered()), this, SLOT(on_pause_decode()));
427                 menu->addAction(pause);
428         }
429
430         QAction *const copy_annotation_to_clipboard =
431                 new QAction(tr("Copy annotation text to clipboard"), this);
432         copy_annotation_to_clipboard->setIcon(QIcon::fromTheme("edit-paste",
433                 QIcon(":/icons/edit-paste.svg")));
434         connect(copy_annotation_to_clipboard, SIGNAL(triggered()), this, SLOT(on_copy_annotation_to_clipboard()));
435         menu->addAction(copy_annotation_to_clipboard);
436
437         menu->addSeparator();
438
439         QAction *const export_all_rows =
440                 new QAction(tr("Export all annotations"), this);
441         export_all_rows->setIcon(QIcon::fromTheme("document-save-as",
442                 QIcon(":/icons/document-save-as.png")));
443         connect(export_all_rows, SIGNAL(triggered()), this, SLOT(on_export_all_rows()));
444         menu->addAction(export_all_rows);
445
446         QAction *const export_row =
447                 new QAction(tr("Export all annotations for this row"), this);
448         export_row->setIcon(QIcon::fromTheme("document-save-as",
449                 QIcon(":/icons/document-save-as.png")));
450         connect(export_row, SIGNAL(triggered()), this, SLOT(on_export_row()));
451         menu->addAction(export_row);
452
453         menu->addSeparator();
454
455         QAction *const export_all_rows_from_here =
456                 new QAction(tr("Export all annotations, starting here"), this);
457         export_all_rows_from_here->setIcon(QIcon::fromTheme("document-save-as",
458                 QIcon(":/icons/document-save-as.png")));
459         connect(export_all_rows_from_here, SIGNAL(triggered()), this, SLOT(on_export_all_rows_from_here()));
460         menu->addAction(export_all_rows_from_here);
461
462         QAction *const export_row_from_here =
463                 new QAction(tr("Export annotations for this row, starting here"), this);
464         export_row_from_here->setIcon(QIcon::fromTheme("document-save-as",
465                 QIcon(":/icons/document-save-as.png")));
466         connect(export_row_from_here, SIGNAL(triggered()), this, SLOT(on_export_row_from_here()));
467         menu->addAction(export_row_from_here);
468
469         menu->addSeparator();
470
471         QAction *const export_all_rows_with_cursor =
472                 new QAction(tr("Export all annotations within cursor range"), this);
473         export_all_rows_with_cursor->setIcon(QIcon::fromTheme("document-save-as",
474                 QIcon(":/icons/document-save-as.png")));
475         connect(export_all_rows_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_all_rows_with_cursor()));
476         menu->addAction(export_all_rows_with_cursor);
477
478         QAction *const export_row_with_cursor =
479                 new QAction(tr("Export annotations for this row within cursor range"), this);
480         export_row_with_cursor->setIcon(QIcon::fromTheme("document-save-as",
481                 QIcon(":/icons/document-save-as.png")));
482         connect(export_row_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_row_with_cursor()));
483         menu->addAction(export_row_with_cursor);
484
485         if (!view->cursors()->enabled()) {
486                 export_all_rows_with_cursor->setEnabled(false);
487                 export_row_with_cursor->setEnabled(false);
488         }
489
490         return menu;
491 }
492
493 void DecodeTrace::delete_pressed()
494 {
495         on_delete();
496 }
497
498 void DecodeTrace::hover_point_changed(const QPoint &hp)
499 {
500         Trace::hover_point_changed(hp);
501
502         assert(owner_);
503
504         RowData* hover_row = get_row_at_point(hp);
505
506         // Row expansion marker handling
507         for (RowData& r : rows_)
508                 r.expand_marker_highlighted = false;
509
510         if (hover_row) {
511                 int row_y = get_row_y(hover_row);
512                 if ((hp.x() > 0) && (hp.x() < 2 * ArrowSize) &&
513                         (hp.y() > (int)(row_y - ArrowSize)) && (hp.y() < (int)(row_y + ArrowSize)))
514                         hover_row->expand_marker_highlighted = true;
515         }
516
517         // Tooltip handling
518         if (hp.x() > 0) {
519                 QString ann = get_annotation_at_point(hp);
520
521                 if (!ann.isEmpty()) {
522                         QFontMetrics m(QToolTip::font());
523                         const QRect text_size = m.boundingRect(QRect(), 0, ann);
524
525                         // This is OS-specific and unfortunately we can't query it, so
526                         // use an approximation to at least try to minimize the error.
527                         const int padding = default_row_height_ + 8;
528
529                         // Make sure the tool tip doesn't overlap with the mouse cursor.
530                         // If it did, the tool tip would constantly hide and re-appear.
531                         // We also push it up by one row so that it appears above the
532                         // decode trace, not below.
533                         QPoint p = hp;
534                         p.setX(hp.x() - (text_size.width() / 2) - padding);
535
536                         p.setY(get_row_y(hover_row) - default_row_height_ -
537                                 text_size.height() - padding);
538
539                         const View *const view = owner_->view();
540                         assert(view);
541                         QToolTip::showText(view->viewport()->mapToGlobal(p), ann);
542
543                 } else
544                         QToolTip::hideText();
545
546         } else
547                 QToolTip::hideText();
548 }
549
550 void DecodeTrace::mouse_left_press_event(const QMouseEvent* event)
551 {
552         // Handle row expansion marker
553         for (RowData& r : rows_) {
554                 if (!r.expand_marker_highlighted)
555                         continue;
556
557                 unsigned int y = get_row_y(&r);
558                 if ((event->x() > 0) && (event->x() <= (int)(ArrowSize + 3)) &&
559                         (event->y() > (int)(y - (default_row_height_ / 2))) &&
560                         (event->y() <= (int)(y + (default_row_height_ / 2)))) {
561
562                         if (r.expanded) {
563                                 r.collapsing = true;
564                                 r.expanded = false;
565                                 r.anim_shape = ArrowSize;
566                         } else {
567                                 r.expanding = true;
568                                 r.anim_shape = 0;
569                         }
570
571                         r.animation_step = 0;
572                         r.anim_height = r.height;
573                         animation_timer_.start();
574                 }
575         }
576 }
577
578 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
579                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
580                 QColor row_color, int row_title_width)
581 {
582         using namespace pv::data::decode;
583
584         Annotation::Class block_class = 0;
585         bool block_class_uniform = true;
586         qreal block_start = 0;
587         int block_ann_count = 0;
588
589         const Annotation *prev_ann;
590         qreal prev_end = INT_MIN;
591
592         qreal a_end;
593
594         double samples_per_pixel, pixels_offset;
595         tie(pixels_offset, samples_per_pixel) =
596                 get_pixels_offset_samples_per_pixel();
597
598         // Sort the annotations by start sample so that decoders
599         // can't confuse us by creating annotations out of order
600         stable_sort(annotations.begin(), annotations.end(),
601                 [](const Annotation &a, const Annotation &b) {
602                         return a.start_sample() < b.start_sample(); });
603
604         // Gather all annotations that form a visual "block" and draw them as such
605         for (const Annotation &a : annotations) {
606
607                 const qreal abs_a_start = a.start_sample() / samples_per_pixel;
608                 const qreal abs_a_end   = a.end_sample() / samples_per_pixel;
609
610                 const qreal a_start = abs_a_start - pixels_offset;
611                 a_end = abs_a_end - pixels_offset;
612
613                 const qreal a_width = a_end - a_start;
614                 const qreal delta = a_end - prev_end;
615
616                 bool a_is_separate = false;
617
618                 // Annotation wider than the threshold for a useful label width?
619                 if (a_width >= min_useful_label_width_) {
620                         for (const QString &ann_text : a.annotations()) {
621                                 const qreal w = p.boundingRect(QRectF(), 0, ann_text).width();
622                                 // Annotation wide enough to fit a label? Don't put it in a block then
623                                 if (w <= a_width) {
624                                         a_is_separate = true;
625                                         break;
626                                 }
627                         }
628                 }
629
630                 // Were the previous and this annotation more than a pixel apart?
631                 if ((abs(delta) > 1) || a_is_separate) {
632                         // Block was broken, draw annotations that form the current block
633                         if (block_ann_count == 1)
634                                 draw_annotation(*prev_ann, p, h, pp, y, row_color,
635                                         row_title_width);
636                         else if (block_ann_count > 0)
637                                 draw_annotation_block(block_start, prev_end, block_class,
638                                         block_class_uniform, p, h, y, row_color);
639
640                         block_ann_count = 0;
641                 }
642
643                 if (a_is_separate) {
644                         draw_annotation(a, p, h, pp, y, row_color, row_title_width);
645                         // Next annotation must start a new block. delta will be > 1
646                         // because we set prev_end to INT_MIN but that's okay since
647                         // block_ann_count will be 0 and nothing will be drawn
648                         prev_end = INT_MIN;
649                         block_ann_count = 0;
650                 } else {
651                         prev_end = a_end;
652                         prev_ann = &a;
653
654                         if (block_ann_count == 0) {
655                                 block_start = a_start;
656                                 block_class = a.ann_class();
657                                 block_class_uniform = true;
658                         } else
659                                 if (a.ann_class() != block_class)
660                                         block_class_uniform = false;
661
662                         block_ann_count++;
663                 }
664         }
665
666         if (block_ann_count == 1)
667                 draw_annotation(*prev_ann, p, h, pp, y, row_color, row_title_width);
668         else if (block_ann_count > 0)
669                 draw_annotation_block(block_start, prev_end, block_class,
670                         block_class_uniform, p, h, y, row_color);
671 }
672
673 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
674         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
675         QColor row_color, int row_title_width) const
676 {
677         double samples_per_pixel, pixels_offset;
678         tie(pixels_offset, samples_per_pixel) =
679                 get_pixels_offset_samples_per_pixel();
680
681         const double start = a.start_sample() / samples_per_pixel -
682                 pixels_offset;
683         const double end = a.end_sample() / samples_per_pixel - pixels_offset;
684
685         QColor color = get_annotation_color(row_color, a.ann_class());
686         p.setPen(color.darker());
687         p.setBrush(color);
688
689         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
690                 return;
691
692         if (a.start_sample() == a.end_sample())
693                 draw_instant(a, p, h, start, y);
694         else
695                 draw_range(a, p, h, start, end, y, pp, row_title_width);
696 }
697
698 void DecodeTrace::draw_annotation_block(qreal start, qreal end,
699         Annotation::Class ann_class, bool use_ann_format, QPainter &p, int h,
700         int y, QColor row_color) const
701 {
702         const double top = y + .5 - h / 2;
703         const double bottom = y + .5 + h / 2;
704
705         const QRectF rect(start, top, end - start, bottom - top);
706         const int r = h / 4;
707
708         p.setPen(QPen(Qt::NoPen));
709         p.setBrush(Qt::white);
710         p.drawRoundedRect(rect, r, r);
711
712         // If all annotations in this block are of the same type, we can use the
713         // one format that all of these annotations have. Otherwise, we should use
714         // a neutral color (i.e. gray)
715         if (use_ann_format) {
716                 const QColor color = get_annotation_color(row_color, ann_class);
717                 p.setPen(color.darker());
718                 p.setBrush(QBrush(color, Qt::Dense4Pattern));
719         } else {
720                 p.setPen(Qt::gray);
721                 p.setBrush(QBrush(Qt::gray, Qt::Dense4Pattern));
722         }
723
724         p.drawRoundedRect(rect, r, r);
725 }
726
727 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
728         int h, qreal x, int y) const
729 {
730         const QString text = a.annotations().empty() ?
731                 QString() : a.annotations().back();
732         const qreal w = min((qreal)p.boundingRect(QRectF(), 0, text).width(),
733                 0.0) + h;
734         const QRectF rect(x - w / 2, y - h / 2, w, h);
735
736         p.drawRoundedRect(rect, h / 2, h / 2);
737
738         p.setPen(Qt::black);
739         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
740 }
741
742 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
743         int h, qreal start, qreal end, int y, const ViewItemPaintParams &pp,
744         int row_title_width) const
745 {
746         const qreal top = y + .5 - h / 2;
747         const qreal bottom = y + .5 + h / 2;
748         const vector<QString> annotations = a.annotations();
749
750         // If the two ends are within 1 pixel, draw a vertical line
751         if (start + 1.0 > end) {
752                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
753                 return;
754         }
755
756         const qreal cap_width = min((end - start) / 4, EndCapWidth);
757
758         QPointF pts[] = {
759                 QPointF(start, y + .5f),
760                 QPointF(start + cap_width, top),
761                 QPointF(end - cap_width, top),
762                 QPointF(end, y + .5f),
763                 QPointF(end - cap_width, bottom),
764                 QPointF(start + cap_width, bottom)
765         };
766
767         p.drawConvexPolygon(pts, countof(pts));
768
769         if (annotations.empty())
770                 return;
771
772         const int ann_start = start + cap_width;
773         const int ann_end = end - cap_width;
774
775         const int real_start = max(ann_start, pp.left() + ArrowSize + row_title_width);
776         const int real_end = min(ann_end, pp.right());
777         const int real_width = real_end - real_start;
778
779         QRectF rect(real_start, y - h / 2, real_width, h);
780         if (rect.width() <= 4)
781                 return;
782
783         p.setPen(Qt::black);
784
785         // Try to find an annotation that will fit
786         QString best_annotation;
787         int best_width = 0;
788
789         for (const QString &a : annotations) {
790                 const int w = p.boundingRect(QRectF(), 0, a).width();
791                 if (w <= rect.width() && w > best_width)
792                         best_annotation = a, best_width = w;
793         }
794
795         if (best_annotation.isEmpty())
796                 best_annotation = annotations.back();
797
798         // If not ellide the last in the list
799         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
800                 best_annotation, Qt::ElideRight, rect.width()));
801 }
802
803 void DecodeTrace::draw_error(QPainter &p, const QString &message,
804         const ViewItemPaintParams &pp)
805 {
806         const int y = get_visual_y();
807
808         double samples_per_pixel, pixels_offset;
809         tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
810
811         p.setPen(ErrorBgColor.darker());
812         p.setBrush(ErrorBgColor);
813
814         const QRectF bounding_rect = QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
815
816         const QRectF text_rect = p.boundingRect(bounding_rect, Qt::AlignCenter, message);
817         const qreal r = text_rect.height() / 4;
818
819         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r, Qt::AbsoluteSize);
820
821         p.setPen(Qt::black);
822         p.drawText(text_rect, message);
823 }
824
825 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
826 {
827         using namespace pv::data;
828         using pv::data::decode::Decoder;
829
830         double samples_per_pixel, pixels_offset;
831
832         const int64_t sample_count = decode_signal_->get_working_sample_count(current_segment_);
833         if (sample_count == 0)
834                 return;
835
836         const int64_t samples_decoded = decode_signal_->get_decoded_sample_count(current_segment_, true);
837         if (sample_count == samples_decoded)
838                 return;
839
840         const int y = get_visual_y();
841
842         tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
843
844         const double start = max(samples_decoded /
845                 samples_per_pixel - pixels_offset, left - 1.0);
846         const double end = min(sample_count / samples_per_pixel -
847                 pixels_offset, right + 1.0);
848         const QRectF no_decode_rect(start, y - (h / 2) - 0.5, end - start, h);
849
850         p.setPen(QPen(Qt::NoPen));
851         p.setBrush(Qt::white);
852         p.drawRect(no_decode_rect);
853
854         p.setPen(NoDecodeColor);
855         p.setBrush(QBrush(NoDecodeColor, Qt::Dense6Pattern));
856         p.drawRect(no_decode_rect);
857 }
858
859 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
860 {
861         assert(owner_);
862
863         const View *view = owner_->view();
864         assert(view);
865
866         const double scale = view->scale();
867         assert(scale > 0);
868
869         const double pixels_offset =
870                 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
871
872         double samplerate = decode_signal_->samplerate();
873
874         // Show sample rate as 1Hz when it is unknown
875         if (samplerate == 0.0)
876                 samplerate = 1.0;
877
878         return make_pair(pixels_offset, samplerate * scale);
879 }
880
881 pair<uint64_t, uint64_t> DecodeTrace::get_view_sample_range(
882         int x_start, int x_end) const
883 {
884         double samples_per_pixel, pixels_offset;
885         tie(pixels_offset, samples_per_pixel) =
886                 get_pixels_offset_samples_per_pixel();
887
888         const uint64_t start = (uint64_t)max(
889                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
890         const uint64_t end = (uint64_t)max(
891                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
892
893         return make_pair(start, end);
894 }
895
896 QColor DecodeTrace::get_row_color(int row_index) const
897 {
898         // For each row color, use the base color hue and add an offset that's
899         // not a dividend of 360
900
901         QColor color;
902         const int h = (base_->color().toHsv().hue() + 20 * row_index) % 360;
903         const int s = DECODETRACE_COLOR_SATURATION;
904         const int v = DECODETRACE_COLOR_VALUE;
905         color.setHsl(h, s, v);
906
907         return color;
908 }
909
910 QColor DecodeTrace::get_annotation_color(QColor row_color, int annotation_index) const
911 {
912         // For each row color, use the base color hue and add an offset that's
913         // not a dividend of 360 and not a multiple of the row offset
914
915         QColor color(row_color);
916         const int h = (color.toHsv().hue() + 55 * annotation_index) % 360;
917         const int s = DECODETRACE_COLOR_SATURATION;
918         const int v = DECODETRACE_COLOR_VALUE;
919         color.setHsl(h, s, v);
920
921         return color;
922 }
923
924 unsigned int DecodeTrace::get_row_y(const RowData* row) const
925 {
926         assert(row);
927
928         unsigned int y = get_visual_y();
929
930         for (const RowData& r : rows_) {
931                 if (!r.currently_visible)
932                         continue;
933
934                 if (row->decode_row == r.decode_row)
935                         break;
936                 else
937                         y += r.height;
938         }
939
940         return y;
941 }
942
943 RowData* DecodeTrace::get_row_at_point(const QPoint &point)
944 {
945         int y = get_visual_y() - (default_row_height_ / 2);
946
947         for (RowData& r : rows_) {
948                 if (!r.currently_visible)
949                         continue;
950
951                 if ((point.y() >= y) && (point.y() < (int)(y + r.height)))
952                         return &r;
953
954                 y += r.height;
955         }
956
957         return nullptr;
958 }
959
960 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
961 {
962         using namespace pv::data::decode;
963
964         if (!enabled())
965                 return QString();
966
967         const pair<uint64_t, uint64_t> sample_range =
968                 get_view_sample_range(point.x(), point.x() + 1);
969         const RowData* r = get_row_at_point(point);
970
971         if (!r)
972                 return QString();
973
974         if (point.y() > (int)(get_row_y(r) + (annotation_height_ / 2)))
975                 return QString();
976
977         vector<Annotation> annotations;
978
979         decode_signal_->get_annotation_subset(annotations, r->decode_row,
980                 current_segment_, sample_range.first, sample_range.second);
981
982         return (annotations.empty()) ?
983                 QString() : annotations[0].annotations().front();
984 }
985
986 void DecodeTrace::create_decoder_form(int index,
987         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
988         QFormLayout *form)
989 {
990         GlobalSettings settings;
991
992         assert(dec);
993         const srd_decoder *const decoder = dec->decoder();
994         assert(decoder);
995
996         const bool decoder_deletable = index > 0;
997
998         pv::widgets::DecoderGroupBox *const group =
999                 new pv::widgets::DecoderGroupBox(
1000                         QString::fromUtf8(decoder->name),
1001                         tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
1002                                 QString::fromUtf8(decoder->desc)),
1003                         nullptr, decoder_deletable);
1004         group->set_decoder_visible(dec->shown());
1005
1006         if (decoder_deletable) {
1007                 delete_mapper_.setMapping(group, index);
1008                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
1009         }
1010
1011         show_hide_mapper_.setMapping(group, index);
1012         connect(group, SIGNAL(show_hide_decoder()),
1013                 &show_hide_mapper_, SLOT(map()));
1014
1015         QFormLayout *const decoder_form = new QFormLayout;
1016         group->add_layout(decoder_form);
1017
1018         const vector<DecodeChannel> channels = decode_signal_->get_channels();
1019
1020         // Add the channels
1021         for (const DecodeChannel& ch : channels) {
1022                 // Ignore channels not part of the decoder we create the form for
1023                 if (ch.decoder_ != dec)
1024                         continue;
1025
1026                 QComboBox *const combo = create_channel_selector(parent, &ch);
1027                 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
1028
1029                 channel_id_map_[combo] = ch.id;
1030                 init_state_map_[combo_init_state] = ch.id;
1031
1032                 connect(combo, SIGNAL(currentIndexChanged(int)),
1033                         this, SLOT(on_channel_selected(int)));
1034                 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
1035                         this, SLOT(on_init_state_changed(int)));
1036
1037                 QHBoxLayout *const hlayout = new QHBoxLayout;
1038                 hlayout->addWidget(combo);
1039                 hlayout->addWidget(combo_init_state);
1040
1041                 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
1042                         combo_init_state->hide();
1043
1044                 const QString required_flag = ch.is_optional ? QString() : QString("*");
1045                 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
1046                         .arg(ch.name, ch.desc, required_flag), hlayout);
1047         }
1048
1049         // Add the options
1050         shared_ptr<binding::Decoder> binding(
1051                 new binding::Decoder(decode_signal_, dec));
1052         binding->add_properties_to_form(decoder_form, true);
1053
1054         bindings_.push_back(binding);
1055
1056         form->addRow(group);
1057         decoder_forms_.push_back(group);
1058 }
1059
1060 QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
1061 {
1062         const auto sigs(session_.signalbases());
1063
1064         // Sort signals in natural order
1065         vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
1066         sort(sig_list.begin(), sig_list.end(),
1067                 [](const shared_ptr<data::SignalBase> &a,
1068                 const shared_ptr<data::SignalBase> &b) {
1069                         return strnatcasecmp(a->name().toStdString(),
1070                                 b->name().toStdString()) < 0; });
1071
1072         QComboBox *selector = new QComboBox(parent);
1073
1074         selector->addItem("-", qVariantFromValue((void*)nullptr));
1075
1076         if (!ch->assigned_signal)
1077                 selector->setCurrentIndex(0);
1078
1079         for (const shared_ptr<data::SignalBase> &b : sig_list) {
1080                 assert(b);
1081                 if (b->logic_data() && b->enabled()) {
1082                         selector->addItem(b->name(),
1083                                 qVariantFromValue((void*)b.get()));
1084
1085                         if (ch->assigned_signal == b.get())
1086                                 selector->setCurrentIndex(selector->count() - 1);
1087                 }
1088         }
1089
1090         return selector;
1091 }
1092
1093 QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
1094         const DecodeChannel *ch)
1095 {
1096         QComboBox *selector = new QComboBox(parent);
1097
1098         selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
1099         selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
1100         selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
1101
1102         selector->setCurrentIndex(ch->initial_pin_state);
1103
1104         selector->setToolTip("Initial (assumed) pin value before the first sample");
1105
1106         return selector;
1107 }
1108
1109 void DecodeTrace::export_annotations(vector<Annotation> *annotations) const
1110 {
1111         using namespace pv::data::decode;
1112
1113         GlobalSettings settings;
1114         const QString dir = settings.value("MainWindow/SaveDirectory").toString();
1115
1116         const QString file_name = QFileDialog::getSaveFileName(
1117                 owner_->view(), tr("Export annotations"), dir, tr("Text Files (*.txt);;All Files (*)"));
1118
1119         if (file_name.isEmpty())
1120                 return;
1121
1122         QString format = settings.value(GlobalSettings::Key_Dec_ExportFormat).toString();
1123         const QString quote = format.contains("%q") ? "\"" : "";
1124         format = format.remove("%q");
1125
1126         QFile file(file_name);
1127         if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
1128                 QTextStream out_stream(&file);
1129
1130                 for (Annotation &ann : *annotations) {
1131                         const QString sample_range = QString("%1-%2") \
1132                                 .arg(QString::number(ann.start_sample()), QString::number(ann.end_sample()));
1133
1134                         const QString class_name = quote + ann.row()->class_name() + quote;
1135
1136                         QString all_ann_text;
1137                         for (const QString &s : ann.annotations())
1138                                 all_ann_text = all_ann_text + quote + s + quote + ",";
1139                         all_ann_text.chop(1);
1140
1141                         const QString first_ann_text = quote + ann.annotations().front() + quote;
1142
1143                         QString out_text = format;
1144                         out_text = out_text.replace("%s", sample_range);
1145                         out_text = out_text.replace("%d",
1146                                 quote + QString::fromUtf8(ann.row()->decoder()->name()) + quote);
1147                         out_text = out_text.replace("%c", class_name);
1148                         out_text = out_text.replace("%1", first_ann_text);
1149                         out_text = out_text.replace("%a", all_ann_text);
1150                         out_stream << out_text << '\n';
1151                 }
1152
1153                 if (out_stream.status() == QTextStream::Ok)
1154                         return;
1155         }
1156
1157         QMessageBox msg(owner_->view());
1158         msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
1159         msg.setStandardButtons(QMessageBox::Ok);
1160         msg.setIcon(QMessageBox::Warning);
1161         msg.exec();
1162 }
1163
1164 void DecodeTrace::update_rows()
1165 {
1166         lock_guard<mutex> lock(row_modification_mutex_);
1167
1168         QFontMetrics m(QApplication::font());
1169
1170         for (RowData& r : rows_)
1171                 r.exists = false;
1172
1173         for (const Row& decode_row : decode_signal_->get_rows()) {
1174                 // Find row in our list
1175                 auto r_it = find_if(rows_.begin(), rows_.end(),
1176                         [&](RowData& r){ return r.decode_row == decode_row; });
1177
1178                 RowData* r = nullptr;
1179                 if (r_it == rows_.end()) {
1180                         // Row doesn't exist yet, create and append it
1181                         RowData nr;
1182                         nr.decode_row = decode_row;
1183                         nr.height = default_row_height_;
1184                         nr.expanded_height = 5*default_row_height_;
1185                         nr.currently_visible = false;
1186                         nr.expand_marker_highlighted = false;
1187                         nr.expanding = false;
1188                         nr.expanded = false;
1189                         nr.collapsing = false;
1190                         nr.expand_marker_shape = default_marker_shape_;
1191
1192                         rows_.push_back(nr);
1193                         r = &rows_.back();
1194                 } else
1195                         r = &(*r_it);
1196
1197                 r->exists = true;
1198
1199                 const int w = m.boundingRect(r->decode_row.title()).width() + RowTitleMargin;
1200                 r->title_width = w;
1201         }
1202
1203         // Remove any rows that no longer exist, obeying that iterators are invalidated
1204         bool any_exists;
1205         do {
1206                 any_exists = false;
1207
1208                 for (unsigned int i = 0; i < rows_.size(); i++)
1209                         if (!rows_[i].exists) {
1210                                 rows_.erase(rows_.begin() + i);
1211                                 any_exists = true;
1212                                 break;
1213                         }
1214         } while (any_exists);
1215 }
1216
1217 void DecodeTrace::on_setting_changed(const QString &key, const QVariant &value)
1218 {
1219         Trace::on_setting_changed(key, value);
1220
1221         if (key == GlobalSettings::Key_Dec_AlwaysShowAllRows) {
1222                 max_visible_rows_ = 0;
1223                 always_show_all_rows_ = value.toBool();
1224         }
1225 }
1226
1227 void DecodeTrace::on_new_annotations()
1228 {
1229         if (!delayed_trace_updater_.isActive())
1230                 delayed_trace_updater_.start();
1231 }
1232
1233 void DecodeTrace::on_delayed_trace_update()
1234 {
1235         if (owner_)
1236                 owner_->row_item_appearance_changed(false, true);
1237 }
1238
1239 void DecodeTrace::on_decode_reset()
1240 {
1241         max_visible_rows_ = 0;
1242         update_rows();
1243
1244         if (owner_)
1245                 owner_->row_item_appearance_changed(false, true);
1246 }
1247
1248 void DecodeTrace::on_decode_finished()
1249 {
1250         if (owner_)
1251                 owner_->row_item_appearance_changed(false, true);
1252 }
1253
1254 void DecodeTrace::on_pause_decode()
1255 {
1256         if (decode_signal_->is_paused())
1257                 decode_signal_->resume_decode();
1258         else
1259                 decode_signal_->pause_decode();
1260 }
1261
1262 void DecodeTrace::on_delete()
1263 {
1264         session_.remove_decode_signal(decode_signal_);
1265 }
1266
1267 void DecodeTrace::on_channel_selected(int)
1268 {
1269         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
1270
1271         // Determine signal that was selected
1272         const data::SignalBase *signal =
1273                 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
1274
1275         // Determine decode channel ID this combo box is the channel selector for
1276         const uint16_t id = channel_id_map_.at(cb);
1277
1278         decode_signal_->assign_signal(id, signal);
1279 }
1280
1281 void DecodeTrace::on_channels_updated()
1282 {
1283         if (owner_)
1284                 owner_->row_item_appearance_changed(false, true);
1285 }
1286
1287 void DecodeTrace::on_init_state_changed(int)
1288 {
1289         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
1290
1291         // Determine inital pin state that was selected
1292         int init_state = cb->itemData(cb->currentIndex()).value<int>();
1293
1294         // Determine decode channel ID this combo box is the channel selector for
1295         const uint16_t id = init_state_map_.at(cb);
1296
1297         decode_signal_->set_initial_pin_state(id, init_state);
1298 }
1299
1300 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
1301 {
1302         decode_signal_->stack_decoder(decoder);
1303         update_rows();
1304
1305         create_popup_form();
1306 }
1307
1308 void DecodeTrace::on_delete_decoder(int index)
1309 {
1310         decode_signal_->remove_decoder(index);
1311         update_rows();
1312
1313         // Force re-calculation of the trace height
1314         max_visible_rows_ = 0;
1315         owner_->extents_changed(false, true);
1316
1317         create_popup_form();
1318 }
1319
1320 void DecodeTrace::on_show_hide_decoder(int index)
1321 {
1322         const bool state = decode_signal_->toggle_decoder_visibility(index);
1323
1324         assert(index < (int)decoder_forms_.size());
1325         decoder_forms_[index]->set_decoder_visible(state);
1326
1327         if (!state) {
1328                 // Force re-calculation of the trace height, see paint_mid()
1329                 max_visible_rows_ = 0;
1330                 owner_->extents_changed(false, true);
1331         }
1332
1333         if (owner_)
1334                 owner_->row_item_appearance_changed(false, true);
1335 }
1336
1337 void DecodeTrace::on_copy_annotation_to_clipboard()
1338 {
1339         using namespace pv::data::decode;
1340
1341         if (!selected_row_)
1342                 return;
1343
1344         vector<Annotation> *annotations = new vector<Annotation>();
1345
1346         decode_signal_->get_annotation_subset(*annotations, *selected_row_,
1347                 current_segment_, selected_sample_range_.first, selected_sample_range_.first);
1348
1349         if (annotations->empty())
1350                 return;
1351
1352         QClipboard *clipboard = QApplication::clipboard();
1353         clipboard->setText(annotations->front().annotations().front(), QClipboard::Clipboard);
1354
1355         if (clipboard->supportsSelection())
1356                 clipboard->setText(annotations->front().annotations().front(), QClipboard::Selection);
1357
1358         delete annotations;
1359 }
1360
1361 void DecodeTrace::on_export_row()
1362 {
1363         selected_sample_range_ = make_pair(0, numeric_limits<uint64_t>::max());
1364         on_export_row_from_here();
1365 }
1366
1367 void DecodeTrace::on_export_all_rows()
1368 {
1369         selected_sample_range_ = make_pair(0, numeric_limits<uint64_t>::max());
1370         on_export_all_rows_from_here();
1371 }
1372
1373 void DecodeTrace::on_export_row_with_cursor()
1374 {
1375         const View *view = owner_->view();
1376         assert(view);
1377
1378         if (!view->cursors()->enabled())
1379                 return;
1380
1381         const double samplerate = session_.get_samplerate();
1382
1383         const pv::util::Timestamp& start_time = view->cursors()->first()->time();
1384         const pv::util::Timestamp& end_time = view->cursors()->second()->time();
1385
1386         const uint64_t start_sample = (uint64_t)max(
1387                 0.0, start_time.convert_to<double>() * samplerate);
1388         const uint64_t end_sample = (uint64_t)max(
1389                 0.0, end_time.convert_to<double>() * samplerate);
1390
1391         // Are both cursors negative and thus were clamped to 0?
1392         if ((start_sample == 0) && (end_sample == 0))
1393                 return;
1394
1395         selected_sample_range_ = make_pair(start_sample, end_sample);
1396         on_export_row_from_here();
1397 }
1398
1399 void DecodeTrace::on_export_all_rows_with_cursor()
1400 {
1401         const View *view = owner_->view();
1402         assert(view);
1403
1404         if (!view->cursors()->enabled())
1405                 return;
1406
1407         const double samplerate = session_.get_samplerate();
1408
1409         const pv::util::Timestamp& start_time = view->cursors()->first()->time();
1410         const pv::util::Timestamp& end_time = view->cursors()->second()->time();
1411
1412         const uint64_t start_sample = (uint64_t)max(
1413                 0.0, start_time.convert_to<double>() * samplerate);
1414         const uint64_t end_sample = (uint64_t)max(
1415                 0.0, end_time.convert_to<double>() * samplerate);
1416
1417         // Are both cursors negative and thus were clamped to 0?
1418         if ((start_sample == 0) && (end_sample == 0))
1419                 return;
1420
1421         selected_sample_range_ = make_pair(start_sample, end_sample);
1422         on_export_all_rows_from_here();
1423 }
1424
1425 void DecodeTrace::on_export_row_from_here()
1426 {
1427         using namespace pv::data::decode;
1428
1429         if (!selected_row_)
1430                 return;
1431
1432         vector<Annotation> *annotations = new vector<Annotation>();
1433
1434         decode_signal_->get_annotation_subset(*annotations, *selected_row_,
1435                 current_segment_, selected_sample_range_.first, selected_sample_range_.second);
1436
1437         if (annotations->empty())
1438                 return;
1439
1440         export_annotations(annotations);
1441         delete annotations;
1442 }
1443
1444 void DecodeTrace::on_export_all_rows_from_here()
1445 {
1446         using namespace pv::data::decode;
1447
1448         vector<Annotation> *annotations = new vector<Annotation>();
1449
1450         decode_signal_->get_annotation_subset(*annotations, current_segment_,
1451                         selected_sample_range_.first, selected_sample_range_.second);
1452
1453         if (!annotations->empty())
1454                 export_annotations(annotations);
1455
1456         delete annotations;
1457 }
1458
1459 void DecodeTrace::on_animation_timer()
1460 {
1461         bool animation_finished = true;
1462
1463         for (RowData& r : rows_) {
1464                 if (!(r.expanding || r.collapsing))
1465                         continue;
1466
1467                 unsigned int height_delta = r.expanded_height - default_row_height_;
1468
1469                 if (r.expanding) {
1470                         if (r.height < r.expanded_height) {
1471                                 r.anim_height += height_delta / (float)AnimationDurationInTicks;
1472                                 r.height = r.anim_height;
1473                                 r.anim_shape += ArrowSize / (float)AnimationDurationInTicks;
1474                                 animation_finished = false;
1475                         } else {
1476                                 r.height = std::min(r.height, r.expanded_height);
1477                                 r.expanding = false;
1478                                 r.expanded = true;
1479                         }
1480                 }
1481
1482                 if (r.collapsing) {
1483                         if (r.height > default_row_height_) {
1484                                 r.anim_height -= height_delta / (float)AnimationDurationInTicks;
1485                                 r.height = r.anim_height;
1486                                 r.anim_shape -= ArrowSize / (float)AnimationDurationInTicks;
1487                                 animation_finished = false;
1488                         } else {
1489                                 r.height = std::max(r.height, default_row_height_);
1490                                 r.collapsing = false;
1491                                 r.expanded = false;
1492                                 r.expand_marker_shape = default_marker_shape_;
1493                         }
1494                 }
1495
1496                 // The expansion marker shape switches between
1497                 // 0/-A, A/0,  0/A (default state; anim_shape=0) and
1498                 // 0/ 0, A/A, 2A/0 (expanded state; anim_shape=ArrowSize)
1499
1500                 r.expand_marker_shape.setPoint(0, 0, -ArrowSize + r.anim_shape);
1501                 r.expand_marker_shape.setPoint(1, ArrowSize, r.anim_shape);
1502                 r.expand_marker_shape.setPoint(2, 2*r.anim_shape, ArrowSize - r.anim_shape);
1503         }
1504
1505         if (animation_finished)
1506                 animation_timer_.stop();
1507
1508         if (owner_)
1509                 owner_->row_item_appearance_changed(false, true);
1510 }
1511
1512 } // namespace trace
1513 } // namespace views
1514 } // namespace pv