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 std::dynamic_pointer_cast;
58 using std::lock_guard;
65 using std::shared_ptr;
72 const QColor DecodeTrace::DecodeColours[4] = {
73 QColor(0xEF, 0x29, 0x29), // Red
74 QColor(0xFC, 0xE9, 0x4F), // Yellow
75 QColor(0x8A, 0xE2, 0x34), // Green
76 QColor(0x72, 0x9F, 0xCF) // Blue
79 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
80 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
82 const int DecodeTrace::ArrowSize = 4;
83 const double DecodeTrace::EndCapWidth = 5;
84 const int DecodeTrace::DrawPadding = 100;
86 const QColor DecodeTrace::Colours[16] = {
87 QColor(0xEF, 0x29, 0x29),
88 QColor(0xF6, 0x6A, 0x32),
89 QColor(0xFC, 0xAE, 0x3E),
90 QColor(0xFB, 0xCA, 0x47),
91 QColor(0xFC, 0xE9, 0x4F),
92 QColor(0xCD, 0xF0, 0x40),
93 QColor(0x8A, 0xE2, 0x34),
94 QColor(0x4E, 0xDC, 0x44),
95 QColor(0x55, 0xD7, 0x95),
96 QColor(0x64, 0xD1, 0xD2),
97 QColor(0x72, 0x9F, 0xCF),
98 QColor(0xD4, 0x76, 0xC4),
99 QColor(0x9D, 0x79, 0xB9),
100 QColor(0xAD, 0x7F, 0xA8),
101 QColor(0xC2, 0x62, 0x9B),
102 QColor(0xD7, 0x47, 0x6F)
105 const QColor DecodeTrace::OutlineColours[16] = {
106 QColor(0x77, 0x14, 0x14),
107 QColor(0x7B, 0x35, 0x19),
108 QColor(0x7E, 0x57, 0x1F),
109 QColor(0x7D, 0x65, 0x23),
110 QColor(0x7E, 0x74, 0x27),
111 QColor(0x66, 0x78, 0x20),
112 QColor(0x45, 0x71, 0x1A),
113 QColor(0x27, 0x6E, 0x22),
114 QColor(0x2A, 0x6B, 0x4A),
115 QColor(0x32, 0x68, 0x69),
116 QColor(0x39, 0x4F, 0x67),
117 QColor(0x6A, 0x3B, 0x62),
118 QColor(0x4E, 0x3C, 0x5C),
119 QColor(0x56, 0x3F, 0x54),
120 QColor(0x61, 0x31, 0x4D),
121 QColor(0x6B, 0x23, 0x37)
124 DecodeTrace::DecodeTrace(pv::SigSession &session,
125 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
126 Trace(QString::fromUtf8(
127 decoder_stack->stack().front()->decoder()->name)),
129 _decoder_stack(decoder_stack),
132 _delete_mapper(this),
133 _show_hide_mapper(this)
135 assert(_decoder_stack);
137 _colour = DecodeColours[index % countof(DecodeColours)];
139 connect(_decoder_stack.get(), SIGNAL(new_decode_data()),
140 this, SLOT(on_new_decode_data()));
141 connect(&_delete_mapper, SIGNAL(mapped(int)),
142 this, SLOT(on_delete_decoder(int)));
143 connect(&_show_hide_mapper, SIGNAL(mapped(int)),
144 this, SLOT(on_show_hide_decoder(int)));
147 bool DecodeTrace::enabled() const
152 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
154 return _decoder_stack;
157 void DecodeTrace::paint_back(QPainter &p, int left, int right)
159 Trace::paint_back(p, left, right);
160 paint_axis(p, get_y(), left, right);
163 void DecodeTrace::paint_mid(QPainter &p, int left, int right)
165 using namespace pv::data::decode;
167 QFontMetrics m(QApplication::font());
168 _text_height = m.boundingRect(QRect(), 0, "Tg").height();
169 _row_height = (_text_height * 6) / 4;
170 const int annotation_height = (_text_height * 5) / 4;
172 assert(_decoder_stack);
173 const QString err = _decoder_stack->error_message();
176 draw_unresolved_period(p, annotation_height, left, right);
177 draw_error(p, err, left, right);
181 // 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 double samples_per_pixel, pixels_offset;
332 tie(pixels_offset, samples_per_pixel) =
333 get_pixels_offset_samples_per_pixel();
335 const double start = a.start_sample() / samples_per_pixel -
337 const double end = a.end_sample() / samples_per_pixel -
340 const size_t colour = (base_colour + a.format()) % countof(Colours);
341 const QColor &fill = Colours[colour];
342 const QColor &outline = OutlineColours[colour];
344 if (start > right + DrawPadding || end < left - DrawPadding)
347 if (a.start_sample() == a.end_sample())
348 draw_instant(a, p, fill, outline, text_color, h,
351 draw_range(a, p, fill, outline, text_color, h,
355 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
356 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
358 const QString text = a.annotations().empty() ?
359 QString() : a.annotations().back();
360 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
362 const QRectF rect(x - w / 2, y - h / 2, w, h);
366 p.drawRoundedRect(rect, h / 2, h / 2);
368 p.setPen(text_color);
369 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
372 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
373 QColor fill, QColor outline, QColor text_color, int h, double start,
374 double end, int y) const
376 const double top = y + .5 - h / 2;
377 const double bottom = y + .5 + h / 2;
378 const vector<QString> annotations = a.annotations();
383 // If the two ends are within 1 pixel, draw a vertical line
384 if (start + 1.0 > end)
386 p.drawLine(QPointF(start, top), QPointF(start, bottom));
390 const double cap_width = min((end - start) / 4, EndCapWidth);
393 QPointF(start, y + .5f),
394 QPointF(start + cap_width, top),
395 QPointF(end - cap_width, top),
396 QPointF(end, y + .5f),
397 QPointF(end - cap_width, bottom),
398 QPointF(start + cap_width, bottom)
401 p.drawConvexPolygon(pts, countof(pts));
403 if (annotations.empty())
406 QRectF rect(start + cap_width, y - h / 2,
407 end - start - cap_width * 2, h);
408 if (rect.width() <= 4)
411 p.setPen(text_color);
413 // Try to find an annotation that will fit
414 QString best_annotation;
417 for (const QString &a : annotations) {
418 const int w = p.boundingRect(QRectF(), 0, a).width();
419 if (w <= rect.width() && w > best_width)
420 best_annotation = a, best_width = w;
423 if (best_annotation.isEmpty())
424 best_annotation = annotations.back();
426 // If not ellide the last in the list
427 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
428 best_annotation, Qt::ElideRight, rect.width()));
431 void DecodeTrace::draw_error(QPainter &p, const QString &message,
434 const int y = get_y();
436 p.setPen(ErrorBgColour.darker());
437 p.setBrush(ErrorBgColour);
439 const QRectF bounding_rect =
440 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
441 const QRectF text_rect = p.boundingRect(bounding_rect,
442 Qt::AlignCenter, message);
443 const float r = text_rect.height() / 4;
445 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
448 p.setPen(get_text_colour());
449 p.drawText(text_rect, message);
452 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
455 using namespace pv::data;
456 using pv::data::decode::Decoder;
458 double samples_per_pixel, pixels_offset;
460 assert(_decoder_stack);
462 shared_ptr<Logic> data;
463 shared_ptr<LogicSignal> logic_signal;
465 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
467 // We get the logic data of the first channel in the list.
468 // This works because we are currently assuming all
469 // LogicSignals have the same data/snapshot
470 for (const shared_ptr<Decoder> &dec : stack)
471 if (dec && !dec->channels().empty() &&
472 ((logic_signal = (*dec->channels().begin()).second)) &&
473 ((data = logic_signal->logic_data())))
476 if (!data || data->get_snapshots().empty())
479 const shared_ptr<LogicSnapshot> snapshot =
480 data->get_snapshots().front();
482 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
483 if (sample_count == 0)
486 const int64_t samples_decoded = _decoder_stack->samples_decoded();
487 if (sample_count == samples_decoded)
490 const int y = get_y();
492 tie(pixels_offset, samples_per_pixel) =
493 get_pixels_offset_samples_per_pixel();
495 const double start = max(samples_decoded /
496 samples_per_pixel - pixels_offset, left - 1.0);
497 const double end = min(sample_count / samples_per_pixel -
498 pixels_offset, right + 1.0);
499 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
501 p.setPen(QPen(Qt::NoPen));
502 p.setBrush(Qt::white);
503 p.drawRect(no_decode_rect);
505 p.setPen(NoDecodeColour);
506 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
507 p.drawRect(no_decode_rect);
510 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
513 assert(_decoder_stack);
515 const View *view = _owner->view();
518 const double scale = view->scale();
521 const double pixels_offset =
522 (view->offset() - _decoder_stack->get_start_time()) / scale;
524 double samplerate = _decoder_stack->samplerate();
526 // Show sample rate as 1Hz when it is unknown
527 if (samplerate == 0.0)
530 return make_pair(pixels_offset, samplerate * scale);
533 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
534 int x_start, int x_end) const
536 double samples_per_pixel, pixels_offset;
537 tie(pixels_offset, samples_per_pixel) =
538 get_pixels_offset_samples_per_pixel();
540 const uint64_t start = (uint64_t)max(
541 (x_start + pixels_offset) * samples_per_pixel, 0.0);
542 const uint64_t end = (uint64_t)max(
543 (x_end + pixels_offset) * samples_per_pixel, 0.0);
545 return make_pair(start, end);
548 int DecodeTrace::get_row_at_point(const QPoint &point)
553 const int row = (point.y() - get_y() + _row_height / 2) / _row_height;
554 if (row < 0 || row >= (int)_visible_rows.size())
560 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
562 using namespace pv::data::decode;
567 const pair<uint64_t, uint64_t> sample_range =
568 get_sample_range(point.x(), point.x() + 1);
569 const int row = get_row_at_point(point);
573 vector<pv::data::decode::Annotation> annotations;
575 assert(_decoder_stack);
576 _decoder_stack->get_annotation_subset(annotations, _visible_rows[row],
577 sample_range.first, sample_range.second);
579 return (annotations.empty()) ?
580 QString() : annotations[0].annotations().front();
583 void DecodeTrace::hide_hover_annotation()
585 QToolTip::hideText();
588 void DecodeTrace::hover_point_changed()
592 const View *const view = _owner->view();
595 QPoint hp = view->hover_point();
596 QString ann = get_annotation_at_point(hp);
602 hide_hover_annotation();
606 const int hover_row = get_row_at_point(hp);
608 QFontMetrics m(QToolTip::font());
609 const QRect text_size = m.boundingRect(QRect(), 0, ann);
611 // This is OS-specific and unfortunately we can't query it, so
612 // use an approximation to at least try to minimize the error.
613 const int padding = 8;
615 // Make sure the tool tip doesn't overlap with the mouse cursor.
616 // If it did, the tool tip would constantly hide and re-appear.
617 // We also push it up by one row so that it appears above the
618 // decode trace, not below.
619 hp.setX(hp.x() - (text_size.width() / 2) - padding);
621 hp.setY(get_y() - (_row_height / 2) + (hover_row * _row_height)
622 - _row_height - text_size.height());
624 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
627 void DecodeTrace::create_decoder_form(int index,
628 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
634 const srd_decoder *const decoder = dec->decoder();
637 pv::widgets::DecoderGroupBox *const group =
638 new pv::widgets::DecoderGroupBox(
639 QString::fromUtf8(decoder->name));
640 group->set_decoder_visible(dec->shown());
642 _delete_mapper.setMapping(group, index);
643 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
645 _show_hide_mapper.setMapping(group, index);
646 connect(group, SIGNAL(show_hide_decoder()),
647 &_show_hide_mapper, SLOT(map()));
649 QFormLayout *const decoder_form = new QFormLayout;
650 group->add_layout(decoder_form);
652 // Add the mandatory channels
653 for(l = decoder->channels; l; l = l->next) {
654 const struct srd_channel *const pdch =
655 (struct srd_channel *)l->data;
656 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
657 connect(combo, SIGNAL(currentIndexChanged(int)),
658 this, SLOT(on_channel_selected(int)));
659 decoder_form->addRow(tr("<b>%1</b> (%2) *")
660 .arg(QString::fromUtf8(pdch->name))
661 .arg(QString::fromUtf8(pdch->desc)), combo);
663 const ChannelSelector s = {combo, dec, pdch};
664 _channel_selectors.push_back(s);
667 // Add the optional channels
668 for(l = decoder->opt_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);
683 shared_ptr<prop::binding::DecoderOptions> binding(
684 new prop::binding::DecoderOptions(_decoder_stack, dec));
685 binding->add_properties_to_form(decoder_form, true);
687 _bindings.push_back(binding);
690 _decoder_forms.push_back(group);
693 QComboBox* DecodeTrace::create_channel_selector(
694 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
695 const srd_channel *const pdch)
699 lock_guard<mutex> lock(_session.signals_mutex());
700 const vector< shared_ptr<Signal> > &sigs(_session.signals());
702 assert(_decoder_stack);
703 const auto channel_iter = dec->channels().find(pdch);
705 QComboBox *selector = new QComboBox(parent);
707 selector->addItem("-", qVariantFromValue((void*)NULL));
709 if (channel_iter == dec->channels().end())
710 selector->setCurrentIndex(0);
712 for(size_t i = 0; i < sigs.size(); i++) {
713 const shared_ptr<view::Signal> s(sigs[i]);
716 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
718 selector->addItem(s->name(),
719 qVariantFromValue((void*)s.get()));
720 if ((*channel_iter).second == s)
721 selector->setCurrentIndex(i + 1);
728 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
732 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
734 lock_guard<mutex> lock(_session.signals_mutex());
735 const vector< shared_ptr<Signal> > &sigs(_session.signals());
737 for (const ChannelSelector &s : _channel_selectors)
739 if(s._decoder != dec)
742 const LogicSignal *const selection =
743 (LogicSignal*)s._combo->itemData(
744 s._combo->currentIndex()).value<void*>();
746 for (shared_ptr<Signal> sig : sigs)
747 if(sig.get() == selection) {
748 channel_map[s._pdch] =
749 dynamic_pointer_cast<LogicSignal>(sig);
754 dec->set_channels(channel_map);
757 void DecodeTrace::commit_channels()
759 assert(_decoder_stack);
760 for (shared_ptr<data::decode::Decoder> dec : _decoder_stack->stack())
761 commit_decoder_channels(dec);
763 _decoder_stack->begin_decode();
766 void DecodeTrace::on_new_decode_data()
769 _owner->update_viewport();
772 void DecodeTrace::delete_pressed()
777 void DecodeTrace::on_delete()
779 _session.remove_decode_signal(this);
782 void DecodeTrace::on_channel_selected(int)
787 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
790 assert(_decoder_stack);
791 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
792 new data::decode::Decoder(decoder)));
793 _decoder_stack->begin_decode();
798 void DecodeTrace::on_delete_decoder(int index)
800 _decoder_stack->remove(index);
805 _decoder_stack->begin_decode();
808 void DecodeTrace::on_show_hide_decoder(int index)
810 using pv::data::decode::Decoder;
812 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
814 // Find the decoder in the stack
815 auto iter = stack.cbegin();
816 for(int i = 0; i < index; i++, iter++)
817 assert(iter != stack.end());
819 shared_ptr<Decoder> dec = *iter;
822 const bool show = !dec->shown();
825 assert(index < (int)_decoder_forms.size());
826 _decoder_forms[index]->set_decoder_visible(show);
828 _owner->update_viewport();