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