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