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/foreach.hpp>
28 #include <boost/functional/hash.hpp>
31 #include <QApplication>
33 #include <QFormLayout>
36 #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 boost::dynamic_pointer_cast;
52 using boost::shared_ptr;
62 const QColor DecodeTrace::DecodeColours[4] = {
63 QColor(0xEF, 0x29, 0x29), // Red
64 QColor(0xFC, 0xE9, 0x4F), // Yellow
65 QColor(0x8A, 0xE2, 0x34), // Green
66 QColor(0x72, 0x9F, 0xCF) // Blue
69 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
70 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
72 const int DecodeTrace::ArrowSize = 4;
73 const double DecodeTrace::EndCapWidth = 5;
74 const int DecodeTrace::DrawPadding = 100;
76 const QColor DecodeTrace::Colours[16] = {
77 QColor(0xEF, 0x29, 0x29),
78 QColor(0xF6, 0x6A, 0x32),
79 QColor(0xFC, 0xAE, 0x3E),
80 QColor(0xFB, 0xCA, 0x47),
81 QColor(0xFC, 0xE9, 0x4F),
82 QColor(0xCD, 0xF0, 0x40),
83 QColor(0x8A, 0xE2, 0x34),
84 QColor(0x4E, 0xDC, 0x44),
85 QColor(0x55, 0xD7, 0x95),
86 QColor(0x64, 0xD1, 0xD2),
87 QColor(0x72, 0x9F, 0xCF),
88 QColor(0xD4, 0x76, 0xC4),
89 QColor(0x9D, 0x79, 0xB9),
90 QColor(0xAD, 0x7F, 0xA8),
91 QColor(0xC2, 0x62, 0x9B),
92 QColor(0xD7, 0x47, 0x6F)
95 const QColor DecodeTrace::OutlineColours[16] = {
96 QColor(0x77, 0x14, 0x14),
97 QColor(0x7B, 0x35, 0x19),
98 QColor(0x7E, 0x57, 0x1F),
99 QColor(0x7D, 0x65, 0x23),
100 QColor(0x7E, 0x74, 0x27),
101 QColor(0x66, 0x78, 0x20),
102 QColor(0x45, 0x71, 0x1A),
103 QColor(0x27, 0x6E, 0x22),
104 QColor(0x2A, 0x6B, 0x4A),
105 QColor(0x32, 0x68, 0x69),
106 QColor(0x39, 0x4F, 0x67),
107 QColor(0x6A, 0x3B, 0x62),
108 QColor(0x4E, 0x3C, 0x5C),
109 QColor(0x56, 0x3F, 0x54),
110 QColor(0x61, 0x31, 0x4D),
111 QColor(0x6B, 0x23, 0x37)
114 DecodeTrace::DecodeTrace(pv::SigSession &session,
115 boost::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
116 Trace(QString::fromUtf8(
117 decoder_stack->stack().front()->decoder()->name)),
119 _decoder_stack(decoder_stack),
120 _delete_mapper(this),
121 _show_hide_mapper(this)
123 assert(_decoder_stack);
125 _colour = DecodeColours[index % countof(DecodeColours)];
127 connect(_decoder_stack.get(), SIGNAL(new_decode_data()),
128 this, SLOT(on_new_decode_data()));
129 connect(&_delete_mapper, SIGNAL(mapped(int)),
130 this, SLOT(on_delete_decoder(int)));
131 connect(&_show_hide_mapper, SIGNAL(mapped(int)),
132 this, SLOT(on_show_hide_decoder(int)));
135 bool DecodeTrace::enabled() const
140 const boost::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
142 return _decoder_stack;
145 void DecodeTrace::set_view(pv::view::View *view)
148 Trace::set_view(view);
151 void DecodeTrace::paint_back(QPainter &p, int left, int right)
153 Trace::paint_back(p, left, right);
154 paint_axis(p, get_y(), left, right);
157 void DecodeTrace::paint_mid(QPainter &p, int left, int right)
159 using namespace pv::data::decode;
161 const double scale = _view->scale();
164 double samplerate = _decoder_stack->samplerate();
166 _cur_row_headings.clear();
168 // Show sample rate as 1Hz when it is unknown
169 if (samplerate == 0.0)
172 const double pixels_offset = (_view->offset() -
173 _decoder_stack->get_start_time()) / scale;
174 const double samples_per_pixel = samplerate * scale;
176 const uint64_t start_sample = (uint64_t)max((left + pixels_offset) *
177 samples_per_pixel, 0.0);
178 const uint64_t end_sample = (uint64_t)max((right + pixels_offset) *
179 samples_per_pixel, 0.0);
181 QFontMetrics m(QApplication::font());
182 const int text_height = m.boundingRect(QRect(), 0, "Tg").height();
183 const int annotation_height = (text_height * 5) / 4;
184 const int row_height = (text_height * 6) / 4;
186 assert(_decoder_stack);
187 const QString err = _decoder_stack->error_message();
190 draw_unresolved_period(p, annotation_height, left, right,
191 samples_per_pixel, pixels_offset);
192 draw_error(p, err, left, right);
196 // Iterate through the rows
200 assert(_decoder_stack);
202 const vector<Row> rows(_decoder_stack->get_visible_rows());
203 for (size_t i = 0; i < rows.size(); i++)
205 const Row &row = rows[i];
207 size_t base_colour = 0x13579BDF;
208 boost::hash_combine(base_colour, this);
209 boost::hash_combine(base_colour, row.decoder());
210 boost::hash_combine(base_colour, row.row());
213 vector<Annotation> annotations;
214 _decoder_stack->get_annotation_subset(annotations, row,
215 start_sample, end_sample);
216 if (!annotations.empty()) {
217 BOOST_FOREACH(const Annotation &a, annotations)
218 draw_annotation(a, p, get_text_colour(),
219 annotation_height, left, right,
220 samples_per_pixel, pixels_offset, y,
224 _cur_row_headings.push_back(row.title());
229 draw_unresolved_period(p, annotation_height, left, right,
230 samples_per_pixel, pixels_offset);
233 void DecodeTrace::paint_fore(QPainter &p, int left, int right)
235 using namespace pv::data::decode;
239 QFontMetrics m(QApplication::font());
240 const int text_height = m.boundingRect(QRect(), 0, "Tg").height();
241 const int row_height = (text_height * 6) / 4;
243 for (size_t i = 0; i < _cur_row_headings.size(); i++)
245 const int y = i * row_height + get_y();
247 p.setPen(QPen(Qt::NoPen));
248 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
252 const QPointF points[] = {
253 QPointF(left, y - ArrowSize),
254 QPointF(left + ArrowSize, y),
255 QPointF(left, y + ArrowSize)
257 p.drawPolygon(points, countof(points));
260 const QRect r(left + ArrowSize * 2, y - row_height / 2,
261 right - left, row_height);
262 const QString h(_cur_row_headings[i]);
263 const int f = Qt::AlignLeft | Qt::AlignVCenter |
267 p.setPen(QApplication::palette().color(QPalette::Base));
268 for (int dx = -1; dx <= 1; dx++)
269 for (int dy = -1; dy <= 1; dy++)
270 if (dx != 0 && dy != 0)
271 p.drawText(r.translated(dx, dy), f, h);
274 p.setPen(QApplication::palette().color(QPalette::WindowText));
279 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
281 using pv::data::decode::Decoder;
285 assert(_decoder_stack);
287 // Add the standard options
288 Trace::populate_popup_form(parent, form);
290 // Add the decoder options
292 _probe_selectors.clear();
293 _decoder_forms.clear();
295 const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
299 QLabel *const l = new QLabel(
300 tr("<p><i>No decoders in the stack</i></p>"));
301 l->setAlignment(Qt::AlignCenter);
306 auto iter = stack.cbegin();
307 for (int i = 0; i < (int)stack.size(); i++, iter++) {
308 shared_ptr<Decoder> dec(*iter);
309 create_decoder_form(i, dec, parent, form);
312 form->addRow(new QLabel(
313 tr("<i>* Required channels</i>"), parent));
316 // Add stacking button
317 pv::widgets::DecoderMenu *const decoder_menu =
318 new pv::widgets::DecoderMenu(parent);
319 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
320 this, SLOT(on_stack_decoder(srd_decoder*)));
322 QPushButton *const stack_button =
323 new QPushButton(tr("Stack Decoder"), parent);
324 stack_button->setMenu(decoder_menu);
326 QHBoxLayout *stack_button_box = new QHBoxLayout;
327 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
328 form->addRow(stack_button_box);
331 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
333 QMenu *const menu = Trace::create_context_menu(parent);
335 menu->addSeparator();
337 QAction *const del = new QAction(tr("Delete"), this);
338 del->setShortcuts(QKeySequence::Delete);
339 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
340 menu->addAction(del);
345 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
346 QPainter &p, QColor text_color, int h, int left, int right,
347 double samples_per_pixel, double pixels_offset, int y,
348 size_t base_colour) const
350 const double start = a.start_sample() / samples_per_pixel -
352 const double end = a.end_sample() / samples_per_pixel -
355 const size_t colour = (base_colour + a.format()) % countof(Colours);
356 const QColor &fill = Colours[colour];
357 const QColor &outline = OutlineColours[colour];
359 if (start > right + DrawPadding || end < left - DrawPadding)
362 if (a.start_sample() == a.end_sample())
363 draw_instant(a, p, fill, outline, text_color, h,
366 draw_range(a, p, fill, outline, text_color, h,
370 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
371 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
373 const QString text = a.annotations().empty() ?
374 QString() : a.annotations().back();
375 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
377 const QRectF rect(x - w / 2, y - h / 2, w, h);
381 p.drawRoundedRect(rect, h / 2, h / 2);
383 p.setPen(text_color);
384 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
387 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
388 QColor fill, QColor outline, QColor text_color, int h, double start,
389 double end, int y) const
391 const double top = y + .5 - h / 2;
392 const double bottom = y + .5 + h / 2;
393 const vector<QString> annotations = a.annotations();
398 // If the two ends are within 1 pixel, draw a vertical line
399 if (start + 1.0 > end)
401 p.drawLine(QPointF(start, top), QPointF(start, bottom));
405 const double cap_width = min((end - start) / 4, EndCapWidth);
408 QPointF(start, y + .5f),
409 QPointF(start + cap_width, top),
410 QPointF(end - cap_width, top),
411 QPointF(end, y + .5f),
412 QPointF(end - cap_width, bottom),
413 QPointF(start + cap_width, bottom)
416 p.drawConvexPolygon(pts, countof(pts));
418 if (annotations.empty())
421 QRectF rect(start + cap_width, y - h / 2,
422 end - start - cap_width * 2, h);
423 if (rect.width() <= 4)
426 p.setPen(text_color);
428 // Try to find an annotation that will fit
429 QString best_annotation;
432 BOOST_FOREACH(const QString &a, annotations) {
433 const int w = p.boundingRect(QRectF(), 0, a).width();
434 if (w <= rect.width() && w > best_width)
435 best_annotation = a, best_width = w;
438 if (best_annotation.isEmpty())
439 best_annotation = annotations.back();
441 // If not ellide the last in the list
442 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
443 best_annotation, Qt::ElideRight, rect.width()));
446 void DecodeTrace::draw_error(QPainter &p, const QString &message,
449 const int y = get_y();
451 p.setPen(ErrorBgColour.darker());
452 p.setBrush(ErrorBgColour);
454 const QRectF bounding_rect =
455 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
456 const QRectF text_rect = p.boundingRect(bounding_rect,
457 Qt::AlignCenter, message);
458 const float r = text_rect.height() / 4;
460 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
463 p.setPen(get_text_colour());
464 p.drawText(text_rect, message);
467 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
468 int right, double samples_per_pixel, double pixels_offset)
470 using namespace pv::data;
471 using pv::data::decode::Decoder;
473 assert(_decoder_stack);
475 shared_ptr<Logic> data;
476 shared_ptr<LogicSignal> logic_signal;
478 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
480 // We get the logic data of the first probe in the list.
481 // This works because we are currently assuming all
482 // LogicSignals have the same data/snapshot
483 BOOST_FOREACH (const shared_ptr<Decoder> &dec, stack)
484 if (dec && !dec->channels().empty() &&
485 ((logic_signal = (*dec->channels().begin()).second)) &&
486 ((data = logic_signal->logic_data())))
489 if (!data || data->get_snapshots().empty())
492 const shared_ptr<LogicSnapshot> snapshot =
493 data->get_snapshots().front();
495 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
496 if (sample_count == 0)
499 const int64_t samples_decoded = _decoder_stack->samples_decoded();
500 if (sample_count == samples_decoded)
503 const int y = get_y();
504 const double start = max(samples_decoded /
505 samples_per_pixel - pixels_offset, left - 1.0);
506 const double end = min(sample_count / samples_per_pixel -
507 pixels_offset, right + 1.0);
508 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
510 p.setPen(QPen(Qt::NoPen));
511 p.setBrush(Qt::white);
512 p.drawRect(no_decode_rect);
514 p.setPen(NoDecodeColour);
515 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
516 p.drawRect(no_decode_rect);
519 void DecodeTrace::create_decoder_form(int index,
520 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
526 const srd_decoder *const decoder = dec->decoder();
529 pv::widgets::DecoderGroupBox *const group =
530 new pv::widgets::DecoderGroupBox(
531 QString::fromUtf8(decoder->name));
532 group->set_decoder_visible(dec->shown());
534 _delete_mapper.setMapping(group, index);
535 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
537 _show_hide_mapper.setMapping(group, index);
538 connect(group, SIGNAL(show_hide_decoder()),
539 &_show_hide_mapper, SLOT(map()));
541 QFormLayout *const decoder_form = new QFormLayout;
542 group->add_layout(decoder_form);
544 // Add the mandatory channels
545 for(l = decoder->channels; l; l = l->next) {
546 const struct srd_channel *const pdch =
547 (struct srd_channel *)l->data;
548 QComboBox *const combo = create_probe_selector(parent, dec, pdch);
549 connect(combo, SIGNAL(currentIndexChanged(int)),
550 this, SLOT(on_probe_selected(int)));
551 decoder_form->addRow(tr("<b>%1</b> (%2) *")
552 .arg(QString::fromUtf8(pdch->name))
553 .arg(QString::fromUtf8(pdch->desc)), combo);
555 const ProbeSelector s = {combo, dec, pdch};
556 _probe_selectors.push_back(s);
559 // Add the optional channels
560 for(l = decoder->opt_channels; l; l = l->next) {
561 const struct srd_channel *const pdch =
562 (struct srd_channel *)l->data;
563 QComboBox *const combo = create_probe_selector(parent, dec, pdch);
564 connect(combo, SIGNAL(currentIndexChanged(int)),
565 this, SLOT(on_probe_selected(int)));
566 decoder_form->addRow(tr("<b>%1</b> (%2)")
567 .arg(QString::fromUtf8(pdch->name))
568 .arg(QString::fromUtf8(pdch->desc)), combo);
570 const ProbeSelector s = {combo, dec, pdch};
571 _probe_selectors.push_back(s);
575 shared_ptr<prop::binding::DecoderOptions> binding(
576 new prop::binding::DecoderOptions(_decoder_stack, dec));
577 binding->add_properties_to_form(decoder_form, true);
579 _bindings.push_back(binding);
582 _decoder_forms.push_back(group);
585 QComboBox* DecodeTrace::create_probe_selector(
586 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
587 const srd_channel *const pdch)
591 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
593 assert(_decoder_stack);
594 const auto probe_iter = dec->channels().find(pdch);
596 QComboBox *selector = new QComboBox(parent);
598 selector->addItem("-", qVariantFromValue((void*)NULL));
600 if (probe_iter == dec->channels().end())
601 selector->setCurrentIndex(0);
603 for(size_t i = 0; i < sigs.size(); i++) {
604 const shared_ptr<view::Signal> s(sigs[i]);
607 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
609 selector->addItem(s->get_name(),
610 qVariantFromValue((void*)s.get()));
611 if ((*probe_iter).second == s)
612 selector->setCurrentIndex(i + 1);
619 void DecodeTrace::commit_decoder_probes(shared_ptr<data::decode::Decoder> &dec)
623 map<const srd_channel*, shared_ptr<LogicSignal> > probe_map;
624 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
626 BOOST_FOREACH(const ProbeSelector &s, _probe_selectors)
628 if(s._decoder != dec)
631 const LogicSignal *const selection =
632 (LogicSignal*)s._combo->itemData(
633 s._combo->currentIndex()).value<void*>();
635 BOOST_FOREACH(shared_ptr<Signal> sig, sigs)
636 if(sig.get() == selection) {
638 dynamic_pointer_cast<LogicSignal>(sig);
643 dec->set_probes(probe_map);
646 void DecodeTrace::commit_probes()
648 assert(_decoder_stack);
649 BOOST_FOREACH(shared_ptr<data::decode::Decoder> dec,
650 _decoder_stack->stack())
651 commit_decoder_probes(dec);
653 _decoder_stack->begin_decode();
656 void DecodeTrace::on_new_decode_data()
659 _view->update_viewport();
662 void DecodeTrace::delete_pressed()
667 void DecodeTrace::on_delete()
669 _session.remove_decode_signal(this);
672 void DecodeTrace::on_probe_selected(int)
677 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
680 assert(_decoder_stack);
681 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
682 new data::decode::Decoder(decoder)));
683 _decoder_stack->begin_decode();
688 void DecodeTrace::on_delete_decoder(int index)
690 _decoder_stack->remove(index);
695 _decoder_stack->begin_decode();
698 void DecodeTrace::on_show_hide_decoder(int index)
700 using pv::data::decode::Decoder;
702 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
704 // Find the decoder in the stack
705 auto iter = stack.cbegin();
706 for(int i = 0; i < index; i++, iter++)
707 assert(iter != stack.end());
709 shared_ptr<Decoder> dec = *iter;
712 const bool show = !dec->shown();
715 assert(index < (int)_decoder_forms.size());
716 _decoder_forms[index]->set_decoder_visible(show);
718 _view->update_viewport();