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