]> sigrok.org Git - pulseview.git/blob - pv/views/trace/decodetrace.cpp
Begin PD multisegment support
[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_reset()),
150                 this, SLOT(on_decode_reset()));
151         connect(decode_signal_.get(), SIGNAL(decode_finished()),
152                 this, SLOT(on_decode_finished()));
153         connect(decode_signal_.get(), SIGNAL(channels_updated()),
154                 this, SLOT(on_channels_updated()));
155
156         connect(&delete_mapper_, SIGNAL(mapped(int)),
157                 this, SLOT(on_delete_decoder(int)));
158         connect(&show_hide_mapper_, SIGNAL(mapped(int)),
159                 this, SLOT(on_show_hide_decoder(int)));
160
161         connect(&delayed_trace_updater_, SIGNAL(timeout()),
162                 this, SLOT(on_delayed_trace_update()));
163         delayed_trace_updater_.setSingleShot(true);
164         delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
165 }
166
167 bool DecodeTrace::enabled() const
168 {
169         return true;
170 }
171
172 shared_ptr<data::SignalBase> DecodeTrace::base() const
173 {
174         return base_;
175 }
176
177 pair<int, int> DecodeTrace::v_extents() const
178 {
179         const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
180
181         // Make an empty decode trace appear symmetrical
182         const int row_count = max(1, max_visible_rows_);
183
184         return make_pair(-row_height, row_height * row_count);
185 }
186
187 void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
188 {
189         Trace::paint_back(p, pp);
190         paint_axis(p, pp, get_visual_y());
191 }
192
193 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
194 {
195         const int text_height = ViewItemPaintParams::text_height();
196         row_height_ = (text_height * 6) / 4;
197         const int annotation_height = (text_height * 5) / 4;
198
199         const QString err = decode_signal_->error_message();
200         if (!err.isEmpty()) {
201                 draw_unresolved_period(
202                         p, annotation_height, pp.left(), pp.right());
203                 draw_error(p, err, pp);
204                 return;
205         }
206
207         // Set default pen to allow for text width calculation
208         p.setPen(Qt::black);
209
210         // Iterate through the rows
211         int y = get_visual_y();
212         pair<uint64_t, uint64_t> sample_range = get_sample_range(
213                 pp.left(), pp.right());
214
215         const vector<Row> rows = decode_signal_->visible_rows();
216
217         visible_rows_.clear();
218         for (const Row& row : rows) {
219                 // Cache the row title widths
220                 int row_title_width;
221                 try {
222                         row_title_width = row_title_widths_.at(row);
223                 } catch (out_of_range) {
224                         const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
225                                 RowTitleMargin;
226                         row_title_widths_[row] = w;
227                         row_title_width = w;
228                 }
229
230                 // Determine the row's color
231                 size_t base_colour = 0x13579BDF;
232                 boost::hash_combine(base_colour, this);
233                 boost::hash_combine(base_colour, row.decoder());
234                 boost::hash_combine(base_colour, row.row());
235                 base_colour >>= 16;
236
237                 vector<Annotation> annotations;
238                 decode_signal_->get_annotation_subset(annotations, row,
239                         sample_range.first, sample_range.second);
240                 if (!annotations.empty()) {
241                         draw_annotations(annotations, p, annotation_height, pp, y,
242                                 base_colour, row_title_width);
243
244                         y += row_height_;
245
246                         visible_rows_.push_back(row);
247                 }
248         }
249
250         // Draw the hatching
251         draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
252
253         if ((int)visible_rows_.size() > max_visible_rows_) {
254                 max_visible_rows_ = (int)visible_rows_.size();
255
256                 // Call order is important, otherwise the lazy event handler won't work
257                 owner_->extents_changed(false, true);
258                 owner_->row_item_appearance_changed(false, true);
259         }
260 }
261
262 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
263 {
264         assert(row_height_);
265
266         for (size_t i = 0; i < visible_rows_.size(); i++) {
267                 const int y = i * row_height_ + get_visual_y();
268
269                 p.setPen(QPen(Qt::NoPen));
270                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
271
272                 if (i != 0) {
273                         const QPointF points[] = {
274                                 QPointF(pp.left(), y - ArrowSize),
275                                 QPointF(pp.left() + ArrowSize, y),
276                                 QPointF(pp.left(), y + ArrowSize)
277                         };
278                         p.drawPolygon(points, countof(points));
279                 }
280
281                 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
282                         pp.right() - pp.left(), row_height_);
283                 const QString h(visible_rows_[i].title());
284                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
285                         Qt::TextDontClip;
286
287                 // Draw the outline
288                 p.setPen(QApplication::palette().color(QPalette::Base));
289                 for (int dx = -1; dx <= 1; dx++)
290                         for (int dy = -1; dy <= 1; dy++)
291                                 if (dx != 0 && dy != 0)
292                                         p.drawText(r.translated(dx, dy), f, h);
293
294                 // Draw the text
295                 p.setPen(QApplication::palette().color(QPalette::WindowText));
296                 p.drawText(r, f, h);
297         }
298 }
299
300 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
301 {
302         using pv::data::decode::Decoder;
303
304         assert(form);
305
306         // Add the standard options
307         Trace::populate_popup_form(parent, form);
308
309         // Add the decoder options
310         bindings_.clear();
311         channel_id_map_.clear();
312         init_state_map_.clear();
313         decoder_forms_.clear();
314
315         const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_stack();
316
317         if (stack.empty()) {
318                 QLabel *const l = new QLabel(
319                         tr("<p><i>No decoders in the stack</i></p>"));
320                 l->setAlignment(Qt::AlignCenter);
321                 form->addRow(l);
322         } else {
323                 auto iter = stack.cbegin();
324                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
325                         shared_ptr<Decoder> dec(*iter);
326                         create_decoder_form(i, dec, parent, form);
327                 }
328
329                 form->addRow(new QLabel(
330                         tr("<i>* Required channels</i>"), parent));
331         }
332
333         // Add stacking button
334         pv::widgets::DecoderMenu *const decoder_menu =
335                 new pv::widgets::DecoderMenu(parent);
336         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
337                 this, SLOT(on_stack_decoder(srd_decoder*)));
338
339         QPushButton *const stack_button =
340                 new QPushButton(tr("Stack Decoder"), parent);
341         stack_button->setMenu(decoder_menu);
342         stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
343
344         QHBoxLayout *stack_button_box = new QHBoxLayout;
345         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
346         form->addRow(stack_button_box);
347 }
348
349 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
350 {
351         QMenu *const menu = Trace::create_context_menu(parent);
352
353         menu->addSeparator();
354
355         QAction *const del = new QAction(tr("Delete"), this);
356         del->setShortcuts(QKeySequence::Delete);
357         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
358         menu->addAction(del);
359
360         return menu;
361 }
362
363 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
364                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
365                 size_t base_colour, int row_title_width)
366 {
367         using namespace pv::data::decode;
368
369         vector<Annotation> a_block;
370         int p_end = INT_MIN;
371
372         double samples_per_pixel, pixels_offset;
373         tie(pixels_offset, samples_per_pixel) =
374                 get_pixels_offset_samples_per_pixel();
375
376         // Sort the annotations by start sample so that decoders
377         // can't confuse us by creating annotations out of order
378         stable_sort(annotations.begin(), annotations.end(),
379                 [](const Annotation &a, const Annotation &b) {
380                         return a.start_sample() < b.start_sample(); });
381
382         // Gather all annotations that form a visual "block" and draw them as such
383         for (const Annotation &a : annotations) {
384
385                 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
386                 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
387                 const int a_width = a_end - a_start;
388
389                 const int delta = a_end - p_end;
390
391                 bool a_is_separate = false;
392
393                 // Annotation wider than the threshold for a useful label width?
394                 if (a_width >= min_useful_label_width_) {
395                         for (const QString &ann_text : a.annotations()) {
396                                 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
397                                 // Annotation wide enough to fit a label? Don't put it in a block then
398                                 if (w <= a_width) {
399                                         a_is_separate = true;
400                                         break;
401                                 }
402                         }
403                 }
404
405                 // Were the previous and this annotation more than a pixel apart?
406                 if ((abs(delta) > 1) || a_is_separate) {
407                         // Block was broken, draw annotations that form the current block
408                         if (a_block.size() == 1) {
409                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
410                                         row_title_width);
411                         }
412                         else
413                                 draw_annotation_block(a_block, p, h, y, base_colour);
414
415                         a_block.clear();
416                 }
417
418                 if (a_is_separate) {
419                         draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
420                         // Next annotation must start a new block. delta will be > 1
421                         // because we set p_end to INT_MIN but that's okay since
422                         // a_block will be empty, so nothing will be drawn
423                         p_end = INT_MIN;
424                 } else {
425                         a_block.push_back(a);
426                         p_end = a_end;
427                 }
428         }
429
430         if (a_block.size() == 1)
431                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
432                         row_title_width);
433         else
434                 draw_annotation_block(a_block, p, h, y, base_colour);
435 }
436
437 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
438         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
439         size_t base_colour, int row_title_width) const
440 {
441         double samples_per_pixel, pixels_offset;
442         tie(pixels_offset, samples_per_pixel) =
443                 get_pixels_offset_samples_per_pixel();
444
445         const double start = a.start_sample() / samples_per_pixel -
446                 pixels_offset;
447         const double end = a.end_sample() / samples_per_pixel - pixels_offset;
448
449         const size_t colour = (base_colour + a.format()) % countof(Colours);
450         p.setPen(OutlineColours[colour]);
451         p.setBrush(Colours[colour]);
452
453         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
454                 return;
455
456         if (a.start_sample() == a.end_sample())
457                 draw_instant(a, p, h, start, y);
458         else
459                 draw_range(a, p, h, start, end, y, pp, row_title_width);
460 }
461
462 void DecodeTrace::draw_annotation_block(
463         vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
464         int y, size_t base_colour) const
465 {
466         using namespace pv::data::decode;
467
468         if (annotations.empty())
469                 return;
470
471         double samples_per_pixel, pixels_offset;
472         tie(pixels_offset, samples_per_pixel) =
473                 get_pixels_offset_samples_per_pixel();
474
475         const double start = annotations.front().start_sample() /
476                 samples_per_pixel - pixels_offset;
477         const double end = annotations.back().end_sample() /
478                 samples_per_pixel - pixels_offset;
479
480         const double top = y + .5 - h / 2;
481         const double bottom = y + .5 + h / 2;
482
483         const size_t colour = (base_colour + annotations.front().format()) %
484                 countof(Colours);
485
486         // Check if all annotations are of the same type (i.e. we can use one color)
487         // or if we should use a neutral color (i.e. gray)
488         const int format = annotations.front().format();
489         const bool single_format = all_of(
490                 annotations.begin(), annotations.end(),
491                 [&](const Annotation &a) { return a.format() == format; });
492
493         const QRectF rect(start, top, end - start, bottom - top);
494         const int r = h / 4;
495
496         p.setPen(QPen(Qt::NoPen));
497         p.setBrush(Qt::white);
498         p.drawRoundedRect(rect, r, r);
499
500         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
501         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
502                 Qt::Dense4Pattern));
503         p.drawRoundedRect(rect, r, r);
504 }
505
506 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
507         int h, double x, int y) const
508 {
509         const QString text = a.annotations().empty() ?
510                 QString() : a.annotations().back();
511         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
512                 0.0) + h;
513         const QRectF rect(x - w / 2, y - h / 2, w, h);
514
515         p.drawRoundedRect(rect, h / 2, h / 2);
516
517         p.setPen(Qt::black);
518         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
519 }
520
521 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
522         int h, double start, double end, int y, const ViewItemPaintParams &pp,
523         int row_title_width) const
524 {
525         const double top = y + .5 - h / 2;
526         const double bottom = y + .5 + h / 2;
527         const vector<QString> annotations = a.annotations();
528
529         // If the two ends are within 1 pixel, draw a vertical line
530         if (start + 1.0 > end) {
531                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
532                 return;
533         }
534
535         const double cap_width = min((end - start) / 4, EndCapWidth);
536
537         QPointF pts[] = {
538                 QPointF(start, y + .5f),
539                 QPointF(start + cap_width, top),
540                 QPointF(end - cap_width, top),
541                 QPointF(end, y + .5f),
542                 QPointF(end - cap_width, bottom),
543                 QPointF(start + cap_width, bottom)
544         };
545
546         p.drawConvexPolygon(pts, countof(pts));
547
548         if (annotations.empty())
549                 return;
550
551         const int ann_start = start + cap_width;
552         const int ann_end = end - cap_width;
553
554         const int real_start = max(ann_start, pp.left() + row_title_width);
555         const int real_end = min(ann_end, pp.right());
556         const int real_width = real_end - real_start;
557
558         QRectF rect(real_start, y - h / 2, real_width, h);
559         if (rect.width() <= 4)
560                 return;
561
562         p.setPen(Qt::black);
563
564         // Try to find an annotation that will fit
565         QString best_annotation;
566         int best_width = 0;
567
568         for (const QString &a : annotations) {
569                 const int w = p.boundingRect(QRectF(), 0, a).width();
570                 if (w <= rect.width() && w > best_width)
571                         best_annotation = a, best_width = w;
572         }
573
574         if (best_annotation.isEmpty())
575                 best_annotation = annotations.back();
576
577         // If not ellide the last in the list
578         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
579                 best_annotation, Qt::ElideRight, rect.width()));
580 }
581
582 void DecodeTrace::draw_error(QPainter &p, const QString &message,
583         const ViewItemPaintParams &pp)
584 {
585         const int y = get_visual_y();
586
587         p.setPen(ErrorBgColour.darker());
588         p.setBrush(ErrorBgColour);
589
590         const QRectF bounding_rect =
591                 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
592         const QRectF text_rect = p.boundingRect(bounding_rect,
593                 Qt::AlignCenter, message);
594         const float r = text_rect.height() / 4;
595
596         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
597                 Qt::AbsoluteSize);
598
599         p.setPen(Qt::black);
600         p.drawText(text_rect, message);
601 }
602
603 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
604 {
605         using namespace pv::data;
606         using pv::data::decode::Decoder;
607
608         double samples_per_pixel, pixels_offset;
609
610         const int64_t sample_count = decode_signal_->get_working_sample_count(current_segment_);
611         if (sample_count == 0)
612                 return;
613
614         const int64_t samples_decoded = decode_signal_->get_decoded_sample_count(current_segment_);
615         if (sample_count == samples_decoded)
616                 return;
617
618         const int y = get_visual_y();
619
620         tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
621
622         const double start = max(samples_decoded /
623                 samples_per_pixel - pixels_offset, left - 1.0);
624         const double end = min(sample_count / samples_per_pixel -
625                 pixels_offset, right + 1.0);
626         const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
627
628         p.setPen(QPen(Qt::NoPen));
629         p.setBrush(Qt::white);
630         p.drawRect(no_decode_rect);
631
632         p.setPen(NoDecodeColour);
633         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
634         p.drawRect(no_decode_rect);
635 }
636
637 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
638 {
639         assert(owner_);
640
641         const View *view = owner_->view();
642         assert(view);
643
644         const double scale = view->scale();
645         assert(scale > 0);
646
647         const double pixels_offset =
648                 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
649
650         double samplerate = decode_signal_->samplerate();
651
652         // Show sample rate as 1Hz when it is unknown
653         if (samplerate == 0.0)
654                 samplerate = 1.0;
655
656         return make_pair(pixels_offset, samplerate * scale);
657 }
658
659 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
660         int x_start, int x_end) const
661 {
662         double samples_per_pixel, pixels_offset;
663         tie(pixels_offset, samples_per_pixel) =
664                 get_pixels_offset_samples_per_pixel();
665
666         const uint64_t start = (uint64_t)max(
667                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
668         const uint64_t end = (uint64_t)max(
669                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
670
671         return make_pair(start, end);
672 }
673
674 int DecodeTrace::get_row_at_point(const QPoint &point)
675 {
676         if (!row_height_)
677                 return -1;
678
679         const int y = (point.y() - get_visual_y() + row_height_ / 2);
680
681         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
682         if (y < 0)
683                 return -1;
684
685         const int row = y / row_height_;
686
687         if (row >= (int)visible_rows_.size())
688                 return -1;
689
690         return row;
691 }
692
693 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
694 {
695         using namespace pv::data::decode;
696
697         if (!enabled())
698                 return QString();
699
700         const pair<uint64_t, uint64_t> sample_range =
701                 get_sample_range(point.x(), point.x() + 1);
702         const int row = get_row_at_point(point);
703         if (row < 0)
704                 return QString();
705
706         vector<pv::data::decode::Annotation> annotations;
707
708         decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
709                 sample_range.first, sample_range.second);
710
711         return (annotations.empty()) ?
712                 QString() : annotations[0].annotations().front();
713 }
714
715 void DecodeTrace::hover_point_changed(const QPoint &hp)
716 {
717         assert(owner_);
718
719         const View *const view = owner_->view();
720         assert(view);
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         QPoint p = hp;
750         p.setX(hp.x() - (text_size.width() / 2) - padding);
751
752         p.setY(get_visual_y() - (row_height_ / 2) +
753                 (hover_row * row_height_) -
754                 row_height_ - text_size.height() - padding);
755
756         QToolTip::showText(view->viewport()->mapToGlobal(p), ann);
757 }
758
759 void DecodeTrace::create_decoder_form(int index,
760         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
761         QFormLayout *form)
762 {
763         GlobalSettings settings;
764
765         assert(dec);
766         const srd_decoder *const decoder = dec->decoder();
767         assert(decoder);
768
769         const bool decoder_deletable = index > 0;
770
771         pv::widgets::DecoderGroupBox *const group =
772                 new pv::widgets::DecoderGroupBox(
773                         QString::fromUtf8(decoder->name),
774                         tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
775                                 QString::fromUtf8(decoder->desc)),
776                         nullptr, decoder_deletable);
777         group->set_decoder_visible(dec->shown());
778
779         if (decoder_deletable) {
780                 delete_mapper_.setMapping(group, index);
781                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
782         }
783
784         show_hide_mapper_.setMapping(group, index);
785         connect(group, SIGNAL(show_hide_decoder()),
786                 &show_hide_mapper_, SLOT(map()));
787
788         QFormLayout *const decoder_form = new QFormLayout;
789         group->add_layout(decoder_form);
790
791         const vector<DecodeChannel> channels = decode_signal_->get_channels();
792
793         // Add the channels
794         for (DecodeChannel ch : channels) {
795                 // Ignore channels not part of the decoder we create the form for
796                 if (ch.decoder_ != dec)
797                         continue;
798
799                 QComboBox *const combo = create_channel_selector(parent, &ch);
800                 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
801
802                 channel_id_map_[combo] = ch.id;
803                 init_state_map_[combo_init_state] = ch.id;
804
805                 connect(combo, SIGNAL(currentIndexChanged(int)),
806                         this, SLOT(on_channel_selected(int)));
807                 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
808                         this, SLOT(on_init_state_changed(int)));
809
810                 QHBoxLayout *const hlayout = new QHBoxLayout;
811                 hlayout->addWidget(combo);
812                 hlayout->addWidget(combo_init_state);
813
814                 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
815                         combo_init_state->hide();
816
817                 const QString required_flag = ch.is_optional ? QString() : QString("*");
818                 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
819                         .arg(ch.name, ch.desc, required_flag), hlayout);
820         }
821
822         // Add the options
823         shared_ptr<binding::Decoder> binding(
824                 new binding::Decoder(decode_signal_, dec));
825         binding->add_properties_to_form(decoder_form, true);
826
827         bindings_.push_back(binding);
828
829         form->addRow(group);
830         decoder_forms_.push_back(group);
831 }
832
833 QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
834 {
835         const auto sigs(session_.signalbases());
836
837         // Sort signals in natural order
838         vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
839         sort(sig_list.begin(), sig_list.end(),
840                 [](const shared_ptr<data::SignalBase> &a,
841                 const shared_ptr<data::SignalBase> &b) {
842                         return strnatcasecmp(a->name().toStdString(),
843                                 b->name().toStdString()) < 0; });
844
845         QComboBox *selector = new QComboBox(parent);
846
847         selector->addItem("-", qVariantFromValue((void*)nullptr));
848
849         if (!ch->assigned_signal)
850                 selector->setCurrentIndex(0);
851
852         for (const shared_ptr<data::SignalBase> &b : sig_list) {
853                 assert(b);
854                 if (b->logic_data() && b->enabled()) {
855                         selector->addItem(b->name(),
856                                 qVariantFromValue((void*)b.get()));
857
858                         if (ch->assigned_signal == b.get())
859                                 selector->setCurrentIndex(selector->count() - 1);
860                 }
861         }
862
863         return selector;
864 }
865
866 QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
867         const DecodeChannel *ch)
868 {
869         QComboBox *selector = new QComboBox(parent);
870
871         selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
872         selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
873         selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
874
875         selector->setCurrentIndex(ch->initial_pin_state);
876
877         selector->setToolTip("Initial (assumed) pin value before the first sample");
878
879         return selector;
880 }
881
882 void DecodeTrace::on_new_annotations()
883 {
884         if (!delayed_trace_updater_.isActive())
885                 delayed_trace_updater_.start();
886 }
887
888 void DecodeTrace::on_delayed_trace_update()
889 {
890         if (owner_)
891                 owner_->row_item_appearance_changed(false, true);
892 }
893
894 void DecodeTrace::on_decode_reset()
895 {
896         visible_rows_.clear();
897         max_visible_rows_ = 0;
898
899         if (owner_)
900                 owner_->row_item_appearance_changed(false, true);
901 }
902
903 void DecodeTrace::on_decode_finished()
904 {
905         if (owner_)
906                 owner_->row_item_appearance_changed(false, true);
907 }
908
909 void DecodeTrace::delete_pressed()
910 {
911         on_delete();
912 }
913
914 void DecodeTrace::on_delete()
915 {
916         session_.remove_decode_signal(decode_signal_);
917 }
918
919 void DecodeTrace::on_channel_selected(int)
920 {
921         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
922
923         // Determine signal that was selected
924         const data::SignalBase *signal =
925                 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
926
927         // Determine decode channel ID this combo box is the channel selector for
928         const uint16_t id = channel_id_map_.at(cb);
929
930         decode_signal_->assign_signal(id, signal);
931 }
932
933 void DecodeTrace::on_channels_updated()
934 {
935         if (owner_)
936                 owner_->row_item_appearance_changed(false, true);
937 }
938
939 void DecodeTrace::on_init_state_changed(int)
940 {
941         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
942
943         // Determine inital pin state that was selected
944         int init_state = cb->itemData(cb->currentIndex()).value<int>();
945
946         // Determine decode channel ID this combo box is the channel selector for
947         const uint16_t id = init_state_map_.at(cb);
948
949         decode_signal_->set_initial_pin_state(id, init_state);
950 }
951
952 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
953 {
954         decode_signal_->stack_decoder(decoder);
955
956         create_popup_form();
957 }
958
959 void DecodeTrace::on_delete_decoder(int index)
960 {
961         decode_signal_->remove_decoder(index);
962
963         // Force re-calculation of the trace height, see paint_mid()
964         max_visible_rows_ = 0;
965         owner_->extents_changed(false, true);
966
967         // Update the popup
968         create_popup_form();
969 }
970
971 void DecodeTrace::on_show_hide_decoder(int index)
972 {
973         const bool state = decode_signal_->toggle_decoder_visibility(index);
974
975         assert(index < (int)decoder_forms_.size());
976         decoder_forms_[index]->set_decoder_visible(state);
977
978         if (!state) {
979                 // Force re-calculation of the trace height, see paint_mid()
980                 max_visible_rows_ = 0;
981                 owner_->extents_changed(false, true);
982         }
983
984         if (owner_)
985                 owner_->row_item_appearance_changed(false, true);
986 }
987
988 } // namespace trace
989 } // namespace views
990 } // namespace pv