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>
31 #include <boost/functional/hash.hpp>
34 #include <QApplication>
36 #include <QFormLayout>
39 #include <QPushButton>
42 #include "decodetrace.h"
44 #include <pv/sigsession.h>
45 #include <pv/data/decoderstack.h>
46 #include <pv/data/decode/decoder.h>
47 #include <pv/data/logic.h>
48 #include <pv/data/logicsnapshot.h>
49 #include <pv/data/decode/annotation.h>
50 #include <pv/view/logicsignal.h>
51 #include <pv/view/view.h>
52 #include <pv/view/viewport.h>
53 #include <pv/widgets/decodergroupbox.h>
54 #include <pv/widgets/decodermenu.h>
56 using boost::shared_lock;
57 using boost::shared_mutex;
58 using std::dynamic_pointer_cast;
60 using std::lock_guard;
66 using std::shared_ptr;
73 const QColor DecodeTrace::DecodeColours[4] = {
74 QColor(0xEF, 0x29, 0x29), // Red
75 QColor(0xFC, 0xE9, 0x4F), // Yellow
76 QColor(0x8A, 0xE2, 0x34), // Green
77 QColor(0x72, 0x9F, 0xCF) // Blue
80 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
81 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
83 const int DecodeTrace::ArrowSize = 4;
84 const double DecodeTrace::EndCapWidth = 5;
85 const int DecodeTrace::DrawPadding = 100;
87 const QColor DecodeTrace::Colours[16] = {
88 QColor(0xEF, 0x29, 0x29),
89 QColor(0xF6, 0x6A, 0x32),
90 QColor(0xFC, 0xAE, 0x3E),
91 QColor(0xFB, 0xCA, 0x47),
92 QColor(0xFC, 0xE9, 0x4F),
93 QColor(0xCD, 0xF0, 0x40),
94 QColor(0x8A, 0xE2, 0x34),
95 QColor(0x4E, 0xDC, 0x44),
96 QColor(0x55, 0xD7, 0x95),
97 QColor(0x64, 0xD1, 0xD2),
98 QColor(0x72, 0x9F, 0xCF),
99 QColor(0xD4, 0x76, 0xC4),
100 QColor(0x9D, 0x79, 0xB9),
101 QColor(0xAD, 0x7F, 0xA8),
102 QColor(0xC2, 0x62, 0x9B),
103 QColor(0xD7, 0x47, 0x6F)
106 const QColor DecodeTrace::OutlineColours[16] = {
107 QColor(0x77, 0x14, 0x14),
108 QColor(0x7B, 0x35, 0x19),
109 QColor(0x7E, 0x57, 0x1F),
110 QColor(0x7D, 0x65, 0x23),
111 QColor(0x7E, 0x74, 0x27),
112 QColor(0x66, 0x78, 0x20),
113 QColor(0x45, 0x71, 0x1A),
114 QColor(0x27, 0x6E, 0x22),
115 QColor(0x2A, 0x6B, 0x4A),
116 QColor(0x32, 0x68, 0x69),
117 QColor(0x39, 0x4F, 0x67),
118 QColor(0x6A, 0x3B, 0x62),
119 QColor(0x4E, 0x3C, 0x5C),
120 QColor(0x56, 0x3F, 0x54),
121 QColor(0x61, 0x31, 0x4D),
122 QColor(0x6B, 0x23, 0x37)
125 DecodeTrace::DecodeTrace(pv::SigSession &session,
126 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
127 Trace(QString::fromUtf8(
128 decoder_stack->stack().front()->decoder()->name)),
130 _decoder_stack(decoder_stack),
133 _delete_mapper(this),
134 _show_hide_mapper(this)
136 assert(_decoder_stack);
138 _colour = DecodeColours[index % countof(DecodeColours)];
140 connect(_decoder_stack.get(), SIGNAL(new_decode_data()),
141 this, SLOT(on_new_decode_data()));
142 connect(&_delete_mapper, SIGNAL(mapped(int)),
143 this, SLOT(on_delete_decoder(int)));
144 connect(&_show_hide_mapper, SIGNAL(mapped(int)),
145 this, SLOT(on_show_hide_decoder(int)));
148 bool DecodeTrace::enabled() const
153 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
155 return _decoder_stack;
158 void DecodeTrace::paint_back(QPainter &p, int left, int right)
160 Trace::paint_back(p, left, right);
161 paint_axis(p, get_y(), left, right);
164 void DecodeTrace::paint_mid(QPainter &p, int left, int right)
166 using namespace pv::data::decode;
168 QFontMetrics m(QApplication::font());
169 _text_height = m.boundingRect(QRect(), 0, "Tg").height();
170 _row_height = (_text_height * 6) / 4;
171 const int annotation_height = (_text_height * 5) / 4;
173 assert(_decoder_stack);
174 const QString err = _decoder_stack->error_message();
177 draw_unresolved_period(p, annotation_height, left, right);
178 draw_error(p, err, left, right);
182 // Iterate through the rows
184 pair<uint64_t, uint64_t> sample_range = get_sample_range(left, right);
186 assert(_decoder_stack);
187 const vector<Row> rows(_decoder_stack->get_visible_rows());
189 _visible_rows.clear();
190 for (size_t i = 0; i < rows.size(); i++)
192 const Row &row = rows[i];
194 size_t base_colour = 0x13579BDF;
195 boost::hash_combine(base_colour, this);
196 boost::hash_combine(base_colour, row.decoder());
197 boost::hash_combine(base_colour, row.row());
200 vector<Annotation> annotations;
201 _decoder_stack->get_annotation_subset(annotations, row,
202 sample_range.first, sample_range.second);
203 if (!annotations.empty()) {
204 for (const Annotation &a : annotations)
205 draw_annotation(a, p, get_text_colour(),
206 annotation_height, left, right, y,
210 _visible_rows.push_back(rows[i]);
215 draw_unresolved_period(p, annotation_height, left, right);
218 void DecodeTrace::paint_fore(QPainter &p, int left, int right)
220 using namespace pv::data::decode;
226 for (size_t i = 0; i < _visible_rows.size(); i++)
228 const int y = i * _row_height + get_y();
230 p.setPen(QPen(Qt::NoPen));
231 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
235 const QPointF points[] = {
236 QPointF(left, y - ArrowSize),
237 QPointF(left + ArrowSize, y),
238 QPointF(left, y + ArrowSize)
240 p.drawPolygon(points, countof(points));
243 const QRect r(left + ArrowSize * 2, y - _row_height / 2,
244 right - left, _row_height);
245 const QString h(_visible_rows[i].title());
246 const int f = Qt::AlignLeft | Qt::AlignVCenter |
250 p.setPen(QApplication::palette().color(QPalette::Base));
251 for (int dx = -1; dx <= 1; dx++)
252 for (int dy = -1; dy <= 1; dy++)
253 if (dx != 0 && dy != 0)
254 p.drawText(r.translated(dx, dy), f, h);
257 p.setPen(QApplication::palette().color(QPalette::WindowText));
262 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
264 using pv::data::decode::Decoder;
268 assert(_decoder_stack);
270 // Add the standard options
271 Trace::populate_popup_form(parent, form);
273 // Add the decoder options
275 _channel_selectors.clear();
276 _decoder_forms.clear();
278 const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
282 QLabel *const l = new QLabel(
283 tr("<p><i>No decoders in the stack</i></p>"));
284 l->setAlignment(Qt::AlignCenter);
289 auto iter = stack.cbegin();
290 for (int i = 0; i < (int)stack.size(); i++, iter++) {
291 shared_ptr<Decoder> dec(*iter);
292 create_decoder_form(i, dec, parent, form);
295 form->addRow(new QLabel(
296 tr("<i>* Required channels</i>"), parent));
299 // Add stacking button
300 pv::widgets::DecoderMenu *const decoder_menu =
301 new pv::widgets::DecoderMenu(parent);
302 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
303 this, SLOT(on_stack_decoder(srd_decoder*)));
305 QPushButton *const stack_button =
306 new QPushButton(tr("Stack Decoder"), parent);
307 stack_button->setMenu(decoder_menu);
309 QHBoxLayout *stack_button_box = new QHBoxLayout;
310 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
311 form->addRow(stack_button_box);
314 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
316 QMenu *const menu = Trace::create_context_menu(parent);
318 menu->addSeparator();
320 QAction *const del = new QAction(tr("Delete"), this);
321 del->setShortcuts(QKeySequence::Delete);
322 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
323 menu->addAction(del);
328 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
329 QPainter &p, QColor text_color, int h, int left, int right, int y,
330 size_t base_colour) const
332 double samples_per_pixel, pixels_offset;
333 tie(pixels_offset, samples_per_pixel) =
334 get_pixels_offset_samples_per_pixel();
336 const double start = a.start_sample() / samples_per_pixel -
338 const double end = a.end_sample() / samples_per_pixel -
341 const size_t colour = (base_colour + a.format()) % countof(Colours);
342 const QColor &fill = Colours[colour];
343 const QColor &outline = OutlineColours[colour];
345 if (start > right + DrawPadding || end < left - DrawPadding)
348 if (a.start_sample() == a.end_sample())
349 draw_instant(a, p, fill, outline, text_color, h,
352 draw_range(a, p, fill, outline, text_color, h,
356 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
357 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
359 const QString text = a.annotations().empty() ?
360 QString() : a.annotations().back();
361 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
363 const QRectF rect(x - w / 2, y - h / 2, w, h);
367 p.drawRoundedRect(rect, h / 2, h / 2);
369 p.setPen(text_color);
370 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
373 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
374 QColor fill, QColor outline, QColor text_color, int h, double start,
375 double end, int y) const
377 const double top = y + .5 - h / 2;
378 const double bottom = y + .5 + h / 2;
379 const vector<QString> annotations = a.annotations();
384 // If the two ends are within 1 pixel, draw a vertical line
385 if (start + 1.0 > end)
387 p.drawLine(QPointF(start, top), QPointF(start, bottom));
391 const double cap_width = min((end - start) / 4, EndCapWidth);
394 QPointF(start, y + .5f),
395 QPointF(start + cap_width, top),
396 QPointF(end - cap_width, top),
397 QPointF(end, y + .5f),
398 QPointF(end - cap_width, bottom),
399 QPointF(start + cap_width, bottom)
402 p.drawConvexPolygon(pts, countof(pts));
404 if (annotations.empty())
407 QRectF rect(start + cap_width, y - h / 2,
408 end - start - cap_width * 2, h);
409 if (rect.width() <= 4)
412 p.setPen(text_color);
414 // Try to find an annotation that will fit
415 QString best_annotation;
418 for (const QString &a : annotations) {
419 const int w = p.boundingRect(QRectF(), 0, a).width();
420 if (w <= rect.width() && w > best_width)
421 best_annotation = a, best_width = w;
424 if (best_annotation.isEmpty())
425 best_annotation = annotations.back();
427 // If not ellide the last in the list
428 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
429 best_annotation, Qt::ElideRight, rect.width()));
432 void DecodeTrace::draw_error(QPainter &p, const QString &message,
435 const int y = get_y();
437 p.setPen(ErrorBgColour.darker());
438 p.setBrush(ErrorBgColour);
440 const QRectF bounding_rect =
441 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
442 const QRectF text_rect = p.boundingRect(bounding_rect,
443 Qt::AlignCenter, message);
444 const float r = text_rect.height() / 4;
446 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
449 p.setPen(get_text_colour());
450 p.drawText(text_rect, message);
453 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
456 using namespace pv::data;
457 using pv::data::decode::Decoder;
459 double samples_per_pixel, pixels_offset;
461 assert(_decoder_stack);
463 shared_ptr<Logic> data;
464 shared_ptr<LogicSignal> logic_signal;
466 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
468 // We get the logic data of the first channel in the list.
469 // This works because we are currently assuming all
470 // LogicSignals have the same data/snapshot
471 for (const shared_ptr<Decoder> &dec : stack)
472 if (dec && !dec->channels().empty() &&
473 ((logic_signal = (*dec->channels().begin()).second)) &&
474 ((data = logic_signal->logic_data())))
477 if (!data || data->get_snapshots().empty())
480 const shared_ptr<LogicSnapshot> snapshot =
481 data->get_snapshots().front();
483 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
484 if (sample_count == 0)
487 const int64_t samples_decoded = _decoder_stack->samples_decoded();
488 if (sample_count == samples_decoded)
491 const int y = get_y();
493 tie(pixels_offset, samples_per_pixel) =
494 get_pixels_offset_samples_per_pixel();
496 const double start = max(samples_decoded /
497 samples_per_pixel - pixels_offset, left - 1.0);
498 const double end = min(sample_count / samples_per_pixel -
499 pixels_offset, right + 1.0);
500 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
502 p.setPen(QPen(Qt::NoPen));
503 p.setBrush(Qt::white);
504 p.drawRect(no_decode_rect);
506 p.setPen(NoDecodeColour);
507 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
508 p.drawRect(no_decode_rect);
511 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
514 assert(_decoder_stack);
516 const View *view = _owner->view();
519 const double scale = view->scale();
522 const double pixels_offset =
523 (view->offset() - _decoder_stack->get_start_time()) / scale;
525 double samplerate = _decoder_stack->samplerate();
527 // Show sample rate as 1Hz when it is unknown
528 if (samplerate == 0.0)
531 return make_pair(pixels_offset, samplerate * scale);
534 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
535 int x_start, int x_end) const
537 double samples_per_pixel, pixels_offset;
538 tie(pixels_offset, samples_per_pixel) =
539 get_pixels_offset_samples_per_pixel();
541 const uint64_t start = (uint64_t)max(
542 (x_start + pixels_offset) * samples_per_pixel, 0.0);
543 const uint64_t end = (uint64_t)max(
544 (x_end + pixels_offset) * samples_per_pixel, 0.0);
546 return make_pair(start, end);
549 int DecodeTrace::get_row_at_point(const QPoint &point)
554 const int row = (point.y() - get_y() + _row_height / 2) / _row_height;
555 if (row < 0 || row >= (int)_visible_rows.size())
561 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
563 using namespace pv::data::decode;
568 const pair<uint64_t, uint64_t> sample_range =
569 get_sample_range(point.x(), point.x() + 1);
570 const int row = get_row_at_point(point);
574 vector<pv::data::decode::Annotation> annotations;
576 assert(_decoder_stack);
577 _decoder_stack->get_annotation_subset(annotations, _visible_rows[row],
578 sample_range.first, sample_range.second);
580 return (annotations.empty()) ?
581 QString() : annotations[0].annotations().front();
584 void DecodeTrace::hide_hover_annotation()
586 QToolTip::hideText();
589 void DecodeTrace::hover_point_changed()
593 const View *const view = _owner->view();
596 QPoint hp = view->hover_point();
597 QString ann = get_annotation_at_point(hp);
603 hide_hover_annotation();
607 const int hover_row = get_row_at_point(hp);
609 QFontMetrics m(QToolTip::font());
610 const QRect text_size = m.boundingRect(QRect(), 0, ann);
612 // This is OS-specific and unfortunately we can't query it, so
613 // use an approximation to at least try to minimize the error.
614 const int padding = 8;
616 // Make sure the tool tip doesn't overlap with the mouse cursor.
617 // If it did, the tool tip would constantly hide and re-appear.
618 // We also push it up by one row so that it appears above the
619 // decode trace, not below.
620 hp.setX(hp.x() - (text_size.width() / 2) - padding);
622 hp.setY(get_y() - (_row_height / 2) + (hover_row * _row_height)
623 - _row_height - text_size.height());
625 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
628 void DecodeTrace::create_decoder_form(int index,
629 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
635 const srd_decoder *const decoder = dec->decoder();
638 pv::widgets::DecoderGroupBox *const group =
639 new pv::widgets::DecoderGroupBox(
640 QString::fromUtf8(decoder->name));
641 group->set_decoder_visible(dec->shown());
643 _delete_mapper.setMapping(group, index);
644 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
646 _show_hide_mapper.setMapping(group, index);
647 connect(group, SIGNAL(show_hide_decoder()),
648 &_show_hide_mapper, SLOT(map()));
650 QFormLayout *const decoder_form = new QFormLayout;
651 group->add_layout(decoder_form);
653 // Add the mandatory channels
654 for(l = decoder->channels; l; l = l->next) {
655 const struct srd_channel *const pdch =
656 (struct srd_channel *)l->data;
657 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
658 connect(combo, SIGNAL(currentIndexChanged(int)),
659 this, SLOT(on_channel_selected(int)));
660 decoder_form->addRow(tr("<b>%1</b> (%2) *")
661 .arg(QString::fromUtf8(pdch->name))
662 .arg(QString::fromUtf8(pdch->desc)), combo);
664 const ChannelSelector s = {combo, dec, pdch};
665 _channel_selectors.push_back(s);
668 // Add the optional channels
669 for(l = decoder->opt_channels; l; l = l->next) {
670 const struct srd_channel *const pdch =
671 (struct srd_channel *)l->data;
672 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
673 connect(combo, SIGNAL(currentIndexChanged(int)),
674 this, SLOT(on_channel_selected(int)));
675 decoder_form->addRow(tr("<b>%1</b> (%2)")
676 .arg(QString::fromUtf8(pdch->name))
677 .arg(QString::fromUtf8(pdch->desc)), combo);
679 const ChannelSelector s = {combo, dec, pdch};
680 _channel_selectors.push_back(s);
684 shared_ptr<prop::binding::DecoderOptions> binding(
685 new prop::binding::DecoderOptions(_decoder_stack, dec));
686 binding->add_properties_to_form(decoder_form, true);
688 _bindings.push_back(binding);
691 _decoder_forms.push_back(group);
694 QComboBox* DecodeTrace::create_channel_selector(
695 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
696 const srd_channel *const pdch)
700 shared_lock<shared_mutex> lock(_session.signals_mutex());
701 const vector< shared_ptr<Signal> > &sigs(_session.signals());
703 assert(_decoder_stack);
704 const auto channel_iter = dec->channels().find(pdch);
706 QComboBox *selector = new QComboBox(parent);
708 selector->addItem("-", qVariantFromValue((void*)NULL));
710 if (channel_iter == dec->channels().end())
711 selector->setCurrentIndex(0);
713 for(size_t i = 0; i < sigs.size(); i++) {
714 const shared_ptr<view::Signal> s(sigs[i]);
717 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
719 selector->addItem(s->name(),
720 qVariantFromValue((void*)s.get()));
721 if ((*channel_iter).second == s)
722 selector->setCurrentIndex(i + 1);
729 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
733 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
735 shared_lock<shared_mutex> lock(_session.signals_mutex());
736 const vector< shared_ptr<Signal> > &sigs(_session.signals());
738 for (const ChannelSelector &s : _channel_selectors)
740 if(s._decoder != dec)
743 const LogicSignal *const selection =
744 (LogicSignal*)s._combo->itemData(
745 s._combo->currentIndex()).value<void*>();
747 for (shared_ptr<Signal> sig : sigs)
748 if(sig.get() == selection) {
749 channel_map[s._pdch] =
750 dynamic_pointer_cast<LogicSignal>(sig);
755 dec->set_channels(channel_map);
758 void DecodeTrace::commit_channels()
760 assert(_decoder_stack);
761 for (shared_ptr<data::decode::Decoder> dec : _decoder_stack->stack())
762 commit_decoder_channels(dec);
764 _decoder_stack->begin_decode();
767 void DecodeTrace::on_new_decode_data()
770 _owner->update_viewport();
773 void DecodeTrace::delete_pressed()
778 void DecodeTrace::on_delete()
780 _session.remove_decode_signal(this);
783 void DecodeTrace::on_channel_selected(int)
788 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
791 assert(_decoder_stack);
792 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
793 new data::decode::Decoder(decoder)));
794 _decoder_stack->begin_decode();
799 void DecodeTrace::on_delete_decoder(int index)
801 _decoder_stack->remove(index);
806 _decoder_stack->begin_decode();
809 void DecodeTrace::on_show_hide_decoder(int index)
811 using pv::data::decode::Decoder;
813 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
815 // Find the decoder in the stack
816 auto iter = stack.cbegin();
817 for(int i = 0; i < index; i++, iter++)
818 assert(iter != stack.end());
820 shared_ptr<Decoder> dec = *iter;
823 const bool show = !dec->shown();
826 assert(index < (int)_decoder_forms.size());
827 _decoder_forms[index]->set_decoder_visible(show);
829 _owner->update_viewport();