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>
27 #include <boost/functional/hash.hpp>
30 #include <QApplication>
32 #include <QFormLayout>
35 #include <QPushButton>
38 #include "decodetrace.h"
40 #include <pv/sigsession.h>
41 #include <pv/data/decoderstack.h>
42 #include <pv/data/decode/decoder.h>
43 #include <pv/data/logic.h>
44 #include <pv/data/logicsnapshot.h>
45 #include <pv/data/decode/annotation.h>
46 #include <pv/view/logicsignal.h>
47 #include <pv/view/view.h>
48 #include <pv/view/viewport.h>
49 #include <pv/widgets/decodergroupbox.h>
50 #include <pv/widgets/decodermenu.h>
52 using std::dynamic_pointer_cast;
59 using std::shared_ptr;
65 const QColor DecodeTrace::DecodeColours[4] = {
66 QColor(0xEF, 0x29, 0x29), // Red
67 QColor(0xFC, 0xE9, 0x4F), // Yellow
68 QColor(0x8A, 0xE2, 0x34), // Green
69 QColor(0x72, 0x9F, 0xCF) // Blue
72 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
73 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
75 const int DecodeTrace::ArrowSize = 4;
76 const double DecodeTrace::EndCapWidth = 5;
77 const int DecodeTrace::DrawPadding = 100;
79 const QColor DecodeTrace::Colours[16] = {
80 QColor(0xEF, 0x29, 0x29),
81 QColor(0xF6, 0x6A, 0x32),
82 QColor(0xFC, 0xAE, 0x3E),
83 QColor(0xFB, 0xCA, 0x47),
84 QColor(0xFC, 0xE9, 0x4F),
85 QColor(0xCD, 0xF0, 0x40),
86 QColor(0x8A, 0xE2, 0x34),
87 QColor(0x4E, 0xDC, 0x44),
88 QColor(0x55, 0xD7, 0x95),
89 QColor(0x64, 0xD1, 0xD2),
90 QColor(0x72, 0x9F, 0xCF),
91 QColor(0xD4, 0x76, 0xC4),
92 QColor(0x9D, 0x79, 0xB9),
93 QColor(0xAD, 0x7F, 0xA8),
94 QColor(0xC2, 0x62, 0x9B),
95 QColor(0xD7, 0x47, 0x6F)
98 const QColor DecodeTrace::OutlineColours[16] = {
99 QColor(0x77, 0x14, 0x14),
100 QColor(0x7B, 0x35, 0x19),
101 QColor(0x7E, 0x57, 0x1F),
102 QColor(0x7D, 0x65, 0x23),
103 QColor(0x7E, 0x74, 0x27),
104 QColor(0x66, 0x78, 0x20),
105 QColor(0x45, 0x71, 0x1A),
106 QColor(0x27, 0x6E, 0x22),
107 QColor(0x2A, 0x6B, 0x4A),
108 QColor(0x32, 0x68, 0x69),
109 QColor(0x39, 0x4F, 0x67),
110 QColor(0x6A, 0x3B, 0x62),
111 QColor(0x4E, 0x3C, 0x5C),
112 QColor(0x56, 0x3F, 0x54),
113 QColor(0x61, 0x31, 0x4D),
114 QColor(0x6B, 0x23, 0x37)
117 DecodeTrace::DecodeTrace(pv::SigSession &session,
118 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
119 Trace(QString::fromUtf8(
120 decoder_stack->stack().front()->decoder()->name)),
122 _decoder_stack(decoder_stack),
125 _delete_mapper(this),
126 _show_hide_mapper(this)
128 assert(_decoder_stack);
130 _colour = DecodeColours[index % countof(DecodeColours)];
132 connect(_decoder_stack.get(), SIGNAL(new_decode_data()),
133 this, SLOT(on_new_decode_data()));
134 connect(&_delete_mapper, SIGNAL(mapped(int)),
135 this, SLOT(on_delete_decoder(int)));
136 connect(&_show_hide_mapper, SIGNAL(mapped(int)),
137 this, SLOT(on_show_hide_decoder(int)));
140 bool DecodeTrace::enabled() const
145 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
147 return _decoder_stack;
150 void DecodeTrace::set_view(pv::view::View *view)
153 Trace::set_view(view);
156 void DecodeTrace::paint_back(QPainter &p, int left, int right)
158 Trace::paint_back(p, left, right);
159 paint_axis(p, get_y(), left, right);
162 void DecodeTrace::paint_mid(QPainter &p, int left, int right)
164 using namespace pv::data::decode;
166 QFontMetrics m(QApplication::font());
167 _text_height = m.boundingRect(QRect(), 0, "Tg").height();
168 _row_height = (_text_height * 6) / 4;
169 const int annotation_height = (_text_height * 5) / 4;
171 assert(_decoder_stack);
172 const QString err = _decoder_stack->error_message();
175 draw_unresolved_period(p, annotation_height, left, right);
176 draw_error(p, err, left, right);
180 // Iterate through the rows
183 pair<uint64_t, uint64_t> sample_range = get_sample_range(left, right);
185 assert(_decoder_stack);
186 const vector<Row> rows(_decoder_stack->get_visible_rows());
188 _visible_rows.clear();
189 for (size_t i = 0; i < rows.size(); i++)
191 const Row &row = rows[i];
193 size_t base_colour = 0x13579BDF;
194 boost::hash_combine(base_colour, this);
195 boost::hash_combine(base_colour, row.decoder());
196 boost::hash_combine(base_colour, row.row());
199 vector<Annotation> annotations;
200 _decoder_stack->get_annotation_subset(annotations, row,
201 sample_range.first, sample_range.second);
202 if (!annotations.empty()) {
203 for (const Annotation &a : annotations)
204 draw_annotation(a, p, get_text_colour(),
205 annotation_height, left, right, y,
209 _visible_rows.push_back(rows[i]);
214 draw_unresolved_period(p, annotation_height, left, right);
217 void DecodeTrace::paint_fore(QPainter &p, int left, int right)
219 using namespace pv::data::decode;
225 for (size_t i = 0; i < _visible_rows.size(); i++)
227 const int y = i * _row_height + get_y();
229 p.setPen(QPen(Qt::NoPen));
230 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
234 const QPointF points[] = {
235 QPointF(left, y - ArrowSize),
236 QPointF(left + ArrowSize, y),
237 QPointF(left, y + ArrowSize)
239 p.drawPolygon(points, countof(points));
242 const QRect r(left + ArrowSize * 2, y - _row_height / 2,
243 right - left, _row_height);
244 const QString h(_visible_rows[i].title());
245 const int f = Qt::AlignLeft | Qt::AlignVCenter |
249 p.setPen(QApplication::palette().color(QPalette::Base));
250 for (int dx = -1; dx <= 1; dx++)
251 for (int dy = -1; dy <= 1; dy++)
252 if (dx != 0 && dy != 0)
253 p.drawText(r.translated(dx, dy), f, h);
256 p.setPen(QApplication::palette().color(QPalette::WindowText));
261 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
263 using pv::data::decode::Decoder;
267 assert(_decoder_stack);
269 // Add the standard options
270 Trace::populate_popup_form(parent, form);
272 // Add the decoder options
274 _channel_selectors.clear();
275 _decoder_forms.clear();
277 const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
281 QLabel *const l = new QLabel(
282 tr("<p><i>No decoders in the stack</i></p>"));
283 l->setAlignment(Qt::AlignCenter);
288 auto iter = stack.cbegin();
289 for (int i = 0; i < (int)stack.size(); i++, iter++) {
290 shared_ptr<Decoder> dec(*iter);
291 create_decoder_form(i, dec, parent, form);
294 form->addRow(new QLabel(
295 tr("<i>* Required channels</i>"), parent));
298 // Add stacking button
299 pv::widgets::DecoderMenu *const decoder_menu =
300 new pv::widgets::DecoderMenu(parent);
301 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
302 this, SLOT(on_stack_decoder(srd_decoder*)));
304 QPushButton *const stack_button =
305 new QPushButton(tr("Stack Decoder"), parent);
306 stack_button->setMenu(decoder_menu);
308 QHBoxLayout *stack_button_box = new QHBoxLayout;
309 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
310 form->addRow(stack_button_box);
313 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
315 QMenu *const menu = Trace::create_context_menu(parent);
317 menu->addSeparator();
319 QAction *const del = new QAction(tr("Delete"), this);
320 del->setShortcuts(QKeySequence::Delete);
321 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
322 menu->addAction(del);
327 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
328 QPainter &p, QColor text_color, int h, int left, int right, int y,
329 size_t base_colour) const
331 const double samples_per_pixel = get_samples_per_pixel();
332 const double pixels_offset = get_pixels_offset();
334 const double start = a.start_sample() / samples_per_pixel -
336 const double end = a.end_sample() / samples_per_pixel -
339 const size_t colour = (base_colour + a.format()) % countof(Colours);
340 const QColor &fill = Colours[colour];
341 const QColor &outline = OutlineColours[colour];
343 if (start > right + DrawPadding || end < left - DrawPadding)
346 if (a.start_sample() == a.end_sample())
347 draw_instant(a, p, fill, outline, text_color, h,
350 draw_range(a, p, fill, outline, text_color, h,
354 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
355 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
357 const QString text = a.annotations().empty() ?
358 QString() : a.annotations().back();
359 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
361 const QRectF rect(x - w / 2, y - h / 2, w, h);
365 p.drawRoundedRect(rect, h / 2, h / 2);
367 p.setPen(text_color);
368 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
371 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
372 QColor fill, QColor outline, QColor text_color, int h, double start,
373 double end, int y) const
375 const double top = y + .5 - h / 2;
376 const double bottom = y + .5 + h / 2;
377 const vector<QString> annotations = a.annotations();
382 // If the two ends are within 1 pixel, draw a vertical line
383 if (start + 1.0 > end)
385 p.drawLine(QPointF(start, top), QPointF(start, bottom));
389 const double cap_width = min((end - start) / 4, EndCapWidth);
392 QPointF(start, y + .5f),
393 QPointF(start + cap_width, top),
394 QPointF(end - cap_width, top),
395 QPointF(end, y + .5f),
396 QPointF(end - cap_width, bottom),
397 QPointF(start + cap_width, bottom)
400 p.drawConvexPolygon(pts, countof(pts));
402 if (annotations.empty())
405 QRectF rect(start + cap_width, y - h / 2,
406 end - start - cap_width * 2, h);
407 if (rect.width() <= 4)
410 p.setPen(text_color);
412 // Try to find an annotation that will fit
413 QString best_annotation;
416 for (const QString &a : annotations) {
417 const int w = p.boundingRect(QRectF(), 0, a).width();
418 if (w <= rect.width() && w > best_width)
419 best_annotation = a, best_width = w;
422 if (best_annotation.isEmpty())
423 best_annotation = annotations.back();
425 // If not ellide the last in the list
426 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
427 best_annotation, Qt::ElideRight, rect.width()));
430 void DecodeTrace::draw_error(QPainter &p, const QString &message,
433 const int y = get_y();
435 p.setPen(ErrorBgColour.darker());
436 p.setBrush(ErrorBgColour);
438 const QRectF bounding_rect =
439 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
440 const QRectF text_rect = p.boundingRect(bounding_rect,
441 Qt::AlignCenter, message);
442 const float r = text_rect.height() / 4;
444 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
447 p.setPen(get_text_colour());
448 p.drawText(text_rect, message);
451 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
454 using namespace pv::data;
455 using pv::data::decode::Decoder;
457 assert(_decoder_stack);
459 shared_ptr<Logic> data;
460 shared_ptr<LogicSignal> logic_signal;
462 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
464 // We get the logic data of the first channel in the list.
465 // This works because we are currently assuming all
466 // LogicSignals have the same data/snapshot
467 for (const shared_ptr<Decoder> &dec : stack)
468 if (dec && !dec->channels().empty() &&
469 ((logic_signal = (*dec->channels().begin()).second)) &&
470 ((data = logic_signal->logic_data())))
473 if (!data || data->get_snapshots().empty())
476 const shared_ptr<LogicSnapshot> snapshot =
477 data->get_snapshots().front();
479 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
480 if (sample_count == 0)
483 const int64_t samples_decoded = _decoder_stack->samples_decoded();
484 if (sample_count == samples_decoded)
487 const int y = get_y();
489 const double samples_per_pixel = get_samples_per_pixel();
490 const double pixels_offset = get_pixels_offset();
492 const double start = max(samples_decoded /
493 samples_per_pixel - pixels_offset, left - 1.0);
494 const double end = min(sample_count / samples_per_pixel -
495 pixels_offset, right + 1.0);
496 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
498 p.setPen(QPen(Qt::NoPen));
499 p.setBrush(Qt::white);
500 p.drawRect(no_decode_rect);
502 p.setPen(NoDecodeColour);
503 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
504 p.drawRect(no_decode_rect);
507 double DecodeTrace::get_pixels_offset() const
510 assert(_decoder_stack);
512 const double scale = _view->scale();
515 return (_view->offset() - _decoder_stack->get_start_time()) / scale;
518 double DecodeTrace::get_samples_per_pixel() const
521 assert(_decoder_stack);
523 const double scale = _view->scale();
526 double samplerate = _decoder_stack->samplerate();
528 // Show sample rate as 1Hz when it is unknown
529 if (samplerate == 0.0)
532 return samplerate * scale;
535 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(int x_start, int x_end) const
538 assert(_decoder_stack);
540 const double samples_per_pixel = get_samples_per_pixel();
541 const double pixels_offset = get_pixels_offset();
545 start = (uint64_t)max((x_start + pixels_offset) * samples_per_pixel, 0.0);
546 end = (uint64_t)max((x_end + pixels_offset) * samples_per_pixel, 0.0);
548 return make_pair(start, end);
551 bool DecodeTrace::hover_point_is_over_trace()
556 // Note: if _row_height is valid then _cur_row_headings is valid, too,
557 // as both are set in paint_mid().
559 // Note: hp.x will be 0 if the cursor is above the header area,
560 // so we set trace.left to 1 to exclude this.
562 QRect trace(1, get_y() - (_row_height/2),
563 _view->width(), _row_height * _visible_rows.size());
565 // Note: We don't need to check for _row_height being 0 here but
566 // we do it anyway to be more robust.
568 return _row_height && enabled() && trace.contains(_view->hover_point());
571 int DecodeTrace::get_row_at_hover_point()
575 assert(_decoder_stack);
577 QPoint hp = _view->hover_point();
578 int hover_row = (hp.y() - get_y() + (_row_height/2)) / _row_height;
580 return min(hover_row, (int)_visible_rows.size() - 1);
583 const QString DecodeTrace::get_annotation_at_hover_point()
585 using namespace pv::data::decode;
588 QPoint hp = _view->hover_point();
590 pair<uint64_t, uint64_t> sample_range = get_sample_range(hp.x(), hp.x() + 1);
592 const int hover_row = get_row_at_hover_point();
594 vector<pv::data::decode::Annotation> annotations;
596 assert(_decoder_stack);
597 _decoder_stack->get_annotation_subset(annotations, _visible_rows[hover_row],
598 sample_range.first, sample_range.second);
600 return (annotations.empty()) ?
601 QString() : annotations[0].annotations().front();
604 void DecodeTrace::show_hover_annotation()
606 QString ann = get_annotation_at_hover_point();
611 if (!ann.isEmpty()) {
612 const int hover_row = get_row_at_hover_point();
614 QFontMetrics m(QToolTip::font());
615 const QRect text_size = m.boundingRect(QRect(), 0, ann);
617 // This is OS-specific and unfortunately we can't query it, so
618 // use an approximation to at least try to minimize the error.
619 const int padding = 8;
621 // Make sure the tool tip doesn't overlap with the mouse cursor.
622 // If it did, the tool tip would constantly hide and re-appear.
623 // We also push it up by one row so that it appears above the
624 // decode trace, not below.
625 QPoint hp = _view->hover_point();
627 hp.setX(hp.x() - (text_size.width() / 2) - padding);
629 hp.setY(get_y() - (_row_height / 2) + (hover_row * _row_height)
630 - _row_height - text_size.height());
632 QToolTip::showText(_view->viewport()->mapToGlobal(hp), ann);
634 hide_hover_annotation();
637 void DecodeTrace::hide_hover_annotation()
639 QToolTip::hideText();
642 void DecodeTrace::hover_point_changed()
644 if (hover_point_is_over_trace())
645 show_hover_annotation();
647 hide_hover_annotation();
650 void DecodeTrace::create_decoder_form(int index,
651 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
657 const srd_decoder *const decoder = dec->decoder();
660 pv::widgets::DecoderGroupBox *const group =
661 new pv::widgets::DecoderGroupBox(
662 QString::fromUtf8(decoder->name));
663 group->set_decoder_visible(dec->shown());
665 _delete_mapper.setMapping(group, index);
666 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
668 _show_hide_mapper.setMapping(group, index);
669 connect(group, SIGNAL(show_hide_decoder()),
670 &_show_hide_mapper, SLOT(map()));
672 QFormLayout *const decoder_form = new QFormLayout;
673 group->add_layout(decoder_form);
675 // Add the mandatory channels
676 for(l = decoder->channels; l; l = l->next) {
677 const struct srd_channel *const pdch =
678 (struct srd_channel *)l->data;
679 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
680 connect(combo, SIGNAL(currentIndexChanged(int)),
681 this, SLOT(on_channel_selected(int)));
682 decoder_form->addRow(tr("<b>%1</b> (%2) *")
683 .arg(QString::fromUtf8(pdch->name))
684 .arg(QString::fromUtf8(pdch->desc)), combo);
686 const ChannelSelector s = {combo, dec, pdch};
687 _channel_selectors.push_back(s);
690 // Add the optional channels
691 for(l = decoder->opt_channels; l; l = l->next) {
692 const struct srd_channel *const pdch =
693 (struct srd_channel *)l->data;
694 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
695 connect(combo, SIGNAL(currentIndexChanged(int)),
696 this, SLOT(on_channel_selected(int)));
697 decoder_form->addRow(tr("<b>%1</b> (%2)")
698 .arg(QString::fromUtf8(pdch->name))
699 .arg(QString::fromUtf8(pdch->desc)), combo);
701 const ChannelSelector s = {combo, dec, pdch};
702 _channel_selectors.push_back(s);
706 shared_ptr<prop::binding::DecoderOptions> binding(
707 new prop::binding::DecoderOptions(_decoder_stack, dec));
708 binding->add_properties_to_form(decoder_form, true);
710 _bindings.push_back(binding);
713 _decoder_forms.push_back(group);
716 QComboBox* DecodeTrace::create_channel_selector(
717 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
718 const srd_channel *const pdch)
722 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
724 assert(_decoder_stack);
725 const auto channel_iter = dec->channels().find(pdch);
727 QComboBox *selector = new QComboBox(parent);
729 selector->addItem("-", qVariantFromValue((void*)NULL));
731 if (channel_iter == dec->channels().end())
732 selector->setCurrentIndex(0);
734 for(size_t i = 0; i < sigs.size(); i++) {
735 const shared_ptr<view::Signal> s(sigs[i]);
738 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
740 selector->addItem(s->get_name(),
741 qVariantFromValue((void*)s.get()));
742 if ((*channel_iter).second == s)
743 selector->setCurrentIndex(i + 1);
750 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
754 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
755 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
757 for (const ChannelSelector &s : _channel_selectors)
759 if(s._decoder != dec)
762 const LogicSignal *const selection =
763 (LogicSignal*)s._combo->itemData(
764 s._combo->currentIndex()).value<void*>();
766 for (shared_ptr<Signal> sig : sigs)
767 if(sig.get() == selection) {
768 channel_map[s._pdch] =
769 dynamic_pointer_cast<LogicSignal>(sig);
774 dec->set_channels(channel_map);
777 void DecodeTrace::commit_channels()
779 assert(_decoder_stack);
780 for (shared_ptr<data::decode::Decoder> dec : _decoder_stack->stack())
781 commit_decoder_channels(dec);
783 _decoder_stack->begin_decode();
786 void DecodeTrace::on_new_decode_data()
789 _view->update_viewport();
792 void DecodeTrace::delete_pressed()
797 void DecodeTrace::on_delete()
799 _session.remove_decode_signal(this);
802 void DecodeTrace::on_channel_selected(int)
807 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
810 assert(_decoder_stack);
811 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
812 new data::decode::Decoder(decoder)));
813 _decoder_stack->begin_decode();
818 void DecodeTrace::on_delete_decoder(int index)
820 _decoder_stack->remove(index);
825 _decoder_stack->begin_decode();
828 void DecodeTrace::on_show_hide_decoder(int index)
830 using pv::data::decode::Decoder;
832 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
834 // Find the decoder in the stack
835 auto iter = stack.cbegin();
836 for(int i = 0; i < index; i++, iter++)
837 assert(iter != stack.end());
839 shared_ptr<Decoder> dec = *iter;
842 const bool show = !dec->shown();
845 assert(index < (int)_decoder_forms.size());
846 _decoder_forms[index]->set_decoder_visible(show);
848 _view->update_viewport();