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
185 pair<uint64_t, uint64_t> sample_range = get_sample_range(left, right);
187 assert(_decoder_stack);
188 const vector<Row> rows(_decoder_stack->get_visible_rows());
190 _visible_rows.clear();
191 for (size_t i = 0; i < rows.size(); i++)
193 const Row &row = rows[i];
195 size_t base_colour = 0x13579BDF;
196 boost::hash_combine(base_colour, this);
197 boost::hash_combine(base_colour, row.decoder());
198 boost::hash_combine(base_colour, row.row());
201 vector<Annotation> annotations;
202 _decoder_stack->get_annotation_subset(annotations, row,
203 sample_range.first, sample_range.second);
204 if (!annotations.empty()) {
205 for (const Annotation &a : annotations)
206 draw_annotation(a, p, get_text_colour(),
207 annotation_height, left, right, y,
211 _visible_rows.push_back(rows[i]);
216 draw_unresolved_period(p, annotation_height, left, right);
219 void DecodeTrace::paint_fore(QPainter &p, int left, int right)
221 using namespace pv::data::decode;
227 for (size_t i = 0; i < _visible_rows.size(); i++)
229 const int y = i * _row_height + get_y();
231 p.setPen(QPen(Qt::NoPen));
232 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
236 const QPointF points[] = {
237 QPointF(left, y - ArrowSize),
238 QPointF(left + ArrowSize, y),
239 QPointF(left, y + ArrowSize)
241 p.drawPolygon(points, countof(points));
244 const QRect r(left + ArrowSize * 2, y - _row_height / 2,
245 right - left, _row_height);
246 const QString h(_visible_rows[i].title());
247 const int f = Qt::AlignLeft | Qt::AlignVCenter |
251 p.setPen(QApplication::palette().color(QPalette::Base));
252 for (int dx = -1; dx <= 1; dx++)
253 for (int dy = -1; dy <= 1; dy++)
254 if (dx != 0 && dy != 0)
255 p.drawText(r.translated(dx, dy), f, h);
258 p.setPen(QApplication::palette().color(QPalette::WindowText));
263 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
265 using pv::data::decode::Decoder;
269 assert(_decoder_stack);
271 // Add the standard options
272 Trace::populate_popup_form(parent, form);
274 // Add the decoder options
276 _channel_selectors.clear();
277 _decoder_forms.clear();
279 const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
283 QLabel *const l = new QLabel(
284 tr("<p><i>No decoders in the stack</i></p>"));
285 l->setAlignment(Qt::AlignCenter);
290 auto iter = stack.cbegin();
291 for (int i = 0; i < (int)stack.size(); i++, iter++) {
292 shared_ptr<Decoder> dec(*iter);
293 create_decoder_form(i, dec, parent, form);
296 form->addRow(new QLabel(
297 tr("<i>* Required channels</i>"), parent));
300 // Add stacking button
301 pv::widgets::DecoderMenu *const decoder_menu =
302 new pv::widgets::DecoderMenu(parent);
303 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
304 this, SLOT(on_stack_decoder(srd_decoder*)));
306 QPushButton *const stack_button =
307 new QPushButton(tr("Stack Decoder"), parent);
308 stack_button->setMenu(decoder_menu);
310 QHBoxLayout *stack_button_box = new QHBoxLayout;
311 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
312 form->addRow(stack_button_box);
315 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
317 QMenu *const menu = Trace::create_context_menu(parent);
319 menu->addSeparator();
321 QAction *const del = new QAction(tr("Delete"), this);
322 del->setShortcuts(QKeySequence::Delete);
323 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
324 menu->addAction(del);
329 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
330 QPainter &p, QColor text_color, int h, int left, int right, int y,
331 size_t base_colour) const
333 double samples_per_pixel, pixels_offset;
334 tie(pixels_offset, samples_per_pixel) =
335 get_pixels_offset_samples_per_pixel();
337 const double start = a.start_sample() / samples_per_pixel -
339 const double end = a.end_sample() / samples_per_pixel -
342 const size_t colour = (base_colour + a.format()) % countof(Colours);
343 const QColor &fill = Colours[colour];
344 const QColor &outline = OutlineColours[colour];
346 if (start > right + DrawPadding || end < left - DrawPadding)
349 if (a.start_sample() == a.end_sample())
350 draw_instant(a, p, fill, outline, text_color, h,
353 draw_range(a, p, fill, outline, text_color, h,
357 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
358 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
360 const QString text = a.annotations().empty() ?
361 QString() : a.annotations().back();
362 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
364 const QRectF rect(x - w / 2, y - h / 2, w, h);
368 p.drawRoundedRect(rect, h / 2, h / 2);
370 p.setPen(text_color);
371 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
374 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
375 QColor fill, QColor outline, QColor text_color, int h, double start,
376 double end, int y) const
378 const double top = y + .5 - h / 2;
379 const double bottom = y + .5 + h / 2;
380 const vector<QString> annotations = a.annotations();
385 // If the two ends are within 1 pixel, draw a vertical line
386 if (start + 1.0 > end)
388 p.drawLine(QPointF(start, top), QPointF(start, bottom));
392 const double cap_width = min((end - start) / 4, EndCapWidth);
395 QPointF(start, y + .5f),
396 QPointF(start + cap_width, top),
397 QPointF(end - cap_width, top),
398 QPointF(end, y + .5f),
399 QPointF(end - cap_width, bottom),
400 QPointF(start + cap_width, bottom)
403 p.drawConvexPolygon(pts, countof(pts));
405 if (annotations.empty())
408 QRectF rect(start + cap_width, y - h / 2,
409 end - start - cap_width * 2, h);
410 if (rect.width() <= 4)
413 p.setPen(text_color);
415 // Try to find an annotation that will fit
416 QString best_annotation;
419 for (const QString &a : annotations) {
420 const int w = p.boundingRect(QRectF(), 0, a).width();
421 if (w <= rect.width() && w > best_width)
422 best_annotation = a, best_width = w;
425 if (best_annotation.isEmpty())
426 best_annotation = annotations.back();
428 // If not ellide the last in the list
429 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
430 best_annotation, Qt::ElideRight, rect.width()));
433 void DecodeTrace::draw_error(QPainter &p, const QString &message,
436 const int y = get_y();
438 p.setPen(ErrorBgColour.darker());
439 p.setBrush(ErrorBgColour);
441 const QRectF bounding_rect =
442 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
443 const QRectF text_rect = p.boundingRect(bounding_rect,
444 Qt::AlignCenter, message);
445 const float r = text_rect.height() / 4;
447 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
450 p.setPen(get_text_colour());
451 p.drawText(text_rect, message);
454 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
457 using namespace pv::data;
458 using pv::data::decode::Decoder;
460 double samples_per_pixel, pixels_offset;
462 assert(_decoder_stack);
464 shared_ptr<Logic> data;
465 shared_ptr<LogicSignal> logic_signal;
467 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
469 // We get the logic data of the first channel in the list.
470 // This works because we are currently assuming all
471 // LogicSignals have the same data/snapshot
472 for (const shared_ptr<Decoder> &dec : stack)
473 if (dec && !dec->channels().empty() &&
474 ((logic_signal = (*dec->channels().begin()).second)) &&
475 ((data = logic_signal->logic_data())))
478 if (!data || data->get_snapshots().empty())
481 const shared_ptr<LogicSnapshot> snapshot =
482 data->get_snapshots().front();
484 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
485 if (sample_count == 0)
488 const int64_t samples_decoded = _decoder_stack->samples_decoded();
489 if (sample_count == samples_decoded)
492 const int y = get_y();
494 tie(pixels_offset, samples_per_pixel) =
495 get_pixels_offset_samples_per_pixel();
497 const double start = max(samples_decoded /
498 samples_per_pixel - pixels_offset, left - 1.0);
499 const double end = min(sample_count / samples_per_pixel -
500 pixels_offset, right + 1.0);
501 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
503 p.setPen(QPen(Qt::NoPen));
504 p.setBrush(Qt::white);
505 p.drawRect(no_decode_rect);
507 p.setPen(NoDecodeColour);
508 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
509 p.drawRect(no_decode_rect);
512 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
515 assert(_decoder_stack);
517 const double scale = _view->scale();
520 const double pixels_offset =
521 (_view->offset() - _decoder_stack->get_start_time()) / scale;
523 double samplerate = _decoder_stack->samplerate();
525 // Show sample rate as 1Hz when it is unknown
526 if (samplerate == 0.0)
529 return make_pair(pixels_offset, samplerate * scale);
532 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
533 int x_start, int x_end) const
535 double samples_per_pixel, pixels_offset;
536 tie(pixels_offset, samples_per_pixel) =
537 get_pixels_offset_samples_per_pixel();
539 const uint64_t start = (uint64_t)max(
540 (x_start + pixels_offset) * samples_per_pixel, 0.0);
541 const uint64_t end = (uint64_t)max(
542 (x_end + pixels_offset) * samples_per_pixel, 0.0);
544 return make_pair(start, end);
547 int DecodeTrace::get_row_at_point(const QPoint &point)
552 const int row = (point.y() - get_y() + _row_height / 2) / _row_height;
553 if (row < 0 || row >= (int)_visible_rows.size())
559 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
561 using namespace pv::data::decode;
566 const pair<uint64_t, uint64_t> sample_range =
567 get_sample_range(point.x(), point.x() + 1);
568 const int row = get_row_at_point(point);
572 vector<pv::data::decode::Annotation> annotations;
574 assert(_decoder_stack);
575 _decoder_stack->get_annotation_subset(annotations, _visible_rows[row],
576 sample_range.first, sample_range.second);
578 return (annotations.empty()) ?
579 QString() : annotations[0].annotations().front();
582 void DecodeTrace::hide_hover_annotation()
584 QToolTip::hideText();
587 void DecodeTrace::hover_point_changed()
589 QPoint hp = _view->hover_point();
590 QString ann = get_annotation_at_point(hp);
596 hide_hover_annotation();
600 const int hover_row = get_row_at_point(hp);
602 QFontMetrics m(QToolTip::font());
603 const QRect text_size = m.boundingRect(QRect(), 0, ann);
605 // This is OS-specific and unfortunately we can't query it, so
606 // use an approximation to at least try to minimize the error.
607 const int padding = 8;
609 // Make sure the tool tip doesn't overlap with the mouse cursor.
610 // If it did, the tool tip would constantly hide and re-appear.
611 // We also push it up by one row so that it appears above the
612 // decode trace, not below.
613 hp.setX(hp.x() - (text_size.width() / 2) - padding);
615 hp.setY(get_y() - (_row_height / 2) + (hover_row * _row_height)
616 - _row_height - text_size.height());
618 QToolTip::showText(_view->viewport()->mapToGlobal(hp), ann);
621 void DecodeTrace::create_decoder_form(int index,
622 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
628 const srd_decoder *const decoder = dec->decoder();
631 pv::widgets::DecoderGroupBox *const group =
632 new pv::widgets::DecoderGroupBox(
633 QString::fromUtf8(decoder->name));
634 group->set_decoder_visible(dec->shown());
636 _delete_mapper.setMapping(group, index);
637 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
639 _show_hide_mapper.setMapping(group, index);
640 connect(group, SIGNAL(show_hide_decoder()),
641 &_show_hide_mapper, SLOT(map()));
643 QFormLayout *const decoder_form = new QFormLayout;
644 group->add_layout(decoder_form);
646 // Add the mandatory channels
647 for(l = decoder->channels; l; l = l->next) {
648 const struct srd_channel *const pdch =
649 (struct srd_channel *)l->data;
650 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
651 connect(combo, SIGNAL(currentIndexChanged(int)),
652 this, SLOT(on_channel_selected(int)));
653 decoder_form->addRow(tr("<b>%1</b> (%2) *")
654 .arg(QString::fromUtf8(pdch->name))
655 .arg(QString::fromUtf8(pdch->desc)), combo);
657 const ChannelSelector s = {combo, dec, pdch};
658 _channel_selectors.push_back(s);
661 // Add the optional channels
662 for(l = decoder->opt_channels; l; l = l->next) {
663 const struct srd_channel *const pdch =
664 (struct srd_channel *)l->data;
665 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
666 connect(combo, SIGNAL(currentIndexChanged(int)),
667 this, SLOT(on_channel_selected(int)));
668 decoder_form->addRow(tr("<b>%1</b> (%2)")
669 .arg(QString::fromUtf8(pdch->name))
670 .arg(QString::fromUtf8(pdch->desc)), combo);
672 const ChannelSelector s = {combo, dec, pdch};
673 _channel_selectors.push_back(s);
677 shared_ptr<prop::binding::DecoderOptions> binding(
678 new prop::binding::DecoderOptions(_decoder_stack, dec));
679 binding->add_properties_to_form(decoder_form, true);
681 _bindings.push_back(binding);
684 _decoder_forms.push_back(group);
687 QComboBox* DecodeTrace::create_channel_selector(
688 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
689 const srd_channel *const pdch)
693 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
695 assert(_decoder_stack);
696 const auto channel_iter = dec->channels().find(pdch);
698 QComboBox *selector = new QComboBox(parent);
700 selector->addItem("-", qVariantFromValue((void*)NULL));
702 if (channel_iter == dec->channels().end())
703 selector->setCurrentIndex(0);
705 for(size_t i = 0; i < sigs.size(); i++) {
706 const shared_ptr<view::Signal> s(sigs[i]);
709 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
711 selector->addItem(s->get_name(),
712 qVariantFromValue((void*)s.get()));
713 if ((*channel_iter).second == s)
714 selector->setCurrentIndex(i + 1);
721 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
725 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
726 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
728 for (const ChannelSelector &s : _channel_selectors)
730 if(s._decoder != dec)
733 const LogicSignal *const selection =
734 (LogicSignal*)s._combo->itemData(
735 s._combo->currentIndex()).value<void*>();
737 for (shared_ptr<Signal> sig : sigs)
738 if(sig.get() == selection) {
739 channel_map[s._pdch] =
740 dynamic_pointer_cast<LogicSignal>(sig);
745 dec->set_channels(channel_map);
748 void DecodeTrace::commit_channels()
750 assert(_decoder_stack);
751 for (shared_ptr<data::decode::Decoder> dec : _decoder_stack->stack())
752 commit_decoder_channels(dec);
754 _decoder_stack->begin_decode();
757 void DecodeTrace::on_new_decode_data()
760 _view->update_viewport();
763 void DecodeTrace::delete_pressed()
768 void DecodeTrace::on_delete()
770 _session.remove_decode_signal(this);
773 void DecodeTrace::on_channel_selected(int)
778 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
781 assert(_decoder_stack);
782 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
783 new data::decode::Decoder(decoder)));
784 _decoder_stack->begin_decode();
789 void DecodeTrace::on_delete_decoder(int index)
791 _decoder_stack->remove(index);
796 _decoder_stack->begin_decode();
799 void DecodeTrace::on_show_hide_decoder(int index)
801 using pv::data::decode::Decoder;
803 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
805 // Find the decoder in the stack
806 auto iter = stack.cbegin();
807 for(int i = 0; i < index; i++, iter++)
808 assert(iter != stack.end());
810 shared_ptr<Decoder> dec = *iter;
813 const bool show = !dec->shown();
816 assert(index < (int)_decoder_forms.size());
817 _decoder_forms[index]->set_decoder_visible(show);
819 _view->update_viewport();