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/functional/hash.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 std::dynamic_pointer_cast;
55 using std::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 std::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 std::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 for (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 auto iter = stack.cbegin();
306 for (int i = 0; i < (int)stack.size(); i++, iter++) {
307 shared_ptr<Decoder> dec(*iter);
308 create_decoder_form(i, dec, parent, form);
311 form->addRow(new QLabel(
312 tr("<i>* Required channels</i>"), parent));
315 // Add stacking button
316 pv::widgets::DecoderMenu *const decoder_menu =
317 new pv::widgets::DecoderMenu(parent);
318 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
319 this, SLOT(on_stack_decoder(srd_decoder*)));
321 QPushButton *const stack_button =
322 new QPushButton(tr("Stack Decoder"), parent);
323 stack_button->setMenu(decoder_menu);
325 QHBoxLayout *stack_button_box = new QHBoxLayout;
326 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
327 form->addRow(stack_button_box);
330 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
332 QMenu *const menu = Trace::create_context_menu(parent);
334 menu->addSeparator();
336 QAction *const del = new QAction(tr("Delete"), this);
337 del->setShortcuts(QKeySequence::Delete);
338 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
339 menu->addAction(del);
344 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
345 QPainter &p, QColor text_color, int h, int left, int right,
346 double samples_per_pixel, double pixels_offset, int y,
347 size_t base_colour) const
349 const double start = a.start_sample() / samples_per_pixel -
351 const double end = a.end_sample() / samples_per_pixel -
354 const size_t colour = (base_colour + a.format()) % countof(Colours);
355 const QColor &fill = Colours[colour];
356 const QColor &outline = OutlineColours[colour];
358 if (start > right + DrawPadding || end < left - DrawPadding)
361 if (a.start_sample() == a.end_sample())
362 draw_instant(a, p, fill, outline, text_color, h,
365 draw_range(a, p, fill, outline, text_color, h,
369 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
370 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
372 const QString text = a.annotations().empty() ?
373 QString() : a.annotations().back();
374 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
376 const QRectF rect(x - w / 2, y - h / 2, w, h);
380 p.drawRoundedRect(rect, h / 2, h / 2);
382 p.setPen(text_color);
383 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
386 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
387 QColor fill, QColor outline, QColor text_color, int h, double start,
388 double end, int y) const
390 const double top = y + .5 - h / 2;
391 const double bottom = y + .5 + h / 2;
392 const vector<QString> annotations = a.annotations();
397 // If the two ends are within 1 pixel, draw a vertical line
398 if (start + 1.0 > end)
400 p.drawLine(QPointF(start, top), QPointF(start, bottom));
404 const double cap_width = min((end - start) / 4, EndCapWidth);
407 QPointF(start, y + .5f),
408 QPointF(start + cap_width, top),
409 QPointF(end - cap_width, top),
410 QPointF(end, y + .5f),
411 QPointF(end - cap_width, bottom),
412 QPointF(start + cap_width, bottom)
415 p.drawConvexPolygon(pts, countof(pts));
417 if (annotations.empty())
420 QRectF rect(start + cap_width, y - h / 2,
421 end - start - cap_width * 2, h);
422 if (rect.width() <= 4)
425 p.setPen(text_color);
427 // Try to find an annotation that will fit
428 QString best_annotation;
431 for (const QString &a : annotations) {
432 const int w = p.boundingRect(QRectF(), 0, a).width();
433 if (w <= rect.width() && w > best_width)
434 best_annotation = a, best_width = w;
437 if (best_annotation.isEmpty())
438 best_annotation = annotations.back();
440 // If not ellide the last in the list
441 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
442 best_annotation, Qt::ElideRight, rect.width()));
445 void DecodeTrace::draw_error(QPainter &p, const QString &message,
448 const int y = get_y();
450 p.setPen(ErrorBgColour.darker());
451 p.setBrush(ErrorBgColour);
453 const QRectF bounding_rect =
454 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
455 const QRectF text_rect = p.boundingRect(bounding_rect,
456 Qt::AlignCenter, message);
457 const float r = text_rect.height() / 4;
459 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
462 p.setPen(get_text_colour());
463 p.drawText(text_rect, message);
466 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
467 int right, double samples_per_pixel, double pixels_offset)
469 using namespace pv::data;
470 using pv::data::decode::Decoder;
472 assert(_decoder_stack);
474 shared_ptr<Logic> data;
475 shared_ptr<LogicSignal> logic_signal;
477 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
479 // We get the logic data of the first probe in the list.
480 // This works because we are currently assuming all
481 // LogicSignals have the same data/snapshot
482 for (const shared_ptr<Decoder> &dec : stack)
483 if (dec && !dec->channels().empty() &&
484 ((logic_signal = (*dec->channels().begin()).second)) &&
485 ((data = logic_signal->logic_data())))
488 if (!data || data->get_snapshots().empty())
491 const shared_ptr<LogicSnapshot> snapshot =
492 data->get_snapshots().front();
494 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
495 if (sample_count == 0)
498 const int64_t samples_decoded = _decoder_stack->samples_decoded();
499 if (sample_count == samples_decoded)
502 const int y = get_y();
503 const double start = max(samples_decoded /
504 samples_per_pixel - pixels_offset, left - 1.0);
505 const double end = min(sample_count / samples_per_pixel -
506 pixels_offset, right + 1.0);
507 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
509 p.setPen(QPen(Qt::NoPen));
510 p.setBrush(Qt::white);
511 p.drawRect(no_decode_rect);
513 p.setPen(NoDecodeColour);
514 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
515 p.drawRect(no_decode_rect);
518 void DecodeTrace::create_decoder_form(int index,
519 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
525 const srd_decoder *const decoder = dec->decoder();
528 pv::widgets::DecoderGroupBox *const group =
529 new pv::widgets::DecoderGroupBox(
530 QString::fromUtf8(decoder->name));
531 group->set_decoder_visible(dec->shown());
533 _delete_mapper.setMapping(group, index);
534 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
536 _show_hide_mapper.setMapping(group, index);
537 connect(group, SIGNAL(show_hide_decoder()),
538 &_show_hide_mapper, SLOT(map()));
540 QFormLayout *const decoder_form = new QFormLayout;
541 group->add_layout(decoder_form);
543 // Add the mandatory channels
544 for(l = decoder->channels; l; l = l->next) {
545 const struct srd_channel *const pdch =
546 (struct srd_channel *)l->data;
547 QComboBox *const combo = create_probe_selector(parent, dec, pdch);
548 connect(combo, SIGNAL(currentIndexChanged(int)),
549 this, SLOT(on_probe_selected(int)));
550 decoder_form->addRow(tr("<b>%1</b> (%2) *")
551 .arg(QString::fromUtf8(pdch->name))
552 .arg(QString::fromUtf8(pdch->desc)), combo);
554 const ProbeSelector s = {combo, dec, pdch};
555 _probe_selectors.push_back(s);
558 // Add the optional channels
559 for(l = decoder->opt_channels; l; l = l->next) {
560 const struct srd_channel *const pdch =
561 (struct srd_channel *)l->data;
562 QComboBox *const combo = create_probe_selector(parent, dec, pdch);
563 connect(combo, SIGNAL(currentIndexChanged(int)),
564 this, SLOT(on_probe_selected(int)));
565 decoder_form->addRow(tr("<b>%1</b> (%2)")
566 .arg(QString::fromUtf8(pdch->name))
567 .arg(QString::fromUtf8(pdch->desc)), combo);
569 const ProbeSelector s = {combo, dec, pdch};
570 _probe_selectors.push_back(s);
574 shared_ptr<prop::binding::DecoderOptions> binding(
575 new prop::binding::DecoderOptions(_decoder_stack, dec));
576 binding->add_properties_to_form(decoder_form, true);
578 _bindings.push_back(binding);
581 _decoder_forms.push_back(group);
584 QComboBox* DecodeTrace::create_probe_selector(
585 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
586 const srd_channel *const pdch)
590 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
592 assert(_decoder_stack);
593 const auto probe_iter = dec->channels().find(pdch);
595 QComboBox *selector = new QComboBox(parent);
597 selector->addItem("-", qVariantFromValue((void*)NULL));
599 if (probe_iter == dec->channels().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_channel*, shared_ptr<LogicSignal> > probe_map;
623 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
625 for (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 for (shared_ptr<Signal> sig : sigs)
635 if(sig.get() == selection) {
637 dynamic_pointer_cast<LogicSignal>(sig);
642 dec->set_probes(probe_map);
645 void DecodeTrace::commit_probes()
647 assert(_decoder_stack);
648 for (shared_ptr<data::decode::Decoder> dec : _decoder_stack->stack())
649 commit_decoder_probes(dec);
651 _decoder_stack->begin_decode();
654 void DecodeTrace::on_new_decode_data()
657 _view->update_viewport();
660 void DecodeTrace::delete_pressed()
665 void DecodeTrace::on_delete()
667 _session.remove_decode_signal(this);
670 void DecodeTrace::on_probe_selected(int)
675 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
678 assert(_decoder_stack);
679 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
680 new data::decode::Decoder(decoder)));
681 _decoder_stack->begin_decode();
686 void DecodeTrace::on_delete_decoder(int index)
688 _decoder_stack->remove(index);
693 _decoder_stack->begin_decode();
696 void DecodeTrace::on_show_hide_decoder(int index)
698 using pv::data::decode::Decoder;
700 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
702 // Find the decoder in the stack
703 auto iter = stack.cbegin();
704 for(int i = 0; i < index; i++, iter++)
705 assert(iter != stack.end());
707 shared_ptr<Decoder> dec = *iter;
710 const bool show = !dec->shown();
713 assert(index < (int)_decoder_forms.size());
714 _decoder_forms[index]->set_decoder_visible(show);
716 _view->update_viewport();