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