]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
f45ecf58e2e1a94dba92b775ccf198cfea917c2c
[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, const 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, const 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, const 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                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
816                 connect(combo, SIGNAL(currentIndexChanged(int)),
817                         this, SLOT(on_channel_selected(int)));
818                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
819                         .arg(QString::fromUtf8(pdch->name),
820                              QString::fromUtf8(pdch->desc)), combo);
821
822                 const ChannelSelector s = {combo, dec, pdch};
823                 channel_selectors_.push_back(s);
824         }
825
826         // Add the optional channels
827         for (l = decoder->opt_channels; l; l = l->next) {
828                 const struct srd_channel *const pdch =
829                         (struct srd_channel *)l->data;
830                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
831                 connect(combo, SIGNAL(currentIndexChanged(int)),
832                         this, SLOT(on_channel_selected(int)));
833                 decoder_form->addRow(tr("<b>%1</b> (%2)")
834                         .arg(QString::fromUtf8(pdch->name),
835                              QString::fromUtf8(pdch->desc)), combo);
836
837                 const ChannelSelector s = {combo, dec, pdch};
838                 channel_selectors_.push_back(s);
839         }
840
841         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
842
843         // Add the options
844         shared_ptr<binding::Decoder> binding(
845                 new binding::Decoder(decoder_stack, dec));
846         binding->add_properties_to_form(decoder_form, true);
847
848         bindings_.push_back(binding);
849
850         form->addRow(group);
851         decoder_forms_.push_back(group);
852 }
853
854 QComboBox* DecodeTrace::create_channel_selector(
855         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
856         const srd_channel *const pdch)
857 {
858         assert(dec);
859
860         const auto sigs(session_.signalbases());
861
862         vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
863         sort(sig_list.begin(), sig_list.end(),
864                 [](const shared_ptr<data::SignalBase> &a,
865                 const shared_ptr<data::SignalBase> &b) {
866                         return strnatcasecmp(a->name().toStdString(),
867                                 b->name().toStdString()) < 0; });
868
869         const auto channel_iter = dec->channels().find(pdch);
870
871         QComboBox *selector = new QComboBox(parent);
872
873         selector->addItem("-", qVariantFromValue((void*)nullptr));
874
875         if (channel_iter == dec->channels().end())
876                 selector->setCurrentIndex(0);
877
878         for (const shared_ptr<data::SignalBase> &b : sig_list) {
879                 assert(b);
880                 if (b->logic_data() && b->enabled()) {
881                         selector->addItem(b->name(),
882                                 qVariantFromValue((void*)b.get()));
883
884                         if (channel_iter != dec->channels().end() &&
885                                 (*channel_iter).second == b)
886                                 selector->setCurrentIndex(
887                                         selector->count() - 1);
888                 }
889         }
890
891         return selector;
892 }
893
894 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
895 {
896         assert(dec);
897
898         map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
899
900         const unordered_set< shared_ptr<data::SignalBase> >
901                 sigs(session_.signalbases());
902
903         for (const ChannelSelector &s : channel_selectors_) {
904                 if (s.decoder_ != dec)
905                         break;
906
907                 const data::SignalBase *const selection =
908                         (data::SignalBase*)s.combo_->itemData(
909                                 s.combo_->currentIndex()).value<void*>();
910
911                 for (shared_ptr<data::SignalBase> sig : sigs)
912                         if (sig.get() == selection) {
913                                 channel_map[s.pdch_] = sig;
914                                 break;
915                         }
916         }
917
918         dec->set_channels(channel_map);
919 }
920
921 void DecodeTrace::commit_channels()
922 {
923         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
924
925         assert(decoder_stack);
926         for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
927                 commit_decoder_channels(dec);
928
929         decoder_stack->begin_decode();
930 }
931
932 void DecodeTrace::on_new_decode_data()
933 {
934         if (owner_)
935                 owner_->row_item_appearance_changed(false, true);
936 }
937
938 void DecodeTrace::delete_pressed()
939 {
940         on_delete();
941 }
942
943 void DecodeTrace::on_delete()
944 {
945         session_.remove_decode_signal(base_);
946 }
947
948 void DecodeTrace::on_channel_selected(int)
949 {
950         commit_channels();
951 }
952
953 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
954 {
955         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
956
957         assert(decoder);
958         assert(decoder_stack);
959         decoder_stack->push(make_shared<data::decode::Decoder>(decoder));
960         decoder_stack->begin_decode();
961
962         create_popup_form();
963 }
964
965 void DecodeTrace::on_delete_decoder(int index)
966 {
967         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
968
969         decoder_stack->remove(index);
970
971         // Update the popup
972         create_popup_form();
973
974         decoder_stack->begin_decode();
975 }
976
977 void DecodeTrace::on_show_hide_decoder(int index)
978 {
979         using pv::data::decode::Decoder;
980
981         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
982
983         const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
984
985         // Find the decoder in the stack
986         auto iter = stack.cbegin();
987         for (int i = 0; i < index; i++, iter++)
988                 assert(iter != stack.end());
989
990         shared_ptr<Decoder> dec = *iter;
991         assert(dec);
992
993         const bool show = !dec->shown();
994         dec->show(show);
995
996         assert(index < (int)decoder_forms_.size());
997         decoder_forms_[index]->set_decoder_visible(show);
998
999         if (owner_)
1000                 owner_->row_item_appearance_changed(false, true);
1001 }
1002
1003 } // namespace TraceView
1004 } // namespace views
1005 } // namespace pv