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