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