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>
30 #include <QApplication>
32 #include <QFormLayout>
35 #include <QPushButton>
37 #include "decodetrace.h"
39 #include <pv/sigsession.h>
40 #include <pv/data/decoderstack.h>
41 #include <pv/data/decode/decoder.h>
42 #include <pv/data/logic.h>
43 #include <pv/data/logicsnapshot.h>
44 #include <pv/data/decode/annotation.h>
45 #include <pv/view/logicsignal.h>
46 #include <pv/view/view.h>
47 #include <pv/widgets/decodergroupbox.h>
48 #include <pv/widgets/decodermenu.h>
50 using boost::dynamic_pointer_cast;
51 using boost::shared_ptr;
61 const QColor DecodeTrace::DecodeColours[4] = {
62 QColor(0xEF, 0x29, 0x29), // Red
63 QColor(0xFC, 0xE9, 0x4F), // Yellow
64 QColor(0x8A, 0xE2, 0x34), // Green
65 QColor(0x72, 0x9F, 0xCF) // Blue
68 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
69 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
71 const int DecodeTrace::ArrowSize = 4;
72 const double DecodeTrace::EndCapWidth = 5;
73 const int DecodeTrace::DrawPadding = 100;
75 const QColor DecodeTrace::Colours[16] = {
76 QColor(0xEF, 0x29, 0x29),
77 QColor(0xF6, 0x6A, 0x32),
78 QColor(0xFC, 0xAE, 0x3E),
79 QColor(0xFB, 0xCA, 0x47),
80 QColor(0xFC, 0xE9, 0x4F),
81 QColor(0xCD, 0xF0, 0x40),
82 QColor(0x8A, 0xE2, 0x34),
83 QColor(0x4E, 0xDC, 0x44),
84 QColor(0x55, 0xD7, 0x95),
85 QColor(0x64, 0xD1, 0xD2),
86 QColor(0x72, 0x9F, 0xCF),
87 QColor(0xD4, 0x76, 0xC4),
88 QColor(0x9D, 0x79, 0xB9),
89 QColor(0xAD, 0x7F, 0xA8),
90 QColor(0xC2, 0x62, 0x9B),
91 QColor(0xD7, 0x47, 0x6F)
94 const QColor DecodeTrace::OutlineColours[16] = {
95 QColor(0x77, 0x14, 0x14),
96 QColor(0x7B, 0x35, 0x19),
97 QColor(0x7E, 0x57, 0x1F),
98 QColor(0x7D, 0x65, 0x23),
99 QColor(0x7E, 0x74, 0x27),
100 QColor(0x66, 0x78, 0x20),
101 QColor(0x45, 0x71, 0x1A),
102 QColor(0x27, 0x6E, 0x22),
103 QColor(0x2A, 0x6B, 0x4A),
104 QColor(0x32, 0x68, 0x69),
105 QColor(0x39, 0x4F, 0x67),
106 QColor(0x6A, 0x3B, 0x62),
107 QColor(0x4E, 0x3C, 0x5C),
108 QColor(0x56, 0x3F, 0x54),
109 QColor(0x61, 0x31, 0x4D),
110 QColor(0x6B, 0x23, 0x37)
113 DecodeTrace::DecodeTrace(pv::SigSession &session,
114 boost::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
115 Trace(QString::fromUtf8(
116 decoder_stack->stack().front()->decoder()->name)),
118 _decoder_stack(decoder_stack),
119 _delete_mapper(this),
120 _show_hide_mapper(this)
122 assert(_decoder_stack);
124 _colour = DecodeColours[index % countof(DecodeColours)];
126 connect(_decoder_stack.get(), SIGNAL(new_decode_data()),
127 this, SLOT(on_new_decode_data()));
128 connect(&_delete_mapper, SIGNAL(mapped(int)),
129 this, SLOT(on_delete_decoder(int)));
130 connect(&_show_hide_mapper, SIGNAL(mapped(int)),
131 this, SLOT(on_show_hide_decoder(int)));
134 bool DecodeTrace::enabled() const
139 const boost::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
141 return _decoder_stack;
144 void DecodeTrace::set_view(pv::view::View *view)
147 Trace::set_view(view);
150 void DecodeTrace::paint_back(QPainter &p, int left, int right)
152 Trace::paint_back(p, left, right);
153 paint_axis(p, get_y(), left, right);
156 void DecodeTrace::paint_mid(QPainter &p, int left, int right)
158 using namespace pv::data::decode;
160 const double scale = _view->scale();
163 double samplerate = _decoder_stack->samplerate();
165 _cur_row_headings.clear();
167 // Show sample rate as 1Hz when it is unknown
168 if (samplerate == 0.0)
171 const double pixels_offset = (_view->offset() -
172 _decoder_stack->get_start_time()) / scale;
173 const double samples_per_pixel = samplerate * scale;
175 const uint64_t start_sample = (uint64_t)max((left + pixels_offset) *
176 samples_per_pixel, 0.0);
177 const uint64_t end_sample = (uint64_t)max((right + pixels_offset) *
178 samples_per_pixel, 0.0);
180 QFontMetrics m(QApplication::font());
181 const int text_height = m.boundingRect(QRect(), 0, "Tg").height();
182 const int annotation_height = (text_height * 5) / 4;
183 const int row_height = (text_height * 6) / 4;
185 assert(_decoder_stack);
186 const QString err = _decoder_stack->error_message();
189 draw_unresolved_period(p, annotation_height, left, right,
190 samples_per_pixel, pixels_offset);
191 draw_error(p, err, left, right);
195 // Iterate through the rows
199 assert(_decoder_stack);
201 const vector<Row> rows(_decoder_stack->get_visible_rows());
202 for (size_t i = 0; i < rows.size(); i++)
204 const Row &row = rows[i];
206 size_t base_colour = 0x13579BDF;
207 boost::hash_combine(base_colour, this);
208 boost::hash_combine(base_colour, row.decoder());
209 boost::hash_combine(base_colour, row.row());
212 vector<Annotation> annotations;
213 _decoder_stack->get_annotation_subset(annotations, row,
214 start_sample, end_sample);
215 if (!annotations.empty()) {
216 BOOST_FOREACH(const Annotation &a, annotations)
217 draw_annotation(a, p, get_text_colour(),
218 annotation_height, left, right,
219 samples_per_pixel, pixels_offset, y,
223 _cur_row_headings.push_back(row.title());
228 draw_unresolved_period(p, annotation_height, left, right,
229 samples_per_pixel, pixels_offset);
232 void DecodeTrace::paint_fore(QPainter &p, int left, int right)
234 using namespace pv::data::decode;
238 QFontMetrics m(QApplication::font());
239 const int text_height = m.boundingRect(QRect(), 0, "Tg").height();
240 const int row_height = (text_height * 6) / 4;
242 for (size_t i = 0; i < _cur_row_headings.size(); i++)
244 const int y = i * row_height + get_y();
246 p.setPen(QPen(Qt::NoPen));
247 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
251 const QPointF points[] = {
252 QPointF(left, y - ArrowSize),
253 QPointF(left + ArrowSize, y),
254 QPointF(left, y + ArrowSize)
256 p.drawPolygon(points, countof(points));
259 const QRect r(left + ArrowSize * 2, y - row_height / 2,
260 right - left, row_height);
261 const QString h(_cur_row_headings[i]);
262 const int f = Qt::AlignLeft | Qt::AlignVCenter |
266 p.setPen(QApplication::palette().color(QPalette::Base));
267 for (int dx = -1; dx <= 1; dx++)
268 for (int dy = -1; dy <= 1; dy++)
269 if (dx != 0 && dy != 0)
270 p.drawText(r.translated(dx, dy), f, h);
273 p.setPen(QApplication::palette().color(QPalette::WindowText));
278 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
280 using pv::data::decode::Decoder;
284 assert(_decoder_stack);
286 // Add the standard options
287 Trace::populate_popup_form(parent, form);
289 // Add the decoder options
291 _probe_selectors.clear();
292 _decoder_forms.clear();
294 const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
298 QLabel *const l = new QLabel(
299 tr("<p><i>No decoders in the stack</i></p>"));
300 l->setAlignment(Qt::AlignCenter);
305 list< shared_ptr<Decoder> >::const_iterator iter =
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 Probes</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(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 p.setPen(text_color);
425 // Try to find an annotation that will fit
426 QString best_annotation;
429 BOOST_FOREACH(const QString &a, annotations) {
430 const int w = p.boundingRect(QRectF(), 0, a).width();
431 if (w <= rect.width() && w > best_width)
432 best_annotation = a, best_width = w;
435 if (best_annotation.isEmpty())
436 best_annotation = annotations.back();
438 // If not ellide the last in the list
439 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
440 best_annotation, Qt::ElideRight, rect.width()));
443 void DecodeTrace::draw_error(QPainter &p, const QString &message,
446 const int y = get_y();
448 p.setPen(ErrorBgColour.darker());
449 p.setBrush(ErrorBgColour);
451 const QRectF bounding_rect =
452 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
453 const QRectF text_rect = p.boundingRect(bounding_rect,
454 Qt::AlignCenter, message);
455 const float r = text_rect.height() / 4;
457 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
460 p.setPen(get_text_colour());
461 p.drawText(text_rect, message);
464 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
465 int right, double samples_per_pixel, double pixels_offset)
467 using namespace pv::data;
468 using pv::data::decode::Decoder;
470 assert(_decoder_stack);
472 shared_ptr<Logic> data;
473 shared_ptr<LogicSignal> logic_signal;
475 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
477 // We get the logic data of the first probe in the list.
478 // This works because we are currently assuming all
479 // LogicSignals have the same data/snapshot
480 BOOST_FOREACH (const shared_ptr<Decoder> &dec, stack)
481 if (dec && !dec->probes().empty() &&
482 ((logic_signal = (*dec->probes().begin()).second)) &&
483 ((data = logic_signal->logic_data())))
486 if (!data || data->get_snapshots().empty())
489 const shared_ptr<LogicSnapshot> snapshot =
490 data->get_snapshots().front();
492 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
493 if (sample_count == 0)
496 const int64_t samples_decoded = _decoder_stack->samples_decoded();
497 if (sample_count == samples_decoded)
500 const int y = get_y();
501 const double start = max(samples_decoded /
502 samples_per_pixel - pixels_offset, left - 1.0);
503 const double end = min(sample_count / samples_per_pixel -
504 pixels_offset, right + 1.0);
505 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
507 p.setPen(QPen(Qt::NoPen));
508 p.setBrush(Qt::white);
509 p.drawRect(no_decode_rect);
511 p.setPen(NoDecodeColour);
512 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
513 p.drawRect(no_decode_rect);
516 void DecodeTrace::create_decoder_form(int index,
517 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
523 const srd_decoder *const decoder = dec->decoder();
526 pv::widgets::DecoderGroupBox *const group =
527 new pv::widgets::DecoderGroupBox(
528 QString::fromUtf8(decoder->name));
529 group->set_decoder_visible(dec->shown());
531 _delete_mapper.setMapping(group, index);
532 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
534 _show_hide_mapper.setMapping(group, index);
535 connect(group, SIGNAL(show_hide_decoder()),
536 &_show_hide_mapper, SLOT(map()));
538 QFormLayout *const decoder_form = new QFormLayout;
539 group->add_layout(decoder_form);
541 // Add the mandatory probes
542 for(probe = decoder->probes; probe; probe = probe->next) {
543 const struct srd_probe *const p =
544 (struct srd_probe *)probe->data;
545 QComboBox *const combo = create_probe_selector(parent, dec, p);
546 connect(combo, SIGNAL(currentIndexChanged(int)),
547 this, SLOT(on_probe_selected(int)));
548 decoder_form->addRow(tr("<b>%1</b> (%2) *")
549 .arg(QString::fromUtf8(p->name))
550 .arg(QString::fromUtf8(p->desc)), combo);
552 const ProbeSelector s = {combo, dec, p};
553 _probe_selectors.push_back(s);
556 // Add the optional probes
557 for(probe = decoder->opt_probes; probe; probe = probe->next) {
558 const struct srd_probe *const p =
559 (struct srd_probe *)probe->data;
560 QComboBox *const combo = create_probe_selector(parent, dec, p);
561 connect(combo, SIGNAL(currentIndexChanged(int)),
562 this, SLOT(on_probe_selected(int)));
563 decoder_form->addRow(tr("<b>%1</b> (%2)")
564 .arg(QString::fromUtf8(p->name))
565 .arg(QString::fromUtf8(p->desc)), combo);
567 const ProbeSelector s = {combo, dec, p};
568 _probe_selectors.push_back(s);
572 shared_ptr<prop::binding::DecoderOptions> binding(
573 new prop::binding::DecoderOptions(_decoder_stack, dec));
574 binding->add_properties_to_form(decoder_form, true);
576 _bindings.push_back(binding);
579 _decoder_forms.push_back(group);
582 QComboBox* DecodeTrace::create_probe_selector(
583 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
584 const srd_probe *const probe)
588 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
590 assert(_decoder_stack);
591 const map<const srd_probe*,
592 shared_ptr<LogicSignal> >::const_iterator probe_iter =
593 dec->probes().find(probe);
595 QComboBox *selector = new QComboBox(parent);
597 selector->addItem("-", qVariantFromValue((void*)NULL));
599 if (probe_iter == dec->probes().end())
600 selector->setCurrentIndex(0);
602 for(size_t i = 0; i < sigs.size(); i++) {
603 const shared_ptr<view::Signal> s(sigs[i]);
606 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
608 selector->addItem(s->get_name(),
609 qVariantFromValue((void*)s.get()));
610 if ((*probe_iter).second == s)
611 selector->setCurrentIndex(i + 1);
618 void DecodeTrace::commit_decoder_probes(shared_ptr<data::decode::Decoder> &dec)
622 map<const srd_probe*, shared_ptr<LogicSignal> > probe_map;
623 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
625 BOOST_FOREACH(const ProbeSelector &s, _probe_selectors)
627 if(s._decoder != dec)
630 const LogicSignal *const selection =
631 (LogicSignal*)s._combo->itemData(
632 s._combo->currentIndex()).value<void*>();
634 BOOST_FOREACH(shared_ptr<Signal> sig, sigs)
635 if(sig.get() == selection) {
636 probe_map[s._probe] =
637 dynamic_pointer_cast<LogicSignal>(sig);
642 dec->set_probes(probe_map);
645 void DecodeTrace::commit_probes()
647 assert(_decoder_stack);
648 BOOST_FOREACH(shared_ptr<data::decode::Decoder> dec,
649 _decoder_stack->stack())
650 commit_decoder_probes(dec);
652 _decoder_stack->begin_decode();
655 void DecodeTrace::on_new_decode_data()
658 _view->update_viewport();
661 void DecodeTrace::delete_pressed()
666 void DecodeTrace::on_delete()
668 _session.remove_decode_signal(this);
671 void DecodeTrace::on_probe_selected(int)
676 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
679 assert(_decoder_stack);
680 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
681 new data::decode::Decoder(decoder)));
682 _decoder_stack->begin_decode();
687 void DecodeTrace::on_delete_decoder(int index)
689 _decoder_stack->remove(index);
694 _decoder_stack->begin_decode();
697 void DecodeTrace::on_show_hide_decoder(int index)
699 using pv::data::decode::Decoder;
701 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
703 // Find the decoder in the stack
704 list< shared_ptr<Decoder> >::const_iterator iter = stack.begin();
705 for(int i = 0; i < index; i++, iter++)
706 assert(iter != stack.end());
708 shared_ptr<Decoder> dec = *iter;
711 const bool show = !dec->shown();
714 assert(index < (int)_decoder_forms.size());
715 _decoder_forms[index]->set_decoder_visible(show);
717 _view->update_viewport();