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