]> sigrok.org Git - pulseview.git/blob - pv/views/trace/decodetrace.cpp
Move trace view files
[pulseview.git] / pv / views / trace / decodetrace.cpp
1 /*
2  * This file is part of the PulseView project.
3  *
4  * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 extern "C" {
21 #include <libsigrokdecode/libsigrokdecode.h>
22 }
23
24 #include <mutex>
25
26 #include <extdef.h>
27
28 #include <tuple>
29
30 #include <boost/functional/hash.hpp>
31
32 #include <QAction>
33 #include <QApplication>
34 #include <QComboBox>
35 #include <QFormLayout>
36 #include <QLabel>
37 #include <QMenu>
38 #include <QPushButton>
39 #include <QToolTip>
40
41 #include "decodetrace.hpp"
42 #include "view.hpp"
43 #include "viewport.hpp"
44
45 #include <pv/globalsettings.hpp>
46 #include <pv/data/decode/annotation.hpp>
47 #include <pv/data/decode/decoder.hpp>
48 #include <pv/data/decoderstack.hpp>
49 #include <pv/data/logic.hpp>
50 #include <pv/data/logicsegment.hpp>
51 #include <pv/session.hpp>
52 #include <pv/strnatcmp.hpp>
53 #include <pv/widgets/decodergroupbox.hpp>
54 #include <pv/widgets/decodermenu.hpp>
55
56 using std::all_of;
57 using std::list;
58 using std::make_pair;
59 using std::max;
60 using std::make_pair;
61 using std::map;
62 using std::min;
63 using std::out_of_range;
64 using std::pair;
65 using std::shared_ptr;
66 using std::make_shared;
67 using std::tie;
68 using std::unordered_set;
69 using std::vector;
70
71 namespace pv {
72 namespace views {
73 namespace trace {
74
75 const QColor DecodeTrace::DecodeColours[4] = {
76         QColor(0xEF, 0x29, 0x29),       // Red
77         QColor(0xFC, 0xE9, 0x4F),       // Yellow
78         QColor(0x8A, 0xE2, 0x34),       // Green
79         QColor(0x72, 0x9F, 0xCF)        // Blue
80 };
81
82 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
83 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
84
85 const int DecodeTrace::ArrowSize = 4;
86 const double DecodeTrace::EndCapWidth = 5;
87 const int DecodeTrace::RowTitleMargin = 10;
88 const int DecodeTrace::DrawPadding = 100;
89
90 const QColor DecodeTrace::Colours[16] = {
91         QColor(0xEF, 0x29, 0x29),
92         QColor(0xF6, 0x6A, 0x32),
93         QColor(0xFC, 0xAE, 0x3E),
94         QColor(0xFB, 0xCA, 0x47),
95         QColor(0xFC, 0xE9, 0x4F),
96         QColor(0xCD, 0xF0, 0x40),
97         QColor(0x8A, 0xE2, 0x34),
98         QColor(0x4E, 0xDC, 0x44),
99         QColor(0x55, 0xD7, 0x95),
100         QColor(0x64, 0xD1, 0xD2),
101         QColor(0x72, 0x9F, 0xCF),
102         QColor(0xD4, 0x76, 0xC4),
103         QColor(0x9D, 0x79, 0xB9),
104         QColor(0xAD, 0x7F, 0xA8),
105         QColor(0xC2, 0x62, 0x9B),
106         QColor(0xD7, 0x47, 0x6F)
107 };
108
109 const QColor DecodeTrace::OutlineColours[16] = {
110         QColor(0x77, 0x14, 0x14),
111         QColor(0x7B, 0x35, 0x19),
112         QColor(0x7E, 0x57, 0x1F),
113         QColor(0x7D, 0x65, 0x23),
114         QColor(0x7E, 0x74, 0x27),
115         QColor(0x66, 0x78, 0x20),
116         QColor(0x45, 0x71, 0x1A),
117         QColor(0x27, 0x6E, 0x22),
118         QColor(0x2A, 0x6B, 0x4A),
119         QColor(0x32, 0x68, 0x69),
120         QColor(0x39, 0x4F, 0x67),
121         QColor(0x6A, 0x3B, 0x62),
122         QColor(0x4E, 0x3C, 0x5C),
123         QColor(0x56, 0x3F, 0x54),
124         QColor(0x61, 0x31, 0x4D),
125         QColor(0x6B, 0x23, 0x37)
126 };
127
128 DecodeTrace::DecodeTrace(pv::Session &session,
129         shared_ptr<data::SignalBase> signalbase, int index) :
130         Trace(signalbase),
131         session_(session),
132         row_height_(0),
133         max_visible_rows_(0),
134         delete_mapper_(this),
135         show_hide_mapper_(this)
136 {
137         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
138
139         // Determine shortest string we want to see displayed in full
140         QFontMetrics m(QApplication::font());
141         min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
142
143         base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
144         base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
145
146         connect(decoder_stack.get(), SIGNAL(new_decode_data()),
147                 this, SLOT(on_new_decode_data()));
148         connect(&delete_mapper_, SIGNAL(mapped(int)),
149                 this, SLOT(on_delete_decoder(int)));
150         connect(&show_hide_mapper_, SIGNAL(mapped(int)),
151                 this, SLOT(on_show_hide_decoder(int)));
152 }
153
154 bool DecodeTrace::enabled() const
155 {
156         return true;
157 }
158
159 shared_ptr<data::SignalBase> DecodeTrace::base() const
160 {
161         return base_;
162 }
163
164 pair<int, int> DecodeTrace::v_extents() const
165 {
166         const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
167
168         // Make an empty decode trace appear symmetrical
169         const int row_count = max(1, max_visible_rows_);
170
171         return make_pair(-row_height, row_height * row_count);
172 }
173
174 void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
175 {
176         Trace::paint_back(p, pp);
177         paint_axis(p, pp, get_visual_y());
178 }
179
180 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
181 {
182         using namespace pv::data::decode;
183
184         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
185
186         const int text_height = ViewItemPaintParams::text_height();
187         row_height_ = (text_height * 6) / 4;
188         const int annotation_height = (text_height * 5) / 4;
189
190         assert(decoder_stack);
191         const QString err = decoder_stack->error_message();
192         if (!err.isEmpty()) {
193                 draw_unresolved_period(
194                         p, annotation_height, pp.left(), pp.right());
195                 draw_error(p, err, pp);
196                 return;
197         }
198
199         // Set default pen to allow for text width calculation
200         p.setPen(Qt::black);
201
202         // Iterate through the rows
203         int y = get_visual_y();
204         pair<uint64_t, uint64_t> sample_range = get_sample_range(
205                 pp.left(), pp.right());
206
207         const vector<Row> rows(decoder_stack->get_visible_rows());
208
209         visible_rows_.clear();
210         for (const Row& row : rows) {
211                 // Cache the row title widths
212                 int row_title_width;
213                 try {
214                         row_title_width = row_title_widths_.at(row);
215                 } catch (out_of_range) {
216                         const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
217                                 RowTitleMargin;
218                         row_title_widths_[row] = w;
219                         row_title_width = w;
220                 }
221
222                 // Determine the row's color
223                 size_t base_colour = 0x13579BDF;
224                 boost::hash_combine(base_colour, this);
225                 boost::hash_combine(base_colour, row.decoder());
226                 boost::hash_combine(base_colour, row.row());
227                 base_colour >>= 16;
228
229                 vector<Annotation> annotations;
230                 decoder_stack->get_annotation_subset(annotations, row,
231                         sample_range.first, sample_range.second);
232                 if (!annotations.empty()) {
233                         draw_annotations(annotations, p, annotation_height, pp, y,
234                                 base_colour, row_title_width);
235
236                         y += row_height_;
237
238                         visible_rows_.push_back(row);
239                 }
240         }
241
242         // Draw the hatching
243         draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
244
245         if ((int)visible_rows_.size() > max_visible_rows_)
246                 owner_->extents_changed(false, true);
247
248         // Update the maximum row count if needed
249         max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
250 }
251
252 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
253 {
254         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         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
297
298         assert(form);
299         assert(parent);
300         assert(decoder_stack);
301
302         // Add the standard options
303         Trace::populate_popup_form(parent, form);
304
305         // Add the decoder options
306         bindings_.clear();
307         channel_selectors_.clear();
308         decoder_forms_.clear();
309
310         const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
311
312         if (stack.empty()) {
313                 QLabel *const l = new QLabel(
314                         tr("<p><i>No decoders in the stack</i></p>"));
315                 l->setAlignment(Qt::AlignCenter);
316                 form->addRow(l);
317         } else {
318                 auto iter = stack.cbegin();
319                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
320                         shared_ptr<Decoder> dec(*iter);
321                         create_decoder_form(i, dec, parent, form);
322                 }
323
324                 form->addRow(new QLabel(
325                         tr("<i>* Required channels</i>"), parent));
326         }
327
328         // Add stacking button
329         pv::widgets::DecoderMenu *const decoder_menu =
330                 new pv::widgets::DecoderMenu(parent);
331         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
332                 this, SLOT(on_stack_decoder(srd_decoder*)));
333
334         QPushButton *const stack_button =
335                 new QPushButton(tr("Stack Decoder"), parent);
336         stack_button->setMenu(decoder_menu);
337         stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
338
339         QHBoxLayout *stack_button_box = new QHBoxLayout;
340         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
341         form->addRow(stack_button_box);
342 }
343
344 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
345 {
346         QMenu *const menu = Trace::create_context_menu(parent);
347
348         menu->addSeparator();
349
350         QAction *const del = new QAction(tr("Delete"), this);
351         del->setShortcuts(QKeySequence::Delete);
352         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
353         menu->addAction(del);
354
355         return menu;
356 }
357
358 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
359                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
360                 size_t base_colour, int row_title_width)
361 {
362         using namespace pv::data::decode;
363
364         vector<Annotation> a_block;
365         int p_end = INT_MIN;
366
367         double samples_per_pixel, pixels_offset;
368         tie(pixels_offset, samples_per_pixel) =
369                 get_pixels_offset_samples_per_pixel();
370
371         // Sort the annotations by start sample so that decoders
372         // can't confuse us by creating annotations out of order
373         stable_sort(annotations.begin(), annotations.end(),
374                 [](const Annotation &a, const Annotation &b) {
375                         return a.start_sample() < b.start_sample(); });
376
377         // Gather all annotations that form a visual "block" and draw them as such
378         for (const Annotation &a : annotations) {
379
380                 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
381                 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
382                 const int a_width = a_end - a_start;
383
384                 const int delta = a_end - p_end;
385
386                 bool a_is_separate = false;
387
388                 // Annotation wider than the threshold for a useful label width?
389                 if (a_width >= min_useful_label_width_) {
390                         for (const QString &ann_text : a.annotations()) {
391                                 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
392                                 // Annotation wide enough to fit a label? Don't put it in a block then
393                                 if (w <= a_width) {
394                                         a_is_separate = true;
395                                         break;
396                                 }
397                         }
398                 }
399
400                 // Were the previous and this annotation more than a pixel apart?
401                 if ((abs(delta) > 1) || a_is_separate) {
402                         // Block was broken, draw annotations that form the current block
403                         if (a_block.size() == 1) {
404                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
405                                         row_title_width);
406                         }
407                         else
408                                 draw_annotation_block(a_block, p, h, y, base_colour);
409
410                         a_block.clear();
411                 }
412
413                 if (a_is_separate) {
414                         draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
415                         // Next annotation must start a new block. delta will be > 1
416                         // because we set p_end to INT_MIN but that's okay since
417                         // a_block will be empty, so nothing will be drawn
418                         p_end = INT_MIN;
419                 } else {
420                         a_block.push_back(a);
421                         p_end = a_end;
422                 }
423         }
424
425         if (a_block.size() == 1)
426                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
427                         row_title_width);
428         else
429                 draw_annotation_block(a_block, p, h, y, base_colour);
430 }
431
432 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
433         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
434         size_t base_colour, int row_title_width) const
435 {
436         double samples_per_pixel, pixels_offset;
437         tie(pixels_offset, samples_per_pixel) =
438                 get_pixels_offset_samples_per_pixel();
439
440         const double start = a.start_sample() / samples_per_pixel -
441                 pixels_offset;
442         const double end = a.end_sample() / samples_per_pixel - pixels_offset;
443
444         const size_t colour = (base_colour + a.format()) % countof(Colours);
445         p.setPen(OutlineColours[colour]);
446         p.setBrush(Colours[colour]);
447
448         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
449                 return;
450
451         if (a.start_sample() == a.end_sample())
452                 draw_instant(a, p, h, start, y);
453         else
454                 draw_range(a, p, h, start, end, y, pp, row_title_width);
455 }
456
457 void DecodeTrace::draw_annotation_block(
458         vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
459         int y, size_t base_colour) const
460 {
461         using namespace pv::data::decode;
462
463         if (annotations.empty())
464                 return;
465
466         double samples_per_pixel, pixels_offset;
467         tie(pixels_offset, samples_per_pixel) =
468                 get_pixels_offset_samples_per_pixel();
469
470         const double start = annotations.front().start_sample() /
471                 samples_per_pixel - pixels_offset;
472         const double end = annotations.back().end_sample() /
473                 samples_per_pixel - pixels_offset;
474
475         const double top = y + .5 - h / 2;
476         const double bottom = y + .5 + h / 2;
477
478         const size_t colour = (base_colour + annotations.front().format()) %
479                 countof(Colours);
480
481         // Check if all annotations are of the same type (i.e. we can use one color)
482         // or if we should use a neutral color (i.e. gray)
483         const int format = annotations.front().format();
484         const bool single_format = all_of(
485                 annotations.begin(), annotations.end(),
486                 [&](const Annotation &a) { return a.format() == format; });
487
488         const QRectF rect(start, top, end - start, bottom - top);
489         const int r = h / 4;
490
491         p.setPen(QPen(Qt::NoPen));
492         p.setBrush(Qt::white);
493         p.drawRoundedRect(rect, r, r);
494
495         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
496         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
497                 Qt::Dense4Pattern));
498         p.drawRoundedRect(rect, r, r);
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 = max(ann_start, pp.left() + row_title_width);
550         const int real_end = 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         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
607
608         assert(decoder_stack);
609
610         shared_ptr<Logic> data;
611         shared_ptr<data::SignalBase> signalbase;
612
613         const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
614
615         // We get the logic data of the first channel in the list.
616         // This works because we are currently assuming all
617         // LogicSignals have the same data/segment
618         for (const shared_ptr<Decoder> &dec : stack)
619                 if (dec && !dec->channels().empty() &&
620                         ((signalbase = (*dec->channels().begin()).second)) &&
621                         ((data = signalbase->logic_data())))
622                         break;
623
624         if (!data || data->logic_segments().empty())
625                 return;
626
627         const shared_ptr<LogicSegment> segment = data->logic_segments().front();
628         assert(segment);
629         const int64_t sample_count = (int64_t)segment->get_sample_count();
630         if (sample_count == 0)
631                 return;
632
633         const int64_t samples_decoded = decoder_stack->samples_decoded();
634         if (sample_count == samples_decoded)
635                 return;
636
637         const int y = get_visual_y();
638
639         tie(pixels_offset, samples_per_pixel) =
640                 get_pixels_offset_samples_per_pixel();
641
642         const double start = max(samples_decoded /
643                 samples_per_pixel - pixels_offset, left - 1.0);
644         const double end = min(sample_count / samples_per_pixel -
645                 pixels_offset, right + 1.0);
646         const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
647
648         p.setPen(QPen(Qt::NoPen));
649         p.setBrush(Qt::white);
650         p.drawRect(no_decode_rect);
651
652         p.setPen(NoDecodeColour);
653         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
654         p.drawRect(no_decode_rect);
655 }
656
657 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
658 {
659         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
660
661         assert(owner_);
662         assert(decoder_stack);
663
664         const View *view = owner_->view();
665         assert(view);
666
667         const double scale = view->scale();
668         assert(scale > 0);
669
670         const double pixels_offset =
671                 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
672
673         double samplerate = decoder_stack->samplerate();
674
675         // Show sample rate as 1Hz when it is unknown
676         if (samplerate == 0.0)
677                 samplerate = 1.0;
678
679         return make_pair(pixels_offset, samplerate * scale);
680 }
681
682 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
683         int x_start, int x_end) const
684 {
685         double samples_per_pixel, pixels_offset;
686         tie(pixels_offset, samples_per_pixel) =
687                 get_pixels_offset_samples_per_pixel();
688
689         const uint64_t start = (uint64_t)max(
690                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
691         const uint64_t end = (uint64_t)max(
692                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
693
694         return make_pair(start, end);
695 }
696
697 int DecodeTrace::get_row_at_point(const QPoint &point)
698 {
699         if (!row_height_)
700                 return -1;
701
702         const int y = (point.y() - get_visual_y() + row_height_ / 2);
703
704         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
705         if (y < 0)
706                 return -1;
707
708         const int row = y / row_height_;
709
710         if (row >= (int)visible_rows_.size())
711                 return -1;
712
713         return row;
714 }
715
716 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
717 {
718         using namespace pv::data::decode;
719
720         if (!enabled())
721                 return QString();
722
723         const pair<uint64_t, uint64_t> sample_range =
724                 get_sample_range(point.x(), point.x() + 1);
725         const int row = get_row_at_point(point);
726         if (row < 0)
727                 return QString();
728
729         vector<pv::data::decode::Annotation> annotations;
730
731         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
732
733         assert(decoder_stack);
734         decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
735                 sample_range.first, sample_range.second);
736
737         return (annotations.empty()) ?
738                 QString() : annotations[0].annotations().front();
739 }
740
741 void DecodeTrace::hover_point_changed()
742 {
743         assert(owner_);
744
745         const View *const view = owner_->view();
746         assert(view);
747
748         QPoint hp = view->hover_point();
749         QString ann = get_annotation_at_point(hp);
750
751         assert(view);
752
753         if (!row_height_ || ann.isEmpty()) {
754                 QToolTip::hideText();
755                 return;
756         }
757
758         const int hover_row = get_row_at_point(hp);
759
760         QFontMetrics m(QToolTip::font());
761         const QRect text_size = m.boundingRect(QRect(), 0, ann);
762
763         // This is OS-specific and unfortunately we can't query it, so
764         // use an approximation to at least try to minimize the error.
765         const int padding = 8;
766
767         // Make sure the tool tip doesn't overlap with the mouse cursor.
768         // If it did, the tool tip would constantly hide and re-appear.
769         // We also push it up by one row so that it appears above the
770         // decode trace, not below.
771         hp.setX(hp.x() - (text_size.width() / 2) - padding);
772
773         hp.setY(get_visual_y() - (row_height_ / 2) +
774                 (hover_row * row_height_) -
775                 row_height_ - text_size.height() - padding);
776
777         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
778 }
779
780 void DecodeTrace::create_decoder_form(int index,
781         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
782         QFormLayout *form)
783 {
784         const GSList *l;
785         GlobalSettings settings;
786
787         assert(dec);
788         const srd_decoder *const decoder = dec->decoder();
789         assert(decoder);
790
791         const bool decoder_deletable = index > 0;
792
793         pv::widgets::DecoderGroupBox *const group =
794                 new pv::widgets::DecoderGroupBox(
795                         QString::fromUtf8(decoder->name),
796                         tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
797                                 QString::fromUtf8(decoder->desc)),
798                         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
818                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
819                 QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch);
820
821                 connect(combo, SIGNAL(currentIndexChanged(int)),
822                         this, SLOT(on_channel_selected(int)));
823                 connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)),
824                         this, SLOT(on_initial_pin_selected(int)));
825
826                 QHBoxLayout *const hlayout = new QHBoxLayout;
827                 hlayout->addWidget(combo);
828                 hlayout->addWidget(combo_initial_pin);
829
830                 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
831                         combo_initial_pin->hide();
832
833                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
834                         .arg(QString::fromUtf8(pdch->name),
835                              QString::fromUtf8(pdch->desc)), hlayout);
836
837                 const ChannelSelector s = {combo, combo_initial_pin, dec, pdch};
838                 channel_selectors_.push_back(s);
839         }
840
841         // Add the optional channels
842         for (l = decoder->opt_channels; l; l = l->next) {
843                 const struct srd_channel *const pdch =
844                         (struct srd_channel *)l->data;
845
846                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
847                 QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch);
848
849                 connect(combo, SIGNAL(currentIndexChanged(int)),
850                         this, SLOT(on_channel_selected(int)));
851                 connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)),
852                         this, SLOT(on_initial_pin_selected(int)));
853
854                 QHBoxLayout *const hlayout = new QHBoxLayout;
855                 hlayout->addWidget(combo);
856                 hlayout->addWidget(combo_initial_pin);
857
858                 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
859                         combo_initial_pin->hide();
860
861                 decoder_form->addRow(tr("<b>%1</b> (%2)")
862                         .arg(QString::fromUtf8(pdch->name),
863                              QString::fromUtf8(pdch->desc)), hlayout);
864
865                 const ChannelSelector s = {combo, combo_initial_pin, dec, pdch};
866                 channel_selectors_.push_back(s);
867         }
868
869         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
870
871         // Add the options
872         shared_ptr<binding::Decoder> binding(
873                 new binding::Decoder(decoder_stack, dec));
874         binding->add_properties_to_form(decoder_form, true);
875
876         bindings_.push_back(binding);
877
878         form->addRow(group);
879         decoder_forms_.push_back(group);
880 }
881
882 QComboBox* DecodeTrace::create_channel_selector(
883         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
884         const srd_channel *const pdch)
885 {
886         assert(dec);
887
888         const auto sigs(session_.signalbases());
889
890         vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
891         sort(sig_list.begin(), sig_list.end(),
892                 [](const shared_ptr<data::SignalBase> &a,
893                 const shared_ptr<data::SignalBase> &b) {
894                         return strnatcasecmp(a->name().toStdString(),
895                                 b->name().toStdString()) < 0; });
896
897         const auto channel_iter = dec->channels().find(pdch);
898
899         QComboBox *selector = new QComboBox(parent);
900
901         selector->addItem("-", qVariantFromValue((void*)nullptr));
902
903         if (channel_iter == dec->channels().end())
904                 selector->setCurrentIndex(0);
905
906         for (const shared_ptr<data::SignalBase> &b : sig_list) {
907                 assert(b);
908                 if (b->logic_data() && b->enabled()) {
909                         selector->addItem(b->name(),
910                                 qVariantFromValue((void*)b.get()));
911
912                         if (channel_iter != dec->channels().end() &&
913                                 (*channel_iter).second == b)
914                                 selector->setCurrentIndex(
915                                         selector->count() - 1);
916                 }
917         }
918
919         return selector;
920 }
921
922 QComboBox* DecodeTrace::create_channel_selector_initial_pin(QWidget *parent,
923         const shared_ptr<data::decode::Decoder> &dec, const srd_channel *const pdch)
924 {
925         QComboBox *selector = new QComboBox(parent);
926
927         selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
928         selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
929         selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
930
931         // Default to index 2 (SRD_INITIAL_PIN_SAME_AS_SAMPLE0).
932         const int idx = (!dec->initial_pins()) ? 2 : dec->initial_pins()->data[pdch->order];
933         selector->setCurrentIndex(idx);
934
935         selector->setToolTip("Initial (assumed) pin value before the first sample");
936
937         return selector;
938 }
939
940 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
941 {
942         assert(dec);
943
944         map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
945
946         const unordered_set< shared_ptr<data::SignalBase> >
947                 sigs(session_.signalbases());
948
949         GArray *const initial_pins = g_array_sized_new(FALSE, TRUE,
950                 sizeof(uint8_t), channel_selectors_.size());
951         g_array_set_size(initial_pins, channel_selectors_.size());
952
953         for (const ChannelSelector &s : channel_selectors_) {
954                 if (s.decoder_ != dec)
955                         break;
956
957                 const data::SignalBase *const selection =
958                         (data::SignalBase*)s.combo_->itemData(
959                                 s.combo_->currentIndex()).value<void*>();
960
961                 for (shared_ptr<data::SignalBase> sig : sigs)
962                         if (sig.get() == selection) {
963                                 channel_map[s.pdch_] = sig;
964                                 break;
965                         }
966
967                 int selection_initial_pin = s.combo_initial_pin_->itemData(
968                         s.combo_initial_pin_->currentIndex()).value<int>();
969
970                 initial_pins->data[s.pdch_->order] = selection_initial_pin;
971         }
972
973         dec->set_channels(channel_map);
974         dec->set_initial_pins(initial_pins);
975 }
976
977 void DecodeTrace::commit_channels()
978 {
979         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
980
981         assert(decoder_stack);
982         for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
983                 commit_decoder_channels(dec);
984
985         decoder_stack->begin_decode();
986 }
987
988 void DecodeTrace::on_new_decode_data()
989 {
990         if (owner_)
991                 owner_->row_item_appearance_changed(false, true);
992 }
993
994 void DecodeTrace::delete_pressed()
995 {
996         on_delete();
997 }
998
999 void DecodeTrace::on_delete()
1000 {
1001         session_.remove_decode_signal(base_);
1002 }
1003
1004 void DecodeTrace::on_channel_selected(int)
1005 {
1006         commit_channels();
1007 }
1008
1009 void DecodeTrace::on_initial_pin_selected(int)
1010 {
1011         commit_channels();
1012 }
1013
1014 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
1015 {
1016         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
1017
1018         assert(decoder);
1019         assert(decoder_stack);
1020         decoder_stack->push(make_shared<data::decode::Decoder>(decoder));
1021         decoder_stack->begin_decode();
1022
1023         create_popup_form();
1024 }
1025
1026 void DecodeTrace::on_delete_decoder(int index)
1027 {
1028         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
1029
1030         decoder_stack->remove(index);
1031
1032         // Update the popup
1033         create_popup_form();
1034
1035         decoder_stack->begin_decode();
1036 }
1037
1038 void DecodeTrace::on_show_hide_decoder(int index)
1039 {
1040         using pv::data::decode::Decoder;
1041
1042         shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
1043
1044         const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
1045
1046         // Find the decoder in the stack
1047         auto iter = stack.cbegin();
1048         for (int i = 0; i < index; i++, iter++)
1049                 assert(iter != stack.end());
1050
1051         shared_ptr<Decoder> dec = *iter;
1052         assert(dec);
1053
1054         const bool show = !dec->shown();
1055         dec->show(show);
1056
1057         assert(index < (int)decoder_forms_.size());
1058         decoder_forms_[index]->set_decoder_visible(show);
1059
1060         if (owner_)
1061                 owner_->row_item_appearance_changed(false, true);
1062 }
1063
1064 } // namespace trace
1065 } // namespace views
1066 } // namespace pv