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 list< shared_ptr<Decoder> >::const_iterator iter =
308 for (int i = 0; i < (int)stack.size(); i++, iter++) {
309 shared_ptr<Decoder> dec(*iter);
310 create_decoder_form(i, dec, parent, form);
313 form->addRow(new QLabel(
314 tr("<i>* Required Probes</i>"), parent));
317 // Add stacking button
318 pv::widgets::DecoderMenu *const decoder_menu =
319 new pv::widgets::DecoderMenu(parent);
320 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
321 this, SLOT(on_stack_decoder(srd_decoder*)));
323 QPushButton *const stack_button =
324 new QPushButton(tr("Stack Decoder"), parent);
325 stack_button->setMenu(decoder_menu);
327 QHBoxLayout *stack_button_box = new QHBoxLayout;
328 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
329 form->addRow(stack_button_box);
332 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
334 QMenu *const menu = Trace::create_context_menu(parent);
336 menu->addSeparator();
338 QAction *const del = new QAction(tr("Delete"), this);
339 del->setShortcuts(QKeySequence::Delete);
340 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
341 menu->addAction(del);
346 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
347 QPainter &p, QColor text_color, int h, int left, int right,
348 double samples_per_pixel, double pixels_offset, int y,
349 size_t base_colour) const
351 const double start = a.start_sample() / samples_per_pixel -
353 const double end = a.end_sample() / samples_per_pixel -
356 const size_t colour = (base_colour + a.format()) % countof(Colours);
357 const QColor &fill = Colours[colour];
358 const QColor &outline = OutlineColours[colour];
360 if (start > right + DrawPadding || end < left - DrawPadding)
363 if (a.start_sample() == a.end_sample())
364 draw_instant(a, p, fill, outline, text_color, h,
367 draw_range(a, p, fill, outline, text_color, h,
371 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
372 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
374 const QString text = a.annotations().empty() ?
375 QString() : a.annotations().back();
376 const double w = min(p.boundingRect(QRectF(), 0, text).width(),
378 const QRectF rect(x - w / 2, y - h / 2, w, h);
382 p.drawRoundedRect(rect, h / 2, h / 2);
384 p.setPen(text_color);
385 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
388 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
389 QColor fill, QColor outline, QColor text_color, int h, double start,
390 double end, int y) const
392 const double top = y + .5 - h / 2;
393 const double bottom = y + .5 + h / 2;
394 const vector<QString> annotations = a.annotations();
399 // If the two ends are within 1 pixel, draw a vertical line
400 if (start + 1.0 > end)
402 p.drawLine(QPointF(start, top), QPointF(start, bottom));
406 const double cap_width = min((end - start) / 4, EndCapWidth);
409 QPointF(start, y + .5f),
410 QPointF(start + cap_width, top),
411 QPointF(end - cap_width, top),
412 QPointF(end, y + .5f),
413 QPointF(end - cap_width, bottom),
414 QPointF(start + cap_width, bottom)
417 p.drawConvexPolygon(pts, countof(pts));
419 if (annotations.empty())
422 QRectF rect(start + cap_width, y - h / 2,
423 end - start - cap_width * 2, h);
424 p.setPen(text_color);
426 // Try to find an annotation that will fit
427 QString best_annotation;
430 BOOST_FOREACH(const QString &a, annotations) {
431 const int w = p.boundingRect(QRectF(), 0, a).width();
432 if (w <= rect.width() && w > best_width)
433 best_annotation = a, best_width = w;
436 if (best_annotation.isEmpty())
437 best_annotation = annotations.back();
439 // If not ellide the last in the list
440 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
441 best_annotation, Qt::ElideRight, rect.width()));
444 void DecodeTrace::draw_error(QPainter &p, const QString &message,
447 const int y = get_y();
449 p.setPen(ErrorBgColour.darker());
450 p.setBrush(ErrorBgColour);
452 const QRectF bounding_rect =
453 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
454 const QRectF text_rect = p.boundingRect(bounding_rect,
455 Qt::AlignCenter, message);
456 const float r = text_rect.height() / 4;
458 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
461 p.setPen(get_text_colour());
462 p.drawText(text_rect, message);
465 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
466 int right, double samples_per_pixel, double pixels_offset)
468 using namespace pv::data;
469 using pv::data::decode::Decoder;
471 assert(_decoder_stack);
473 shared_ptr<Logic> data;
474 shared_ptr<LogicSignal> logic_signal;
476 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
478 // We get the logic data of the first probe in the list.
479 // This works because we are currently assuming all
480 // LogicSignals have the same data/snapshot
481 BOOST_FOREACH (const shared_ptr<Decoder> &dec, stack)
482 if (dec && !dec->probes().empty() &&
483 ((logic_signal = (*dec->probes().begin()).second)) &&
484 ((data = logic_signal->logic_data())))
487 if (!data || data->get_snapshots().empty())
490 const shared_ptr<LogicSnapshot> snapshot =
491 data->get_snapshots().front();
493 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
494 if (sample_count == 0)
497 const int64_t samples_decoded = _decoder_stack->samples_decoded();
498 if (sample_count == samples_decoded)
501 const int y = get_y();
502 const double start = max(samples_decoded /
503 samples_per_pixel - pixels_offset, left - 1.0);
504 const double end = min(sample_count / samples_per_pixel -
505 pixels_offset, right + 1.0);
506 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
508 p.setPen(QPen(Qt::NoPen));
509 p.setBrush(Qt::white);
510 p.drawRect(no_decode_rect);
512 p.setPen(NoDecodeColour);
513 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
514 p.drawRect(no_decode_rect);
517 void DecodeTrace::create_decoder_form(int index,
518 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
524 const srd_decoder *const decoder = dec->decoder();
527 pv::widgets::DecoderGroupBox *const group =
528 new pv::widgets::DecoderGroupBox(
529 QString::fromUtf8(decoder->name));
530 group->set_decoder_visible(dec->shown());
532 _delete_mapper.setMapping(group, index);
533 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
535 _show_hide_mapper.setMapping(group, index);
536 connect(group, SIGNAL(show_hide_decoder()),
537 &_show_hide_mapper, SLOT(map()));
539 QFormLayout *const decoder_form = new QFormLayout;
540 group->add_layout(decoder_form);
542 // Add the mandatory probes
543 for(probe = decoder->probes; probe; probe = probe->next) {
544 const struct srd_probe *const p =
545 (struct srd_probe *)probe->data;
546 QComboBox *const combo = create_probe_selector(parent, dec, p);
547 connect(combo, SIGNAL(currentIndexChanged(int)),
548 this, SLOT(on_probe_selected(int)));
549 decoder_form->addRow(tr("<b>%1</b> (%2) *")
550 .arg(QString::fromUtf8(p->name))
551 .arg(QString::fromUtf8(p->desc)), combo);
553 const ProbeSelector s = {combo, dec, p};
554 _probe_selectors.push_back(s);
557 // Add the optional probes
558 for(probe = decoder->opt_probes; probe; probe = probe->next) {
559 const struct srd_probe *const p =
560 (struct srd_probe *)probe->data;
561 QComboBox *const combo = create_probe_selector(parent, dec, p);
562 connect(combo, SIGNAL(currentIndexChanged(int)),
563 this, SLOT(on_probe_selected(int)));
564 decoder_form->addRow(tr("<b>%1</b> (%2)")
565 .arg(QString::fromUtf8(p->name))
566 .arg(QString::fromUtf8(p->desc)), combo);
568 const ProbeSelector s = {combo, dec, p};
569 _probe_selectors.push_back(s);
573 shared_ptr<prop::binding::DecoderOptions> binding(
574 new prop::binding::DecoderOptions(_decoder_stack, dec));
575 binding->add_properties_to_form(decoder_form, true);
577 _bindings.push_back(binding);
580 _decoder_forms.push_back(group);
583 QComboBox* DecodeTrace::create_probe_selector(
584 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
585 const srd_probe *const probe)
589 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
591 assert(_decoder_stack);
592 const map<const srd_probe*,
593 shared_ptr<LogicSignal> >::const_iterator probe_iter =
594 dec->probes().find(probe);
596 QComboBox *selector = new QComboBox(parent);
598 selector->addItem("-", qVariantFromValue((void*)NULL));
600 if (probe_iter == dec->probes().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_probe*, 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) {
637 probe_map[s._probe] =
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 list< shared_ptr<Decoder> >::const_iterator iter = stack.begin();
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();