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