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/widgets/decodergroupbox.h>
49 #include <pv/widgets/decodermenu.h>
51 using std::dynamic_pointer_cast;
58 using std::shared_ptr;
64 const QColor DecodeTrace::DecodeColours[4] = {
65 QColor(0xEF, 0x29, 0x29), // Red
66 QColor(0xFC, 0xE9, 0x4F), // Yellow
67 QColor(0x8A, 0xE2, 0x34), // Green
68 QColor(0x72, 0x9F, 0xCF) // Blue
71 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
72 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
74 const int DecodeTrace::ArrowSize = 4;
75 const double DecodeTrace::EndCapWidth = 5;
76 const int DecodeTrace::DrawPadding = 100;
78 const QColor DecodeTrace::Colours[16] = {
79 QColor(0xEF, 0x29, 0x29),
80 QColor(0xF6, 0x6A, 0x32),
81 QColor(0xFC, 0xAE, 0x3E),
82 QColor(0xFB, 0xCA, 0x47),
83 QColor(0xFC, 0xE9, 0x4F),
84 QColor(0xCD, 0xF0, 0x40),
85 QColor(0x8A, 0xE2, 0x34),
86 QColor(0x4E, 0xDC, 0x44),
87 QColor(0x55, 0xD7, 0x95),
88 QColor(0x64, 0xD1, 0xD2),
89 QColor(0x72, 0x9F, 0xCF),
90 QColor(0xD4, 0x76, 0xC4),
91 QColor(0x9D, 0x79, 0xB9),
92 QColor(0xAD, 0x7F, 0xA8),
93 QColor(0xC2, 0x62, 0x9B),
94 QColor(0xD7, 0x47, 0x6F)
97 const QColor DecodeTrace::OutlineColours[16] = {
98 QColor(0x77, 0x14, 0x14),
99 QColor(0x7B, 0x35, 0x19),
100 QColor(0x7E, 0x57, 0x1F),
101 QColor(0x7D, 0x65, 0x23),
102 QColor(0x7E, 0x74, 0x27),
103 QColor(0x66, 0x78, 0x20),
104 QColor(0x45, 0x71, 0x1A),
105 QColor(0x27, 0x6E, 0x22),
106 QColor(0x2A, 0x6B, 0x4A),
107 QColor(0x32, 0x68, 0x69),
108 QColor(0x39, 0x4F, 0x67),
109 QColor(0x6A, 0x3B, 0x62),
110 QColor(0x4E, 0x3C, 0x5C),
111 QColor(0x56, 0x3F, 0x54),
112 QColor(0x61, 0x31, 0x4D),
113 QColor(0x6B, 0x23, 0x37)
116 DecodeTrace::DecodeTrace(pv::SigSession &session,
117 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
118 Trace(QString::fromUtf8(
119 decoder_stack->stack().front()->decoder()->name)),
121 _decoder_stack(decoder_stack),
124 _delete_mapper(this),
125 _show_hide_mapper(this)
127 assert(_decoder_stack);
129 _colour = DecodeColours[index % countof(DecodeColours)];
131 connect(_decoder_stack.get(), SIGNAL(new_decode_data()),
132 this, SLOT(on_new_decode_data()));
133 connect(&_delete_mapper, SIGNAL(mapped(int)),
134 this, SLOT(on_delete_decoder(int)));
135 connect(&_show_hide_mapper, SIGNAL(mapped(int)),
136 this, SLOT(on_show_hide_decoder(int)));
139 bool DecodeTrace::enabled() const
144 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
146 return _decoder_stack;
149 void DecodeTrace::set_view(pv::view::View *view)
152 Trace::set_view(view);
155 void DecodeTrace::paint_back(QPainter &p, int left, int right)
157 Trace::paint_back(p, left, right);
158 paint_axis(p, get_y(), left, right);
161 void DecodeTrace::paint_mid(QPainter &p, int left, int right)
163 using namespace pv::data::decode;
165 QFontMetrics m(QApplication::font());
166 _text_height = m.boundingRect(QRect(), 0, "Tg").height();
167 _row_height = (_text_height * 6) / 4;
168 const int annotation_height = (_text_height * 5) / 4;
170 assert(_decoder_stack);
171 const QString err = _decoder_stack->error_message();
174 draw_unresolved_period(p, annotation_height, left, right);
175 draw_error(p, err, left, right);
179 // Iterate through the rows
182 pair<uint64_t, uint64_t> sample_range = get_sample_range(left, right);
184 assert(_decoder_stack);
185 const vector<Row> rows(_decoder_stack->get_visible_rows());
187 _visible_rows.clear();
188 for (size_t i = 0; i < rows.size(); i++)
190 const Row &row = rows[i];
192 size_t base_colour = 0x13579BDF;
193 boost::hash_combine(base_colour, this);
194 boost::hash_combine(base_colour, row.decoder());
195 boost::hash_combine(base_colour, row.row());
198 vector<Annotation> annotations;
199 _decoder_stack->get_annotation_subset(annotations, row,
200 sample_range.first, sample_range.second);
201 if (!annotations.empty()) {
202 for (const Annotation &a : annotations)
203 draw_annotation(a, p, get_text_colour(),
204 annotation_height, left, right, y,
208 _visible_rows.push_back(rows[i]);
213 draw_unresolved_period(p, annotation_height, left, right);
216 void DecodeTrace::paint_fore(QPainter &p, int left, int right)
218 using namespace pv::data::decode;
224 for (size_t i = 0; i < _visible_rows.size(); i++)
226 const int y = i * _row_height + get_y();
228 p.setPen(QPen(Qt::NoPen));
229 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
233 const QPointF points[] = {
234 QPointF(left, y - ArrowSize),
235 QPointF(left + ArrowSize, y),
236 QPointF(left, y + ArrowSize)
238 p.drawPolygon(points, countof(points));
241 const QRect r(left + ArrowSize * 2, y - _row_height / 2,
242 right - left, _row_height);
243 const QString h(_visible_rows[i].title());
244 const int f = Qt::AlignLeft | Qt::AlignVCenter |
248 p.setPen(QApplication::palette().color(QPalette::Base));
249 for (int dx = -1; dx <= 1; dx++)
250 for (int dy = -1; dy <= 1; dy++)
251 if (dx != 0 && dy != 0)
252 p.drawText(r.translated(dx, dy), f, h);
255 p.setPen(QApplication::palette().color(QPalette::WindowText));
260 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
262 using pv::data::decode::Decoder;
266 assert(_decoder_stack);
268 // Add the standard options
269 Trace::populate_popup_form(parent, form);
271 // Add the decoder options
273 _channel_selectors.clear();
274 _decoder_forms.clear();
276 const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
280 QLabel *const l = new QLabel(
281 tr("<p><i>No decoders in the stack</i></p>"));
282 l->setAlignment(Qt::AlignCenter);
287 auto iter = stack.cbegin();
288 for (int i = 0; i < (int)stack.size(); i++, iter++) {
289 shared_ptr<Decoder> dec(*iter);
290 create_decoder_form(i, dec, parent, form);
293 form->addRow(new QLabel(
294 tr("<i>* Required channels</i>"), parent));
297 // Add stacking button
298 pv::widgets::DecoderMenu *const decoder_menu =
299 new pv::widgets::DecoderMenu(parent);
300 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
301 this, SLOT(on_stack_decoder(srd_decoder*)));
303 QPushButton *const stack_button =
304 new QPushButton(tr("Stack Decoder"), parent);
305 stack_button->setMenu(decoder_menu);
307 QHBoxLayout *stack_button_box = new QHBoxLayout;
308 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
309 form->addRow(stack_button_box);
312 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
314 QMenu *const menu = Trace::create_context_menu(parent);
316 menu->addSeparator();
318 QAction *const del = new QAction(tr("Delete"), this);
319 del->setShortcuts(QKeySequence::Delete);
320 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
321 menu->addAction(del);
326 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
327 QPainter &p, QColor text_color, int h, int left, int right, int y,
328 size_t base_colour) const
330 const double samples_per_pixel = get_samples_per_pixel();
331 const double pixels_offset = get_pixels_offset();
333 const double start = a.start_sample() / samples_per_pixel -
335 const double end = a.end_sample() / samples_per_pixel -
338 const size_t colour = (base_colour + a.format()) % countof(Colours);
339 const QColor &fill = Colours[colour];
340 const QColor &outline = OutlineColours[colour];
342 if (start > right + DrawPadding || end < left - DrawPadding)
345 if (a.start_sample() == a.end_sample())
346 draw_instant(a, p, fill, outline, text_color, h,
349 draw_range(a, p, fill, outline, text_color, h,
353 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
354 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
356 const QString text = a.annotations().empty() ?
357 QString() : a.annotations().back();
358 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
360 const QRectF rect(x - w / 2, y - h / 2, w, h);
364 p.drawRoundedRect(rect, h / 2, h / 2);
366 p.setPen(text_color);
367 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
370 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
371 QColor fill, QColor outline, QColor text_color, int h, double start,
372 double end, int y) const
374 const double top = y + .5 - h / 2;
375 const double bottom = y + .5 + h / 2;
376 const vector<QString> annotations = a.annotations();
381 // If the two ends are within 1 pixel, draw a vertical line
382 if (start + 1.0 > end)
384 p.drawLine(QPointF(start, top), QPointF(start, bottom));
388 const double cap_width = min((end - start) / 4, EndCapWidth);
391 QPointF(start, y + .5f),
392 QPointF(start + cap_width, top),
393 QPointF(end - cap_width, top),
394 QPointF(end, y + .5f),
395 QPointF(end - cap_width, bottom),
396 QPointF(start + cap_width, bottom)
399 p.drawConvexPolygon(pts, countof(pts));
401 if (annotations.empty())
404 QRectF rect(start + cap_width, y - h / 2,
405 end - start - cap_width * 2, h);
406 if (rect.width() <= 4)
409 p.setPen(text_color);
411 // Try to find an annotation that will fit
412 QString best_annotation;
415 for (const QString &a : annotations) {
416 const int w = p.boundingRect(QRectF(), 0, a).width();
417 if (w <= rect.width() && w > best_width)
418 best_annotation = a, best_width = w;
421 if (best_annotation.isEmpty())
422 best_annotation = annotations.back();
424 // If not ellide the last in the list
425 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
426 best_annotation, Qt::ElideRight, rect.width()));
429 void DecodeTrace::draw_error(QPainter &p, const QString &message,
432 const int y = get_y();
434 p.setPen(ErrorBgColour.darker());
435 p.setBrush(ErrorBgColour);
437 const QRectF bounding_rect =
438 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
439 const QRectF text_rect = p.boundingRect(bounding_rect,
440 Qt::AlignCenter, message);
441 const float r = text_rect.height() / 4;
443 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
446 p.setPen(get_text_colour());
447 p.drawText(text_rect, message);
450 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
453 using namespace pv::data;
454 using pv::data::decode::Decoder;
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 const double samples_per_pixel = get_samples_per_pixel();
489 const double pixels_offset = get_pixels_offset();
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 double DecodeTrace::get_pixels_offset() const
509 assert(_decoder_stack);
511 const double scale = _view->scale();
514 return (_view->offset() - _decoder_stack->get_start_time()) / scale;
517 double DecodeTrace::get_samples_per_pixel() const
520 assert(_decoder_stack);
522 const double scale = _view->scale();
525 double samplerate = _decoder_stack->samplerate();
527 // Show sample rate as 1Hz when it is unknown
528 if (samplerate == 0.0)
531 return samplerate * scale;
534 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(int x_start, int x_end) const
537 assert(_decoder_stack);
539 const double samples_per_pixel = get_samples_per_pixel();
540 const double pixels_offset = get_pixels_offset();
544 start = (uint64_t)max((x_start + pixels_offset) * samples_per_pixel, 0.0);
545 end = (uint64_t)max((x_end + pixels_offset) * samples_per_pixel, 0.0);
547 return make_pair(start, end);
550 bool DecodeTrace::hover_point_is_over_trace()
555 // Note: if _row_height is valid then _cur_row_headings is valid, too,
556 // as both are set in paint_mid().
558 // Note: hp.x will be 0 if the cursor is above the header area,
559 // so we set trace.left to 1 to exclude this.
561 QRect trace(1, get_y() - (_row_height/2),
562 _view->width(), _row_height * _visible_rows.size());
564 // Note: We don't need to check for _row_height being 0 here but
565 // we do it anyway to be more robust.
567 return _row_height && enabled() && trace.contains(_view->hover_point());
570 int DecodeTrace::get_row_at_hover_point()
574 assert(_decoder_stack);
576 QPoint hp = _view->hover_point();
577 int hover_row = (hp.y() - get_y() + (_row_height/2)) / _row_height;
579 return min(hover_row, (int)_visible_rows.size() - 1);
582 const QString DecodeTrace::get_annotation_at_hover_point()
584 using namespace pv::data::decode;
587 QPoint hp = _view->hover_point();
589 pair<uint64_t, uint64_t> sample_range = get_sample_range(hp.x(), hp.x() + 1);
591 const int hover_row = get_row_at_hover_point();
593 vector<pv::data::decode::Annotation> annotations;
595 assert(_decoder_stack);
596 _decoder_stack->get_annotation_subset(annotations, _visible_rows[hover_row],
597 sample_range.first, sample_range.second);
599 return (annotations.empty()) ?
600 QString() : annotations[0].annotations().front();
603 void DecodeTrace::show_hover_annotation()
605 QString ann = get_annotation_at_hover_point();
609 assert(_text_height);
611 if (!ann.isEmpty()) {
612 const int hover_row = get_row_at_hover_point();
614 // Make sure the tool tip doesn't overlap with the mouse cursor.
615 // If it did, the tool tip would constantly hide and re-appear.
616 QPoint hp = _view->hover_point();
617 hp.setY(get_y() - (_row_height/2) +
618 (hover_row * _row_height) - _text_height);
620 QToolTip::showText(_view->mapToGlobal(hp), ann);
622 hide_hover_annotation();
625 void DecodeTrace::hide_hover_annotation()
627 QToolTip::hideText();
630 void DecodeTrace::hover_point_changed()
632 if (hover_point_is_over_trace())
633 show_hover_annotation();
635 hide_hover_annotation();
638 void DecodeTrace::create_decoder_form(int index,
639 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
645 const srd_decoder *const decoder = dec->decoder();
648 pv::widgets::DecoderGroupBox *const group =
649 new pv::widgets::DecoderGroupBox(
650 QString::fromUtf8(decoder->name));
651 group->set_decoder_visible(dec->shown());
653 _delete_mapper.setMapping(group, index);
654 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
656 _show_hide_mapper.setMapping(group, index);
657 connect(group, SIGNAL(show_hide_decoder()),
658 &_show_hide_mapper, SLOT(map()));
660 QFormLayout *const decoder_form = new QFormLayout;
661 group->add_layout(decoder_form);
663 // Add the mandatory channels
664 for(l = decoder->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);
678 // Add the optional channels
679 for(l = decoder->opt_channels; l; l = l->next) {
680 const struct srd_channel *const pdch =
681 (struct srd_channel *)l->data;
682 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
683 connect(combo, SIGNAL(currentIndexChanged(int)),
684 this, SLOT(on_channel_selected(int)));
685 decoder_form->addRow(tr("<b>%1</b> (%2)")
686 .arg(QString::fromUtf8(pdch->name))
687 .arg(QString::fromUtf8(pdch->desc)), combo);
689 const ChannelSelector s = {combo, dec, pdch};
690 _channel_selectors.push_back(s);
694 shared_ptr<prop::binding::DecoderOptions> binding(
695 new prop::binding::DecoderOptions(_decoder_stack, dec));
696 binding->add_properties_to_form(decoder_form, true);
698 _bindings.push_back(binding);
701 _decoder_forms.push_back(group);
704 QComboBox* DecodeTrace::create_channel_selector(
705 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
706 const srd_channel *const pdch)
710 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
712 assert(_decoder_stack);
713 const auto channel_iter = dec->channels().find(pdch);
715 QComboBox *selector = new QComboBox(parent);
717 selector->addItem("-", qVariantFromValue((void*)NULL));
719 if (channel_iter == dec->channels().end())
720 selector->setCurrentIndex(0);
722 for(size_t i = 0; i < sigs.size(); i++) {
723 const shared_ptr<view::Signal> s(sigs[i]);
726 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
728 selector->addItem(s->get_name(),
729 qVariantFromValue((void*)s.get()));
730 if ((*channel_iter).second == s)
731 selector->setCurrentIndex(i + 1);
738 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
742 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
743 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
745 for (const ChannelSelector &s : _channel_selectors)
747 if(s._decoder != dec)
750 const LogicSignal *const selection =
751 (LogicSignal*)s._combo->itemData(
752 s._combo->currentIndex()).value<void*>();
754 for (shared_ptr<Signal> sig : sigs)
755 if(sig.get() == selection) {
756 channel_map[s._pdch] =
757 dynamic_pointer_cast<LogicSignal>(sig);
762 dec->set_channels(channel_map);
765 void DecodeTrace::commit_channels()
767 assert(_decoder_stack);
768 for (shared_ptr<data::decode::Decoder> dec : _decoder_stack->stack())
769 commit_decoder_channels(dec);
771 _decoder_stack->begin_decode();
774 void DecodeTrace::on_new_decode_data()
777 _view->update_viewport();
780 void DecodeTrace::delete_pressed()
785 void DecodeTrace::on_delete()
787 _session.remove_decode_signal(this);
790 void DecodeTrace::on_channel_selected(int)
795 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
798 assert(_decoder_stack);
799 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
800 new data::decode::Decoder(decoder)));
801 _decoder_stack->begin_decode();
806 void DecodeTrace::on_delete_decoder(int index)
808 _decoder_stack->remove(index);
813 _decoder_stack->begin_decode();
816 void DecodeTrace::on_show_hide_decoder(int index)
818 using pv::data::decode::Decoder;
820 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
822 // Find the decoder in the stack
823 auto iter = stack.cbegin();
824 for(int i = 0; i < index; i++, iter++)
825 assert(iter != stack.end());
827 shared_ptr<Decoder> dec = *iter;
830 const bool show = !dec->shown();
833 assert(index < (int)_decoder_forms.size());
834 _decoder_forms[index]->set_decoder_visible(show);
836 _view->update_viewport();