]> sigrok.org Git - pulseview.git/blob - pv/views/trace/decodetrace.cpp
b0395eaa05dc685828a6d31d3d69a1383be59361
[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::min;
60 using std::out_of_range;
61 using std::pair;
62 using std::shared_ptr;
63 using std::tie;
64 using std::vector;
65
66 using pv::data::decode::Annotation;
67 using pv::data::decode::Row;
68 using pv::data::DecodeChannel;
69 using pv::data::DecodeSignal;
70
71 namespace pv {
72 namespace views {
73 namespace trace {
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 int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz
91
92 const QColor DecodeTrace::Colours[16] = {
93         QColor(0xEF, 0x29, 0x29),
94         QColor(0xF6, 0x6A, 0x32),
95         QColor(0xFC, 0xAE, 0x3E),
96         QColor(0xFB, 0xCA, 0x47),
97         QColor(0xFC, 0xE9, 0x4F),
98         QColor(0xCD, 0xF0, 0x40),
99         QColor(0x8A, 0xE2, 0x34),
100         QColor(0x4E, 0xDC, 0x44),
101         QColor(0x55, 0xD7, 0x95),
102         QColor(0x64, 0xD1, 0xD2),
103         QColor(0x72, 0x9F, 0xCF),
104         QColor(0xD4, 0x76, 0xC4),
105         QColor(0x9D, 0x79, 0xB9),
106         QColor(0xAD, 0x7F, 0xA8),
107         QColor(0xC2, 0x62, 0x9B),
108         QColor(0xD7, 0x47, 0x6F)
109 };
110
111 const QColor DecodeTrace::OutlineColours[16] = {
112         QColor(0x77, 0x14, 0x14),
113         QColor(0x7B, 0x35, 0x19),
114         QColor(0x7E, 0x57, 0x1F),
115         QColor(0x7D, 0x65, 0x23),
116         QColor(0x7E, 0x74, 0x27),
117         QColor(0x66, 0x78, 0x20),
118         QColor(0x45, 0x71, 0x1A),
119         QColor(0x27, 0x6E, 0x22),
120         QColor(0x2A, 0x6B, 0x4A),
121         QColor(0x32, 0x68, 0x69),
122         QColor(0x39, 0x4F, 0x67),
123         QColor(0x6A, 0x3B, 0x62),
124         QColor(0x4E, 0x3C, 0x5C),
125         QColor(0x56, 0x3F, 0x54),
126         QColor(0x61, 0x31, 0x4D),
127         QColor(0x6B, 0x23, 0x37)
128 };
129
130 DecodeTrace::DecodeTrace(pv::Session &session,
131         shared_ptr<data::SignalBase> signalbase, int index) :
132         Trace(signalbase),
133         session_(session),
134         row_height_(0),
135         max_visible_rows_(0),
136         delete_mapper_(this),
137         show_hide_mapper_(this)
138 {
139         decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
140
141         // Determine shortest string we want to see displayed in full
142         QFontMetrics m(QApplication::font());
143         min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
144
145         base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
146
147         connect(decode_signal_.get(), SIGNAL(new_annotations()),
148                 this, SLOT(on_new_annotations()));
149         connect(decode_signal_.get(), SIGNAL(decode_finished()),
150                 this, SLOT(on_decode_finished()));
151         connect(decode_signal_.get(), SIGNAL(channels_updated()),
152                 this, SLOT(on_channels_updated()));
153
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         connect(&delayed_trace_updater_, SIGNAL(timeout()),
160                 this, SLOT(on_delayed_trace_update()));
161         delayed_trace_updater_.setSingleShot(true);
162         delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
163 }
164
165 bool DecodeTrace::enabled() const
166 {
167         return true;
168 }
169
170 shared_ptr<data::SignalBase> DecodeTrace::base() const
171 {
172         return base_;
173 }
174
175 pair<int, int> DecodeTrace::v_extents() const
176 {
177         const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
178
179         // Make an empty decode trace appear symmetrical
180         const int row_count = max(1, max_visible_rows_);
181
182         return make_pair(-row_height, row_height * row_count);
183 }
184
185 void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
186 {
187         Trace::paint_back(p, pp);
188         paint_axis(p, pp, get_visual_y());
189 }
190
191 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
192 {
193         const int text_height = ViewItemPaintParams::text_height();
194         row_height_ = (text_height * 6) / 4;
195         const int annotation_height = (text_height * 5) / 4;
196
197         const QString err = decode_signal_->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 = decode_signal_->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                 decode_signal_->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, ViewItemPaintParams &pp)
259 {
260         assert(row_height_);
261
262         for (size_t i = 0; i < visible_rows_.size(); i++) {
263                 const int y = i * row_height_ + get_visual_y();
264
265                 p.setPen(QPen(Qt::NoPen));
266                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
267
268                 if (i != 0) {
269                         const QPointF points[] = {
270                                 QPointF(pp.left(), y - ArrowSize),
271                                 QPointF(pp.left() + ArrowSize, y),
272                                 QPointF(pp.left(), y + ArrowSize)
273                         };
274                         p.drawPolygon(points, countof(points));
275                 }
276
277                 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
278                         pp.right() - pp.left(), row_height_);
279                 const QString h(visible_rows_[i].title());
280                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
281                         Qt::TextDontClip;
282
283                 // Draw the outline
284                 p.setPen(QApplication::palette().color(QPalette::Base));
285                 for (int dx = -1; dx <= 1; dx++)
286                         for (int dy = -1; dy <= 1; dy++)
287                                 if (dx != 0 && dy != 0)
288                                         p.drawText(r.translated(dx, dy), f, h);
289
290                 // Draw the text
291                 p.setPen(QApplication::palette().color(QPalette::WindowText));
292                 p.drawText(r, f, h);
293         }
294 }
295
296 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
297 {
298         using pv::data::decode::Decoder;
299
300         assert(form);
301
302         // Add the standard options
303         Trace::populate_popup_form(parent, form);
304
305         // Add the decoder options
306         bindings_.clear();
307         channel_id_map_.clear();
308         init_state_map_.clear();
309         decoder_forms_.clear();
310
311         const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_stack();
312
313         if (stack.empty()) {
314                 QLabel *const l = new QLabel(
315                         tr("<p><i>No decoders in the stack</i></p>"));
316                 l->setAlignment(Qt::AlignCenter);
317                 form->addRow(l);
318         } else {
319                 auto iter = stack.cbegin();
320                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
321                         shared_ptr<Decoder> dec(*iter);
322                         create_decoder_form(i, dec, parent, form);
323                 }
324
325                 form->addRow(new QLabel(
326                         tr("<i>* Required channels</i>"), parent));
327         }
328
329         // Add stacking button
330         pv::widgets::DecoderMenu *const decoder_menu =
331                 new pv::widgets::DecoderMenu(parent);
332         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
333                 this, SLOT(on_stack_decoder(srd_decoder*)));
334
335         QPushButton *const stack_button =
336                 new QPushButton(tr("Stack Decoder"), parent);
337         stack_button->setMenu(decoder_menu);
338         stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
339
340         QHBoxLayout *stack_button_box = new QHBoxLayout;
341         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
342         form->addRow(stack_button_box);
343 }
344
345 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
346 {
347         QMenu *const menu = Trace::create_context_menu(parent);
348
349         menu->addSeparator();
350
351         QAction *const del = new QAction(tr("Delete"), this);
352         del->setShortcuts(QKeySequence::Delete);
353         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
354         menu->addAction(del);
355
356         return menu;
357 }
358
359 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
360                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
361                 size_t base_colour, int row_title_width)
362 {
363         using namespace pv::data::decode;
364
365         vector<Annotation> a_block;
366         int p_end = INT_MIN;
367
368         double samples_per_pixel, pixels_offset;
369         tie(pixels_offset, samples_per_pixel) =
370                 get_pixels_offset_samples_per_pixel();
371
372         // Sort the annotations by start sample so that decoders
373         // can't confuse us by creating annotations out of order
374         stable_sort(annotations.begin(), annotations.end(),
375                 [](const Annotation &a, const Annotation &b) {
376                         return a.start_sample() < b.start_sample(); });
377
378         // Gather all annotations that form a visual "block" and draw them as such
379         for (const Annotation &a : annotations) {
380
381                 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
382                 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
383                 const int a_width = a_end - a_start;
384
385                 const int delta = a_end - p_end;
386
387                 bool a_is_separate = false;
388
389                 // Annotation wider than the threshold for a useful label width?
390                 if (a_width >= min_useful_label_width_) {
391                         for (const QString &ann_text : a.annotations()) {
392                                 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
393                                 // Annotation wide enough to fit a label? Don't put it in a block then
394                                 if (w <= a_width) {
395                                         a_is_separate = true;
396                                         break;
397                                 }
398                         }
399                 }
400
401                 // Were the previous and this annotation more than a pixel apart?
402                 if ((abs(delta) > 1) || a_is_separate) {
403                         // Block was broken, draw annotations that form the current block
404                         if (a_block.size() == 1) {
405                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
406                                         row_title_width);
407                         }
408                         else
409                                 draw_annotation_block(a_block, p, h, y, base_colour);
410
411                         a_block.clear();
412                 }
413
414                 if (a_is_separate) {
415                         draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
416                         // Next annotation must start a new block. delta will be > 1
417                         // because we set p_end to INT_MIN but that's okay since
418                         // a_block will be empty, so nothing will be drawn
419                         p_end = INT_MIN;
420                 } else {
421                         a_block.push_back(a);
422                         p_end = a_end;
423                 }
424         }
425
426         if (a_block.size() == 1)
427                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
428                         row_title_width);
429         else
430                 draw_annotation_block(a_block, p, h, y, base_colour);
431 }
432
433 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
434         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
435         size_t base_colour, int row_title_width) const
436 {
437         double samples_per_pixel, pixels_offset;
438         tie(pixels_offset, samples_per_pixel) =
439                 get_pixels_offset_samples_per_pixel();
440
441         const double start = a.start_sample() / samples_per_pixel -
442                 pixels_offset;
443         const double end = a.end_sample() / samples_per_pixel - pixels_offset;
444
445         const size_t colour = (base_colour + a.format()) % countof(Colours);
446         p.setPen(OutlineColours[colour]);
447         p.setBrush(Colours[colour]);
448
449         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
450                 return;
451
452         if (a.start_sample() == a.end_sample())
453                 draw_instant(a, p, h, start, y);
454         else
455                 draw_range(a, p, h, start, end, y, pp, 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         const QRectF rect(start, top, end - start, bottom - top);
490         const int r = h / 4;
491
492         p.setPen(QPen(Qt::NoPen));
493         p.setBrush(Qt::white);
494         p.drawRoundedRect(rect, r, r);
495
496         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
497         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
498                 Qt::Dense4Pattern));
499         p.drawRoundedRect(rect, r, r);
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.left(), INT_MIN / 2 + y, pp.right(), 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, int right) const
600 {
601         using namespace pv::data;
602         using pv::data::decode::Decoder;
603
604         double samples_per_pixel, pixels_offset;
605
606         const int64_t sample_count = decode_signal_->get_working_sample_count();
607         if (sample_count == 0)
608                 return;
609
610         const int64_t samples_decoded = decode_signal_->get_decoded_sample_count();
611         if (sample_count == samples_decoded)
612                 return;
613
614         const int y = get_visual_y();
615
616         tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
617
618         const double start = max(samples_decoded /
619                 samples_per_pixel - pixels_offset, left - 1.0);
620         const double end = min(sample_count / samples_per_pixel -
621                 pixels_offset, right + 1.0);
622         const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
623
624         p.setPen(QPen(Qt::NoPen));
625         p.setBrush(Qt::white);
626         p.drawRect(no_decode_rect);
627
628         p.setPen(NoDecodeColour);
629         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
630         p.drawRect(no_decode_rect);
631 }
632
633 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
634 {
635         assert(owner_);
636
637         const View *view = owner_->view();
638         assert(view);
639
640         const double scale = view->scale();
641         assert(scale > 0);
642
643         const double pixels_offset =
644                 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
645
646         double samplerate = decode_signal_->samplerate();
647
648         // Show sample rate as 1Hz when it is unknown
649         if (samplerate == 0.0)
650                 samplerate = 1.0;
651
652         return make_pair(pixels_offset, samplerate * scale);
653 }
654
655 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
656         int x_start, int x_end) const
657 {
658         double samples_per_pixel, pixels_offset;
659         tie(pixels_offset, samples_per_pixel) =
660                 get_pixels_offset_samples_per_pixel();
661
662         const uint64_t start = (uint64_t)max(
663                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
664         const uint64_t end = (uint64_t)max(
665                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
666
667         return make_pair(start, end);
668 }
669
670 int DecodeTrace::get_row_at_point(const QPoint &point)
671 {
672         if (!row_height_)
673                 return -1;
674
675         const int y = (point.y() - get_visual_y() + row_height_ / 2);
676
677         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
678         if (y < 0)
679                 return -1;
680
681         const int row = y / row_height_;
682
683         if (row >= (int)visible_rows_.size())
684                 return -1;
685
686         return row;
687 }
688
689 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
690 {
691         using namespace pv::data::decode;
692
693         if (!enabled())
694                 return QString();
695
696         const pair<uint64_t, uint64_t> sample_range =
697                 get_sample_range(point.x(), point.x() + 1);
698         const int row = get_row_at_point(point);
699         if (row < 0)
700                 return QString();
701
702         vector<pv::data::decode::Annotation> annotations;
703
704         decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
705                 sample_range.first, sample_range.second);
706
707         return (annotations.empty()) ?
708                 QString() : annotations[0].annotations().front();
709 }
710
711 void DecodeTrace::hover_point_changed()
712 {
713         assert(owner_);
714
715         const View *const view = owner_->view();
716         assert(view);
717
718         QPoint hp = view->hover_point();
719         QString ann = get_annotation_at_point(hp);
720
721         assert(view);
722
723         if (!row_height_ || ann.isEmpty()) {
724                 QToolTip::hideText();
725                 return;
726         }
727
728         const int hover_row = get_row_at_point(hp);
729
730         QFontMetrics m(QToolTip::font());
731         const QRect text_size = m.boundingRect(QRect(), 0, ann);
732
733         // This is OS-specific and unfortunately we can't query it, so
734         // use an approximation to at least try to minimize the error.
735         const int padding = 8;
736
737         // Make sure the tool tip doesn't overlap with the mouse cursor.
738         // If it did, the tool tip would constantly hide and re-appear.
739         // We also push it up by one row so that it appears above the
740         // decode trace, not below.
741         hp.setX(hp.x() - (text_size.width() / 2) - padding);
742
743         hp.setY(get_visual_y() - (row_height_ / 2) +
744                 (hover_row * row_height_) -
745                 row_height_ - text_size.height() - padding);
746
747         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
748 }
749
750 void DecodeTrace::create_decoder_form(int index,
751         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
752         QFormLayout *form)
753 {
754         GlobalSettings settings;
755
756         assert(dec);
757         const srd_decoder *const decoder = dec->decoder();
758         assert(decoder);
759
760         const bool decoder_deletable = index > 0;
761
762         pv::widgets::DecoderGroupBox *const group =
763                 new pv::widgets::DecoderGroupBox(
764                         QString::fromUtf8(decoder->name),
765                         tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
766                                 QString::fromUtf8(decoder->desc)),
767                         nullptr, decoder_deletable);
768         group->set_decoder_visible(dec->shown());
769
770         if (decoder_deletable) {
771                 delete_mapper_.setMapping(group, index);
772                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
773         }
774
775         show_hide_mapper_.setMapping(group, index);
776         connect(group, SIGNAL(show_hide_decoder()),
777                 &show_hide_mapper_, SLOT(map()));
778
779         QFormLayout *const decoder_form = new QFormLayout;
780         group->add_layout(decoder_form);
781
782         const vector<DecodeChannel> channels = decode_signal_->get_channels();
783
784         // Add the channels
785         for (DecodeChannel ch : channels) {
786                 // Ignore channels not part of the decoder we create the form for
787                 if (ch.decoder_ != dec)
788                         continue;
789
790                 QComboBox *const combo = create_channel_selector(parent, &ch);
791                 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
792
793                 channel_id_map_[combo] = ch.id;
794                 init_state_map_[combo_init_state] = ch.id;
795
796                 connect(combo, SIGNAL(currentIndexChanged(int)),
797                         this, SLOT(on_channel_selected(int)));
798                 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
799                         this, SLOT(on_init_state_changed(int)));
800
801                 QHBoxLayout *const hlayout = new QHBoxLayout;
802                 hlayout->addWidget(combo);
803                 hlayout->addWidget(combo_init_state);
804
805                 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
806                         combo_init_state->hide();
807
808                 const QString required_flag = ch.is_optional ? QString() : QString("*");
809                 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
810                         .arg(ch.name, ch.desc, required_flag), hlayout);
811         }
812
813         // Add the options
814         shared_ptr<binding::Decoder> binding(
815                 new binding::Decoder(decode_signal_, dec));
816         binding->add_properties_to_form(decoder_form, true);
817
818         bindings_.push_back(binding);
819
820         form->addRow(group);
821         decoder_forms_.push_back(group);
822 }
823
824 QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
825 {
826         const auto sigs(session_.signalbases());
827
828         // Sort signals in natural order
829         vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
830         sort(sig_list.begin(), sig_list.end(),
831                 [](const shared_ptr<data::SignalBase> &a,
832                 const shared_ptr<data::SignalBase> &b) {
833                         return strnatcasecmp(a->name().toStdString(),
834                                 b->name().toStdString()) < 0; });
835
836         QComboBox *selector = new QComboBox(parent);
837
838         selector->addItem("-", qVariantFromValue((void*)nullptr));
839
840         if (!ch->assigned_signal)
841                 selector->setCurrentIndex(0);
842
843         for (const shared_ptr<data::SignalBase> &b : sig_list) {
844                 assert(b);
845                 if (b->logic_data() && b->enabled()) {
846                         selector->addItem(b->name(),
847                                 qVariantFromValue((void*)b.get()));
848
849                         if (ch->assigned_signal == b.get())
850                                 selector->setCurrentIndex(selector->count() - 1);
851                 }
852         }
853
854         return selector;
855 }
856
857 QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
858         const DecodeChannel *ch)
859 {
860         QComboBox *selector = new QComboBox(parent);
861
862         selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
863         selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
864         selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
865
866         selector->setCurrentIndex(ch->initial_pin_state);
867
868         selector->setToolTip("Initial (assumed) pin value before the first sample");
869
870         return selector;
871 }
872
873 void DecodeTrace::on_new_annotations()
874 {
875         if (!delayed_trace_updater_.isActive())
876                 delayed_trace_updater_.start();
877 }
878
879 void DecodeTrace::on_delayed_trace_update()
880 {
881         if (owner_)
882                 owner_->row_item_appearance_changed(false, true);
883 }
884
885 void DecodeTrace::on_decode_finished()
886 {
887         if (owner_)
888                 owner_->row_item_appearance_changed(false, true);
889 }
890
891 void DecodeTrace::delete_pressed()
892 {
893         on_delete();
894 }
895
896 void DecodeTrace::on_delete()
897 {
898         session_.remove_decode_signal(decode_signal_);
899 }
900
901 void DecodeTrace::on_channel_selected(int)
902 {
903         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
904
905         // Determine signal that was selected
906         const data::SignalBase *signal =
907                 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
908
909         // Determine decode channel ID this combo box is the channel selector for
910         const uint16_t id = channel_id_map_.at(cb);
911
912         decode_signal_->assign_signal(id, signal);
913 }
914
915 void DecodeTrace::on_channels_updated()
916 {
917         if (owner_)
918                 owner_->row_item_appearance_changed(false, true);
919 }
920
921 void DecodeTrace::on_init_state_changed(int)
922 {
923         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
924
925         // Determine inital pin state that was selected
926         int init_state = cb->itemData(cb->currentIndex()).value<int>();
927
928         // Determine decode channel ID this combo box is the channel selector for
929         const uint16_t id = init_state_map_.at(cb);
930
931         decode_signal_->set_initial_pin_state(id, init_state);
932 }
933
934 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
935 {
936         decode_signal_->stack_decoder(decoder);
937
938         create_popup_form();
939 }
940
941 void DecodeTrace::on_delete_decoder(int index)
942 {
943         decode_signal_->remove_decoder(index);
944
945         // Update the popup
946         create_popup_form();
947 }
948
949 void DecodeTrace::on_show_hide_decoder(int index)
950 {
951         const bool state = decode_signal_->toggle_decoder_visibility(index);
952
953         assert(index < (int)decoder_forms_.size());
954         decoder_forms_[index]->set_decoder_visible(state);
955
956         if (owner_)
957                 owner_->row_item_appearance_changed(false, true);
958 }
959
960 } // namespace trace
961 } // namespace views
962 } // namespace pv