]> sigrok.org Git - pulseview.git/blob - pv/views/trace/decodetrace.cpp
DecodeTrace: Fix trace resizing when new annotation classes appear
[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                 max_visible_rows_ = (int)visible_rows_.size();
253
254                 // Call order is important, otherwise the lazy event handler won't work
255                 owner_->extents_changed(false, true);
256                 owner_->row_item_appearance_changed(false, true);
257         }
258 }
259
260 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
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         assert(form);
303
304         // Add the standard options
305         Trace::populate_popup_form(parent, form);
306
307         // Add the decoder options
308         bindings_.clear();
309         channel_id_map_.clear();
310         init_state_map_.clear();
311         decoder_forms_.clear();
312
313         const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_stack();
314
315         if (stack.empty()) {
316                 QLabel *const l = new QLabel(
317                         tr("<p><i>No decoders in the stack</i></p>"));
318                 l->setAlignment(Qt::AlignCenter);
319                 form->addRow(l);
320         } else {
321                 auto iter = stack.cbegin();
322                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
323                         shared_ptr<Decoder> dec(*iter);
324                         create_decoder_form(i, dec, parent, form);
325                 }
326
327                 form->addRow(new QLabel(
328                         tr("<i>* Required channels</i>"), parent));
329         }
330
331         // Add stacking button
332         pv::widgets::DecoderMenu *const decoder_menu =
333                 new pv::widgets::DecoderMenu(parent);
334         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
335                 this, SLOT(on_stack_decoder(srd_decoder*)));
336
337         QPushButton *const stack_button =
338                 new QPushButton(tr("Stack Decoder"), parent);
339         stack_button->setMenu(decoder_menu);
340         stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
341
342         QHBoxLayout *stack_button_box = new QHBoxLayout;
343         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
344         form->addRow(stack_button_box);
345 }
346
347 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
348 {
349         QMenu *const menu = Trace::create_context_menu(parent);
350
351         menu->addSeparator();
352
353         QAction *const del = new QAction(tr("Delete"), this);
354         del->setShortcuts(QKeySequence::Delete);
355         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
356         menu->addAction(del);
357
358         return menu;
359 }
360
361 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
362                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
363                 size_t base_colour, int row_title_width)
364 {
365         using namespace pv::data::decode;
366
367         vector<Annotation> a_block;
368         int p_end = INT_MIN;
369
370         double samples_per_pixel, pixels_offset;
371         tie(pixels_offset, samples_per_pixel) =
372                 get_pixels_offset_samples_per_pixel();
373
374         // Sort the annotations by start sample so that decoders
375         // can't confuse us by creating annotations out of order
376         stable_sort(annotations.begin(), annotations.end(),
377                 [](const Annotation &a, const Annotation &b) {
378                         return a.start_sample() < b.start_sample(); });
379
380         // Gather all annotations that form a visual "block" and draw them as such
381         for (const Annotation &a : annotations) {
382
383                 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
384                 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
385                 const int a_width = a_end - a_start;
386
387                 const int delta = a_end - p_end;
388
389                 bool a_is_separate = false;
390
391                 // Annotation wider than the threshold for a useful label width?
392                 if (a_width >= min_useful_label_width_) {
393                         for (const QString &ann_text : a.annotations()) {
394                                 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
395                                 // Annotation wide enough to fit a label? Don't put it in a block then
396                                 if (w <= a_width) {
397                                         a_is_separate = true;
398                                         break;
399                                 }
400                         }
401                 }
402
403                 // Were the previous and this annotation more than a pixel apart?
404                 if ((abs(delta) > 1) || a_is_separate) {
405                         // Block was broken, draw annotations that form the current block
406                         if (a_block.size() == 1) {
407                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
408                                         row_title_width);
409                         }
410                         else
411                                 draw_annotation_block(a_block, p, h, y, base_colour);
412
413                         a_block.clear();
414                 }
415
416                 if (a_is_separate) {
417                         draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
418                         // Next annotation must start a new block. delta will be > 1
419                         // because we set p_end to INT_MIN but that's okay since
420                         // a_block will be empty, so nothing will be drawn
421                         p_end = INT_MIN;
422                 } else {
423                         a_block.push_back(a);
424                         p_end = a_end;
425                 }
426         }
427
428         if (a_block.size() == 1)
429                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
430                         row_title_width);
431         else
432                 draw_annotation_block(a_block, p, h, y, base_colour);
433 }
434
435 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
436         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
437         size_t base_colour, int row_title_width) const
438 {
439         double samples_per_pixel, pixels_offset;
440         tie(pixels_offset, samples_per_pixel) =
441                 get_pixels_offset_samples_per_pixel();
442
443         const double start = a.start_sample() / samples_per_pixel -
444                 pixels_offset;
445         const double end = a.end_sample() / samples_per_pixel - pixels_offset;
446
447         const size_t colour = (base_colour + a.format()) % countof(Colours);
448         p.setPen(OutlineColours[colour]);
449         p.setBrush(Colours[colour]);
450
451         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
452                 return;
453
454         if (a.start_sample() == a.end_sample())
455                 draw_instant(a, p, h, start, y);
456         else
457                 draw_range(a, p, h, start, end, y, pp, row_title_width);
458 }
459
460 void DecodeTrace::draw_annotation_block(
461         vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
462         int y, size_t base_colour) const
463 {
464         using namespace pv::data::decode;
465
466         if (annotations.empty())
467                 return;
468
469         double samples_per_pixel, pixels_offset;
470         tie(pixels_offset, samples_per_pixel) =
471                 get_pixels_offset_samples_per_pixel();
472
473         const double start = annotations.front().start_sample() /
474                 samples_per_pixel - pixels_offset;
475         const double end = annotations.back().end_sample() /
476                 samples_per_pixel - pixels_offset;
477
478         const double top = y + .5 - h / 2;
479         const double bottom = y + .5 + h / 2;
480
481         const size_t colour = (base_colour + annotations.front().format()) %
482                 countof(Colours);
483
484         // Check if all annotations are of the same type (i.e. we can use one color)
485         // or if we should use a neutral color (i.e. gray)
486         const int format = annotations.front().format();
487         const bool single_format = all_of(
488                 annotations.begin(), annotations.end(),
489                 [&](const Annotation &a) { return a.format() == format; });
490
491         const QRectF rect(start, top, end - start, bottom - top);
492         const int r = h / 4;
493
494         p.setPen(QPen(Qt::NoPen));
495         p.setBrush(Qt::white);
496         p.drawRoundedRect(rect, r, r);
497
498         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
499         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
500                 Qt::Dense4Pattern));
501         p.drawRoundedRect(rect, r, r);
502 }
503
504 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
505         int h, double x, int y) const
506 {
507         const QString text = a.annotations().empty() ?
508                 QString() : a.annotations().back();
509         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
510                 0.0) + h;
511         const QRectF rect(x - w / 2, y - h / 2, w, h);
512
513         p.drawRoundedRect(rect, h / 2, h / 2);
514
515         p.setPen(Qt::black);
516         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
517 }
518
519 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
520         int h, double start, double end, int y, const ViewItemPaintParams &pp,
521         int row_title_width) const
522 {
523         const double top = y + .5 - h / 2;
524         const double bottom = y + .5 + h / 2;
525         const vector<QString> annotations = a.annotations();
526
527         // If the two ends are within 1 pixel, draw a vertical line
528         if (start + 1.0 > end) {
529                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
530                 return;
531         }
532
533         const double cap_width = min((end - start) / 4, EndCapWidth);
534
535         QPointF pts[] = {
536                 QPointF(start, y + .5f),
537                 QPointF(start + cap_width, top),
538                 QPointF(end - cap_width, top),
539                 QPointF(end, y + .5f),
540                 QPointF(end - cap_width, bottom),
541                 QPointF(start + cap_width, bottom)
542         };
543
544         p.drawConvexPolygon(pts, countof(pts));
545
546         if (annotations.empty())
547                 return;
548
549         const int ann_start = start + cap_width;
550         const int ann_end = end - cap_width;
551
552         const int real_start = max(ann_start, pp.left() + row_title_width);
553         const int real_end = min(ann_end, pp.right());
554         const int real_width = real_end - real_start;
555
556         QRectF rect(real_start, y - h / 2, real_width, h);
557         if (rect.width() <= 4)
558                 return;
559
560         p.setPen(Qt::black);
561
562         // Try to find an annotation that will fit
563         QString best_annotation;
564         int best_width = 0;
565
566         for (const QString &a : annotations) {
567                 const int w = p.boundingRect(QRectF(), 0, a).width();
568                 if (w <= rect.width() && w > best_width)
569                         best_annotation = a, best_width = w;
570         }
571
572         if (best_annotation.isEmpty())
573                 best_annotation = annotations.back();
574
575         // If not ellide the last in the list
576         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
577                 best_annotation, Qt::ElideRight, rect.width()));
578 }
579
580 void DecodeTrace::draw_error(QPainter &p, const QString &message,
581         const ViewItemPaintParams &pp)
582 {
583         const int y = get_visual_y();
584
585         p.setPen(ErrorBgColour.darker());
586         p.setBrush(ErrorBgColour);
587
588         const QRectF bounding_rect =
589                 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
590         const QRectF text_rect = p.boundingRect(bounding_rect,
591                 Qt::AlignCenter, message);
592         const float r = text_rect.height() / 4;
593
594         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
595                 Qt::AbsoluteSize);
596
597         p.setPen(Qt::black);
598         p.drawText(text_rect, message);
599 }
600
601 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
602 {
603         using namespace pv::data;
604         using pv::data::decode::Decoder;
605
606         double samples_per_pixel, pixels_offset;
607
608         const int64_t sample_count = decode_signal_->get_working_sample_count();
609         if (sample_count == 0)
610                 return;
611
612         const int64_t samples_decoded = decode_signal_->get_decoded_sample_count();
613         if (sample_count == samples_decoded)
614                 return;
615
616         const int y = get_visual_y();
617
618         tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
619
620         const double start = max(samples_decoded /
621                 samples_per_pixel - pixels_offset, left - 1.0);
622         const double end = min(sample_count / samples_per_pixel -
623                 pixels_offset, right + 1.0);
624         const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
625
626         p.setPen(QPen(Qt::NoPen));
627         p.setBrush(Qt::white);
628         p.drawRect(no_decode_rect);
629
630         p.setPen(NoDecodeColour);
631         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
632         p.drawRect(no_decode_rect);
633 }
634
635 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
636 {
637         assert(owner_);
638
639         const View *view = owner_->view();
640         assert(view);
641
642         const double scale = view->scale();
643         assert(scale > 0);
644
645         const double pixels_offset =
646                 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
647
648         double samplerate = decode_signal_->samplerate();
649
650         // Show sample rate as 1Hz when it is unknown
651         if (samplerate == 0.0)
652                 samplerate = 1.0;
653
654         return make_pair(pixels_offset, samplerate * scale);
655 }
656
657 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
658         int x_start, int x_end) const
659 {
660         double samples_per_pixel, pixels_offset;
661         tie(pixels_offset, samples_per_pixel) =
662                 get_pixels_offset_samples_per_pixel();
663
664         const uint64_t start = (uint64_t)max(
665                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
666         const uint64_t end = (uint64_t)max(
667                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
668
669         return make_pair(start, end);
670 }
671
672 int DecodeTrace::get_row_at_point(const QPoint &point)
673 {
674         if (!row_height_)
675                 return -1;
676
677         const int y = (point.y() - get_visual_y() + row_height_ / 2);
678
679         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
680         if (y < 0)
681                 return -1;
682
683         const int row = y / row_height_;
684
685         if (row >= (int)visible_rows_.size())
686                 return -1;
687
688         return row;
689 }
690
691 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
692 {
693         using namespace pv::data::decode;
694
695         if (!enabled())
696                 return QString();
697
698         const pair<uint64_t, uint64_t> sample_range =
699                 get_sample_range(point.x(), point.x() + 1);
700         const int row = get_row_at_point(point);
701         if (row < 0)
702                 return QString();
703
704         vector<pv::data::decode::Annotation> annotations;
705
706         decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
707                 sample_range.first, sample_range.second);
708
709         return (annotations.empty()) ?
710                 QString() : annotations[0].annotations().front();
711 }
712
713 void DecodeTrace::hover_point_changed()
714 {
715         assert(owner_);
716
717         const View *const view = owner_->view();
718         assert(view);
719
720         QPoint hp = view->hover_point();
721
722         if (hp.x() == 0) {
723                 QToolTip::hideText();
724                 return;
725         }
726
727         QString ann = get_annotation_at_point(hp);
728
729         assert(view);
730
731         if (!row_height_ || ann.isEmpty()) {
732                 QToolTip::hideText();
733                 return;
734         }
735
736         const int hover_row = get_row_at_point(hp);
737
738         QFontMetrics m(QToolTip::font());
739         const QRect text_size = m.boundingRect(QRect(), 0, ann);
740
741         // This is OS-specific and unfortunately we can't query it, so
742         // use an approximation to at least try to minimize the error.
743         const int padding = 8;
744
745         // Make sure the tool tip doesn't overlap with the mouse cursor.
746         // If it did, the tool tip would constantly hide and re-appear.
747         // We also push it up by one row so that it appears above the
748         // decode trace, not below.
749         hp.setX(hp.x() - (text_size.width() / 2) - padding);
750
751         hp.setY(get_visual_y() - (row_height_ / 2) +
752                 (hover_row * row_height_) -
753                 row_height_ - text_size.height() - padding);
754
755         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
756 }
757
758 void DecodeTrace::create_decoder_form(int index,
759         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
760         QFormLayout *form)
761 {
762         GlobalSettings settings;
763
764         assert(dec);
765         const srd_decoder *const decoder = dec->decoder();
766         assert(decoder);
767
768         const bool decoder_deletable = index > 0;
769
770         pv::widgets::DecoderGroupBox *const group =
771                 new pv::widgets::DecoderGroupBox(
772                         QString::fromUtf8(decoder->name),
773                         tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
774                                 QString::fromUtf8(decoder->desc)),
775                         nullptr, decoder_deletable);
776         group->set_decoder_visible(dec->shown());
777
778         if (decoder_deletable) {
779                 delete_mapper_.setMapping(group, index);
780                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
781         }
782
783         show_hide_mapper_.setMapping(group, index);
784         connect(group, SIGNAL(show_hide_decoder()),
785                 &show_hide_mapper_, SLOT(map()));
786
787         QFormLayout *const decoder_form = new QFormLayout;
788         group->add_layout(decoder_form);
789
790         const vector<DecodeChannel> channels = decode_signal_->get_channels();
791
792         // Add the channels
793         for (DecodeChannel ch : channels) {
794                 // Ignore channels not part of the decoder we create the form for
795                 if (ch.decoder_ != dec)
796                         continue;
797
798                 QComboBox *const combo = create_channel_selector(parent, &ch);
799                 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
800
801                 channel_id_map_[combo] = ch.id;
802                 init_state_map_[combo_init_state] = ch.id;
803
804                 connect(combo, SIGNAL(currentIndexChanged(int)),
805                         this, SLOT(on_channel_selected(int)));
806                 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
807                         this, SLOT(on_init_state_changed(int)));
808
809                 QHBoxLayout *const hlayout = new QHBoxLayout;
810                 hlayout->addWidget(combo);
811                 hlayout->addWidget(combo_init_state);
812
813                 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
814                         combo_init_state->hide();
815
816                 const QString required_flag = ch.is_optional ? QString() : QString("*");
817                 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
818                         .arg(ch.name, ch.desc, required_flag), hlayout);
819         }
820
821         // Add the options
822         shared_ptr<binding::Decoder> binding(
823                 new binding::Decoder(decode_signal_, dec));
824         binding->add_properties_to_form(decoder_form, true);
825
826         bindings_.push_back(binding);
827
828         form->addRow(group);
829         decoder_forms_.push_back(group);
830 }
831
832 QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
833 {
834         const auto sigs(session_.signalbases());
835
836         // Sort signals in natural order
837         vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
838         sort(sig_list.begin(), sig_list.end(),
839                 [](const shared_ptr<data::SignalBase> &a,
840                 const shared_ptr<data::SignalBase> &b) {
841                         return strnatcasecmp(a->name().toStdString(),
842                                 b->name().toStdString()) < 0; });
843
844         QComboBox *selector = new QComboBox(parent);
845
846         selector->addItem("-", qVariantFromValue((void*)nullptr));
847
848         if (!ch->assigned_signal)
849                 selector->setCurrentIndex(0);
850
851         for (const shared_ptr<data::SignalBase> &b : sig_list) {
852                 assert(b);
853                 if (b->logic_data() && b->enabled()) {
854                         selector->addItem(b->name(),
855                                 qVariantFromValue((void*)b.get()));
856
857                         if (ch->assigned_signal == b.get())
858                                 selector->setCurrentIndex(selector->count() - 1);
859                 }
860         }
861
862         return selector;
863 }
864
865 QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
866         const DecodeChannel *ch)
867 {
868         QComboBox *selector = new QComboBox(parent);
869
870         selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
871         selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
872         selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
873
874         selector->setCurrentIndex(ch->initial_pin_state);
875
876         selector->setToolTip("Initial (assumed) pin value before the first sample");
877
878         return selector;
879 }
880
881 void DecodeTrace::on_new_annotations()
882 {
883         if (!delayed_trace_updater_.isActive())
884                 delayed_trace_updater_.start();
885 }
886
887 void DecodeTrace::on_delayed_trace_update()
888 {
889         if (owner_)
890                 owner_->row_item_appearance_changed(false, true);
891 }
892
893 void DecodeTrace::on_decode_finished()
894 {
895         if (owner_)
896                 owner_->row_item_appearance_changed(false, true);
897 }
898
899 void DecodeTrace::delete_pressed()
900 {
901         on_delete();
902 }
903
904 void DecodeTrace::on_delete()
905 {
906         session_.remove_decode_signal(decode_signal_);
907 }
908
909 void DecodeTrace::on_channel_selected(int)
910 {
911         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
912
913         // Determine signal that was selected
914         const data::SignalBase *signal =
915                 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
916
917         // Determine decode channel ID this combo box is the channel selector for
918         const uint16_t id = channel_id_map_.at(cb);
919
920         decode_signal_->assign_signal(id, signal);
921 }
922
923 void DecodeTrace::on_channels_updated()
924 {
925         if (owner_)
926                 owner_->row_item_appearance_changed(false, true);
927 }
928
929 void DecodeTrace::on_init_state_changed(int)
930 {
931         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
932
933         // Determine inital pin state that was selected
934         int init_state = cb->itemData(cb->currentIndex()).value<int>();
935
936         // Determine decode channel ID this combo box is the channel selector for
937         const uint16_t id = init_state_map_.at(cb);
938
939         decode_signal_->set_initial_pin_state(id, init_state);
940 }
941
942 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
943 {
944         decode_signal_->stack_decoder(decoder);
945
946         create_popup_form();
947 }
948
949 void DecodeTrace::on_delete_decoder(int index)
950 {
951         decode_signal_->remove_decoder(index);
952
953         // Update the popup
954         create_popup_form();
955 }
956
957 void DecodeTrace::on_show_hide_decoder(int index)
958 {
959         const bool state = decode_signal_->toggle_decoder_visibility(index);
960
961         assert(index < (int)decoder_forms_.size());
962         decoder_forms_[index]->set_decoder_visible(state);
963
964         if (owner_)
965                 owner_->row_item_appearance_changed(false, true);
966 }
967
968 } // namespace trace
969 } // namespace views
970 } // namespace pv