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