]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
DecodeTrace: Let annotation labels be pushed aside by the row title
[pulseview.git] / pv / view / 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, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  */
20
21 extern "C" {
22 #include <libsigrokdecode/libsigrokdecode.h>
23 }
24
25 #include <mutex>
26
27 #include <extdef.h>
28
29 #include <tuple>
30
31 #include <boost/functional/hash.hpp>
32 #include <boost/thread/locks.hpp>
33 #include <boost/thread/shared_mutex.hpp>
34
35 #include <QAction>
36 #include <QApplication>
37 #include <QComboBox>
38 #include <QFormLayout>
39 #include <QLabel>
40 #include <QMenu>
41 #include <QPushButton>
42 #include <QToolTip>
43
44 #include "decodetrace.hpp"
45
46 #include <pv/session.hpp>
47 #include <pv/data/decoderstack.hpp>
48 #include <pv/data/decode/decoder.hpp>
49 #include <pv/data/logic.hpp>
50 #include <pv/data/logicsegment.hpp>
51 #include <pv/data/decode/annotation.hpp>
52 #include <pv/view/logicsignal.hpp>
53 #include <pv/view/view.hpp>
54 #include <pv/view/viewport.hpp>
55 #include <pv/widgets/decodergroupbox.hpp>
56 #include <pv/widgets/decodermenu.hpp>
57
58 using boost::shared_lock;
59 using boost::shared_mutex;
60 using std::dynamic_pointer_cast;
61 using std::list;
62 using std::lock_guard;
63 using std::make_pair;
64 using std::max;
65 using std::make_pair;
66 using std::map;
67 using std::min;
68 using std::pair;
69 using std::shared_ptr;
70 using std::tie;
71 using std::unordered_set;
72 using std::vector;
73
74 namespace pv {
75 namespace view {
76
77 const QColor DecodeTrace::DecodeColours[4] = {
78         QColor(0xEF, 0x29, 0x29),       // Red
79         QColor(0xFC, 0xE9, 0x4F),       // Yellow
80         QColor(0x8A, 0xE2, 0x34),       // Green
81         QColor(0x72, 0x9F, 0xCF)        // Blue
82 };
83
84 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
85 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
86
87 const int DecodeTrace::ArrowSize = 4;
88 const double DecodeTrace::EndCapWidth = 5;
89 const int DecodeTrace::RowTitleMargin = 10;
90 const int DecodeTrace::DrawPadding = 100;
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         std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
132         Trace(QString::fromUtf8(
133                 decoder_stack->stack().front()->decoder()->name)),
134         session_(session),
135         decoder_stack_(decoder_stack),
136         row_height_(0),
137         max_visible_rows_(0),
138         delete_mapper_(this),
139         show_hide_mapper_(this)
140 {
141         assert(decoder_stack_);
142
143         set_colour(DecodeColours[index % countof(DecodeColours)]);
144
145         connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
146                 this, SLOT(on_new_decode_data()));
147         connect(&delete_mapper_, SIGNAL(mapped(int)),
148                 this, SLOT(on_delete_decoder(int)));
149         connect(&show_hide_mapper_, SIGNAL(mapped(int)),
150                 this, SLOT(on_show_hide_decoder(int)));
151 }
152
153 bool DecodeTrace::enabled() const
154 {
155         return true;
156 }
157
158 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
159 {
160         return decoder_stack_;
161 }
162
163 pair<int, int> DecodeTrace::v_extents() const
164 {
165         const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
166
167         return make_pair(-row_height, row_height * max_visible_rows_);
168 }
169
170 void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
171 {
172         Trace::paint_back(p, pp);
173         paint_axis(p, pp, get_visual_y());
174 }
175
176 void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
177 {
178         using namespace pv::data::decode;
179
180         const int text_height = ViewItemPaintParams::text_height();
181         row_height_ = (text_height * 6) / 4;
182         const int annotation_height = (text_height * 5) / 4;
183
184         assert(decoder_stack_);
185         const QString err = decoder_stack_->error_message();
186         if (!err.isEmpty()) {
187                 draw_unresolved_period(
188                         p, annotation_height, pp.left(), pp.right());
189                 draw_error(p, err, pp);
190                 return;
191         }
192
193         // Iterate through the rows
194         int y = get_visual_y();
195         pair<uint64_t, uint64_t> sample_range = get_sample_range(
196                 pp.left(), pp.right());
197
198         assert(decoder_stack_);
199         const vector<Row> rows(decoder_stack_->get_visible_rows());
200
201         visible_rows_.clear();
202         for (size_t i = 0; i < rows.size(); i++) {
203                 const Row &row = rows[i];
204
205                 // Cache the row title widths
206                 int row_title_width;
207                 try {
208                         row_title_width = row_title_widths_.at(row);
209                 } catch (std::out_of_range) {
210                         const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
211                                 RowTitleMargin;
212                         row_title_widths_[row] = w;
213                         row_title_width = w;
214                 }
215
216                 // Determine the row's color
217                 size_t base_colour = 0x13579BDF;
218                 boost::hash_combine(base_colour, this);
219                 boost::hash_combine(base_colour, row.decoder());
220                 boost::hash_combine(base_colour, row.row());
221                 base_colour >>= 16;
222
223                 vector<Annotation> annotations;
224                 decoder_stack_->get_annotation_subset(annotations, row,
225                         sample_range.first, sample_range.second);
226                 if (!annotations.empty()) {
227                         draw_annotations(annotations, p, annotation_height, pp, y,
228                                 base_colour, row_title_width);
229
230                         y += row_height_;
231
232                         visible_rows_.push_back(rows[i]);
233                 }
234         }
235
236         // Draw the hatching
237         draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
238
239         // Update the maximum row count if needed
240         max_visible_rows_ = std::max(max_visible_rows_, (int)visible_rows_.size());
241 }
242
243 void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
244 {
245         using namespace pv::data::decode;
246
247         assert(row_height_);
248
249         for (size_t i = 0; i < visible_rows_.size(); i++) {
250                 const int y = i * row_height_ + get_visual_y();
251
252                 p.setPen(QPen(Qt::NoPen));
253                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
254
255                 if (i != 0) {
256                         const QPointF points[] = {
257                                 QPointF(pp.left(), y - ArrowSize),
258                                 QPointF(pp.left() + ArrowSize, y),
259                                 QPointF(pp.left(), y + ArrowSize)
260                         };
261                         p.drawPolygon(points, countof(points));
262                 }
263
264                 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
265                         pp.right() - pp.left(), row_height_);
266                 const QString h(visible_rows_[i].title());
267                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
268                         Qt::TextDontClip;
269
270                 // Draw the outline
271                 p.setPen(QApplication::palette().color(QPalette::Base));
272                 for (int dx = -1; dx <= 1; dx++)
273                         for (int dy = -1; dy <= 1; dy++)
274                                 if (dx != 0 && dy != 0)
275                                         p.drawText(r.translated(dx, dy), f, h);
276
277                 // Draw the text
278                 p.setPen(QApplication::palette().color(QPalette::WindowText));
279                 p.drawText(r, f, h);
280         }
281 }
282
283 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
284 {
285         using pv::data::decode::Decoder;
286
287         assert(form);
288         assert(parent);
289         assert(decoder_stack_);
290
291         // Add the standard options
292         Trace::populate_popup_form(parent, form);
293
294         // Add the decoder options
295         bindings_.clear();
296         channel_selectors_.clear();
297         decoder_forms_.clear();
298
299         const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
300
301         if (stack.empty()) {
302                 QLabel *const l = new QLabel(
303                         tr("<p><i>No decoders in the stack</i></p>"));
304                 l->setAlignment(Qt::AlignCenter);
305                 form->addRow(l);
306         } else {
307                 auto iter = stack.cbegin();
308                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
309                         shared_ptr<Decoder> dec(*iter);
310                         create_decoder_form(i, dec, parent, form);
311                 }
312
313                 form->addRow(new QLabel(
314                         tr("<i>* Required channels</i>"), parent));
315         }
316
317         // Add stacking button
318         pv::widgets::DecoderMenu *const decoder_menu =
319                 new pv::widgets::DecoderMenu(parent);
320         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
321                 this, SLOT(on_stack_decoder(srd_decoder*)));
322
323         QPushButton *const stack_button =
324                 new QPushButton(tr("Stack Decoder"), parent);
325         stack_button->setMenu(decoder_menu);
326
327         QHBoxLayout *stack_button_box = new QHBoxLayout;
328         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
329         form->addRow(stack_button_box);
330 }
331
332 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
333 {
334         QMenu *const menu = Trace::create_context_menu(parent);
335
336         menu->addSeparator();
337
338         QAction *const del = new QAction(tr("Delete"), this);
339         del->setShortcuts(QKeySequence::Delete);
340         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
341         menu->addAction(del);
342
343         return menu;
344 }
345
346 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
347                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
348                 size_t base_colour, int row_title_width)
349 {
350         using namespace pv::data::decode;
351
352         vector<Annotation> a_block;
353         int p_end = INT_MIN;
354
355         double samples_per_pixel, pixels_offset;
356         tie(pixels_offset, samples_per_pixel) =
357                 get_pixels_offset_samples_per_pixel();
358
359         // Sort the annotations by start sample so that decoders
360         // can't confuse us by creating annotations out of order
361         stable_sort(annotations.begin(), annotations.end(),
362                 [](const Annotation &a, const Annotation &b) {
363                         return a.start_sample() < b.start_sample(); });
364
365         // Gather all annotations that form a visual "block" and draw them as such
366         for (const Annotation &a : annotations) {
367
368                 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
369                 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
370                 const int a_width = a_end - a_start;
371
372                 const int delta = a_end - p_end;
373
374                 bool a_is_separate = false;
375
376                 // Annotation wider than the threshold for a useful label width?
377                 if (a_width > 20) {
378                         for (const QString &ann_text : a.annotations()) {
379                                 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
380                                 // Annotation wide enough to fit a label? Don't put it in a block then
381                                 if (w <= a_width) {
382                                         a_is_separate = true;
383                                         break;
384                                 }
385                         }
386                 }
387
388                 // Were the previous and this annotation more than a pixel apart?
389                 if ((abs(delta) > 1) || a_is_separate) {
390                         // Block was broken, draw annotations that form the current block
391                         if (a_block.size() == 1) {
392                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
393                                         row_title_width);
394                         }
395                         else
396                                 draw_annotation_block(a_block, p, h, y, base_colour);
397
398                         a_block.clear();
399                 }
400
401                 if (a_is_separate) {
402                         draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
403                         // Next annotation must start a new block. delta will be > 1
404                         // because we set p_end to INT_MIN but that's okay since
405                         // a_block will be empty, so nothing will be drawn
406                         p_end = INT_MIN;
407                 } else {
408                         a_block.push_back(a);
409                         p_end = a_end;
410                 }
411         }
412
413         if (a_block.size() == 1)
414                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
415                         row_title_width);
416         else
417                 draw_annotation_block(a_block, p, h, y, base_colour);
418 }
419
420 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
421         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
422         size_t base_colour, int row_title_width) const
423 {
424         double samples_per_pixel, pixels_offset;
425         tie(pixels_offset, samples_per_pixel) =
426                 get_pixels_offset_samples_per_pixel();
427
428         const double start = a.start_sample() / samples_per_pixel -
429                 pixels_offset;
430         const double end = a.end_sample() / samples_per_pixel -
431                 pixels_offset;
432
433         const size_t colour = (base_colour + a.format()) % countof(Colours);
434         const QColor &fill = Colours[colour];
435         const QColor &outline = OutlineColours[colour];
436
437         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
438                 return;
439
440         if (a.start_sample() == a.end_sample())
441                 draw_instant(a, p, fill, outline, h, start, y);
442         else
443                 draw_range(a, p, fill, outline, h, start, end, y, pp,
444                         row_title_width);
445 }
446
447 void DecodeTrace::draw_annotation_block(
448         vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
449         int y, size_t base_colour) const
450 {
451         using namespace pv::data::decode;
452
453         if (annotations.empty())
454                 return;
455
456         double samples_per_pixel, pixels_offset;
457         tie(pixels_offset, samples_per_pixel) =
458                 get_pixels_offset_samples_per_pixel();
459
460         const double start = annotations.front().start_sample() /
461                 samples_per_pixel - pixels_offset;
462         const double end = annotations.back().end_sample() /
463                 samples_per_pixel - pixels_offset;
464
465         const double top = y + .5 - h / 2;
466         const double bottom = y + .5 + h / 2;
467
468         const size_t colour = (base_colour + annotations.front().format()) %
469                 countof(Colours);
470
471         // Check if all annotations are of the same type (i.e. we can use one color)
472         // or if we should use a neutral color (i.e. gray)
473         const int format = annotations.front().format();
474         const bool single_format = std::all_of(
475                 annotations.begin(), annotations.end(),
476                 [&](const Annotation &a) { return a.format() == format; });
477
478         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
479         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
480                 Qt::Dense4Pattern));
481         p.drawRoundedRect(
482                 QRectF(start, top, end - start, bottom - top), h/4, h/4);
483 }
484
485 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
486         QColor fill, QColor outline, int h, double x, int y) const
487 {
488         const QString text = a.annotations().empty() ?
489                 QString() : a.annotations().back();
490         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
491                 0.0) + h;
492         const QRectF rect(x - w / 2, y - h / 2, w, h);
493
494         p.setPen(outline);
495         p.setBrush(fill);
496         p.drawRoundedRect(rect, h / 2, h / 2);
497
498         p.setPen(Qt::black);
499         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
500 }
501
502 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
503         QColor fill, QColor outline, int h, double start,
504         double end, int y, const ViewItemPaintParams &pp, int row_title_width) const
505 {
506         const double top = y + .5 - h / 2;
507         const double bottom = y + .5 + h / 2;
508         const vector<QString> annotations = a.annotations();
509
510         p.setPen(outline);
511         p.setBrush(fill);
512
513         // If the two ends are within 1 pixel, draw a vertical line
514         if (start + 1.0 > end) {
515                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
516                 return;
517         }
518
519         const double cap_width = min((end - start) / 4, EndCapWidth);
520
521         QPointF pts[] = {
522                 QPointF(start, y + .5f),
523                 QPointF(start + cap_width, top),
524                 QPointF(end - cap_width, top),
525                 QPointF(end, y + .5f),
526                 QPointF(end - cap_width, bottom),
527                 QPointF(start + cap_width, bottom)
528         };
529
530         p.drawConvexPolygon(pts, countof(pts));
531
532         if (annotations.empty())
533                 return;
534
535         const int ann_start = start + cap_width;
536         const int ann_end = end - cap_width;
537
538         const int real_start = std::max(ann_start, pp.left() + row_title_width);
539         const int real_end = std::min(ann_end, pp.right());
540         const int real_width = real_end - real_start;
541
542         QRectF rect(real_start, y - h / 2, real_width, h);
543         if (rect.width() <= 4)
544                 return;
545
546         p.setPen(Qt::black);
547
548         // Try to find an annotation that will fit
549         QString best_annotation;
550         int best_width = 0;
551
552         for (const QString &a : annotations) {
553                 const int w = p.boundingRect(QRectF(), 0, a).width();
554                 if (w <= rect.width() && w > best_width)
555                         best_annotation = a, best_width = w;
556         }
557
558         if (best_annotation.isEmpty())
559                 best_annotation = annotations.back();
560
561         // If not ellide the last in the list
562         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
563                 best_annotation, Qt::ElideRight, rect.width()));
564 }
565
566 void DecodeTrace::draw_error(QPainter &p, const QString &message,
567         const ViewItemPaintParams &pp)
568 {
569         const int y = get_visual_y();
570
571         p.setPen(ErrorBgColour.darker());
572         p.setBrush(ErrorBgColour);
573
574         const QRectF bounding_rect =
575                 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
576         const QRectF text_rect = p.boundingRect(bounding_rect,
577                 Qt::AlignCenter, message);
578         const float r = text_rect.height() / 4;
579
580         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
581                 Qt::AbsoluteSize);
582
583         p.setPen(Qt::black);
584         p.drawText(text_rect, message);
585 }
586
587 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
588         int right) const
589 {
590         using namespace pv::data;
591         using pv::data::decode::Decoder;
592
593         double samples_per_pixel, pixels_offset;
594
595         assert(decoder_stack_); 
596
597         shared_ptr<Logic> data;
598         shared_ptr<LogicSignal> logic_signal;
599
600         const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
601
602         // We get the logic data of the first channel in the list.
603         // This works because we are currently assuming all
604         // LogicSignals have the same data/segment
605         for (const shared_ptr<Decoder> &dec : stack)
606                 if (dec && !dec->channels().empty() &&
607                         ((logic_signal = (*dec->channels().begin()).second)) &&
608                         ((data = logic_signal->logic_data())))
609                         break;
610
611         if (!data || data->logic_segments().empty())
612                 return;
613
614         const shared_ptr<LogicSegment> segment =
615                 data->logic_segments().front();
616         assert(segment);
617         const int64_t sample_count = (int64_t)segment->get_sample_count();
618         if (sample_count == 0)
619                 return;
620
621         const int64_t samples_decoded = decoder_stack_->samples_decoded();
622         if (sample_count == samples_decoded)
623                 return;
624
625         const int y = get_visual_y();
626
627         tie(pixels_offset, samples_per_pixel) =
628                 get_pixels_offset_samples_per_pixel();
629
630         const double start = max(samples_decoded /
631                 samples_per_pixel - pixels_offset, left - 1.0);
632         const double end = min(sample_count / samples_per_pixel -
633                 pixels_offset, right + 1.0);
634         const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
635
636         p.setPen(QPen(Qt::NoPen));
637         p.setBrush(Qt::white);
638         p.drawRect(no_decode_rect);
639
640         p.setPen(NoDecodeColour);
641         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
642         p.drawRect(no_decode_rect);
643 }
644
645 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
646 {
647         assert(owner_);
648         assert(decoder_stack_);
649
650         const View *view = owner_->view();
651         assert(view);
652
653         const double scale = view->scale();
654         assert(scale > 0);
655
656         const double pixels_offset =
657                 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
658
659         double samplerate = decoder_stack_->samplerate();
660
661         // Show sample rate as 1Hz when it is unknown
662         if (samplerate == 0.0)
663                 samplerate = 1.0;
664
665         return make_pair(pixels_offset, samplerate * scale);
666 }
667
668 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
669         int x_start, int x_end) const
670 {
671         double samples_per_pixel, pixels_offset;
672         tie(pixels_offset, samples_per_pixel) =
673                 get_pixels_offset_samples_per_pixel();
674
675         const uint64_t start = (uint64_t)max(
676                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
677         const uint64_t end = (uint64_t)max(
678                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
679
680         return make_pair(start, end);
681 }
682
683 int DecodeTrace::get_row_at_point(const QPoint &point)
684 {
685         if (!row_height_)
686                 return -1;
687
688         const int y = (point.y() - get_visual_y() + row_height_ / 2);
689
690         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
691         if (y < 0)
692                 return -1;
693
694         const int row = y / row_height_;
695
696         if (row >= (int)visible_rows_.size())
697                 return -1;
698
699         return row;
700 }
701
702 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
703 {
704         using namespace pv::data::decode;
705
706         if (!enabled())
707                 return QString();
708
709         const pair<uint64_t, uint64_t> sample_range =
710                 get_sample_range(point.x(), point.x() + 1);
711         const int row = get_row_at_point(point);
712         if (row < 0)
713                 return QString();
714
715         vector<pv::data::decode::Annotation> annotations;
716
717         assert(decoder_stack_);
718         decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
719                 sample_range.first, sample_range.second);
720
721         return (annotations.empty()) ?
722                 QString() : annotations[0].annotations().front();
723 }
724
725 void DecodeTrace::hover_point_changed()
726 {
727         assert(owner_);
728
729         const View *const view = owner_->view();
730         assert(view);
731
732         QPoint hp = view->hover_point();
733         QString ann = get_annotation_at_point(hp);
734
735         assert(view);
736
737         if (!row_height_ || ann.isEmpty()) {
738                 QToolTip::hideText();
739                 return;
740         }
741
742         const int hover_row = get_row_at_point(hp);
743
744         QFontMetrics m(QToolTip::font());
745         const QRect text_size = m.boundingRect(QRect(), 0, ann);
746
747         // This is OS-specific and unfortunately we can't query it, so
748         // use an approximation to at least try to minimize the error.
749         const int padding = 8;
750
751         // Make sure the tool tip doesn't overlap with the mouse cursor.
752         // If it did, the tool tip would constantly hide and re-appear.
753         // We also push it up by one row so that it appears above the
754         // decode trace, not below.
755         hp.setX(hp.x() - (text_size.width() / 2) - padding);
756
757         hp.setY(get_visual_y() - (row_height_ / 2) +
758                 (hover_row * row_height_) -
759                 row_height_ - text_size.height() - padding);
760
761         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
762 }
763
764 void DecodeTrace::create_decoder_form(int index,
765         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
766         QFormLayout *form)
767 {
768         const GSList *l;
769
770         assert(dec);
771         const srd_decoder *const decoder = dec->decoder();
772         assert(decoder);
773
774         const bool decoder_deletable = index > 0;
775
776         pv::widgets::DecoderGroupBox *const group =
777                 new pv::widgets::DecoderGroupBox(
778                         QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
779         group->set_decoder_visible(dec->shown());
780
781         if (decoder_deletable) {
782                 delete_mapper_.setMapping(group, index);
783                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
784         }
785
786         show_hide_mapper_.setMapping(group, index);
787         connect(group, SIGNAL(show_hide_decoder()),
788                 &show_hide_mapper_, SLOT(map()));
789
790         QFormLayout *const decoder_form = new QFormLayout;
791         group->add_layout(decoder_form);
792
793         // Add the mandatory channels
794         for (l = decoder->channels; l; l = l->next) {
795                 const struct srd_channel *const pdch =
796                         (struct srd_channel *)l->data;
797                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
798                 connect(combo, SIGNAL(currentIndexChanged(int)),
799                         this, SLOT(on_channel_selected(int)));
800                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
801                         .arg(QString::fromUtf8(pdch->name))
802                         .arg(QString::fromUtf8(pdch->desc)), combo);
803
804                 const ChannelSelector s = {combo, dec, pdch};
805                 channel_selectors_.push_back(s);
806         }
807
808         // Add the optional channels
809         for (l = decoder->opt_channels; l; l = l->next) {
810                 const struct srd_channel *const pdch =
811                         (struct srd_channel *)l->data;
812                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
813                 connect(combo, SIGNAL(currentIndexChanged(int)),
814                         this, SLOT(on_channel_selected(int)));
815                 decoder_form->addRow(tr("<b>%1</b> (%2)")
816                         .arg(QString::fromUtf8(pdch->name))
817                         .arg(QString::fromUtf8(pdch->desc)), combo);
818
819                 const ChannelSelector s = {combo, dec, pdch};
820                 channel_selectors_.push_back(s);
821         }
822
823         // Add the options
824         shared_ptr<binding::Decoder> binding(
825                 new binding::Decoder(decoder_stack_, dec));
826         binding->add_properties_to_form(decoder_form, true);
827
828         bindings_.push_back(binding);
829
830         form->addRow(group);
831         decoder_forms_.push_back(group);
832 }
833
834 QComboBox* DecodeTrace::create_channel_selector(
835         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
836         const srd_channel *const pdch)
837 {
838         assert(dec);
839
840         const auto sigs(session_.signals());
841
842         vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
843         std::sort(sig_list.begin(), sig_list.end(),
844                 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
845                         return a->name().compare(b->name()) < 0; });
846
847         assert(decoder_stack_);
848         const auto channel_iter = dec->channels().find(pdch);
849
850         QComboBox *selector = new QComboBox(parent);
851
852         selector->addItem("-", qVariantFromValue((void*)nullptr));
853
854         if (channel_iter == dec->channels().end())
855                 selector->setCurrentIndex(0);
856
857         for (const shared_ptr<view::Signal> &s : sig_list) {
858                 assert(s);
859                 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled()) {
860                         selector->addItem(s->name(),
861                                 qVariantFromValue((void*)s.get()));
862
863                         if (channel_iter != dec->channels().end() &&
864                                 (*channel_iter).second == s)
865                                 selector->setCurrentIndex(
866                                         selector->count() - 1);
867                 }
868         }
869
870         return selector;
871 }
872
873 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
874 {
875         assert(dec);
876
877         map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
878
879         const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
880
881         for (const ChannelSelector &s : channel_selectors_) {
882                 if (s.decoder_ != dec)
883                         break;
884
885                 const LogicSignal *const selection =
886                         (LogicSignal*)s.combo_->itemData(
887                                 s.combo_->currentIndex()).value<void*>();
888
889                 for (shared_ptr<Signal> sig : sigs)
890                         if (sig.get() == selection) {
891                                 channel_map[s.pdch_] =
892                                         dynamic_pointer_cast<LogicSignal>(sig);
893                                 break;
894                         }
895         }
896
897         dec->set_channels(channel_map);
898 }
899
900 void DecodeTrace::commit_channels()
901 {
902         assert(decoder_stack_);
903         for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
904                 commit_decoder_channels(dec);
905
906         decoder_stack_->begin_decode();
907 }
908
909 void DecodeTrace::on_new_decode_data()
910 {
911         if (owner_)
912                 owner_->row_item_appearance_changed(false, true);
913 }
914
915 void DecodeTrace::delete_pressed()
916 {
917         on_delete();
918 }
919
920 void DecodeTrace::on_delete()
921 {
922         session_.remove_decode_signal(this);
923 }
924
925 void DecodeTrace::on_channel_selected(int)
926 {
927         commit_channels();
928 }
929
930 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
931 {
932         assert(decoder);
933         assert(decoder_stack_);
934         decoder_stack_->push(shared_ptr<data::decode::Decoder>(
935                 new data::decode::Decoder(decoder)));
936         decoder_stack_->begin_decode();
937
938         create_popup_form();
939 }
940
941 void DecodeTrace::on_delete_decoder(int index)
942 {
943         decoder_stack_->remove(index);
944
945         // Update the popup
946         create_popup_form();    
947
948         decoder_stack_->begin_decode();
949 }
950
951 void DecodeTrace::on_show_hide_decoder(int index)
952 {
953         using pv::data::decode::Decoder;
954
955         const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
956
957         // Find the decoder in the stack
958         auto iter = stack.cbegin();
959         for (int i = 0; i < index; i++, iter++)
960                 assert(iter != stack.end());
961
962         shared_ptr<Decoder> dec = *iter;
963         assert(dec);
964
965         const bool show = !dec->shown();
966         dec->show(show);
967
968         assert(index < (int)decoder_forms_.size());
969         decoder_forms_[index]->set_decoder_visible(show);
970
971         if (owner_)
972                 owner_->row_item_appearance_changed(false, true);
973 }
974
975 } // namespace view
976 } // namespace pv