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::paint_back(QPainter &p, int left, int right)
155 Trace::paint_back(p, left, right);
156 paint_axis(p, get_y(), left, right);
159 void DecodeTrace::paint_mid(QPainter &p, int left, int right)
161 using namespace pv::data::decode;
163 QFontMetrics m(QApplication::font());
164 _text_height = m.boundingRect(QRect(), 0, "Tg").height();
165 _row_height = (_text_height * 6) / 4;
166 const int annotation_height = (_text_height * 5) / 4;
168 assert(_decoder_stack);
169 const QString err = _decoder_stack->error_message();
172 draw_unresolved_period(p, annotation_height, left, right);
173 draw_error(p, err, left, right);
177 // Iterate through the rows
179 pair<uint64_t, uint64_t> sample_range = get_sample_range(left, right);
181 assert(_decoder_stack);
182 const vector<Row> rows(_decoder_stack->get_visible_rows());
184 _visible_rows.clear();
185 for (size_t i = 0; i < rows.size(); i++)
187 const Row &row = rows[i];
189 size_t base_colour = 0x13579BDF;
190 boost::hash_combine(base_colour, this);
191 boost::hash_combine(base_colour, row.decoder());
192 boost::hash_combine(base_colour, row.row());
195 vector<Annotation> annotations;
196 _decoder_stack->get_annotation_subset(annotations, row,
197 sample_range.first, sample_range.second);
198 if (!annotations.empty()) {
199 for (const Annotation &a : annotations)
200 draw_annotation(a, p, get_text_colour(),
201 annotation_height, left, right, y,
205 _visible_rows.push_back(rows[i]);
210 draw_unresolved_period(p, annotation_height, left, right);
213 void DecodeTrace::paint_fore(QPainter &p, int left, int right)
215 using namespace pv::data::decode;
221 for (size_t i = 0; i < _visible_rows.size(); i++)
223 const int y = i * _row_height + get_y();
225 p.setPen(QPen(Qt::NoPen));
226 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
230 const QPointF points[] = {
231 QPointF(left, y - ArrowSize),
232 QPointF(left + ArrowSize, y),
233 QPointF(left, y + ArrowSize)
235 p.drawPolygon(points, countof(points));
238 const QRect r(left + ArrowSize * 2, y - _row_height / 2,
239 right - left, _row_height);
240 const QString h(_visible_rows[i].title());
241 const int f = Qt::AlignLeft | Qt::AlignVCenter |
245 p.setPen(QApplication::palette().color(QPalette::Base));
246 for (int dx = -1; dx <= 1; dx++)
247 for (int dy = -1; dy <= 1; dy++)
248 if (dx != 0 && dy != 0)
249 p.drawText(r.translated(dx, dy), f, h);
252 p.setPen(QApplication::palette().color(QPalette::WindowText));
257 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
259 using pv::data::decode::Decoder;
263 assert(_decoder_stack);
265 // Add the standard options
266 Trace::populate_popup_form(parent, form);
268 // Add the decoder options
270 _channel_selectors.clear();
271 _decoder_forms.clear();
273 const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
277 QLabel *const l = new QLabel(
278 tr("<p><i>No decoders in the stack</i></p>"));
279 l->setAlignment(Qt::AlignCenter);
284 auto iter = stack.cbegin();
285 for (int i = 0; i < (int)stack.size(); i++, iter++) {
286 shared_ptr<Decoder> dec(*iter);
287 create_decoder_form(i, dec, parent, form);
290 form->addRow(new QLabel(
291 tr("<i>* Required channels</i>"), parent));
294 // Add stacking button
295 pv::widgets::DecoderMenu *const decoder_menu =
296 new pv::widgets::DecoderMenu(parent);
297 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
298 this, SLOT(on_stack_decoder(srd_decoder*)));
300 QPushButton *const stack_button =
301 new QPushButton(tr("Stack Decoder"), parent);
302 stack_button->setMenu(decoder_menu);
304 QHBoxLayout *stack_button_box = new QHBoxLayout;
305 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
306 form->addRow(stack_button_box);
309 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
311 QMenu *const menu = Trace::create_context_menu(parent);
313 menu->addSeparator();
315 QAction *const del = new QAction(tr("Delete"), this);
316 del->setShortcuts(QKeySequence::Delete);
317 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
318 menu->addAction(del);
323 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
324 QPainter &p, QColor text_color, int h, int left, int right, int y,
325 size_t base_colour) const
327 double samples_per_pixel, pixels_offset;
328 tie(pixels_offset, samples_per_pixel) =
329 get_pixels_offset_samples_per_pixel();
331 const double start = a.start_sample() / samples_per_pixel -
333 const double end = a.end_sample() / samples_per_pixel -
336 const size_t colour = (base_colour + a.format()) % countof(Colours);
337 const QColor &fill = Colours[colour];
338 const QColor &outline = OutlineColours[colour];
340 if (start > right + DrawPadding || end < left - DrawPadding)
343 if (a.start_sample() == a.end_sample())
344 draw_instant(a, p, fill, outline, text_color, h,
347 draw_range(a, p, fill, outline, text_color, h,
351 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
352 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
354 const QString text = a.annotations().empty() ?
355 QString() : a.annotations().back();
356 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
358 const QRectF rect(x - w / 2, y - h / 2, w, h);
362 p.drawRoundedRect(rect, h / 2, h / 2);
364 p.setPen(text_color);
365 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
368 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
369 QColor fill, QColor outline, QColor text_color, int h, double start,
370 double end, int y) const
372 const double top = y + .5 - h / 2;
373 const double bottom = y + .5 + h / 2;
374 const vector<QString> annotations = a.annotations();
379 // If the two ends are within 1 pixel, draw a vertical line
380 if (start + 1.0 > end)
382 p.drawLine(QPointF(start, top), QPointF(start, bottom));
386 const double cap_width = min((end - start) / 4, EndCapWidth);
389 QPointF(start, y + .5f),
390 QPointF(start + cap_width, top),
391 QPointF(end - cap_width, top),
392 QPointF(end, y + .5f),
393 QPointF(end - cap_width, bottom),
394 QPointF(start + cap_width, bottom)
397 p.drawConvexPolygon(pts, countof(pts));
399 if (annotations.empty())
402 QRectF rect(start + cap_width, y - h / 2,
403 end - start - cap_width * 2, h);
404 if (rect.width() <= 4)
407 p.setPen(text_color);
409 // Try to find an annotation that will fit
410 QString best_annotation;
413 for (const QString &a : annotations) {
414 const int w = p.boundingRect(QRectF(), 0, a).width();
415 if (w <= rect.width() && w > best_width)
416 best_annotation = a, best_width = w;
419 if (best_annotation.isEmpty())
420 best_annotation = annotations.back();
422 // If not ellide the last in the list
423 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
424 best_annotation, Qt::ElideRight, rect.width()));
427 void DecodeTrace::draw_error(QPainter &p, const QString &message,
430 const int y = get_y();
432 p.setPen(ErrorBgColour.darker());
433 p.setBrush(ErrorBgColour);
435 const QRectF bounding_rect =
436 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
437 const QRectF text_rect = p.boundingRect(bounding_rect,
438 Qt::AlignCenter, message);
439 const float r = text_rect.height() / 4;
441 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
444 p.setPen(get_text_colour());
445 p.drawText(text_rect, message);
448 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
451 using namespace pv::data;
452 using pv::data::decode::Decoder;
454 double samples_per_pixel, pixels_offset;
456 assert(_decoder_stack);
458 shared_ptr<Logic> data;
459 shared_ptr<LogicSignal> logic_signal;
461 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
463 // We get the logic data of the first channel in the list.
464 // This works because we are currently assuming all
465 // LogicSignals have the same data/snapshot
466 for (const shared_ptr<Decoder> &dec : stack)
467 if (dec && !dec->channels().empty() &&
468 ((logic_signal = (*dec->channels().begin()).second)) &&
469 ((data = logic_signal->logic_data())))
472 if (!data || data->get_snapshots().empty())
475 const shared_ptr<LogicSnapshot> snapshot =
476 data->get_snapshots().front();
478 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
479 if (sample_count == 0)
482 const int64_t samples_decoded = _decoder_stack->samples_decoded();
483 if (sample_count == samples_decoded)
486 const int y = get_y();
488 tie(pixels_offset, samples_per_pixel) =
489 get_pixels_offset_samples_per_pixel();
491 const double start = max(samples_decoded /
492 samples_per_pixel - pixels_offset, left - 1.0);
493 const double end = min(sample_count / samples_per_pixel -
494 pixels_offset, right + 1.0);
495 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
497 p.setPen(QPen(Qt::NoPen));
498 p.setBrush(Qt::white);
499 p.drawRect(no_decode_rect);
501 p.setPen(NoDecodeColour);
502 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
503 p.drawRect(no_decode_rect);
506 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
509 assert(_decoder_stack);
511 const View *view = _owner->view();
514 const double scale = view->scale();
517 const double pixels_offset =
518 (view->offset() - _decoder_stack->get_start_time()) / scale;
520 double samplerate = _decoder_stack->samplerate();
522 // Show sample rate as 1Hz when it is unknown
523 if (samplerate == 0.0)
526 return make_pair(pixels_offset, samplerate * scale);
529 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
530 int x_start, int x_end) const
532 double samples_per_pixel, pixels_offset;
533 tie(pixels_offset, samples_per_pixel) =
534 get_pixels_offset_samples_per_pixel();
536 const uint64_t start = (uint64_t)max(
537 (x_start + pixels_offset) * samples_per_pixel, 0.0);
538 const uint64_t end = (uint64_t)max(
539 (x_end + pixels_offset) * samples_per_pixel, 0.0);
541 return make_pair(start, end);
544 int DecodeTrace::get_row_at_point(const QPoint &point)
549 const int row = (point.y() - get_y() + _row_height / 2) / _row_height;
550 if (row < 0 || row >= (int)_visible_rows.size())
556 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
558 using namespace pv::data::decode;
563 const pair<uint64_t, uint64_t> sample_range =
564 get_sample_range(point.x(), point.x() + 1);
565 const int row = get_row_at_point(point);
569 vector<pv::data::decode::Annotation> annotations;
571 assert(_decoder_stack);
572 _decoder_stack->get_annotation_subset(annotations, _visible_rows[row],
573 sample_range.first, sample_range.second);
575 return (annotations.empty()) ?
576 QString() : annotations[0].annotations().front();
579 void DecodeTrace::hide_hover_annotation()
581 QToolTip::hideText();
584 void DecodeTrace::hover_point_changed()
588 const View *const view = _owner->view();
591 QPoint hp = view->hover_point();
592 QString ann = get_annotation_at_point(hp);
598 hide_hover_annotation();
602 const int hover_row = get_row_at_point(hp);
604 QFontMetrics m(QToolTip::font());
605 const QRect text_size = m.boundingRect(QRect(), 0, ann);
607 // This is OS-specific and unfortunately we can't query it, so
608 // use an approximation to at least try to minimize the error.
609 const int padding = 8;
611 // Make sure the tool tip doesn't overlap with the mouse cursor.
612 // If it did, the tool tip would constantly hide and re-appear.
613 // We also push it up by one row so that it appears above the
614 // decode trace, not below.
615 hp.setX(hp.x() - (text_size.width() / 2) - padding);
617 hp.setY(get_y() - (_row_height / 2) + (hover_row * _row_height)
618 - _row_height - text_size.height());
620 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
623 void DecodeTrace::create_decoder_form(int index,
624 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
630 const srd_decoder *const decoder = dec->decoder();
633 pv::widgets::DecoderGroupBox *const group =
634 new pv::widgets::DecoderGroupBox(
635 QString::fromUtf8(decoder->name));
636 group->set_decoder_visible(dec->shown());
638 _delete_mapper.setMapping(group, index);
639 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
641 _show_hide_mapper.setMapping(group, index);
642 connect(group, SIGNAL(show_hide_decoder()),
643 &_show_hide_mapper, SLOT(map()));
645 QFormLayout *const decoder_form = new QFormLayout;
646 group->add_layout(decoder_form);
648 // Add the mandatory channels
649 for(l = decoder->channels; l; l = l->next) {
650 const struct srd_channel *const pdch =
651 (struct srd_channel *)l->data;
652 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
653 connect(combo, SIGNAL(currentIndexChanged(int)),
654 this, SLOT(on_channel_selected(int)));
655 decoder_form->addRow(tr("<b>%1</b> (%2) *")
656 .arg(QString::fromUtf8(pdch->name))
657 .arg(QString::fromUtf8(pdch->desc)), combo);
659 const ChannelSelector s = {combo, dec, pdch};
660 _channel_selectors.push_back(s);
663 // Add the optional channels
664 for(l = decoder->opt_channels; l; l = l->next) {
665 const struct srd_channel *const pdch =
666 (struct srd_channel *)l->data;
667 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
668 connect(combo, SIGNAL(currentIndexChanged(int)),
669 this, SLOT(on_channel_selected(int)));
670 decoder_form->addRow(tr("<b>%1</b> (%2)")
671 .arg(QString::fromUtf8(pdch->name))
672 .arg(QString::fromUtf8(pdch->desc)), combo);
674 const ChannelSelector s = {combo, dec, pdch};
675 _channel_selectors.push_back(s);
679 shared_ptr<prop::binding::DecoderOptions> binding(
680 new prop::binding::DecoderOptions(_decoder_stack, dec));
681 binding->add_properties_to_form(decoder_form, true);
683 _bindings.push_back(binding);
686 _decoder_forms.push_back(group);
689 QComboBox* DecodeTrace::create_channel_selector(
690 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
691 const srd_channel *const pdch)
695 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
697 assert(_decoder_stack);
698 const auto channel_iter = dec->channels().find(pdch);
700 QComboBox *selector = new QComboBox(parent);
702 selector->addItem("-", qVariantFromValue((void*)NULL));
704 if (channel_iter == dec->channels().end())
705 selector->setCurrentIndex(0);
707 for(size_t i = 0; i < sigs.size(); i++) {
708 const shared_ptr<view::Signal> s(sigs[i]);
711 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
713 selector->addItem(s->name(),
714 qVariantFromValue((void*)s.get()));
715 if ((*channel_iter).second == s)
716 selector->setCurrentIndex(i + 1);
723 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
727 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
728 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
730 for (const ChannelSelector &s : _channel_selectors)
732 if(s._decoder != dec)
735 const LogicSignal *const selection =
736 (LogicSignal*)s._combo->itemData(
737 s._combo->currentIndex()).value<void*>();
739 for (shared_ptr<Signal> sig : sigs)
740 if(sig.get() == selection) {
741 channel_map[s._pdch] =
742 dynamic_pointer_cast<LogicSignal>(sig);
747 dec->set_channels(channel_map);
750 void DecodeTrace::commit_channels()
752 assert(_decoder_stack);
753 for (shared_ptr<data::decode::Decoder> dec : _decoder_stack->stack())
754 commit_decoder_channels(dec);
756 _decoder_stack->begin_decode();
759 void DecodeTrace::on_new_decode_data()
762 _owner->update_viewport();
765 void DecodeTrace::delete_pressed()
770 void DecodeTrace::on_delete()
772 _session.remove_decode_signal(this);
775 void DecodeTrace::on_channel_selected(int)
780 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
783 assert(_decoder_stack);
784 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
785 new data::decode::Decoder(decoder)));
786 _decoder_stack->begin_decode();
791 void DecodeTrace::on_delete_decoder(int index)
793 _decoder_stack->remove(index);
798 _decoder_stack->begin_decode();
801 void DecodeTrace::on_show_hide_decoder(int index)
803 using pv::data::decode::Decoder;
805 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
807 // Find the decoder in the stack
808 auto iter = stack.cbegin();
809 for(int i = 0; i < index; i++, iter++)
810 assert(iter != stack.end());
812 shared_ptr<Decoder> dec = *iter;
815 const bool show = !dec->shown();
818 assert(index < (int)_decoder_forms.size());
819 _decoder_forms[index]->set_decoder_visible(show);
821 _owner->update_viewport();