]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
Random simplifications, cosmetics/whitespace/consistency fixes.
[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/session.hpp>
44 #include <pv/strnatcmp.hpp>
45 #include <pv/data/decoderstack.hpp>
46 #include <pv/data/decode/decoder.hpp>
47 #include <pv/data/logic.hpp>
48 #include <pv/data/logicsegment.hpp>
49 #include <pv/data/decode/annotation.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
337         QHBoxLayout *stack_button_box = new QHBoxLayout;
338         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
339         form->addRow(stack_button_box);
340 }
341
342 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
343 {
344         QMenu *const menu = Trace::create_context_menu(parent);
345
346         menu->addSeparator();
347
348         QAction *const del = new QAction(tr("Delete"), this);
349         del->setShortcuts(QKeySequence::Delete);
350         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
351         menu->addAction(del);
352
353         return menu;
354 }
355
356 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
357                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
358                 size_t base_colour, int row_title_width)
359 {
360         using namespace pv::data::decode;
361
362         vector<Annotation> a_block;
363         int p_end = INT_MIN;
364
365         double samples_per_pixel, pixels_offset;
366         tie(pixels_offset, samples_per_pixel) =
367                 get_pixels_offset_samples_per_pixel();
368
369         // Sort the annotations by start sample so that decoders
370         // can't confuse us by creating annotations out of order
371         stable_sort(annotations.begin(), annotations.end(),
372                 [](const Annotation &a, const Annotation &b) {
373                         return a.start_sample() < b.start_sample(); });
374
375         // Gather all annotations that form a visual "block" and draw them as such
376         for (const Annotation &a : annotations) {
377
378                 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
379                 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
380                 const int a_width = a_end - a_start;
381
382                 const int delta = a_end - p_end;
383
384                 bool a_is_separate = false;
385
386                 // Annotation wider than the threshold for a useful label width?
387                 if (a_width >= min_useful_label_width_) {
388                         for (const QString &ann_text : a.annotations()) {
389                                 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
390                                 // Annotation wide enough to fit a label? Don't put it in a block then
391                                 if (w <= a_width) {
392                                         a_is_separate = true;
393                                         break;
394                                 }
395                         }
396                 }
397
398                 // Were the previous and this annotation more than a pixel apart?
399                 if ((abs(delta) > 1) || a_is_separate) {
400                         // Block was broken, draw annotations that form the current block
401                         if (a_block.size() == 1) {
402                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
403                                         row_title_width);
404                         }
405                         else
406                                 draw_annotation_block(a_block, p, h, y, base_colour);
407
408                         a_block.clear();
409                 }
410
411                 if (a_is_separate) {
412                         draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
413                         // Next annotation must start a new block. delta will be > 1
414                         // because we set p_end to INT_MIN but that's okay since
415                         // a_block will be empty, so nothing will be drawn
416                         p_end = INT_MIN;
417                 } else {
418                         a_block.push_back(a);
419                         p_end = a_end;
420                 }
421         }
422
423         if (a_block.size() == 1)
424                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
425                         row_title_width);
426         else
427                 draw_annotation_block(a_block, p, h, y, base_colour);
428 }
429
430 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
431         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
432         size_t base_colour, int row_title_width) const
433 {
434         double samples_per_pixel, pixels_offset;
435         tie(pixels_offset, samples_per_pixel) =
436                 get_pixels_offset_samples_per_pixel();
437
438         const double start = a.start_sample() / samples_per_pixel -
439                 pixels_offset;
440         const double end = a.end_sample() / samples_per_pixel - pixels_offset;
441
442         const size_t colour = (base_colour + a.format()) % countof(Colours);
443         p.setPen(OutlineColours[colour]);
444         p.setBrush(Colours[colour]);
445
446         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
447                 return;
448
449         if (a.start_sample() == a.end_sample())
450                 draw_instant(a, p, h, start, y);
451         else
452                 draw_range(a, p, h, start, end, y, pp, row_title_width);
453 }
454
455 void DecodeTrace::draw_annotation_block(
456         vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
457         int y, size_t base_colour) const
458 {
459         using namespace pv::data::decode;
460
461         if (annotations.empty())
462                 return;
463
464         double samples_per_pixel, pixels_offset;
465         tie(pixels_offset, samples_per_pixel) =
466                 get_pixels_offset_samples_per_pixel();
467
468         const double start = annotations.front().start_sample() /
469                 samples_per_pixel - pixels_offset;
470         const double end = annotations.back().end_sample() /
471                 samples_per_pixel - pixels_offset;
472
473         const double top = y + .5 - h / 2;
474         const double bottom = y + .5 + h / 2;
475
476         const size_t colour = (base_colour + annotations.front().format()) %
477                 countof(Colours);
478
479         // Check if all annotations are of the same type (i.e. we can use one color)
480         // or if we should use a neutral color (i.e. gray)
481         const int format = annotations.front().format();
482         const bool single_format = all_of(
483                 annotations.begin(), annotations.end(),
484                 [&](const Annotation &a) { return a.format() == format; });
485
486         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
487         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
488                 Qt::Dense4Pattern));
489         p.drawRoundedRect(
490                 QRectF(start, top, end - start, bottom - top), h / 4, h / 4);
491 }
492
493 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
494         int h, double x, int y) const
495 {
496         const QString text = a.annotations().empty() ?
497                 QString() : a.annotations().back();
498         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
499                 0.0) + h;
500         const QRectF rect(x - w / 2, y - h / 2, w, h);
501
502         p.drawRoundedRect(rect, h / 2, h / 2);
503
504         p.setPen(Qt::black);
505         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
506 }
507
508 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
509         int h, double start, double end, int y, const ViewItemPaintParams &pp,
510         int row_title_width) const
511 {
512         const double top = y + .5 - h / 2;
513         const double bottom = y + .5 + h / 2;
514         const vector<QString> annotations = a.annotations();
515
516         // If the two ends are within 1 pixel, draw a vertical line
517         if (start + 1.0 > end) {
518                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
519                 return;
520         }
521
522         const double cap_width = min((end - start) / 4, EndCapWidth);
523
524         QPointF pts[] = {
525                 QPointF(start, y + .5f),
526                 QPointF(start + cap_width, top),
527                 QPointF(end - cap_width, top),
528                 QPointF(end, y + .5f),
529                 QPointF(end - cap_width, bottom),
530                 QPointF(start + cap_width, bottom)
531         };
532
533         p.drawConvexPolygon(pts, countof(pts));
534
535         if (annotations.empty())
536                 return;
537
538         const int ann_start = start + cap_width;
539         const int ann_end = end - cap_width;
540
541         const int real_start = max(ann_start, pp.left() + row_title_width);
542         const int real_end = min(ann_end, pp.right());
543         const int real_width = real_end - real_start;
544
545         QRectF rect(real_start, y - h / 2, real_width, h);
546         if (rect.width() <= 4)
547                 return;
548
549         p.setPen(Qt::black);
550
551         // Try to find an annotation that will fit
552         QString best_annotation;
553         int best_width = 0;
554
555         for (const QString &a : annotations) {
556                 const int w = p.boundingRect(QRectF(), 0, a).width();
557                 if (w <= rect.width() && w > best_width)
558                         best_annotation = a, best_width = w;
559         }
560
561         if (best_annotation.isEmpty())
562                 best_annotation = annotations.back();
563
564         // If not ellide the last in the list
565         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
566                 best_annotation, Qt::ElideRight, rect.width()));
567 }
568
569 void DecodeTrace::draw_error(QPainter &p, const QString &message,
570         const ViewItemPaintParams &pp)
571 {
572         const int y = get_visual_y();
573
574         p.setPen(ErrorBgColour.darker());
575         p.setBrush(ErrorBgColour);
576
577         const QRectF bounding_rect =
578                 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
579         const QRectF text_rect = p.boundingRect(bounding_rect,
580                 Qt::AlignCenter, message);
581         const float r = text_rect.height() / 4;
582
583         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
584                 Qt::AbsoluteSize);
585
586         p.setPen(Qt::black);
587         p.drawText(text_rect, message);
588 }
589
590 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
591         int right) const
592 {
593         using namespace pv::data;
594         using pv::data::decode::Decoder;
595
596         double samples_per_pixel, pixels_offset;
597
598         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
599
600         assert(decoder_stack);
601
602         shared_ptr<Logic> data;
603         shared_ptr<data::SignalBase> signalbase;
604
605         const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
606
607         // We get the logic data of the first channel in the list.
608         // This works because we are currently assuming all
609         // LogicSignals have the same data/segment
610         for (const shared_ptr<Decoder> &dec : stack)
611                 if (dec && !dec->channels().empty() &&
612                         ((signalbase = (*dec->channels().begin()).second)) &&
613                         ((data = signalbase->logic_data())))
614                         break;
615
616         if (!data || data->logic_segments().empty())
617                 return;
618
619         const shared_ptr<LogicSegment> segment = data->logic_segments().front();
620         assert(segment);
621         const int64_t sample_count = (int64_t)segment->get_sample_count();
622         if (sample_count == 0)
623                 return;
624
625         const int64_t samples_decoded = decoder_stack->samples_decoded();
626         if (sample_count == samples_decoded)
627                 return;
628
629         const int y = get_visual_y();
630
631         tie(pixels_offset, samples_per_pixel) =
632                 get_pixels_offset_samples_per_pixel();
633
634         const double start = max(samples_decoded /
635                 samples_per_pixel - pixels_offset, left - 1.0);
636         const double end = min(sample_count / samples_per_pixel -
637                 pixels_offset, right + 1.0);
638         const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
639
640         p.setPen(QPen(Qt::NoPen));
641         p.setBrush(Qt::white);
642         p.drawRect(no_decode_rect);
643
644         p.setPen(NoDecodeColour);
645         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
646         p.drawRect(no_decode_rect);
647 }
648
649 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
650 {
651         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
652
653         assert(owner_);
654         assert(decoder_stack);
655
656         const View *view = owner_->view();
657         assert(view);
658
659         const double scale = view->scale();
660         assert(scale > 0);
661
662         const double pixels_offset =
663                 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
664
665         double samplerate = decoder_stack->samplerate();
666
667         // Show sample rate as 1Hz when it is unknown
668         if (samplerate == 0.0)
669                 samplerate = 1.0;
670
671         return make_pair(pixels_offset, samplerate * scale);
672 }
673
674 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
675         int x_start, int x_end) const
676 {
677         double samples_per_pixel, pixels_offset;
678         tie(pixels_offset, samples_per_pixel) =
679                 get_pixels_offset_samples_per_pixel();
680
681         const uint64_t start = (uint64_t)max(
682                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
683         const uint64_t end = (uint64_t)max(
684                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
685
686         return make_pair(start, end);
687 }
688
689 int DecodeTrace::get_row_at_point(const QPoint &point)
690 {
691         if (!row_height_)
692                 return -1;
693
694         const int y = (point.y() - get_visual_y() + row_height_ / 2);
695
696         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
697         if (y < 0)
698                 return -1;
699
700         const int row = y / row_height_;
701
702         if (row >= (int)visible_rows_.size())
703                 return -1;
704
705         return row;
706 }
707
708 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
709 {
710         using namespace pv::data::decode;
711
712         if (!enabled())
713                 return QString();
714
715         const pair<uint64_t, uint64_t> sample_range =
716                 get_sample_range(point.x(), point.x() + 1);
717         const int row = get_row_at_point(point);
718         if (row < 0)
719                 return QString();
720
721         vector<pv::data::decode::Annotation> annotations;
722
723         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
724
725         assert(decoder_stack);
726         decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
727                 sample_range.first, sample_range.second);
728
729         return (annotations.empty()) ?
730                 QString() : annotations[0].annotations().front();
731 }
732
733 void DecodeTrace::hover_point_changed()
734 {
735         assert(owner_);
736
737         const View *const view = owner_->view();
738         assert(view);
739
740         QPoint hp = view->hover_point();
741         QString ann = get_annotation_at_point(hp);
742
743         assert(view);
744
745         if (!row_height_ || ann.isEmpty()) {
746                 QToolTip::hideText();
747                 return;
748         }
749
750         const int hover_row = get_row_at_point(hp);
751
752         QFontMetrics m(QToolTip::font());
753         const QRect text_size = m.boundingRect(QRect(), 0, ann);
754
755         // This is OS-specific and unfortunately we can't query it, so
756         // use an approximation to at least try to minimize the error.
757         const int padding = 8;
758
759         // Make sure the tool tip doesn't overlap with the mouse cursor.
760         // If it did, the tool tip would constantly hide and re-appear.
761         // We also push it up by one row so that it appears above the
762         // decode trace, not below.
763         hp.setX(hp.x() - (text_size.width() / 2) - padding);
764
765         hp.setY(get_visual_y() - (row_height_ / 2) +
766                 (hover_row * row_height_) -
767                 row_height_ - text_size.height() - padding);
768
769         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
770 }
771
772 void DecodeTrace::create_decoder_form(int index,
773         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
774         QFormLayout *form)
775 {
776         const GSList *l;
777
778         assert(dec);
779         const srd_decoder *const decoder = dec->decoder();
780         assert(decoder);
781
782         const bool decoder_deletable = index > 0;
783
784         pv::widgets::DecoderGroupBox *const group =
785                 new pv::widgets::DecoderGroupBox(
786                         QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
787         group->set_decoder_visible(dec->shown());
788
789         if (decoder_deletable) {
790                 delete_mapper_.setMapping(group, index);
791                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
792         }
793
794         show_hide_mapper_.setMapping(group, index);
795         connect(group, SIGNAL(show_hide_decoder()),
796                 &show_hide_mapper_, SLOT(map()));
797
798         QFormLayout *const decoder_form = new QFormLayout;
799         group->add_layout(decoder_form);
800
801         // Add the mandatory channels
802         for (l = decoder->channels; l; l = l->next) {
803                 const struct srd_channel *const pdch =
804                         (struct srd_channel *)l->data;
805                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
806                 connect(combo, SIGNAL(currentIndexChanged(int)),
807                         this, SLOT(on_channel_selected(int)));
808                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
809                         .arg(QString::fromUtf8(pdch->name),
810                              QString::fromUtf8(pdch->desc)), combo);
811
812                 const ChannelSelector s = {combo, dec, pdch};
813                 channel_selectors_.push_back(s);
814         }
815
816         // Add the optional channels
817         for (l = decoder->opt_channels; l; l = l->next) {
818                 const struct srd_channel *const pdch =
819                         (struct srd_channel *)l->data;
820                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
821                 connect(combo, SIGNAL(currentIndexChanged(int)),
822                         this, SLOT(on_channel_selected(int)));
823                 decoder_form->addRow(tr("<b>%1</b> (%2)")
824                         .arg(QString::fromUtf8(pdch->name),
825                              QString::fromUtf8(pdch->desc)), combo);
826
827                 const ChannelSelector s = {combo, dec, pdch};
828                 channel_selectors_.push_back(s);
829         }
830
831         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
832
833         // Add the options
834         shared_ptr<binding::Decoder> binding(
835                 new binding::Decoder(decoder_stack, dec));
836         binding->add_properties_to_form(decoder_form, true);
837
838         bindings_.push_back(binding);
839
840         form->addRow(group);
841         decoder_forms_.push_back(group);
842 }
843
844 QComboBox* DecodeTrace::create_channel_selector(
845         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
846         const srd_channel *const pdch)
847 {
848         assert(dec);
849
850         const auto sigs(session_.signalbases());
851
852         vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
853         sort(sig_list.begin(), sig_list.end(),
854                 [](const shared_ptr<data::SignalBase> &a,
855                 const shared_ptr<data::SignalBase> &b) {
856                         return strnatcasecmp(a->name().toStdString(),
857                                 b->name().toStdString()) < 0; });
858
859         const auto channel_iter = dec->channels().find(pdch);
860
861         QComboBox *selector = new QComboBox(parent);
862
863         selector->addItem("-", qVariantFromValue((void*)nullptr));
864
865         if (channel_iter == dec->channels().end())
866                 selector->setCurrentIndex(0);
867
868         for (const shared_ptr<data::SignalBase> &b : sig_list) {
869                 assert(b);
870                 if (b->logic_data() && b->enabled()) {
871                         selector->addItem(b->name(),
872                                 qVariantFromValue((void*)b.get()));
873
874                         if (channel_iter != dec->channels().end() &&
875                                 (*channel_iter).second == b)
876                                 selector->setCurrentIndex(
877                                         selector->count() - 1);
878                 }
879         }
880
881         return selector;
882 }
883
884 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
885 {
886         assert(dec);
887
888         map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
889
890         const unordered_set< shared_ptr<data::SignalBase> >
891                 sigs(session_.signalbases());
892
893         for (const ChannelSelector &s : channel_selectors_) {
894                 if (s.decoder_ != dec)
895                         break;
896
897                 const data::SignalBase *const selection =
898                         (data::SignalBase*)s.combo_->itemData(
899                                 s.combo_->currentIndex()).value<void*>();
900
901                 for (shared_ptr<data::SignalBase> sig : sigs)
902                         if (sig.get() == selection) {
903                                 channel_map[s.pdch_] = sig;
904                                 break;
905                         }
906         }
907
908         dec->set_channels(channel_map);
909 }
910
911 void DecodeTrace::commit_channels()
912 {
913         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
914
915         assert(decoder_stack);
916         for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
917                 commit_decoder_channels(dec);
918
919         decoder_stack->begin_decode();
920 }
921
922 void DecodeTrace::on_new_decode_data()
923 {
924         if (owner_)
925                 owner_->row_item_appearance_changed(false, true);
926 }
927
928 void DecodeTrace::delete_pressed()
929 {
930         on_delete();
931 }
932
933 void DecodeTrace::on_delete()
934 {
935         session_.remove_decode_signal(base_);
936 }
937
938 void DecodeTrace::on_channel_selected(int)
939 {
940         commit_channels();
941 }
942
943 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
944 {
945         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
946
947         assert(decoder);
948         assert(decoder_stack);
949         decoder_stack->push(make_shared<data::decode::Decoder>(decoder));
950         decoder_stack->begin_decode();
951
952         create_popup_form();
953 }
954
955 void DecodeTrace::on_delete_decoder(int index)
956 {
957         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
958
959         decoder_stack->remove(index);
960
961         // Update the popup
962         create_popup_form();
963
964         decoder_stack->begin_decode();
965 }
966
967 void DecodeTrace::on_show_hide_decoder(int index)
968 {
969         using pv::data::decode::Decoder;
970
971         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
972
973         const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
974
975         // Find the decoder in the stack
976         auto iter = stack.cbegin();
977         for (int i = 0; i < index; i++, iter++)
978                 assert(iter != stack.end());
979
980         shared_ptr<Decoder> dec = *iter;
981         assert(dec);
982
983         const bool show = !dec->shown();
984         dec->show(show);
985
986         assert(index < (int)decoder_forms_.size());
987         decoder_forms_[index]->set_decoder_visible(show);
988
989         if (owner_)
990                 owner_->row_item_appearance_changed(false, true);
991 }
992
993 } // namespace TraceView
994 } // namespace views
995 } // namespace pv