2 * This file is part of the PulseView project.
4 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
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.
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.
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
22 #include <libsigrokdecode/libsigrokdecode.h>
29 #include <boost/functional/hash.hpp>
32 #include <QApplication>
34 #include <QFormLayout>
37 #include <QPushButton>
40 #include "decodetrace.h"
42 #include <pv/sigsession.h>
43 #include <pv/data/decoderstack.h>
44 #include <pv/data/decode/decoder.h>
45 #include <pv/data/logic.h>
46 #include <pv/data/logicsnapshot.h>
47 #include <pv/data/decode/annotation.h>
48 #include <pv/view/logicsignal.h>
49 #include <pv/view/view.h>
50 #include <pv/view/viewport.h>
51 #include <pv/widgets/decodergroupbox.h>
52 #include <pv/widgets/decodermenu.h>
54 using std::dynamic_pointer_cast;
61 using std::shared_ptr;
68 const QColor DecodeTrace::DecodeColours[4] = {
69 QColor(0xEF, 0x29, 0x29), // Red
70 QColor(0xFC, 0xE9, 0x4F), // Yellow
71 QColor(0x8A, 0xE2, 0x34), // Green
72 QColor(0x72, 0x9F, 0xCF) // Blue
75 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
76 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
78 const int DecodeTrace::ArrowSize = 4;
79 const double DecodeTrace::EndCapWidth = 5;
80 const int DecodeTrace::DrawPadding = 100;
82 const QColor DecodeTrace::Colours[16] = {
83 QColor(0xEF, 0x29, 0x29),
84 QColor(0xF6, 0x6A, 0x32),
85 QColor(0xFC, 0xAE, 0x3E),
86 QColor(0xFB, 0xCA, 0x47),
87 QColor(0xFC, 0xE9, 0x4F),
88 QColor(0xCD, 0xF0, 0x40),
89 QColor(0x8A, 0xE2, 0x34),
90 QColor(0x4E, 0xDC, 0x44),
91 QColor(0x55, 0xD7, 0x95),
92 QColor(0x64, 0xD1, 0xD2),
93 QColor(0x72, 0x9F, 0xCF),
94 QColor(0xD4, 0x76, 0xC4),
95 QColor(0x9D, 0x79, 0xB9),
96 QColor(0xAD, 0x7F, 0xA8),
97 QColor(0xC2, 0x62, 0x9B),
98 QColor(0xD7, 0x47, 0x6F)
101 const QColor DecodeTrace::OutlineColours[16] = {
102 QColor(0x77, 0x14, 0x14),
103 QColor(0x7B, 0x35, 0x19),
104 QColor(0x7E, 0x57, 0x1F),
105 QColor(0x7D, 0x65, 0x23),
106 QColor(0x7E, 0x74, 0x27),
107 QColor(0x66, 0x78, 0x20),
108 QColor(0x45, 0x71, 0x1A),
109 QColor(0x27, 0x6E, 0x22),
110 QColor(0x2A, 0x6B, 0x4A),
111 QColor(0x32, 0x68, 0x69),
112 QColor(0x39, 0x4F, 0x67),
113 QColor(0x6A, 0x3B, 0x62),
114 QColor(0x4E, 0x3C, 0x5C),
115 QColor(0x56, 0x3F, 0x54),
116 QColor(0x61, 0x31, 0x4D),
117 QColor(0x6B, 0x23, 0x37)
120 DecodeTrace::DecodeTrace(pv::SigSession &session,
121 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
122 Trace(QString::fromUtf8(
123 decoder_stack->stack().front()->decoder()->name)),
125 _decoder_stack(decoder_stack),
128 _delete_mapper(this),
129 _show_hide_mapper(this)
131 assert(_decoder_stack);
133 _colour = DecodeColours[index % countof(DecodeColours)];
135 connect(_decoder_stack.get(), SIGNAL(new_decode_data()),
136 this, SLOT(on_new_decode_data()));
137 connect(&_delete_mapper, SIGNAL(mapped(int)),
138 this, SLOT(on_delete_decoder(int)));
139 connect(&_show_hide_mapper, SIGNAL(mapped(int)),
140 this, SLOT(on_show_hide_decoder(int)));
143 bool DecodeTrace::enabled() const
148 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
150 return _decoder_stack;
153 void DecodeTrace::set_view(pv::view::View *view)
156 Trace::set_view(view);
159 void DecodeTrace::paint_back(QPainter &p, int left, int right)
161 Trace::paint_back(p, left, right);
162 paint_axis(p, get_y(), left, right);
165 void DecodeTrace::paint_mid(QPainter &p, int left, int right)
167 using namespace pv::data::decode;
169 QFontMetrics m(QApplication::font());
170 _text_height = m.boundingRect(QRect(), 0, "Tg").height();
171 _row_height = (_text_height * 6) / 4;
172 const int annotation_height = (_text_height * 5) / 4;
174 assert(_decoder_stack);
175 const QString err = _decoder_stack->error_message();
178 draw_unresolved_period(p, annotation_height, left, right);
179 draw_error(p, err, left, right);
183 // Iterate through the rows
186 pair<uint64_t, uint64_t> sample_range = get_sample_range(left, right);
188 assert(_decoder_stack);
189 const vector<Row> rows(_decoder_stack->get_visible_rows());
191 _visible_rows.clear();
192 for (size_t i = 0; i < rows.size(); i++)
194 const Row &row = rows[i];
196 size_t base_colour = 0x13579BDF;
197 boost::hash_combine(base_colour, this);
198 boost::hash_combine(base_colour, row.decoder());
199 boost::hash_combine(base_colour, row.row());
202 vector<Annotation> annotations;
203 _decoder_stack->get_annotation_subset(annotations, row,
204 sample_range.first, sample_range.second);
205 if (!annotations.empty()) {
206 for (const Annotation &a : annotations)
207 draw_annotation(a, p, get_text_colour(),
208 annotation_height, left, right, y,
212 _visible_rows.push_back(rows[i]);
217 draw_unresolved_period(p, annotation_height, left, right);
220 void DecodeTrace::paint_fore(QPainter &p, int left, int right)
222 using namespace pv::data::decode;
228 for (size_t i = 0; i < _visible_rows.size(); i++)
230 const int y = i * _row_height + get_y();
232 p.setPen(QPen(Qt::NoPen));
233 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
237 const QPointF points[] = {
238 QPointF(left, y - ArrowSize),
239 QPointF(left + ArrowSize, y),
240 QPointF(left, y + ArrowSize)
242 p.drawPolygon(points, countof(points));
245 const QRect r(left + ArrowSize * 2, y - _row_height / 2,
246 right - left, _row_height);
247 const QString h(_visible_rows[i].title());
248 const int f = Qt::AlignLeft | Qt::AlignVCenter |
252 p.setPen(QApplication::palette().color(QPalette::Base));
253 for (int dx = -1; dx <= 1; dx++)
254 for (int dy = -1; dy <= 1; dy++)
255 if (dx != 0 && dy != 0)
256 p.drawText(r.translated(dx, dy), f, h);
259 p.setPen(QApplication::palette().color(QPalette::WindowText));
264 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
266 using pv::data::decode::Decoder;
270 assert(_decoder_stack);
272 // Add the standard options
273 Trace::populate_popup_form(parent, form);
275 // Add the decoder options
277 _channel_selectors.clear();
278 _decoder_forms.clear();
280 const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
284 QLabel *const l = new QLabel(
285 tr("<p><i>No decoders in the stack</i></p>"));
286 l->setAlignment(Qt::AlignCenter);
291 auto iter = stack.cbegin();
292 for (int i = 0; i < (int)stack.size(); i++, iter++) {
293 shared_ptr<Decoder> dec(*iter);
294 create_decoder_form(i, dec, parent, form);
297 form->addRow(new QLabel(
298 tr("<i>* Required channels</i>"), parent));
301 // Add stacking button
302 pv::widgets::DecoderMenu *const decoder_menu =
303 new pv::widgets::DecoderMenu(parent);
304 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
305 this, SLOT(on_stack_decoder(srd_decoder*)));
307 QPushButton *const stack_button =
308 new QPushButton(tr("Stack Decoder"), parent);
309 stack_button->setMenu(decoder_menu);
311 QHBoxLayout *stack_button_box = new QHBoxLayout;
312 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
313 form->addRow(stack_button_box);
316 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
318 QMenu *const menu = Trace::create_context_menu(parent);
320 menu->addSeparator();
322 QAction *const del = new QAction(tr("Delete"), this);
323 del->setShortcuts(QKeySequence::Delete);
324 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
325 menu->addAction(del);
330 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
331 QPainter &p, QColor text_color, int h, int left, int right, int y,
332 size_t base_colour) const
334 double samples_per_pixel, pixels_offset;
335 tie(pixels_offset, samples_per_pixel) =
336 get_pixels_offset_samples_per_pixel();
338 const double start = a.start_sample() / samples_per_pixel -
340 const double end = a.end_sample() / samples_per_pixel -
343 const size_t colour = (base_colour + a.format()) % countof(Colours);
344 const QColor &fill = Colours[colour];
345 const QColor &outline = OutlineColours[colour];
347 if (start > right + DrawPadding || end < left - DrawPadding)
350 if (a.start_sample() == a.end_sample())
351 draw_instant(a, p, fill, outline, text_color, h,
354 draw_range(a, p, fill, outline, text_color, h,
358 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
359 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
361 const QString text = a.annotations().empty() ?
362 QString() : a.annotations().back();
363 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
365 const QRectF rect(x - w / 2, y - h / 2, w, h);
369 p.drawRoundedRect(rect, h / 2, h / 2);
371 p.setPen(text_color);
372 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
375 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
376 QColor fill, QColor outline, QColor text_color, int h, double start,
377 double end, int y) const
379 const double top = y + .5 - h / 2;
380 const double bottom = y + .5 + h / 2;
381 const vector<QString> annotations = a.annotations();
386 // If the two ends are within 1 pixel, draw a vertical line
387 if (start + 1.0 > end)
389 p.drawLine(QPointF(start, top), QPointF(start, bottom));
393 const double cap_width = min((end - start) / 4, EndCapWidth);
396 QPointF(start, y + .5f),
397 QPointF(start + cap_width, top),
398 QPointF(end - cap_width, top),
399 QPointF(end, y + .5f),
400 QPointF(end - cap_width, bottom),
401 QPointF(start + cap_width, bottom)
404 p.drawConvexPolygon(pts, countof(pts));
406 if (annotations.empty())
409 QRectF rect(start + cap_width, y - h / 2,
410 end - start - cap_width * 2, h);
411 if (rect.width() <= 4)
414 p.setPen(text_color);
416 // Try to find an annotation that will fit
417 QString best_annotation;
420 for (const QString &a : annotations) {
421 const int w = p.boundingRect(QRectF(), 0, a).width();
422 if (w <= rect.width() && w > best_width)
423 best_annotation = a, best_width = w;
426 if (best_annotation.isEmpty())
427 best_annotation = annotations.back();
429 // If not ellide the last in the list
430 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
431 best_annotation, Qt::ElideRight, rect.width()));
434 void DecodeTrace::draw_error(QPainter &p, const QString &message,
437 const int y = get_y();
439 p.setPen(ErrorBgColour.darker());
440 p.setBrush(ErrorBgColour);
442 const QRectF bounding_rect =
443 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
444 const QRectF text_rect = p.boundingRect(bounding_rect,
445 Qt::AlignCenter, message);
446 const float r = text_rect.height() / 4;
448 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
451 p.setPen(get_text_colour());
452 p.drawText(text_rect, message);
455 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
458 using namespace pv::data;
459 using pv::data::decode::Decoder;
461 double samples_per_pixel, pixels_offset;
463 assert(_decoder_stack);
465 shared_ptr<Logic> data;
466 shared_ptr<LogicSignal> logic_signal;
468 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
470 // We get the logic data of the first channel in the list.
471 // This works because we are currently assuming all
472 // LogicSignals have the same data/snapshot
473 for (const shared_ptr<Decoder> &dec : stack)
474 if (dec && !dec->channels().empty() &&
475 ((logic_signal = (*dec->channels().begin()).second)) &&
476 ((data = logic_signal->logic_data())))
479 if (!data || data->get_snapshots().empty())
482 const shared_ptr<LogicSnapshot> snapshot =
483 data->get_snapshots().front();
485 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
486 if (sample_count == 0)
489 const int64_t samples_decoded = _decoder_stack->samples_decoded();
490 if (sample_count == samples_decoded)
493 const int y = get_y();
495 tie(pixels_offset, samples_per_pixel) =
496 get_pixels_offset_samples_per_pixel();
498 const double start = max(samples_decoded /
499 samples_per_pixel - pixels_offset, left - 1.0);
500 const double end = min(sample_count / samples_per_pixel -
501 pixels_offset, right + 1.0);
502 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
504 p.setPen(QPen(Qt::NoPen));
505 p.setBrush(Qt::white);
506 p.drawRect(no_decode_rect);
508 p.setPen(NoDecodeColour);
509 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
510 p.drawRect(no_decode_rect);
513 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
516 assert(_decoder_stack);
518 const double scale = _view->scale();
521 const double pixels_offset =
522 (_view->offset() - _decoder_stack->get_start_time()) / scale;
524 double samplerate = _decoder_stack->samplerate();
526 // Show sample rate as 1Hz when it is unknown
527 if (samplerate == 0.0)
530 return make_pair(pixels_offset, samplerate * scale);
533 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
534 int x_start, int x_end) const
536 double samples_per_pixel, pixels_offset;
537 tie(pixels_offset, samples_per_pixel) =
538 get_pixels_offset_samples_per_pixel();
540 const uint64_t start = (uint64_t)max(
541 (x_start + pixels_offset) * samples_per_pixel, 0.0);
542 const uint64_t end = (uint64_t)max(
543 (x_end + pixels_offset) * samples_per_pixel, 0.0);
545 return make_pair(start, end);
548 int DecodeTrace::get_row_at_point(const QPoint &point)
553 const int row = (point.y() - get_y() + _row_height / 2) / _row_height;
554 if (row < 0 || row >= (int)_visible_rows.size())
560 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
562 using namespace pv::data::decode;
567 const pair<uint64_t, uint64_t> sample_range =
568 get_sample_range(point.x(), point.x() + 1);
569 const int row = get_row_at_point(point);
573 vector<pv::data::decode::Annotation> annotations;
575 assert(_decoder_stack);
576 _decoder_stack->get_annotation_subset(annotations, _visible_rows[row],
577 sample_range.first, sample_range.second);
579 return (annotations.empty()) ?
580 QString() : annotations[0].annotations().front();
583 void DecodeTrace::hide_hover_annotation()
585 QToolTip::hideText();
588 void DecodeTrace::hover_point_changed()
590 QPoint hp = _view->hover_point();
591 QString ann = get_annotation_at_point(hp);
597 hide_hover_annotation();
601 const int hover_row = get_row_at_point(hp);
603 QFontMetrics m(QToolTip::font());
604 const QRect text_size = m.boundingRect(QRect(), 0, ann);
606 // This is OS-specific and unfortunately we can't query it, so
607 // use an approximation to at least try to minimize the error.
608 const int padding = 8;
610 // Make sure the tool tip doesn't overlap with the mouse cursor.
611 // If it did, the tool tip would constantly hide and re-appear.
612 // We also push it up by one row so that it appears above the
613 // decode trace, not below.
614 hp.setX(hp.x() - (text_size.width() / 2) - padding);
616 hp.setY(get_y() - (_row_height / 2) + (hover_row * _row_height)
617 - _row_height - text_size.height());
619 QToolTip::showText(_view->viewport()->mapToGlobal(hp), ann);
622 void DecodeTrace::create_decoder_form(int index,
623 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
629 const srd_decoder *const decoder = dec->decoder();
632 pv::widgets::DecoderGroupBox *const group =
633 new pv::widgets::DecoderGroupBox(
634 QString::fromUtf8(decoder->name));
635 group->set_decoder_visible(dec->shown());
637 _delete_mapper.setMapping(group, index);
638 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
640 _show_hide_mapper.setMapping(group, index);
641 connect(group, SIGNAL(show_hide_decoder()),
642 &_show_hide_mapper, SLOT(map()));
644 QFormLayout *const decoder_form = new QFormLayout;
645 group->add_layout(decoder_form);
647 // Add the mandatory channels
648 for(l = decoder->channels; l; l = l->next) {
649 const struct srd_channel *const pdch =
650 (struct srd_channel *)l->data;
651 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
652 connect(combo, SIGNAL(currentIndexChanged(int)),
653 this, SLOT(on_channel_selected(int)));
654 decoder_form->addRow(tr("<b>%1</b> (%2) *")
655 .arg(QString::fromUtf8(pdch->name))
656 .arg(QString::fromUtf8(pdch->desc)), combo);
658 const ChannelSelector s = {combo, dec, pdch};
659 _channel_selectors.push_back(s);
662 // Add the optional channels
663 for(l = decoder->opt_channels; l; l = l->next) {
664 const struct srd_channel *const pdch =
665 (struct srd_channel *)l->data;
666 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
667 connect(combo, SIGNAL(currentIndexChanged(int)),
668 this, SLOT(on_channel_selected(int)));
669 decoder_form->addRow(tr("<b>%1</b> (%2)")
670 .arg(QString::fromUtf8(pdch->name))
671 .arg(QString::fromUtf8(pdch->desc)), combo);
673 const ChannelSelector s = {combo, dec, pdch};
674 _channel_selectors.push_back(s);
678 shared_ptr<prop::binding::DecoderOptions> binding(
679 new prop::binding::DecoderOptions(_decoder_stack, dec));
680 binding->add_properties_to_form(decoder_form, true);
682 _bindings.push_back(binding);
685 _decoder_forms.push_back(group);
688 QComboBox* DecodeTrace::create_channel_selector(
689 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
690 const srd_channel *const pdch)
694 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
696 assert(_decoder_stack);
697 const auto channel_iter = dec->channels().find(pdch);
699 QComboBox *selector = new QComboBox(parent);
701 selector->addItem("-", qVariantFromValue((void*)NULL));
703 if (channel_iter == dec->channels().end())
704 selector->setCurrentIndex(0);
706 for(size_t i = 0; i < sigs.size(); i++) {
707 const shared_ptr<view::Signal> s(sigs[i]);
710 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
712 selector->addItem(s->get_name(),
713 qVariantFromValue((void*)s.get()));
714 if ((*channel_iter).second == s)
715 selector->setCurrentIndex(i + 1);
722 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
726 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
727 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
729 for (const ChannelSelector &s : _channel_selectors)
731 if(s._decoder != dec)
734 const LogicSignal *const selection =
735 (LogicSignal*)s._combo->itemData(
736 s._combo->currentIndex()).value<void*>();
738 for (shared_ptr<Signal> sig : sigs)
739 if(sig.get() == selection) {
740 channel_map[s._pdch] =
741 dynamic_pointer_cast<LogicSignal>(sig);
746 dec->set_channels(channel_map);
749 void DecodeTrace::commit_channels()
751 assert(_decoder_stack);
752 for (shared_ptr<data::decode::Decoder> dec : _decoder_stack->stack())
753 commit_decoder_channels(dec);
755 _decoder_stack->begin_decode();
758 void DecodeTrace::on_new_decode_data()
761 _view->update_viewport();
764 void DecodeTrace::delete_pressed()
769 void DecodeTrace::on_delete()
771 _session.remove_decode_signal(this);
774 void DecodeTrace::on_channel_selected(int)
779 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
782 assert(_decoder_stack);
783 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
784 new data::decode::Decoder(decoder)));
785 _decoder_stack->begin_decode();
790 void DecodeTrace::on_delete_decoder(int index)
792 _decoder_stack->remove(index);
797 _decoder_stack->begin_decode();
800 void DecodeTrace::on_show_hide_decoder(int index)
802 using pv::data::decode::Decoder;
804 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
806 // Find the decoder in the stack
807 auto iter = stack.cbegin();
808 for(int i = 0; i < index; i++, iter++)
809 assert(iter != stack.end());
811 shared_ptr<Decoder> dec = *iter;
814 const bool show = !dec->shown();
817 assert(index < (int)_decoder_forms.size());
818 _decoder_forms[index]->set_decoder_visible(show);
820 _view->update_viewport();