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