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, see <http://www.gnu.org/licenses/>.
21 #include <libsigrokdecode/libsigrokdecode.h>
30 #include <boost/functional/hash.hpp>
33 #include <QApplication>
35 #include <QFormLayout>
38 #include <QPushButton>
41 #include "decodetrace.hpp"
43 #include "viewport.hpp"
45 #include <pv/globalsettings.hpp>
46 #include <pv/session.hpp>
47 #include <pv/strnatcmp.hpp>
48 #include <pv/data/decodesignal.hpp>
49 #include <pv/data/decode/annotation.hpp>
50 #include <pv/data/decode/decoder.hpp>
51 #include <pv/data/decoderstack.hpp>
52 #include <pv/data/logic.hpp>
53 #include <pv/data/logicsegment.hpp>
54 #include <pv/widgets/decodergroupbox.hpp>
55 #include <pv/widgets/decodermenu.hpp>
64 using std::out_of_range;
66 using std::shared_ptr;
67 using std::make_shared;
69 using std::unordered_set;
76 const QColor DecodeTrace::DecodeColours[4] = {
77 QColor(0xEF, 0x29, 0x29), // Red
78 QColor(0xFC, 0xE9, 0x4F), // Yellow
79 QColor(0x8A, 0xE2, 0x34), // Green
80 QColor(0x72, 0x9F, 0xCF) // Blue
83 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
84 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
86 const int DecodeTrace::ArrowSize = 4;
87 const double DecodeTrace::EndCapWidth = 5;
88 const int DecodeTrace::RowTitleMargin = 10;
89 const int DecodeTrace::DrawPadding = 100;
91 const QColor DecodeTrace::Colours[16] = {
92 QColor(0xEF, 0x29, 0x29),
93 QColor(0xF6, 0x6A, 0x32),
94 QColor(0xFC, 0xAE, 0x3E),
95 QColor(0xFB, 0xCA, 0x47),
96 QColor(0xFC, 0xE9, 0x4F),
97 QColor(0xCD, 0xF0, 0x40),
98 QColor(0x8A, 0xE2, 0x34),
99 QColor(0x4E, 0xDC, 0x44),
100 QColor(0x55, 0xD7, 0x95),
101 QColor(0x64, 0xD1, 0xD2),
102 QColor(0x72, 0x9F, 0xCF),
103 QColor(0xD4, 0x76, 0xC4),
104 QColor(0x9D, 0x79, 0xB9),
105 QColor(0xAD, 0x7F, 0xA8),
106 QColor(0xC2, 0x62, 0x9B),
107 QColor(0xD7, 0x47, 0x6F)
110 const QColor DecodeTrace::OutlineColours[16] = {
111 QColor(0x77, 0x14, 0x14),
112 QColor(0x7B, 0x35, 0x19),
113 QColor(0x7E, 0x57, 0x1F),
114 QColor(0x7D, 0x65, 0x23),
115 QColor(0x7E, 0x74, 0x27),
116 QColor(0x66, 0x78, 0x20),
117 QColor(0x45, 0x71, 0x1A),
118 QColor(0x27, 0x6E, 0x22),
119 QColor(0x2A, 0x6B, 0x4A),
120 QColor(0x32, 0x68, 0x69),
121 QColor(0x39, 0x4F, 0x67),
122 QColor(0x6A, 0x3B, 0x62),
123 QColor(0x4E, 0x3C, 0x5C),
124 QColor(0x56, 0x3F, 0x54),
125 QColor(0x61, 0x31, 0x4D),
126 QColor(0x6B, 0x23, 0x37)
129 DecodeTrace::DecodeTrace(pv::Session &session,
130 shared_ptr<data::SignalBase> signalbase, int index) :
134 max_visible_rows_(0),
135 delete_mapper_(this),
136 show_hide_mapper_(this)
138 decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
140 // Determine shortest string we want to see displayed in full
141 QFontMetrics m(QApplication::font());
142 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
144 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
146 connect(decode_signal_.get(), SIGNAL(new_annotations()),
147 this, SLOT(on_new_annotations()));
148 connect(&delete_mapper_, SIGNAL(mapped(int)),
149 this, SLOT(on_delete_decoder(int)));
150 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
151 this, SLOT(on_show_hide_decoder(int)));
154 bool DecodeTrace::enabled() const
159 shared_ptr<data::SignalBase> DecodeTrace::base() const
164 pair<int, int> DecodeTrace::v_extents() const
166 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
168 // Make an empty decode trace appear symmetrical
169 const int row_count = max(1, max_visible_rows_);
171 return make_pair(-row_height, row_height * row_count);
174 void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
176 Trace::paint_back(p, pp);
177 paint_axis(p, pp, get_visual_y());
180 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
182 using namespace pv::data::decode;
184 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
186 const int text_height = ViewItemPaintParams::text_height();
187 row_height_ = (text_height * 6) / 4;
188 const int annotation_height = (text_height * 5) / 4;
190 assert(decoder_stack);
191 const QString err = decoder_stack->error_message();
192 if (!err.isEmpty()) {
193 draw_unresolved_period(
194 p, annotation_height, pp.left(), pp.right());
195 draw_error(p, err, pp);
199 // Set default pen to allow for text width calculation
202 // Iterate through the rows
203 int y = get_visual_y();
204 pair<uint64_t, uint64_t> sample_range = get_sample_range(
205 pp.left(), pp.right());
207 const vector<Row> rows(decoder_stack->get_visible_rows());
209 visible_rows_.clear();
210 for (const Row& row : rows) {
211 // Cache the row title widths
214 row_title_width = row_title_widths_.at(row);
215 } catch (out_of_range) {
216 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
218 row_title_widths_[row] = w;
222 // Determine the row's color
223 size_t base_colour = 0x13579BDF;
224 boost::hash_combine(base_colour, this);
225 boost::hash_combine(base_colour, row.decoder());
226 boost::hash_combine(base_colour, row.row());
229 vector<Annotation> annotations;
230 decoder_stack->get_annotation_subset(annotations, row,
231 sample_range.first, sample_range.second);
232 if (!annotations.empty()) {
233 draw_annotations(annotations, p, annotation_height, pp, y,
234 base_colour, row_title_width);
238 visible_rows_.push_back(row);
243 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
245 if ((int)visible_rows_.size() > max_visible_rows_)
246 owner_->extents_changed(false, true);
248 // Update the maximum row count if needed
249 max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
252 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
254 using namespace pv::data::decode;
258 for (size_t i = 0; i < visible_rows_.size(); i++) {
259 const int y = i * row_height_ + get_visual_y();
261 p.setPen(QPen(Qt::NoPen));
262 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
265 const QPointF points[] = {
266 QPointF(pp.left(), y - ArrowSize),
267 QPointF(pp.left() + ArrowSize, y),
268 QPointF(pp.left(), y + ArrowSize)
270 p.drawPolygon(points, countof(points));
273 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
274 pp.right() - pp.left(), row_height_);
275 const QString h(visible_rows_[i].title());
276 const int f = Qt::AlignLeft | Qt::AlignVCenter |
280 p.setPen(QApplication::palette().color(QPalette::Base));
281 for (int dx = -1; dx <= 1; dx++)
282 for (int dy = -1; dy <= 1; dy++)
283 if (dx != 0 && dy != 0)
284 p.drawText(r.translated(dx, dy), f, h);
287 p.setPen(QApplication::palette().color(QPalette::WindowText));
292 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
294 using pv::data::decode::Decoder;
296 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
300 assert(decoder_stack);
302 // Add the standard options
303 Trace::populate_popup_form(parent, form);
305 // Add the decoder options
307 channel_selectors_.clear();
308 decoder_forms_.clear();
310 const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
313 QLabel *const l = new QLabel(
314 tr("<p><i>No decoders in the stack</i></p>"));
315 l->setAlignment(Qt::AlignCenter);
318 auto iter = stack.cbegin();
319 for (int i = 0; i < (int)stack.size(); i++, iter++) {
320 shared_ptr<Decoder> dec(*iter);
321 create_decoder_form(i, dec, parent, form);
324 form->addRow(new QLabel(
325 tr("<i>* Required channels</i>"), parent));
328 // Add stacking button
329 pv::widgets::DecoderMenu *const decoder_menu =
330 new pv::widgets::DecoderMenu(parent);
331 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
332 this, SLOT(on_stack_decoder(srd_decoder*)));
334 QPushButton *const stack_button =
335 new QPushButton(tr("Stack Decoder"), parent);
336 stack_button->setMenu(decoder_menu);
337 stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
339 QHBoxLayout *stack_button_box = new QHBoxLayout;
340 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
341 form->addRow(stack_button_box);
344 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
346 QMenu *const menu = Trace::create_context_menu(parent);
348 menu->addSeparator();
350 QAction *const del = new QAction(tr("Delete"), this);
351 del->setShortcuts(QKeySequence::Delete);
352 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
353 menu->addAction(del);
358 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
359 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
360 size_t base_colour, int row_title_width)
362 using namespace pv::data::decode;
364 vector<Annotation> a_block;
367 double samples_per_pixel, pixels_offset;
368 tie(pixels_offset, samples_per_pixel) =
369 get_pixels_offset_samples_per_pixel();
371 // Sort the annotations by start sample so that decoders
372 // can't confuse us by creating annotations out of order
373 stable_sort(annotations.begin(), annotations.end(),
374 [](const Annotation &a, const Annotation &b) {
375 return a.start_sample() < b.start_sample(); });
377 // Gather all annotations that form a visual "block" and draw them as such
378 for (const Annotation &a : annotations) {
380 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
381 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
382 const int a_width = a_end - a_start;
384 const int delta = a_end - p_end;
386 bool a_is_separate = false;
388 // Annotation wider than the threshold for a useful label width?
389 if (a_width >= min_useful_label_width_) {
390 for (const QString &ann_text : a.annotations()) {
391 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
392 // Annotation wide enough to fit a label? Don't put it in a block then
394 a_is_separate = true;
400 // Were the previous and this annotation more than a pixel apart?
401 if ((abs(delta) > 1) || a_is_separate) {
402 // Block was broken, draw annotations that form the current block
403 if (a_block.size() == 1) {
404 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
408 draw_annotation_block(a_block, p, h, y, base_colour);
414 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
415 // Next annotation must start a new block. delta will be > 1
416 // because we set p_end to INT_MIN but that's okay since
417 // a_block will be empty, so nothing will be drawn
420 a_block.push_back(a);
425 if (a_block.size() == 1)
426 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
429 draw_annotation_block(a_block, p, h, y, base_colour);
432 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
433 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
434 size_t base_colour, int row_title_width) const
436 double samples_per_pixel, pixels_offset;
437 tie(pixels_offset, samples_per_pixel) =
438 get_pixels_offset_samples_per_pixel();
440 const double start = a.start_sample() / samples_per_pixel -
442 const double end = a.end_sample() / samples_per_pixel - pixels_offset;
444 const size_t colour = (base_colour + a.format()) % countof(Colours);
445 p.setPen(OutlineColours[colour]);
446 p.setBrush(Colours[colour]);
448 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
451 if (a.start_sample() == a.end_sample())
452 draw_instant(a, p, h, start, y);
454 draw_range(a, p, h, start, end, y, pp, row_title_width);
457 void DecodeTrace::draw_annotation_block(
458 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
459 int y, size_t base_colour) const
461 using namespace pv::data::decode;
463 if (annotations.empty())
466 double samples_per_pixel, pixels_offset;
467 tie(pixels_offset, samples_per_pixel) =
468 get_pixels_offset_samples_per_pixel();
470 const double start = annotations.front().start_sample() /
471 samples_per_pixel - pixels_offset;
472 const double end = annotations.back().end_sample() /
473 samples_per_pixel - pixels_offset;
475 const double top = y + .5 - h / 2;
476 const double bottom = y + .5 + h / 2;
478 const size_t colour = (base_colour + annotations.front().format()) %
481 // Check if all annotations are of the same type (i.e. we can use one color)
482 // or if we should use a neutral color (i.e. gray)
483 const int format = annotations.front().format();
484 const bool single_format = all_of(
485 annotations.begin(), annotations.end(),
486 [&](const Annotation &a) { return a.format() == format; });
488 const QRectF rect(start, top, end - start, bottom - top);
491 p.setPen(QPen(Qt::NoPen));
492 p.setBrush(Qt::white);
493 p.drawRoundedRect(rect, r, r);
495 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
496 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
498 p.drawRoundedRect(rect, r, r);
501 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
502 int h, double x, int y) const
504 const QString text = a.annotations().empty() ?
505 QString() : a.annotations().back();
506 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
508 const QRectF rect(x - w / 2, y - h / 2, w, h);
510 p.drawRoundedRect(rect, h / 2, h / 2);
513 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
516 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
517 int h, double start, double end, int y, const ViewItemPaintParams &pp,
518 int row_title_width) const
520 const double top = y + .5 - h / 2;
521 const double bottom = y + .5 + h / 2;
522 const vector<QString> annotations = a.annotations();
524 // If the two ends are within 1 pixel, draw a vertical line
525 if (start + 1.0 > end) {
526 p.drawLine(QPointF(start, top), QPointF(start, bottom));
530 const double cap_width = min((end - start) / 4, EndCapWidth);
533 QPointF(start, y + .5f),
534 QPointF(start + cap_width, top),
535 QPointF(end - cap_width, top),
536 QPointF(end, y + .5f),
537 QPointF(end - cap_width, bottom),
538 QPointF(start + cap_width, bottom)
541 p.drawConvexPolygon(pts, countof(pts));
543 if (annotations.empty())
546 const int ann_start = start + cap_width;
547 const int ann_end = end - cap_width;
549 const int real_start = max(ann_start, pp.left() + row_title_width);
550 const int real_end = min(ann_end, pp.right());
551 const int real_width = real_end - real_start;
553 QRectF rect(real_start, y - h / 2, real_width, h);
554 if (rect.width() <= 4)
559 // Try to find an annotation that will fit
560 QString best_annotation;
563 for (const QString &a : annotations) {
564 const int w = p.boundingRect(QRectF(), 0, a).width();
565 if (w <= rect.width() && w > best_width)
566 best_annotation = a, best_width = w;
569 if (best_annotation.isEmpty())
570 best_annotation = annotations.back();
572 // If not ellide the last in the list
573 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
574 best_annotation, Qt::ElideRight, rect.width()));
577 void DecodeTrace::draw_error(QPainter &p, const QString &message,
578 const ViewItemPaintParams &pp)
580 const int y = get_visual_y();
582 p.setPen(ErrorBgColour.darker());
583 p.setBrush(ErrorBgColour);
585 const QRectF bounding_rect =
586 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
587 const QRectF text_rect = p.boundingRect(bounding_rect,
588 Qt::AlignCenter, message);
589 const float r = text_rect.height() / 4;
591 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
595 p.drawText(text_rect, message);
598 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
601 using namespace pv::data;
602 using pv::data::decode::Decoder;
604 double samples_per_pixel, pixels_offset;
606 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
608 assert(decoder_stack);
610 shared_ptr<Logic> data;
611 shared_ptr<data::SignalBase> signalbase;
613 const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
615 // We get the logic data of the first channel in the list.
616 // This works because we are currently assuming all
617 // LogicSignals have the same data/segment
618 for (const shared_ptr<Decoder> &dec : stack)
619 if (dec && !dec->channels().empty() &&
620 ((signalbase = (*dec->channels().begin()).second)) &&
621 ((data = signalbase->logic_data())))
624 if (!data || data->logic_segments().empty())
627 const shared_ptr<LogicSegment> segment = data->logic_segments().front();
629 const int64_t sample_count = (int64_t)segment->get_sample_count();
630 if (sample_count == 0)
633 const int64_t samples_decoded = decoder_stack->samples_decoded();
634 if (sample_count == samples_decoded)
637 const int y = get_visual_y();
639 tie(pixels_offset, samples_per_pixel) =
640 get_pixels_offset_samples_per_pixel();
642 const double start = max(samples_decoded /
643 samples_per_pixel - pixels_offset, left - 1.0);
644 const double end = min(sample_count / samples_per_pixel -
645 pixels_offset, right + 1.0);
646 const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
648 p.setPen(QPen(Qt::NoPen));
649 p.setBrush(Qt::white);
650 p.drawRect(no_decode_rect);
652 p.setPen(NoDecodeColour);
653 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
654 p.drawRect(no_decode_rect);
657 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
659 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
662 assert(decoder_stack);
664 const View *view = owner_->view();
667 const double scale = view->scale();
670 const double pixels_offset =
671 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
673 double samplerate = decoder_stack->samplerate();
675 // Show sample rate as 1Hz when it is unknown
676 if (samplerate == 0.0)
679 return make_pair(pixels_offset, samplerate * scale);
682 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
683 int x_start, int x_end) const
685 double samples_per_pixel, pixels_offset;
686 tie(pixels_offset, samples_per_pixel) =
687 get_pixels_offset_samples_per_pixel();
689 const uint64_t start = (uint64_t)max(
690 (x_start + pixels_offset) * samples_per_pixel, 0.0);
691 const uint64_t end = (uint64_t)max(
692 (x_end + pixels_offset) * samples_per_pixel, 0.0);
694 return make_pair(start, end);
697 int DecodeTrace::get_row_at_point(const QPoint &point)
702 const int y = (point.y() - get_visual_y() + row_height_ / 2);
704 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
708 const int row = y / row_height_;
710 if (row >= (int)visible_rows_.size())
716 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
718 using namespace pv::data::decode;
723 const pair<uint64_t, uint64_t> sample_range =
724 get_sample_range(point.x(), point.x() + 1);
725 const int row = get_row_at_point(point);
729 vector<pv::data::decode::Annotation> annotations;
731 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
733 assert(decoder_stack);
734 decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
735 sample_range.first, sample_range.second);
737 return (annotations.empty()) ?
738 QString() : annotations[0].annotations().front();
741 void DecodeTrace::hover_point_changed()
745 const View *const view = owner_->view();
748 QPoint hp = view->hover_point();
749 QString ann = get_annotation_at_point(hp);
753 if (!row_height_ || ann.isEmpty()) {
754 QToolTip::hideText();
758 const int hover_row = get_row_at_point(hp);
760 QFontMetrics m(QToolTip::font());
761 const QRect text_size = m.boundingRect(QRect(), 0, ann);
763 // This is OS-specific and unfortunately we can't query it, so
764 // use an approximation to at least try to minimize the error.
765 const int padding = 8;
767 // Make sure the tool tip doesn't overlap with the mouse cursor.
768 // If it did, the tool tip would constantly hide and re-appear.
769 // We also push it up by one row so that it appears above the
770 // decode trace, not below.
771 hp.setX(hp.x() - (text_size.width() / 2) - padding);
773 hp.setY(get_visual_y() - (row_height_ / 2) +
774 (hover_row * row_height_) -
775 row_height_ - text_size.height() - padding);
777 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
780 void DecodeTrace::create_decoder_form(int index,
781 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
785 GlobalSettings settings;
788 const srd_decoder *const decoder = dec->decoder();
791 const bool decoder_deletable = index > 0;
793 pv::widgets::DecoderGroupBox *const group =
794 new pv::widgets::DecoderGroupBox(
795 QString::fromUtf8(decoder->name),
796 tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
797 QString::fromUtf8(decoder->desc)),
798 nullptr, decoder_deletable);
799 group->set_decoder_visible(dec->shown());
801 if (decoder_deletable) {
802 delete_mapper_.setMapping(group, index);
803 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
806 show_hide_mapper_.setMapping(group, index);
807 connect(group, SIGNAL(show_hide_decoder()),
808 &show_hide_mapper_, SLOT(map()));
810 QFormLayout *const decoder_form = new QFormLayout;
811 group->add_layout(decoder_form);
813 // Add the mandatory channels
814 for (l = decoder->channels; l; l = l->next) {
815 const struct srd_channel *const pdch =
816 (struct srd_channel *)l->data;
818 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
819 QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch);
821 connect(combo, SIGNAL(currentIndexChanged(int)),
822 this, SLOT(on_channel_selected(int)));
823 connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)),
824 this, SLOT(on_initial_pin_selected(int)));
826 QHBoxLayout *const hlayout = new QHBoxLayout;
827 hlayout->addWidget(combo);
828 hlayout->addWidget(combo_initial_pin);
830 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
831 combo_initial_pin->hide();
833 decoder_form->addRow(tr("<b>%1</b> (%2) *")
834 .arg(QString::fromUtf8(pdch->name),
835 QString::fromUtf8(pdch->desc)), hlayout);
837 const ChannelSelector s = {combo, combo_initial_pin, dec, pdch};
838 channel_selectors_.push_back(s);
841 // Add the optional channels
842 for (l = decoder->opt_channels; l; l = l->next) {
843 const struct srd_channel *const pdch =
844 (struct srd_channel *)l->data;
846 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
847 QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch);
849 connect(combo, SIGNAL(currentIndexChanged(int)),
850 this, SLOT(on_channel_selected(int)));
851 connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)),
852 this, SLOT(on_initial_pin_selected(int)));
854 QHBoxLayout *const hlayout = new QHBoxLayout;
855 hlayout->addWidget(combo);
856 hlayout->addWidget(combo_initial_pin);
858 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
859 combo_initial_pin->hide();
861 decoder_form->addRow(tr("<b>%1</b> (%2)")
862 .arg(QString::fromUtf8(pdch->name),
863 QString::fromUtf8(pdch->desc)), hlayout);
865 const ChannelSelector s = {combo, combo_initial_pin, dec, pdch};
866 channel_selectors_.push_back(s);
869 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
872 shared_ptr<binding::Decoder> binding(
873 new binding::Decoder(decoder_stack, dec));
874 binding->add_properties_to_form(decoder_form, true);
876 bindings_.push_back(binding);
879 decoder_forms_.push_back(group);
882 QComboBox* DecodeTrace::create_channel_selector(
883 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
884 const srd_channel *const pdch)
888 const auto sigs(session_.signalbases());
890 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
891 sort(sig_list.begin(), sig_list.end(),
892 [](const shared_ptr<data::SignalBase> &a,
893 const shared_ptr<data::SignalBase> &b) {
894 return strnatcasecmp(a->name().toStdString(),
895 b->name().toStdString()) < 0; });
897 const auto channel_iter = dec->channels().find(pdch);
899 QComboBox *selector = new QComboBox(parent);
901 selector->addItem("-", qVariantFromValue((void*)nullptr));
903 if (channel_iter == dec->channels().end())
904 selector->setCurrentIndex(0);
906 for (const shared_ptr<data::SignalBase> &b : sig_list) {
908 if (b->logic_data() && b->enabled()) {
909 selector->addItem(b->name(),
910 qVariantFromValue((void*)b.get()));
912 if (channel_iter != dec->channels().end() &&
913 (*channel_iter).second == b)
914 selector->setCurrentIndex(
915 selector->count() - 1);
922 QComboBox* DecodeTrace::create_channel_selector_initial_pin(QWidget *parent,
923 const shared_ptr<data::decode::Decoder> &dec, const srd_channel *const pdch)
925 QComboBox *selector = new QComboBox(parent);
927 selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
928 selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
929 selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
931 // Default to index 2 (SRD_INITIAL_PIN_SAME_AS_SAMPLE0).
932 const int idx = (!dec->initial_pins()) ? 2 : dec->initial_pins()->data[pdch->order];
933 selector->setCurrentIndex(idx);
935 selector->setToolTip("Initial (assumed) pin value before the first sample");
940 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
944 map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
946 const unordered_set< shared_ptr<data::SignalBase> >
947 sigs(session_.signalbases());
949 GArray *const initial_pins = g_array_sized_new(FALSE, TRUE,
950 sizeof(uint8_t), channel_selectors_.size());
951 g_array_set_size(initial_pins, channel_selectors_.size());
953 for (const ChannelSelector &s : channel_selectors_) {
954 if (s.decoder_ != dec)
957 const data::SignalBase *const selection =
958 (data::SignalBase*)s.combo_->itemData(
959 s.combo_->currentIndex()).value<void*>();
961 for (shared_ptr<data::SignalBase> sig : sigs)
962 if (sig.get() == selection) {
963 channel_map[s.pdch_] = sig;
967 int selection_initial_pin = s.combo_initial_pin_->itemData(
968 s.combo_initial_pin_->currentIndex()).value<int>();
970 initial_pins->data[s.pdch_->order] = selection_initial_pin;
973 dec->set_channels(channel_map);
974 dec->set_initial_pins(initial_pins);
977 void DecodeTrace::commit_channels()
979 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
981 assert(decoder_stack);
982 for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
983 commit_decoder_channels(dec);
985 decoder_stack->begin_decode();
988 void DecodeTrace::on_new_annotations()
991 owner_->row_item_appearance_changed(false, true);
994 void DecodeTrace::delete_pressed()
999 void DecodeTrace::on_delete()
1001 session_.remove_decode_signal(decode_signal_);
1004 void DecodeTrace::on_channel_selected(int)
1009 void DecodeTrace::on_initial_pin_selected(int)
1014 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
1016 decode_signal_->stack_decoder(decoder);
1018 create_popup_form();
1021 void DecodeTrace::on_delete_decoder(int index)
1023 decode_signal_->remove_decoder(index);
1026 create_popup_form();
1029 void DecodeTrace::on_show_hide_decoder(int index)
1031 const bool state = decode_signal_->toggle_decoder_visibility(index);
1033 assert(index < (int)decoder_forms_.size());
1034 decoder_forms_[index]->set_decoder_visible(state);
1037 owner_->row_item_appearance_changed(false, true);
1040 } // namespace trace
1041 } // namespace views