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