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