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;
72 using pv::data::decode::Annotation;
73 using pv::data::decode::Row;
74 using pv::data::DecodeChannel;
75 using pv::data::DecodeSignal;
81 const QColor DecodeTrace::DecodeColours[4] = {
82 QColor(0xEF, 0x29, 0x29), // Red
83 QColor(0xFC, 0xE9, 0x4F), // Yellow
84 QColor(0x8A, 0xE2, 0x34), // Green
85 QColor(0x72, 0x9F, 0xCF) // Blue
88 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
89 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
91 const int DecodeTrace::ArrowSize = 4;
92 const double DecodeTrace::EndCapWidth = 5;
93 const int DecodeTrace::RowTitleMargin = 10;
94 const int DecodeTrace::DrawPadding = 100;
96 const QColor DecodeTrace::Colours[16] = {
97 QColor(0xEF, 0x29, 0x29),
98 QColor(0xF6, 0x6A, 0x32),
99 QColor(0xFC, 0xAE, 0x3E),
100 QColor(0xFB, 0xCA, 0x47),
101 QColor(0xFC, 0xE9, 0x4F),
102 QColor(0xCD, 0xF0, 0x40),
103 QColor(0x8A, 0xE2, 0x34),
104 QColor(0x4E, 0xDC, 0x44),
105 QColor(0x55, 0xD7, 0x95),
106 QColor(0x64, 0xD1, 0xD2),
107 QColor(0x72, 0x9F, 0xCF),
108 QColor(0xD4, 0x76, 0xC4),
109 QColor(0x9D, 0x79, 0xB9),
110 QColor(0xAD, 0x7F, 0xA8),
111 QColor(0xC2, 0x62, 0x9B),
112 QColor(0xD7, 0x47, 0x6F)
115 const QColor DecodeTrace::OutlineColours[16] = {
116 QColor(0x77, 0x14, 0x14),
117 QColor(0x7B, 0x35, 0x19),
118 QColor(0x7E, 0x57, 0x1F),
119 QColor(0x7D, 0x65, 0x23),
120 QColor(0x7E, 0x74, 0x27),
121 QColor(0x66, 0x78, 0x20),
122 QColor(0x45, 0x71, 0x1A),
123 QColor(0x27, 0x6E, 0x22),
124 QColor(0x2A, 0x6B, 0x4A),
125 QColor(0x32, 0x68, 0x69),
126 QColor(0x39, 0x4F, 0x67),
127 QColor(0x6A, 0x3B, 0x62),
128 QColor(0x4E, 0x3C, 0x5C),
129 QColor(0x56, 0x3F, 0x54),
130 QColor(0x61, 0x31, 0x4D),
131 QColor(0x6B, 0x23, 0x37)
134 DecodeTrace::DecodeTrace(pv::Session &session,
135 shared_ptr<data::SignalBase> signalbase, int index) :
139 max_visible_rows_(0),
140 delete_mapper_(this),
141 show_hide_mapper_(this)
143 decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
145 // Determine shortest string we want to see displayed in full
146 QFontMetrics m(QApplication::font());
147 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
149 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
151 connect(decode_signal_.get(), SIGNAL(new_annotations()),
152 this, SLOT(on_new_annotations()));
153 connect(decode_signal_.get(), SIGNAL(channels_updated()),
154 this, SLOT(on_channels_updated()));
156 connect(&delete_mapper_, SIGNAL(mapped(int)),
157 this, SLOT(on_delete_decoder(int)));
158 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
159 this, SLOT(on_show_hide_decoder(int)));
162 bool DecodeTrace::enabled() const
167 shared_ptr<data::SignalBase> DecodeTrace::base() const
172 pair<int, int> DecodeTrace::v_extents() const
174 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
176 // Make an empty decode trace appear symmetrical
177 const int row_count = max(1, max_visible_rows_);
179 return make_pair(-row_height, row_height * row_count);
182 void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
184 Trace::paint_back(p, pp);
185 paint_axis(p, pp, get_visual_y());
188 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
190 const int text_height = ViewItemPaintParams::text_height();
191 row_height_ = (text_height * 6) / 4;
192 const int annotation_height = (text_height * 5) / 4;
194 const QString err = decode_signal_->error_message();
195 if (!err.isEmpty()) {
196 draw_unresolved_period(
197 p, annotation_height, pp.left(), pp.right());
198 draw_error(p, err, pp);
202 // Set default pen to allow for text width calculation
205 // Iterate through the rows
206 int y = get_visual_y();
207 pair<uint64_t, uint64_t> sample_range = get_sample_range(
208 pp.left(), pp.right());
210 const vector<Row> rows = decode_signal_->visible_rows();
212 visible_rows_.clear();
213 for (const Row& row : rows) {
214 // Cache the row title widths
217 row_title_width = row_title_widths_.at(row);
218 } catch (out_of_range) {
219 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
221 row_title_widths_[row] = w;
225 // Determine the row's color
226 size_t base_colour = 0x13579BDF;
227 boost::hash_combine(base_colour, this);
228 boost::hash_combine(base_colour, row.decoder());
229 boost::hash_combine(base_colour, row.row());
232 vector<Annotation> annotations;
233 decode_signal_->get_annotation_subset(annotations, row,
234 sample_range.first, sample_range.second);
235 if (!annotations.empty()) {
236 draw_annotations(annotations, p, annotation_height, pp, y,
237 base_colour, row_title_width);
241 visible_rows_.push_back(row);
246 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
248 if ((int)visible_rows_.size() > max_visible_rows_)
249 owner_->extents_changed(false, true);
251 // Update the maximum row count if needed
252 max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
255 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
259 for (size_t i = 0; i < visible_rows_.size(); i++) {
260 const int y = i * row_height_ + get_visual_y();
262 p.setPen(QPen(Qt::NoPen));
263 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
266 const QPointF points[] = {
267 QPointF(pp.left(), y - ArrowSize),
268 QPointF(pp.left() + ArrowSize, y),
269 QPointF(pp.left(), y + ArrowSize)
271 p.drawPolygon(points, countof(points));
274 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
275 pp.right() - pp.left(), row_height_);
276 const QString h(visible_rows_[i].title());
277 const int f = Qt::AlignLeft | Qt::AlignVCenter |
281 p.setPen(QApplication::palette().color(QPalette::Base));
282 for (int dx = -1; dx <= 1; dx++)
283 for (int dy = -1; dy <= 1; dy++)
284 if (dx != 0 && dy != 0)
285 p.drawText(r.translated(dx, dy), f, h);
288 p.setPen(QApplication::palette().color(QPalette::WindowText));
293 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
295 using pv::data::decode::Decoder;
299 // Add the standard options
300 Trace::populate_popup_form(parent, form);
302 // Add the decoder options
304 channel_id_map_.clear();
305 init_state_map_.clear();
306 decoder_forms_.clear();
308 const list< shared_ptr<Decoder> >& stack =
309 decode_signal_->decoder_stack_list();
312 QLabel *const l = new QLabel(
313 tr("<p><i>No decoders in the stack</i></p>"));
314 l->setAlignment(Qt::AlignCenter);
317 auto iter = stack.cbegin();
318 for (int i = 0; i < (int)stack.size(); i++, iter++) {
319 shared_ptr<Decoder> dec(*iter);
320 create_decoder_form(i, dec, parent, form);
323 form->addRow(new QLabel(
324 tr("<i>* Required channels</i>"), parent));
327 // Add stacking button
328 pv::widgets::DecoderMenu *const decoder_menu =
329 new pv::widgets::DecoderMenu(parent);
330 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
331 this, SLOT(on_stack_decoder(srd_decoder*)));
333 QPushButton *const stack_button =
334 new QPushButton(tr("Stack Decoder"), parent);
335 stack_button->setMenu(decoder_menu);
336 stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
338 QHBoxLayout *stack_button_box = new QHBoxLayout;
339 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
340 form->addRow(stack_button_box);
343 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
345 QMenu *const menu = Trace::create_context_menu(parent);
347 menu->addSeparator();
349 QAction *const del = new QAction(tr("Delete"), this);
350 del->setShortcuts(QKeySequence::Delete);
351 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
352 menu->addAction(del);
357 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
358 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
359 size_t base_colour, int row_title_width)
361 using namespace pv::data::decode;
363 vector<Annotation> a_block;
366 double samples_per_pixel, pixels_offset;
367 tie(pixels_offset, samples_per_pixel) =
368 get_pixels_offset_samples_per_pixel();
370 // Sort the annotations by start sample so that decoders
371 // can't confuse us by creating annotations out of order
372 stable_sort(annotations.begin(), annotations.end(),
373 [](const Annotation &a, const Annotation &b) {
374 return a.start_sample() < b.start_sample(); });
376 // Gather all annotations that form a visual "block" and draw them as such
377 for (const Annotation &a : annotations) {
379 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
380 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
381 const int a_width = a_end - a_start;
383 const int delta = a_end - p_end;
385 bool a_is_separate = false;
387 // Annotation wider than the threshold for a useful label width?
388 if (a_width >= min_useful_label_width_) {
389 for (const QString &ann_text : a.annotations()) {
390 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
391 // Annotation wide enough to fit a label? Don't put it in a block then
393 a_is_separate = true;
399 // Were the previous and this annotation more than a pixel apart?
400 if ((abs(delta) > 1) || a_is_separate) {
401 // Block was broken, draw annotations that form the current block
402 if (a_block.size() == 1) {
403 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
407 draw_annotation_block(a_block, p, h, y, base_colour);
413 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
414 // Next annotation must start a new block. delta will be > 1
415 // because we set p_end to INT_MIN but that's okay since
416 // a_block will be empty, so nothing will be drawn
419 a_block.push_back(a);
424 if (a_block.size() == 1)
425 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
428 draw_annotation_block(a_block, p, h, y, base_colour);
431 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
432 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
433 size_t base_colour, int row_title_width) const
435 double samples_per_pixel, pixels_offset;
436 tie(pixels_offset, samples_per_pixel) =
437 get_pixels_offset_samples_per_pixel();
439 const double start = a.start_sample() / samples_per_pixel -
441 const double end = a.end_sample() / samples_per_pixel - pixels_offset;
443 const size_t colour = (base_colour + a.format()) % countof(Colours);
444 p.setPen(OutlineColours[colour]);
445 p.setBrush(Colours[colour]);
447 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
450 if (a.start_sample() == a.end_sample())
451 draw_instant(a, p, h, start, y);
453 draw_range(a, p, h, start, end, y, pp, row_title_width);
456 void DecodeTrace::draw_annotation_block(
457 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
458 int y, size_t base_colour) const
460 using namespace pv::data::decode;
462 if (annotations.empty())
465 double samples_per_pixel, pixels_offset;
466 tie(pixels_offset, samples_per_pixel) =
467 get_pixels_offset_samples_per_pixel();
469 const double start = annotations.front().start_sample() /
470 samples_per_pixel - pixels_offset;
471 const double end = annotations.back().end_sample() /
472 samples_per_pixel - pixels_offset;
474 const double top = y + .5 - h / 2;
475 const double bottom = y + .5 + h / 2;
477 const size_t colour = (base_colour + annotations.front().format()) %
480 // Check if all annotations are of the same type (i.e. we can use one color)
481 // or if we should use a neutral color (i.e. gray)
482 const int format = annotations.front().format();
483 const bool single_format = all_of(
484 annotations.begin(), annotations.end(),
485 [&](const Annotation &a) { return a.format() == format; });
487 const QRectF rect(start, top, end - start, bottom - top);
490 p.setPen(QPen(Qt::NoPen));
491 p.setBrush(Qt::white);
492 p.drawRoundedRect(rect, r, r);
494 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
495 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
497 p.drawRoundedRect(rect, r, r);
500 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
501 int h, double x, int y) const
503 const QString text = a.annotations().empty() ?
504 QString() : a.annotations().back();
505 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
507 const QRectF rect(x - w / 2, y - h / 2, w, h);
509 p.drawRoundedRect(rect, h / 2, h / 2);
512 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
515 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
516 int h, double start, double end, int y, const ViewItemPaintParams &pp,
517 int row_title_width) const
519 const double top = y + .5 - h / 2;
520 const double bottom = y + .5 + h / 2;
521 const vector<QString> annotations = a.annotations();
523 // If the two ends are within 1 pixel, draw a vertical line
524 if (start + 1.0 > end) {
525 p.drawLine(QPointF(start, top), QPointF(start, bottom));
529 const double cap_width = min((end - start) / 4, EndCapWidth);
532 QPointF(start, y + .5f),
533 QPointF(start + cap_width, top),
534 QPointF(end - cap_width, top),
535 QPointF(end, y + .5f),
536 QPointF(end - cap_width, bottom),
537 QPointF(start + cap_width, bottom)
540 p.drawConvexPolygon(pts, countof(pts));
542 if (annotations.empty())
545 const int ann_start = start + cap_width;
546 const int ann_end = end - cap_width;
548 const int real_start = max(ann_start, pp.left() + row_title_width);
549 const int real_end = min(ann_end, pp.right());
550 const int real_width = real_end - real_start;
552 QRectF rect(real_start, y - h / 2, real_width, h);
553 if (rect.width() <= 4)
558 // Try to find an annotation that will fit
559 QString best_annotation;
562 for (const QString &a : annotations) {
563 const int w = p.boundingRect(QRectF(), 0, a).width();
564 if (w <= rect.width() && w > best_width)
565 best_annotation = a, best_width = w;
568 if (best_annotation.isEmpty())
569 best_annotation = annotations.back();
571 // If not ellide the last in the list
572 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
573 best_annotation, Qt::ElideRight, rect.width()));
576 void DecodeTrace::draw_error(QPainter &p, const QString &message,
577 const ViewItemPaintParams &pp)
579 const int y = get_visual_y();
581 p.setPen(ErrorBgColour.darker());
582 p.setBrush(ErrorBgColour);
584 const QRectF bounding_rect =
585 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
586 const QRectF text_rect = p.boundingRect(bounding_rect,
587 Qt::AlignCenter, message);
588 const float r = text_rect.height() / 4;
590 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
594 p.drawText(text_rect, message);
597 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
600 using namespace pv::data;
601 using pv::data::decode::Decoder;
603 double samples_per_pixel, pixels_offset;
605 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
607 assert(decoder_stack);
609 shared_ptr<Logic> data;
610 shared_ptr<data::SignalBase> signalbase;
612 const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
614 // We get the logic data of the first channel in the list.
615 // This works because we are currently assuming all
616 // LogicSignals have the same data/segment
617 for (const shared_ptr<Decoder> &dec : stack)
618 if (dec && !dec->channels().empty() &&
619 ((signalbase = (*dec->channels().begin()).second)) &&
620 ((data = signalbase->logic_data())))
623 if (!data || data->logic_segments().empty())
626 const shared_ptr<LogicSegment> segment = data->logic_segments().front();
628 const int64_t sample_count = (int64_t)segment->get_sample_count();
629 if (sample_count == 0)
632 const int64_t samples_decoded = decoder_stack->samples_decoded();
633 if (sample_count == samples_decoded)
636 const int y = get_visual_y();
638 tie(pixels_offset, samples_per_pixel) =
639 get_pixels_offset_samples_per_pixel();
641 const double start = max(samples_decoded /
642 samples_per_pixel - pixels_offset, left - 1.0);
643 const double end = min(sample_count / samples_per_pixel -
644 pixels_offset, right + 1.0);
645 const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
647 p.setPen(QPen(Qt::NoPen));
648 p.setBrush(Qt::white);
649 p.drawRect(no_decode_rect);
651 p.setPen(NoDecodeColour);
652 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
653 p.drawRect(no_decode_rect);
656 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
658 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
661 assert(decoder_stack);
663 const View *view = owner_->view();
666 const double scale = view->scale();
669 const double pixels_offset =
670 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
672 double samplerate = decoder_stack->samplerate();
674 // Show sample rate as 1Hz when it is unknown
675 if (samplerate == 0.0)
678 return make_pair(pixels_offset, samplerate * scale);
681 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
682 int x_start, int x_end) const
684 double samples_per_pixel, pixels_offset;
685 tie(pixels_offset, samples_per_pixel) =
686 get_pixels_offset_samples_per_pixel();
688 const uint64_t start = (uint64_t)max(
689 (x_start + pixels_offset) * samples_per_pixel, 0.0);
690 const uint64_t end = (uint64_t)max(
691 (x_end + pixels_offset) * samples_per_pixel, 0.0);
693 return make_pair(start, end);
696 int DecodeTrace::get_row_at_point(const QPoint &point)
701 const int y = (point.y() - get_visual_y() + row_height_ / 2);
703 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
707 const int row = y / row_height_;
709 if (row >= (int)visible_rows_.size())
715 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
717 using namespace pv::data::decode;
722 const pair<uint64_t, uint64_t> sample_range =
723 get_sample_range(point.x(), point.x() + 1);
724 const int row = get_row_at_point(point);
728 vector<pv::data::decode::Annotation> annotations;
730 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
732 assert(decoder_stack);
733 decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
734 sample_range.first, sample_range.second);
736 return (annotations.empty()) ?
737 QString() : annotations[0].annotations().front();
740 void DecodeTrace::hover_point_changed()
744 const View *const view = owner_->view();
747 QPoint hp = view->hover_point();
748 QString ann = get_annotation_at_point(hp);
752 if (!row_height_ || ann.isEmpty()) {
753 QToolTip::hideText();
757 const int hover_row = get_row_at_point(hp);
759 QFontMetrics m(QToolTip::font());
760 const QRect text_size = m.boundingRect(QRect(), 0, ann);
762 // This is OS-specific and unfortunately we can't query it, so
763 // use an approximation to at least try to minimize the error.
764 const int padding = 8;
766 // Make sure the tool tip doesn't overlap with the mouse cursor.
767 // If it did, the tool tip would constantly hide and re-appear.
768 // We also push it up by one row so that it appears above the
769 // decode trace, not below.
770 hp.setX(hp.x() - (text_size.width() / 2) - padding);
772 hp.setY(get_visual_y() - (row_height_ / 2) +
773 (hover_row * row_height_) -
774 row_height_ - text_size.height() - padding);
776 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
779 void DecodeTrace::create_decoder_form(int index,
780 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
783 GlobalSettings settings;
786 const srd_decoder *const decoder = dec->decoder();
789 const bool decoder_deletable = index > 0;
791 pv::widgets::DecoderGroupBox *const group =
792 new pv::widgets::DecoderGroupBox(
793 QString::fromUtf8(decoder->name),
794 tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
795 QString::fromUtf8(decoder->desc)),
796 nullptr, decoder_deletable);
797 group->set_decoder_visible(dec->shown());
799 if (decoder_deletable) {
800 delete_mapper_.setMapping(group, index);
801 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
804 show_hide_mapper_.setMapping(group, index);
805 connect(group, SIGNAL(show_hide_decoder()),
806 &show_hide_mapper_, SLOT(map()));
808 QFormLayout *const decoder_form = new QFormLayout;
809 group->add_layout(decoder_form);
811 const list<DecodeChannel> channels = decode_signal_->get_channels();
814 for (DecodeChannel ch : channels) {
815 // Ignore channels not part of the decoder we create the form for
816 if (ch.decoder_ != dec)
819 QComboBox *const combo = create_channel_selector(parent, &ch);
820 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
822 channel_id_map_[combo] = ch.id;
823 init_state_map_[combo_init_state] = ch.id;
825 connect(combo, SIGNAL(currentIndexChanged(int)),
826 this, SLOT(on_channel_selected(int)));
827 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
828 this, SLOT(on_init_state_changed(int)));
830 QHBoxLayout *const hlayout = new QHBoxLayout;
831 hlayout->addWidget(combo);
832 hlayout->addWidget(combo_init_state);
834 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
835 combo_init_state->hide();
837 const QString required_flag = ch.is_optional ? QString() : QString("*");
838 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
839 .arg(ch.name, ch.desc, required_flag), hlayout);
842 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
845 shared_ptr<binding::Decoder> binding(
846 new binding::Decoder(decoder_stack, dec));
847 binding->add_properties_to_form(decoder_form, true);
849 bindings_.push_back(binding);
852 decoder_forms_.push_back(group);
855 QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
857 const auto sigs(session_.signalbases());
859 // Sort signals in natural order
860 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
861 sort(sig_list.begin(), sig_list.end(),
862 [](const shared_ptr<data::SignalBase> &a,
863 const shared_ptr<data::SignalBase> &b) {
864 return strnatcasecmp(a->name().toStdString(),
865 b->name().toStdString()) < 0; });
867 QComboBox *selector = new QComboBox(parent);
869 selector->addItem("-", qVariantFromValue((void*)nullptr));
871 if (!ch->assigned_signal)
872 selector->setCurrentIndex(0);
874 for (const shared_ptr<data::SignalBase> &b : sig_list) {
876 if (b->logic_data() && b->enabled()) {
877 selector->addItem(b->name(),
878 qVariantFromValue((void*)b.get()));
880 if (ch->assigned_signal == b.get())
881 selector->setCurrentIndex(selector->count() - 1);
888 QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
889 const DecodeChannel *ch)
891 QComboBox *selector = new QComboBox(parent);
893 selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
894 selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
895 selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
897 selector->setCurrentIndex(ch->initial_pin_state);
899 selector->setToolTip("Initial (assumed) pin value before the first sample");
904 void DecodeTrace::on_new_annotations()
907 owner_->row_item_appearance_changed(false, true);
910 void DecodeTrace::delete_pressed()
915 void DecodeTrace::on_delete()
917 session_.remove_decode_signal(decode_signal_);
920 void DecodeTrace::on_channel_selected(int)
922 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
924 // Determine signal that was selected
925 const data::SignalBase *signal =
926 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
928 // Determine decode channel ID this combo box is the channel selector for
929 const uint16_t id = channel_id_map_.at(cb);
931 decode_signal_->assign_signal(id, signal);
934 void DecodeTrace::on_channels_updated()
937 owner_->row_item_appearance_changed(false, true);
940 void DecodeTrace::on_init_state_changed(int)
942 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
944 // Determine inital pin state that was selected
945 int init_state = cb->itemData(cb->currentIndex()).value<int>();
947 // Determine decode channel ID this combo box is the channel selector for
948 const uint16_t id = init_state_map_.at(cb);
950 decode_signal_->set_initial_pin_state(id, init_state);
953 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
955 decode_signal_->stack_decoder(decoder);
960 void DecodeTrace::on_delete_decoder(int index)
962 decode_signal_->remove_decoder(index);
968 void DecodeTrace::on_show_hide_decoder(int index)
970 const bool state = decode_signal_->toggle_decoder_visibility(index);
972 assert(index < (int)decoder_forms_.size());
973 decoder_forms_[index]->set_decoder_visible(state);
976 owner_->row_item_appearance_changed(false, true);