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