]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
DecodeTrace: Change initial pin config description from ? to X
[pulseview.git] / pv / view / 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 <mutex>
25
26 #include <extdef.h>
27
28 #include <tuple>
29
30 #include <boost/functional/hash.hpp>
31
32 #include <QAction>
33 #include <QApplication>
34 #include <QComboBox>
35 #include <QFormLayout>
36 #include <QLabel>
37 #include <QMenu>
38 #include <QPushButton>
39 #include <QToolTip>
40
41 #include "decodetrace.hpp"
42
43 #include <pv/data/decode/annotation.hpp>
44 #include <pv/data/decode/decoder.hpp>
45 #include <pv/data/decoderstack.hpp>
46 #include <pv/data/logic.hpp>
47 #include <pv/data/logicsegment.hpp>
48 #include <pv/session.hpp>
49 #include <pv/strnatcmp.hpp>
50 #include <pv/view/view.hpp>
51 #include <pv/view/viewport.hpp>
52 #include <pv/widgets/decodergroupbox.hpp>
53 #include <pv/widgets/decodermenu.hpp>
54
55 using std::all_of;
56 using std::list;
57 using std::make_pair;
58 using std::max;
59 using std::make_pair;
60 using std::map;
61 using std::min;
62 using std::out_of_range;
63 using std::pair;
64 using std::shared_ptr;
65 using std::make_shared;
66 using std::tie;
67 using std::unordered_set;
68 using std::vector;
69
70 namespace pv {
71 namespace views {
72 namespace TraceView {
73
74 const QColor DecodeTrace::DecodeColours[4] = {
75         QColor(0xEF, 0x29, 0x29),       // Red
76         QColor(0xFC, 0xE9, 0x4F),       // Yellow
77         QColor(0x8A, 0xE2, 0x34),       // Green
78         QColor(0x72, 0x9F, 0xCF)        // Blue
79 };
80
81 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
82 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
83
84 const int DecodeTrace::ArrowSize = 4;
85 const double DecodeTrace::EndCapWidth = 5;
86 const int DecodeTrace::RowTitleMargin = 10;
87 const int DecodeTrace::DrawPadding = 100;
88
89 const QColor DecodeTrace::Colours[16] = {
90         QColor(0xEF, 0x29, 0x29),
91         QColor(0xF6, 0x6A, 0x32),
92         QColor(0xFC, 0xAE, 0x3E),
93         QColor(0xFB, 0xCA, 0x47),
94         QColor(0xFC, 0xE9, 0x4F),
95         QColor(0xCD, 0xF0, 0x40),
96         QColor(0x8A, 0xE2, 0x34),
97         QColor(0x4E, 0xDC, 0x44),
98         QColor(0x55, 0xD7, 0x95),
99         QColor(0x64, 0xD1, 0xD2),
100         QColor(0x72, 0x9F, 0xCF),
101         QColor(0xD4, 0x76, 0xC4),
102         QColor(0x9D, 0x79, 0xB9),
103         QColor(0xAD, 0x7F, 0xA8),
104         QColor(0xC2, 0x62, 0x9B),
105         QColor(0xD7, 0x47, 0x6F)
106 };
107
108 const QColor DecodeTrace::OutlineColours[16] = {
109         QColor(0x77, 0x14, 0x14),
110         QColor(0x7B, 0x35, 0x19),
111         QColor(0x7E, 0x57, 0x1F),
112         QColor(0x7D, 0x65, 0x23),
113         QColor(0x7E, 0x74, 0x27),
114         QColor(0x66, 0x78, 0x20),
115         QColor(0x45, 0x71, 0x1A),
116         QColor(0x27, 0x6E, 0x22),
117         QColor(0x2A, 0x6B, 0x4A),
118         QColor(0x32, 0x68, 0x69),
119         QColor(0x39, 0x4F, 0x67),
120         QColor(0x6A, 0x3B, 0x62),
121         QColor(0x4E, 0x3C, 0x5C),
122         QColor(0x56, 0x3F, 0x54),
123         QColor(0x61, 0x31, 0x4D),
124         QColor(0x6B, 0x23, 0x37)
125 };
126
127 DecodeTrace::DecodeTrace(pv::Session &session,
128         shared_ptr<data::SignalBase> signalbase, int index) :
129         Trace(signalbase),
130         session_(session),
131         row_height_(0),
132         max_visible_rows_(0),
133         delete_mapper_(this),
134         show_hide_mapper_(this)
135 {
136         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
137
138         // Determine shortest string we want to see displayed in full
139         QFontMetrics m(QApplication::font());
140         min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
141
142         base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
143         base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
144
145         connect(decoder_stack.get(), SIGNAL(new_decode_data()),
146                 this, SLOT(on_new_decode_data()));
147         connect(&delete_mapper_, SIGNAL(mapped(int)),
148                 this, SLOT(on_delete_decoder(int)));
149         connect(&show_hide_mapper_, SIGNAL(mapped(int)),
150                 this, SLOT(on_show_hide_decoder(int)));
151 }
152
153 bool DecodeTrace::enabled() const
154 {
155         return true;
156 }
157
158 shared_ptr<data::SignalBase> DecodeTrace::base() const
159 {
160         return base_;
161 }
162
163 pair<int, int> DecodeTrace::v_extents() const
164 {
165         const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
166
167         // Make an empty decode trace appear symmetrical
168         const int row_count = max(1, max_visible_rows_);
169
170         return make_pair(-row_height, row_height * row_count);
171 }
172
173 void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
174 {
175         Trace::paint_back(p, pp);
176         paint_axis(p, pp, get_visual_y());
177 }
178
179 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
180 {
181         using namespace pv::data::decode;
182
183         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
184
185         const int text_height = ViewItemPaintParams::text_height();
186         row_height_ = (text_height * 6) / 4;
187         const int annotation_height = (text_height * 5) / 4;
188
189         assert(decoder_stack);
190         const QString err = decoder_stack->error_message();
191         if (!err.isEmpty()) {
192                 draw_unresolved_period(
193                         p, annotation_height, pp.left(), pp.right());
194                 draw_error(p, err, pp);
195                 return;
196         }
197
198         // Set default pen to allow for text width calculation
199         p.setPen(Qt::black);
200
201         // Iterate through the rows
202         int y = get_visual_y();
203         pair<uint64_t, uint64_t> sample_range = get_sample_range(
204                 pp.left(), pp.right());
205
206         const vector<Row> rows(decoder_stack->get_visible_rows());
207
208         visible_rows_.clear();
209         for (const Row& row : rows) {
210                 // Cache the row title widths
211                 int row_title_width;
212                 try {
213                         row_title_width = row_title_widths_.at(row);
214                 } catch (out_of_range) {
215                         const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
216                                 RowTitleMargin;
217                         row_title_widths_[row] = w;
218                         row_title_width = w;
219                 }
220
221                 // Determine the row's color
222                 size_t base_colour = 0x13579BDF;
223                 boost::hash_combine(base_colour, this);
224                 boost::hash_combine(base_colour, row.decoder());
225                 boost::hash_combine(base_colour, row.row());
226                 base_colour >>= 16;
227
228                 vector<Annotation> annotations;
229                 decoder_stack->get_annotation_subset(annotations, row,
230                         sample_range.first, sample_range.second);
231                 if (!annotations.empty()) {
232                         draw_annotations(annotations, p, annotation_height, pp, y,
233                                 base_colour, row_title_width);
234
235                         y += row_height_;
236
237                         visible_rows_.push_back(row);
238                 }
239         }
240
241         // Draw the hatching
242         draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
243
244         if ((int)visible_rows_.size() > max_visible_rows_)
245                 owner_->extents_changed(false, true);
246
247         // Update the maximum row count if needed
248         max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
249 }
250
251 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
252 {
253         using namespace pv::data::decode;
254
255         assert(row_height_);
256
257         for (size_t i = 0; i < visible_rows_.size(); i++) {
258                 const int y = i * row_height_ + get_visual_y();
259
260                 p.setPen(QPen(Qt::NoPen));
261                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
262
263                 if (i != 0) {
264                         const QPointF points[] = {
265                                 QPointF(pp.left(), y - ArrowSize),
266                                 QPointF(pp.left() + ArrowSize, y),
267                                 QPointF(pp.left(), y + ArrowSize)
268                         };
269                         p.drawPolygon(points, countof(points));
270                 }
271
272                 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
273                         pp.right() - pp.left(), row_height_);
274                 const QString h(visible_rows_[i].title());
275                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
276                         Qt::TextDontClip;
277
278                 // Draw the outline
279                 p.setPen(QApplication::palette().color(QPalette::Base));
280                 for (int dx = -1; dx <= 1; dx++)
281                         for (int dy = -1; dy <= 1; dy++)
282                                 if (dx != 0 && dy != 0)
283                                         p.drawText(r.translated(dx, dy), f, h);
284
285                 // Draw the text
286                 p.setPen(QApplication::palette().color(QPalette::WindowText));
287                 p.drawText(r, f, h);
288         }
289 }
290
291 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
292 {
293         using pv::data::decode::Decoder;
294
295         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
296
297         assert(form);
298         assert(parent);
299         assert(decoder_stack);
300
301         // Add the standard options
302         Trace::populate_popup_form(parent, form);
303
304         // Add the decoder options
305         bindings_.clear();
306         channel_selectors_.clear();
307         decoder_forms_.clear();
308
309         const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
310
311         if (stack.empty()) {
312                 QLabel *const l = new QLabel(
313                         tr("<p><i>No decoders in the stack</i></p>"));
314                 l->setAlignment(Qt::AlignCenter);
315                 form->addRow(l);
316         } else {
317                 auto iter = stack.cbegin();
318                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
319                         shared_ptr<Decoder> dec(*iter);
320                         create_decoder_form(i, dec, parent, form);
321                 }
322
323                 form->addRow(new QLabel(
324                         tr("<i>* Required channels</i>"), parent));
325         }
326
327         // Add stacking button
328         pv::widgets::DecoderMenu *const decoder_menu =
329                 new pv::widgets::DecoderMenu(parent);
330         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
331                 this, SLOT(on_stack_decoder(srd_decoder*)));
332
333         QPushButton *const stack_button =
334                 new QPushButton(tr("Stack Decoder"), parent);
335         stack_button->setMenu(decoder_menu);
336         stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
337
338         QHBoxLayout *stack_button_box = new QHBoxLayout;
339         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
340         form->addRow(stack_button_box);
341 }
342
343 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
344 {
345         QMenu *const menu = Trace::create_context_menu(parent);
346
347         menu->addSeparator();
348
349         QAction *const del = new QAction(tr("Delete"), this);
350         del->setShortcuts(QKeySequence::Delete);
351         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
352         menu->addAction(del);
353
354         return menu;
355 }
356
357 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
358                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
359                 size_t base_colour, int row_title_width)
360 {
361         using namespace pv::data::decode;
362
363         vector<Annotation> a_block;
364         int p_end = INT_MIN;
365
366         double samples_per_pixel, pixels_offset;
367         tie(pixels_offset, samples_per_pixel) =
368                 get_pixels_offset_samples_per_pixel();
369
370         // Sort the annotations by start sample so that decoders
371         // can't confuse us by creating annotations out of order
372         stable_sort(annotations.begin(), annotations.end(),
373                 [](const Annotation &a, const Annotation &b) {
374                         return a.start_sample() < b.start_sample(); });
375
376         // Gather all annotations that form a visual "block" and draw them as such
377         for (const Annotation &a : annotations) {
378
379                 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
380                 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
381                 const int a_width = a_end - a_start;
382
383                 const int delta = a_end - p_end;
384
385                 bool a_is_separate = false;
386
387                 // Annotation wider than the threshold for a useful label width?
388                 if (a_width >= min_useful_label_width_) {
389                         for (const QString &ann_text : a.annotations()) {
390                                 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
391                                 // Annotation wide enough to fit a label? Don't put it in a block then
392                                 if (w <= a_width) {
393                                         a_is_separate = true;
394                                         break;
395                                 }
396                         }
397                 }
398
399                 // Were the previous and this annotation more than a pixel apart?
400                 if ((abs(delta) > 1) || a_is_separate) {
401                         // Block was broken, draw annotations that form the current block
402                         if (a_block.size() == 1) {
403                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
404                                         row_title_width);
405                         }
406                         else
407                                 draw_annotation_block(a_block, p, h, y, base_colour);
408
409                         a_block.clear();
410                 }
411
412                 if (a_is_separate) {
413                         draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
414                         // Next annotation must start a new block. delta will be > 1
415                         // because we set p_end to INT_MIN but that's okay since
416                         // a_block will be empty, so nothing will be drawn
417                         p_end = INT_MIN;
418                 } else {
419                         a_block.push_back(a);
420                         p_end = a_end;
421                 }
422         }
423
424         if (a_block.size() == 1)
425                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
426                         row_title_width);
427         else
428                 draw_annotation_block(a_block, p, h, y, base_colour);
429 }
430
431 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
432         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
433         size_t base_colour, int row_title_width) const
434 {
435         double samples_per_pixel, pixels_offset;
436         tie(pixels_offset, samples_per_pixel) =
437                 get_pixels_offset_samples_per_pixel();
438
439         const double start = a.start_sample() / samples_per_pixel -
440                 pixels_offset;
441         const double end = a.end_sample() / samples_per_pixel - pixels_offset;
442
443         const size_t colour = (base_colour + a.format()) % countof(Colours);
444         p.setPen(OutlineColours[colour]);
445         p.setBrush(Colours[colour]);
446
447         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
448                 return;
449
450         if (a.start_sample() == a.end_sample())
451                 draw_instant(a, p, h, start, y);
452         else
453                 draw_range(a, p, h, start, end, y, pp, row_title_width);
454 }
455
456 void DecodeTrace::draw_annotation_block(
457         vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
458         int y, size_t base_colour) const
459 {
460         using namespace pv::data::decode;
461
462         if (annotations.empty())
463                 return;
464
465         double samples_per_pixel, pixels_offset;
466         tie(pixels_offset, samples_per_pixel) =
467                 get_pixels_offset_samples_per_pixel();
468
469         const double start = annotations.front().start_sample() /
470                 samples_per_pixel - pixels_offset;
471         const double end = annotations.back().end_sample() /
472                 samples_per_pixel - pixels_offset;
473
474         const double top = y + .5 - h / 2;
475         const double bottom = y + .5 + h / 2;
476
477         const size_t colour = (base_colour + annotations.front().format()) %
478                 countof(Colours);
479
480         // Check if all annotations are of the same type (i.e. we can use one color)
481         // or if we should use a neutral color (i.e. gray)
482         const int format = annotations.front().format();
483         const bool single_format = all_of(
484                 annotations.begin(), annotations.end(),
485                 [&](const Annotation &a) { return a.format() == format; });
486
487         const QRectF rect(start, top, end - start, bottom - top);
488         const int r = h / 4;
489
490         p.setPen(QPen(Qt::NoPen));
491         p.setBrush(Qt::white);
492         p.drawRoundedRect(rect, r, r);
493
494         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
495         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
496                 Qt::Dense4Pattern));
497         p.drawRoundedRect(rect, r, r);
498 }
499
500 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
501         int h, double x, int y) const
502 {
503         const QString text = a.annotations().empty() ?
504                 QString() : a.annotations().back();
505         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
506                 0.0) + h;
507         const QRectF rect(x - w / 2, y - h / 2, w, h);
508
509         p.drawRoundedRect(rect, h / 2, h / 2);
510
511         p.setPen(Qt::black);
512         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
513 }
514
515 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
516         int h, double start, double end, int y, const ViewItemPaintParams &pp,
517         int row_title_width) const
518 {
519         const double top = y + .5 - h / 2;
520         const double bottom = y + .5 + h / 2;
521         const vector<QString> annotations = a.annotations();
522
523         // If the two ends are within 1 pixel, draw a vertical line
524         if (start + 1.0 > end) {
525                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
526                 return;
527         }
528
529         const double cap_width = min((end - start) / 4, EndCapWidth);
530
531         QPointF pts[] = {
532                 QPointF(start, y + .5f),
533                 QPointF(start + cap_width, top),
534                 QPointF(end - cap_width, top),
535                 QPointF(end, y + .5f),
536                 QPointF(end - cap_width, bottom),
537                 QPointF(start + cap_width, bottom)
538         };
539
540         p.drawConvexPolygon(pts, countof(pts));
541
542         if (annotations.empty())
543                 return;
544
545         const int ann_start = start + cap_width;
546         const int ann_end = end - cap_width;
547
548         const int real_start = max(ann_start, pp.left() + row_title_width);
549         const int real_end = min(ann_end, pp.right());
550         const int real_width = real_end - real_start;
551
552         QRectF rect(real_start, y - h / 2, real_width, h);
553         if (rect.width() <= 4)
554                 return;
555
556         p.setPen(Qt::black);
557
558         // Try to find an annotation that will fit
559         QString best_annotation;
560         int best_width = 0;
561
562         for (const QString &a : annotations) {
563                 const int w = p.boundingRect(QRectF(), 0, a).width();
564                 if (w <= rect.width() && w > best_width)
565                         best_annotation = a, best_width = w;
566         }
567
568         if (best_annotation.isEmpty())
569                 best_annotation = annotations.back();
570
571         // If not ellide the last in the list
572         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
573                 best_annotation, Qt::ElideRight, rect.width()));
574 }
575
576 void DecodeTrace::draw_error(QPainter &p, const QString &message,
577         const ViewItemPaintParams &pp)
578 {
579         const int y = get_visual_y();
580
581         p.setPen(ErrorBgColour.darker());
582         p.setBrush(ErrorBgColour);
583
584         const QRectF bounding_rect =
585                 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
586         const QRectF text_rect = p.boundingRect(bounding_rect,
587                 Qt::AlignCenter, message);
588         const float r = text_rect.height() / 4;
589
590         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
591                 Qt::AbsoluteSize);
592
593         p.setPen(Qt::black);
594         p.drawText(text_rect, message);
595 }
596
597 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
598         int right) const
599 {
600         using namespace pv::data;
601         using pv::data::decode::Decoder;
602
603         double samples_per_pixel, pixels_offset;
604
605         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
606
607         assert(decoder_stack);
608
609         shared_ptr<Logic> data;
610         shared_ptr<data::SignalBase> signalbase;
611
612         const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
613
614         // We get the logic data of the first channel in the list.
615         // This works because we are currently assuming all
616         // LogicSignals have the same data/segment
617         for (const shared_ptr<Decoder> &dec : stack)
618                 if (dec && !dec->channels().empty() &&
619                         ((signalbase = (*dec->channels().begin()).second)) &&
620                         ((data = signalbase->logic_data())))
621                         break;
622
623         if (!data || data->logic_segments().empty())
624                 return;
625
626         const shared_ptr<LogicSegment> segment = data->logic_segments().front();
627         assert(segment);
628         const int64_t sample_count = (int64_t)segment->get_sample_count();
629         if (sample_count == 0)
630                 return;
631
632         const int64_t samples_decoded = decoder_stack->samples_decoded();
633         if (sample_count == samples_decoded)
634                 return;
635
636         const int y = get_visual_y();
637
638         tie(pixels_offset, samples_per_pixel) =
639                 get_pixels_offset_samples_per_pixel();
640
641         const double start = max(samples_decoded /
642                 samples_per_pixel - pixels_offset, left - 1.0);
643         const double end = min(sample_count / samples_per_pixel -
644                 pixels_offset, right + 1.0);
645         const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
646
647         p.setPen(QPen(Qt::NoPen));
648         p.setBrush(Qt::white);
649         p.drawRect(no_decode_rect);
650
651         p.setPen(NoDecodeColour);
652         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
653         p.drawRect(no_decode_rect);
654 }
655
656 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
657 {
658         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
659
660         assert(owner_);
661         assert(decoder_stack);
662
663         const View *view = owner_->view();
664         assert(view);
665
666         const double scale = view->scale();
667         assert(scale > 0);
668
669         const double pixels_offset =
670                 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
671
672         double samplerate = decoder_stack->samplerate();
673
674         // Show sample rate as 1Hz when it is unknown
675         if (samplerate == 0.0)
676                 samplerate = 1.0;
677
678         return make_pair(pixels_offset, samplerate * scale);
679 }
680
681 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
682         int x_start, int x_end) const
683 {
684         double samples_per_pixel, pixels_offset;
685         tie(pixels_offset, samples_per_pixel) =
686                 get_pixels_offset_samples_per_pixel();
687
688         const uint64_t start = (uint64_t)max(
689                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
690         const uint64_t end = (uint64_t)max(
691                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
692
693         return make_pair(start, end);
694 }
695
696 int DecodeTrace::get_row_at_point(const QPoint &point)
697 {
698         if (!row_height_)
699                 return -1;
700
701         const int y = (point.y() - get_visual_y() + row_height_ / 2);
702
703         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
704         if (y < 0)
705                 return -1;
706
707         const int row = y / row_height_;
708
709         if (row >= (int)visible_rows_.size())
710                 return -1;
711
712         return row;
713 }
714
715 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
716 {
717         using namespace pv::data::decode;
718
719         if (!enabled())
720                 return QString();
721
722         const pair<uint64_t, uint64_t> sample_range =
723                 get_sample_range(point.x(), point.x() + 1);
724         const int row = get_row_at_point(point);
725         if (row < 0)
726                 return QString();
727
728         vector<pv::data::decode::Annotation> annotations;
729
730         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
731
732         assert(decoder_stack);
733         decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
734                 sample_range.first, sample_range.second);
735
736         return (annotations.empty()) ?
737                 QString() : annotations[0].annotations().front();
738 }
739
740 void DecodeTrace::hover_point_changed()
741 {
742         assert(owner_);
743
744         const View *const view = owner_->view();
745         assert(view);
746
747         QPoint hp = view->hover_point();
748         QString ann = get_annotation_at_point(hp);
749
750         assert(view);
751
752         if (!row_height_ || ann.isEmpty()) {
753                 QToolTip::hideText();
754                 return;
755         }
756
757         const int hover_row = get_row_at_point(hp);
758
759         QFontMetrics m(QToolTip::font());
760         const QRect text_size = m.boundingRect(QRect(), 0, ann);
761
762         // This is OS-specific and unfortunately we can't query it, so
763         // use an approximation to at least try to minimize the error.
764         const int padding = 8;
765
766         // Make sure the tool tip doesn't overlap with the mouse cursor.
767         // If it did, the tool tip would constantly hide and re-appear.
768         // We also push it up by one row so that it appears above the
769         // decode trace, not below.
770         hp.setX(hp.x() - (text_size.width() / 2) - padding);
771
772         hp.setY(get_visual_y() - (row_height_ / 2) +
773                 (hover_row * row_height_) -
774                 row_height_ - text_size.height() - padding);
775
776         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
777 }
778
779 void DecodeTrace::create_decoder_form(int index,
780         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
781         QFormLayout *form)
782 {
783         const GSList *l;
784
785         assert(dec);
786         const srd_decoder *const decoder = dec->decoder();
787         assert(decoder);
788
789         const bool decoder_deletable = index > 0;
790
791         pv::widgets::DecoderGroupBox *const group =
792                 new pv::widgets::DecoderGroupBox(
793                         QString::fromUtf8(decoder->name),
794                         tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
795                                 QString::fromUtf8(decoder->desc)),
796                         nullptr, decoder_deletable);
797         group->set_decoder_visible(dec->shown());
798
799         if (decoder_deletable) {
800                 delete_mapper_.setMapping(group, index);
801                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
802         }
803
804         show_hide_mapper_.setMapping(group, index);
805         connect(group, SIGNAL(show_hide_decoder()),
806                 &show_hide_mapper_, SLOT(map()));
807
808         QFormLayout *const decoder_form = new QFormLayout;
809         group->add_layout(decoder_form);
810
811         // Add the mandatory channels
812         for (l = decoder->channels; l; l = l->next) {
813                 const struct srd_channel *const pdch =
814                         (struct srd_channel *)l->data;
815
816                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
817                 QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch);
818
819                 connect(combo, SIGNAL(currentIndexChanged(int)),
820                         this, SLOT(on_channel_selected(int)));
821                 connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)),
822                         this, SLOT(on_initial_pin_selected(int)));
823
824                 QHBoxLayout *const hlayout = new QHBoxLayout;
825                 hlayout->addWidget(combo);
826                 hlayout->addWidget(combo_initial_pin);
827
828                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
829                         .arg(QString::fromUtf8(pdch->name),
830                              QString::fromUtf8(pdch->desc)), hlayout);
831
832                 const ChannelSelector s = {combo, combo_initial_pin, dec, pdch};
833                 channel_selectors_.push_back(s);
834         }
835
836         // Add the optional channels
837         for (l = decoder->opt_channels; l; l = l->next) {
838                 const struct srd_channel *const pdch =
839                         (struct srd_channel *)l->data;
840
841                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
842                 QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch);
843
844                 connect(combo, SIGNAL(currentIndexChanged(int)),
845                         this, SLOT(on_channel_selected(int)));
846                 connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)),
847                         this, SLOT(on_initial_pin_selected(int)));
848
849                 QHBoxLayout *const hlayout = new QHBoxLayout;
850                 hlayout->addWidget(combo);
851                 hlayout->addWidget(combo_initial_pin);
852
853                 decoder_form->addRow(tr("<b>%1</b> (%2)")
854                         .arg(QString::fromUtf8(pdch->name),
855                              QString::fromUtf8(pdch->desc)), hlayout);
856
857                 const ChannelSelector s = {combo, combo_initial_pin, dec, pdch};
858                 channel_selectors_.push_back(s);
859         }
860
861         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
862
863         // Add the options
864         shared_ptr<binding::Decoder> binding(
865                 new binding::Decoder(decoder_stack, dec));
866         binding->add_properties_to_form(decoder_form, true);
867
868         bindings_.push_back(binding);
869
870         form->addRow(group);
871         decoder_forms_.push_back(group);
872 }
873
874 QComboBox* DecodeTrace::create_channel_selector(
875         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
876         const srd_channel *const pdch)
877 {
878         assert(dec);
879
880         const auto sigs(session_.signalbases());
881
882         vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
883         sort(sig_list.begin(), sig_list.end(),
884                 [](const shared_ptr<data::SignalBase> &a,
885                 const shared_ptr<data::SignalBase> &b) {
886                         return strnatcasecmp(a->name().toStdString(),
887                                 b->name().toStdString()) < 0; });
888
889         const auto channel_iter = dec->channels().find(pdch);
890
891         QComboBox *selector = new QComboBox(parent);
892
893         selector->addItem("-", qVariantFromValue((void*)nullptr));
894
895         if (channel_iter == dec->channels().end())
896                 selector->setCurrentIndex(0);
897
898         for (const shared_ptr<data::SignalBase> &b : sig_list) {
899                 assert(b);
900                 if (b->logic_data() && b->enabled()) {
901                         selector->addItem(b->name(),
902                                 qVariantFromValue((void*)b.get()));
903
904                         if (channel_iter != dec->channels().end() &&
905                                 (*channel_iter).second == b)
906                                 selector->setCurrentIndex(
907                                         selector->count() - 1);
908                 }
909         }
910
911         return selector;
912 }
913
914 QComboBox* DecodeTrace::create_channel_selector_initial_pin(QWidget *parent,
915         const shared_ptr<data::decode::Decoder> &dec, const srd_channel *const pdch)
916 {
917         QComboBox *selector = new QComboBox(parent);
918
919         selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
920         selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
921         selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
922
923         // Default to index 2 (SRD_INITIAL_PIN_SAME_AS_SAMPLE0).
924         const int idx = (!dec->initial_pins()) ? 2 : dec->initial_pins()->data[pdch->order];
925         selector->setCurrentIndex(idx);
926
927         selector->setToolTip("Initial (assumed) pin value before the first sample");
928
929         return selector;
930 }
931
932 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
933 {
934         assert(dec);
935
936         map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
937
938         const unordered_set< shared_ptr<data::SignalBase> >
939                 sigs(session_.signalbases());
940
941         GArray *const initial_pins = g_array_sized_new(FALSE, TRUE,
942                 sizeof(uint8_t), channel_selectors_.size());
943         g_array_set_size(initial_pins, channel_selectors_.size());
944
945         for (const ChannelSelector &s : channel_selectors_) {
946                 if (s.decoder_ != dec)
947                         break;
948
949                 const data::SignalBase *const selection =
950                         (data::SignalBase*)s.combo_->itemData(
951                                 s.combo_->currentIndex()).value<void*>();
952
953                 for (shared_ptr<data::SignalBase> sig : sigs)
954                         if (sig.get() == selection) {
955                                 channel_map[s.pdch_] = sig;
956                                 break;
957                         }
958
959                 int selection_initial_pin = s.combo_initial_pin_->itemData(
960                         s.combo_initial_pin_->currentIndex()).value<int>();
961
962                 initial_pins->data[s.pdch_->order] = selection_initial_pin;
963         }
964
965         dec->set_channels(channel_map);
966         dec->set_initial_pins(initial_pins);
967 }
968
969 void DecodeTrace::commit_channels()
970 {
971         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
972
973         assert(decoder_stack);
974         for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
975                 commit_decoder_channels(dec);
976
977         decoder_stack->begin_decode();
978 }
979
980 void DecodeTrace::on_new_decode_data()
981 {
982         if (owner_)
983                 owner_->row_item_appearance_changed(false, true);
984 }
985
986 void DecodeTrace::delete_pressed()
987 {
988         on_delete();
989 }
990
991 void DecodeTrace::on_delete()
992 {
993         session_.remove_decode_signal(base_);
994 }
995
996 void DecodeTrace::on_channel_selected(int)
997 {
998         commit_channels();
999 }
1000
1001 void DecodeTrace::on_initial_pin_selected(int)
1002 {
1003         commit_channels();
1004 }
1005
1006 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
1007 {
1008         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
1009
1010         assert(decoder);
1011         assert(decoder_stack);
1012         decoder_stack->push(make_shared<data::decode::Decoder>(decoder));
1013         decoder_stack->begin_decode();
1014
1015         create_popup_form();
1016 }
1017
1018 void DecodeTrace::on_delete_decoder(int index)
1019 {
1020         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
1021
1022         decoder_stack->remove(index);
1023
1024         // Update the popup
1025         create_popup_form();
1026
1027         decoder_stack->begin_decode();
1028 }
1029
1030 void DecodeTrace::on_show_hide_decoder(int index)
1031 {
1032         using pv::data::decode::Decoder;
1033
1034         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
1035
1036         const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
1037
1038         // Find the decoder in the stack
1039         auto iter = stack.cbegin();
1040         for (int i = 0; i < index; i++, iter++)
1041                 assert(iter != stack.end());
1042
1043         shared_ptr<Decoder> dec = *iter;
1044         assert(dec);
1045
1046         const bool show = !dec->shown();
1047         dec->show(show);
1048
1049         assert(index < (int)decoder_forms_.size());
1050         decoder_forms_[index]->set_decoder_visible(show);
1051
1052         if (owner_)
1053                 owner_->row_item_appearance_changed(false, true);
1054 }
1055
1056 } // namespace TraceView
1057 } // namespace views
1058 } // namespace pv