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 _cur_row_headings.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 _cur_row_headings.push_back(row.title());
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 < _cur_row_headings.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(_cur_row_headings[i]);
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 * _cur_row_headings.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 const vector<pv::data::decode::Row> rows(_decoder_stack->get_visible_rows());
581 return min(hover_row, (int)rows.size() - 1);
584 const QString DecodeTrace::get_annotation_at_hover_point()
586 using namespace pv::data::decode;
589 QPoint hp = _view->hover_point();
591 pair<uint64_t, uint64_t> sample_range = get_sample_range(hp.x(), hp.x() + 1);
593 assert(_decoder_stack);
594 const vector<pv::data::decode::Row> rows(_decoder_stack->get_visible_rows());
596 const int hover_row = get_row_at_hover_point();
598 vector<pv::data::decode::Annotation> annotations;
600 _decoder_stack->get_annotation_subset(annotations, rows[hover_row],
601 sample_range.first, sample_range.second);
603 return (annotations.empty()) ?
604 QString() : annotations[0].annotations().front();
607 void DecodeTrace::show_hover_annotation()
609 QString ann = get_annotation_at_hover_point();
613 assert(_text_height);
615 if (!ann.isEmpty()) {
616 const int hover_row = get_row_at_hover_point();
618 // Make sure the tool tip doesn't overlap with the mouse cursor.
619 // If it did, the tool tip would constantly hide and re-appear.
620 QPoint hp = _view->hover_point();
621 hp.setY(get_y() - (_row_height/2) +
622 (hover_row * _row_height) - _text_height);
624 QToolTip::showText(_view->mapToGlobal(hp), ann);
626 hide_hover_annotation();
629 void DecodeTrace::hide_hover_annotation()
631 QToolTip::hideText();
634 void DecodeTrace::hover_point_changed()
636 if (hover_point_is_over_trace())
637 show_hover_annotation();
639 hide_hover_annotation();
642 void DecodeTrace::create_decoder_form(int index,
643 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
649 const srd_decoder *const decoder = dec->decoder();
652 pv::widgets::DecoderGroupBox *const group =
653 new pv::widgets::DecoderGroupBox(
654 QString::fromUtf8(decoder->name));
655 group->set_decoder_visible(dec->shown());
657 _delete_mapper.setMapping(group, index);
658 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
660 _show_hide_mapper.setMapping(group, index);
661 connect(group, SIGNAL(show_hide_decoder()),
662 &_show_hide_mapper, SLOT(map()));
664 QFormLayout *const decoder_form = new QFormLayout;
665 group->add_layout(decoder_form);
667 // Add the mandatory channels
668 for(l = decoder->channels; l; l = l->next) {
669 const struct srd_channel *const pdch =
670 (struct srd_channel *)l->data;
671 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
672 connect(combo, SIGNAL(currentIndexChanged(int)),
673 this, SLOT(on_channel_selected(int)));
674 decoder_form->addRow(tr("<b>%1</b> (%2) *")
675 .arg(QString::fromUtf8(pdch->name))
676 .arg(QString::fromUtf8(pdch->desc)), combo);
678 const ChannelSelector s = {combo, dec, pdch};
679 _channel_selectors.push_back(s);
682 // Add the optional channels
683 for(l = decoder->opt_channels; l; l = l->next) {
684 const struct srd_channel *const pdch =
685 (struct srd_channel *)l->data;
686 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
687 connect(combo, SIGNAL(currentIndexChanged(int)),
688 this, SLOT(on_channel_selected(int)));
689 decoder_form->addRow(tr("<b>%1</b> (%2)")
690 .arg(QString::fromUtf8(pdch->name))
691 .arg(QString::fromUtf8(pdch->desc)), combo);
693 const ChannelSelector s = {combo, dec, pdch};
694 _channel_selectors.push_back(s);
698 shared_ptr<prop::binding::DecoderOptions> binding(
699 new prop::binding::DecoderOptions(_decoder_stack, dec));
700 binding->add_properties_to_form(decoder_form, true);
702 _bindings.push_back(binding);
705 _decoder_forms.push_back(group);
708 QComboBox* DecodeTrace::create_channel_selector(
709 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
710 const srd_channel *const pdch)
714 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
716 assert(_decoder_stack);
717 const auto channel_iter = dec->channels().find(pdch);
719 QComboBox *selector = new QComboBox(parent);
721 selector->addItem("-", qVariantFromValue((void*)NULL));
723 if (channel_iter == dec->channels().end())
724 selector->setCurrentIndex(0);
726 for(size_t i = 0; i < sigs.size(); i++) {
727 const shared_ptr<view::Signal> s(sigs[i]);
730 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
732 selector->addItem(s->get_name(),
733 qVariantFromValue((void*)s.get()));
734 if ((*channel_iter).second == s)
735 selector->setCurrentIndex(i + 1);
742 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
746 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
747 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
749 for (const ChannelSelector &s : _channel_selectors)
751 if(s._decoder != dec)
754 const LogicSignal *const selection =
755 (LogicSignal*)s._combo->itemData(
756 s._combo->currentIndex()).value<void*>();
758 for (shared_ptr<Signal> sig : sigs)
759 if(sig.get() == selection) {
760 channel_map[s._pdch] =
761 dynamic_pointer_cast<LogicSignal>(sig);
766 dec->set_channels(channel_map);
769 void DecodeTrace::commit_channels()
771 assert(_decoder_stack);
772 for (shared_ptr<data::decode::Decoder> dec : _decoder_stack->stack())
773 commit_decoder_channels(dec);
775 _decoder_stack->begin_decode();
778 void DecodeTrace::on_new_decode_data()
781 _view->update_viewport();
784 void DecodeTrace::delete_pressed()
789 void DecodeTrace::on_delete()
791 _session.remove_decode_signal(this);
794 void DecodeTrace::on_channel_selected(int)
799 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
802 assert(_decoder_stack);
803 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
804 new data::decode::Decoder(decoder)));
805 _decoder_stack->begin_decode();
810 void DecodeTrace::on_delete_decoder(int index)
812 _decoder_stack->remove(index);
817 _decoder_stack->begin_decode();
820 void DecodeTrace::on_show_hide_decoder(int index)
822 using pv::data::decode::Decoder;
824 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
826 // Find the decoder in the stack
827 auto iter = stack.cbegin();
828 for(int i = 0; i < index; i++, iter++)
829 assert(iter != stack.end());
831 shared_ptr<Decoder> dec = *iter;
834 const bool show = !dec->shown();
837 assert(index < (int)_decoder_forms.size());
838 _decoder_forms[index]->set_decoder_visible(show);
840 _view->update_viewport();