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