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 channels</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((double)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 if (rect.width() <= 4)
427 p.setPen(text_color);
429 // Try to find an annotation that will fit
430 QString best_annotation;
433 BOOST_FOREACH(const QString &a, annotations) {
434 const int w = p.boundingRect(QRectF(), 0, a).width();
435 if (w <= rect.width() && w > best_width)
436 best_annotation = a, best_width = w;
439 if (best_annotation.isEmpty())
440 best_annotation = annotations.back();
442 // If not ellide the last in the list
443 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
444 best_annotation, Qt::ElideRight, rect.width()));
447 void DecodeTrace::draw_error(QPainter &p, const QString &message,
450 const int y = get_y();
452 p.setPen(ErrorBgColour.darker());
453 p.setBrush(ErrorBgColour);
455 const QRectF bounding_rect =
456 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
457 const QRectF text_rect = p.boundingRect(bounding_rect,
458 Qt::AlignCenter, message);
459 const float r = text_rect.height() / 4;
461 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
464 p.setPen(get_text_colour());
465 p.drawText(text_rect, message);
468 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
469 int right, double samples_per_pixel, double pixels_offset)
471 using namespace pv::data;
472 using pv::data::decode::Decoder;
474 assert(_decoder_stack);
476 shared_ptr<Logic> data;
477 shared_ptr<LogicSignal> logic_signal;
479 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
481 // We get the logic data of the first probe in the list.
482 // This works because we are currently assuming all
483 // LogicSignals have the same data/snapshot
484 BOOST_FOREACH (const shared_ptr<Decoder> &dec, stack)
485 if (dec && !dec->channels().empty() &&
486 ((logic_signal = (*dec->channels().begin()).second)) &&
487 ((data = logic_signal->logic_data())))
490 if (!data || data->get_snapshots().empty())
493 const shared_ptr<LogicSnapshot> snapshot =
494 data->get_snapshots().front();
496 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
497 if (sample_count == 0)
500 const int64_t samples_decoded = _decoder_stack->samples_decoded();
501 if (sample_count == samples_decoded)
504 const int y = get_y();
505 const double start = max(samples_decoded /
506 samples_per_pixel - pixels_offset, left - 1.0);
507 const double end = min(sample_count / samples_per_pixel -
508 pixels_offset, right + 1.0);
509 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
511 p.setPen(QPen(Qt::NoPen));
512 p.setBrush(Qt::white);
513 p.drawRect(no_decode_rect);
515 p.setPen(NoDecodeColour);
516 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
517 p.drawRect(no_decode_rect);
520 void DecodeTrace::create_decoder_form(int index,
521 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
527 const srd_decoder *const decoder = dec->decoder();
530 pv::widgets::DecoderGroupBox *const group =
531 new pv::widgets::DecoderGroupBox(
532 QString::fromUtf8(decoder->name));
533 group->set_decoder_visible(dec->shown());
535 _delete_mapper.setMapping(group, index);
536 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
538 _show_hide_mapper.setMapping(group, index);
539 connect(group, SIGNAL(show_hide_decoder()),
540 &_show_hide_mapper, SLOT(map()));
542 QFormLayout *const decoder_form = new QFormLayout;
543 group->add_layout(decoder_form);
545 // Add the mandatory channels
546 for(l = decoder->channels; l; l = l->next) {
547 const struct srd_channel *const pdch =
548 (struct srd_channel *)l->data;
549 QComboBox *const combo = create_probe_selector(parent, dec, pdch);
550 connect(combo, SIGNAL(currentIndexChanged(int)),
551 this, SLOT(on_probe_selected(int)));
552 decoder_form->addRow(tr("<b>%1</b> (%2) *")
553 .arg(QString::fromUtf8(pdch->name))
554 .arg(QString::fromUtf8(pdch->desc)), combo);
556 const ProbeSelector s = {combo, dec, pdch};
557 _probe_selectors.push_back(s);
560 // Add the optional channels
561 for(l = decoder->opt_channels; l; l = l->next) {
562 const struct srd_channel *const pdch =
563 (struct srd_channel *)l->data;
564 QComboBox *const combo = create_probe_selector(parent, dec, pdch);
565 connect(combo, SIGNAL(currentIndexChanged(int)),
566 this, SLOT(on_probe_selected(int)));
567 decoder_form->addRow(tr("<b>%1</b> (%2)")
568 .arg(QString::fromUtf8(pdch->name))
569 .arg(QString::fromUtf8(pdch->desc)), combo);
571 const ProbeSelector s = {combo, dec, pdch};
572 _probe_selectors.push_back(s);
576 shared_ptr<prop::binding::DecoderOptions> binding(
577 new prop::binding::DecoderOptions(_decoder_stack, dec));
578 binding->add_properties_to_form(decoder_form, true);
580 _bindings.push_back(binding);
583 _decoder_forms.push_back(group);
586 QComboBox* DecodeTrace::create_probe_selector(
587 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
588 const srd_channel *const pdch)
592 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
594 assert(_decoder_stack);
595 const map<const srd_channel*,
596 shared_ptr<LogicSignal> >::const_iterator probe_iter =
597 dec->channels().find(pdch);
599 QComboBox *selector = new QComboBox(parent);
601 selector->addItem("-", qVariantFromValue((void*)NULL));
603 if (probe_iter == dec->channels().end())
604 selector->setCurrentIndex(0);
606 for(size_t i = 0; i < sigs.size(); i++) {
607 const shared_ptr<view::Signal> s(sigs[i]);
610 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
612 selector->addItem(s->get_name(),
613 qVariantFromValue((void*)s.get()));
614 if ((*probe_iter).second == s)
615 selector->setCurrentIndex(i + 1);
622 void DecodeTrace::commit_decoder_probes(shared_ptr<data::decode::Decoder> &dec)
626 map<const srd_channel*, shared_ptr<LogicSignal> > probe_map;
627 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
629 BOOST_FOREACH(const ProbeSelector &s, _probe_selectors)
631 if(s._decoder != dec)
634 const LogicSignal *const selection =
635 (LogicSignal*)s._combo->itemData(
636 s._combo->currentIndex()).value<void*>();
638 BOOST_FOREACH(shared_ptr<Signal> sig, sigs)
639 if(sig.get() == selection) {
641 dynamic_pointer_cast<LogicSignal>(sig);
646 dec->set_probes(probe_map);
649 void DecodeTrace::commit_probes()
651 assert(_decoder_stack);
652 BOOST_FOREACH(shared_ptr<data::decode::Decoder> dec,
653 _decoder_stack->stack())
654 commit_decoder_probes(dec);
656 _decoder_stack->begin_decode();
659 void DecodeTrace::on_new_decode_data()
662 _view->update_viewport();
665 void DecodeTrace::delete_pressed()
670 void DecodeTrace::on_delete()
672 _session.remove_decode_signal(this);
675 void DecodeTrace::on_probe_selected(int)
680 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
683 assert(_decoder_stack);
684 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
685 new data::decode::Decoder(decoder)));
686 _decoder_stack->begin_decode();
691 void DecodeTrace::on_delete_decoder(int index)
693 _decoder_stack->remove(index);
698 _decoder_stack->begin_decode();
701 void DecodeTrace::on_show_hide_decoder(int index)
703 using pv::data::decode::Decoder;
705 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
707 // Find the decoder in the stack
708 list< shared_ptr<Decoder> >::const_iterator iter = stack.begin();
709 for(int i = 0; i < index; i++, iter++)
710 assert(iter != stack.end());
712 shared_ptr<Decoder> dec = *iter;
715 const bool show = !dec->shown();
718 assert(index < (int)_decoder_forms.size());
719 _decoder_forms[index]->set_decoder_visible(show);
721 _view->update_viewport();