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>
31 #include <boost/functional/hash.hpp>
32 #include <boost/thread/locks.hpp>
33 #include <boost/thread/shared_mutex.hpp>
36 #include <QApplication>
38 #include <QFormLayout>
41 #include <QPushButton>
44 #include "decodetrace.hpp"
46 #include <pv/session.hpp>
47 #include <pv/strnatcmp.hpp>
48 #include <pv/data/decoderstack.hpp>
49 #include <pv/data/decode/decoder.hpp>
50 #include <pv/data/logic.hpp>
51 #include <pv/data/logicsegment.hpp>
52 #include <pv/data/decode/annotation.hpp>
53 #include <pv/view/signal.hpp>
54 #include <pv/view/view.hpp>
55 #include <pv/view/viewport.hpp>
56 #include <pv/widgets/decodergroupbox.hpp>
57 #include <pv/widgets/decodermenu.hpp>
59 using boost::shared_lock;
60 using boost::shared_mutex;
61 using std::dynamic_pointer_cast;
63 using std::lock_guard;
70 using std::shared_ptr;
72 using std::unordered_set;
78 const QColor DecodeTrace::DecodeColours[4] = {
79 QColor(0xEF, 0x29, 0x29), // Red
80 QColor(0xFC, 0xE9, 0x4F), // Yellow
81 QColor(0x8A, 0xE2, 0x34), // Green
82 QColor(0x72, 0x9F, 0xCF) // Blue
85 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
86 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
88 const int DecodeTrace::ArrowSize = 4;
89 const double DecodeTrace::EndCapWidth = 5;
90 const int DecodeTrace::RowTitleMargin = 10;
91 const int DecodeTrace::DrawPadding = 100;
93 const QColor DecodeTrace::Colours[16] = {
94 QColor(0xEF, 0x29, 0x29),
95 QColor(0xF6, 0x6A, 0x32),
96 QColor(0xFC, 0xAE, 0x3E),
97 QColor(0xFB, 0xCA, 0x47),
98 QColor(0xFC, 0xE9, 0x4F),
99 QColor(0xCD, 0xF0, 0x40),
100 QColor(0x8A, 0xE2, 0x34),
101 QColor(0x4E, 0xDC, 0x44),
102 QColor(0x55, 0xD7, 0x95),
103 QColor(0x64, 0xD1, 0xD2),
104 QColor(0x72, 0x9F, 0xCF),
105 QColor(0xD4, 0x76, 0xC4),
106 QColor(0x9D, 0x79, 0xB9),
107 QColor(0xAD, 0x7F, 0xA8),
108 QColor(0xC2, 0x62, 0x9B),
109 QColor(0xD7, 0x47, 0x6F)
112 const QColor DecodeTrace::OutlineColours[16] = {
113 QColor(0x77, 0x14, 0x14),
114 QColor(0x7B, 0x35, 0x19),
115 QColor(0x7E, 0x57, 0x1F),
116 QColor(0x7D, 0x65, 0x23),
117 QColor(0x7E, 0x74, 0x27),
118 QColor(0x66, 0x78, 0x20),
119 QColor(0x45, 0x71, 0x1A),
120 QColor(0x27, 0x6E, 0x22),
121 QColor(0x2A, 0x6B, 0x4A),
122 QColor(0x32, 0x68, 0x69),
123 QColor(0x39, 0x4F, 0x67),
124 QColor(0x6A, 0x3B, 0x62),
125 QColor(0x4E, 0x3C, 0x5C),
126 QColor(0x56, 0x3F, 0x54),
127 QColor(0x61, 0x31, 0x4D),
128 QColor(0x6B, 0x23, 0x37)
131 DecodeTrace::DecodeTrace(pv::Session &session,
132 shared_ptr<data::SignalBase> signalbase,
133 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
136 signalbase_(signalbase),
137 decoder_stack_(decoder_stack),
139 max_visible_rows_(0),
140 delete_mapper_(this),
141 show_hide_mapper_(this)
143 assert(decoder_stack_);
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 signalbase_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
150 signalbase_->set_colour(DecodeColours[index % countof(DecodeColours)]);
152 connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
153 this, SLOT(on_new_decode_data()));
154 connect(&delete_mapper_, SIGNAL(mapped(int)),
155 this, SLOT(on_delete_decoder(int)));
156 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
157 this, SLOT(on_show_hide_decoder(int)));
160 bool DecodeTrace::enabled() const
165 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
167 return decoder_stack_;
170 pair<int, int> DecodeTrace::v_extents() const
172 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
174 // Make an empty decode trace appear symmetrical
175 const int row_count = max(1, max_visible_rows_);
177 return make_pair(-row_height, row_height * row_count);
180 void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
182 Trace::paint_back(p, pp);
183 paint_axis(p, pp, get_visual_y());
186 void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
188 using namespace pv::data::decode;
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 assert(decoder_stack_);
195 const QString err = decoder_stack_->error_message();
196 if (!err.isEmpty()) {
197 draw_unresolved_period(
198 p, annotation_height, pp.left(), pp.right());
199 draw_error(p, err, pp);
203 // Set default pen to allow for text width calculation
206 // Iterate through the rows
207 int y = get_visual_y();
208 pair<uint64_t, uint64_t> sample_range = get_sample_range(
209 pp.left(), pp.right());
211 assert(decoder_stack_);
212 const vector<Row> rows(decoder_stack_->get_visible_rows());
214 visible_rows_.clear();
215 for (const Row& row : rows) {
216 // Cache the row title widths
219 row_title_width = row_title_widths_.at(row);
220 } catch (std::out_of_range) {
221 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
223 row_title_widths_[row] = w;
227 // Determine the row's color
228 size_t base_colour = 0x13579BDF;
229 boost::hash_combine(base_colour, this);
230 boost::hash_combine(base_colour, row.decoder());
231 boost::hash_combine(base_colour, row.row());
234 vector<Annotation> annotations;
235 decoder_stack_->get_annotation_subset(annotations, row,
236 sample_range.first, sample_range.second);
237 if (!annotations.empty()) {
238 draw_annotations(annotations, p, annotation_height, pp, y,
239 base_colour, row_title_width);
243 visible_rows_.push_back(row);
248 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
250 if ((int)visible_rows_.size() > max_visible_rows_)
251 owner_->extents_changed(false, true);
253 // Update the maximum row count if needed
254 max_visible_rows_ = std::max(max_visible_rows_, (int)visible_rows_.size());
257 void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
259 using namespace pv::data::decode;
263 for (size_t i = 0; i < visible_rows_.size(); i++) {
264 const int y = i * row_height_ + get_visual_y();
266 p.setPen(QPen(Qt::NoPen));
267 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
270 const QPointF points[] = {
271 QPointF(pp.left(), y - ArrowSize),
272 QPointF(pp.left() + ArrowSize, y),
273 QPointF(pp.left(), y + ArrowSize)
275 p.drawPolygon(points, countof(points));
278 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
279 pp.right() - pp.left(), row_height_);
280 const QString h(visible_rows_[i].title());
281 const int f = Qt::AlignLeft | Qt::AlignVCenter |
285 p.setPen(QApplication::palette().color(QPalette::Base));
286 for (int dx = -1; dx <= 1; dx++)
287 for (int dy = -1; dy <= 1; dy++)
288 if (dx != 0 && dy != 0)
289 p.drawText(r.translated(dx, dy), f, h);
292 p.setPen(QApplication::palette().color(QPalette::WindowText));
297 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
299 using pv::data::decode::Decoder;
303 assert(decoder_stack_);
305 // Add the standard options
306 Trace::populate_popup_form(parent, form);
308 // Add the decoder options
310 channel_selectors_.clear();
311 decoder_forms_.clear();
313 const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
316 QLabel *const l = new QLabel(
317 tr("<p><i>No decoders in the stack</i></p>"));
318 l->setAlignment(Qt::AlignCenter);
321 auto iter = stack.cbegin();
322 for (int i = 0; i < (int)stack.size(); i++, iter++) {
323 shared_ptr<Decoder> dec(*iter);
324 create_decoder_form(i, dec, parent, form);
327 form->addRow(new QLabel(
328 tr("<i>* Required channels</i>"), parent));
331 // Add stacking button
332 pv::widgets::DecoderMenu *const decoder_menu =
333 new pv::widgets::DecoderMenu(parent);
334 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
335 this, SLOT(on_stack_decoder(srd_decoder*)));
337 QPushButton *const stack_button =
338 new QPushButton(tr("Stack Decoder"), parent);
339 stack_button->setMenu(decoder_menu);
341 QHBoxLayout *stack_button_box = new QHBoxLayout;
342 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
343 form->addRow(stack_button_box);
346 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
348 QMenu *const menu = Trace::create_context_menu(parent);
350 menu->addSeparator();
352 QAction *const del = new QAction(tr("Delete"), this);
353 del->setShortcuts(QKeySequence::Delete);
354 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
355 menu->addAction(del);
360 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
361 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
362 size_t base_colour, int row_title_width)
364 using namespace pv::data::decode;
366 vector<Annotation> a_block;
369 double samples_per_pixel, pixels_offset;
370 tie(pixels_offset, samples_per_pixel) =
371 get_pixels_offset_samples_per_pixel();
373 // Sort the annotations by start sample so that decoders
374 // can't confuse us by creating annotations out of order
375 stable_sort(annotations.begin(), annotations.end(),
376 [](const Annotation &a, const Annotation &b) {
377 return a.start_sample() < b.start_sample(); });
379 // Gather all annotations that form a visual "block" and draw them as such
380 for (const Annotation &a : annotations) {
382 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
383 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
384 const int a_width = a_end - a_start;
386 const int delta = a_end - p_end;
388 bool a_is_separate = false;
390 // Annotation wider than the threshold for a useful label width?
391 if (a_width >= min_useful_label_width_) {
392 for (const QString &ann_text : a.annotations()) {
393 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
394 // Annotation wide enough to fit a label? Don't put it in a block then
396 a_is_separate = true;
402 // Were the previous and this annotation more than a pixel apart?
403 if ((abs(delta) > 1) || a_is_separate) {
404 // Block was broken, draw annotations that form the current block
405 if (a_block.size() == 1) {
406 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
410 draw_annotation_block(a_block, p, h, y, base_colour);
416 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
417 // Next annotation must start a new block. delta will be > 1
418 // because we set p_end to INT_MIN but that's okay since
419 // a_block will be empty, so nothing will be drawn
422 a_block.push_back(a);
427 if (a_block.size() == 1)
428 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
431 draw_annotation_block(a_block, p, h, y, base_colour);
434 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
435 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
436 size_t base_colour, int row_title_width) const
438 double samples_per_pixel, pixels_offset;
439 tie(pixels_offset, samples_per_pixel) =
440 get_pixels_offset_samples_per_pixel();
442 const double start = a.start_sample() / samples_per_pixel -
444 const double end = a.end_sample() / samples_per_pixel -
447 const size_t colour = (base_colour + a.format()) % countof(Colours);
448 p.setPen(OutlineColours[colour]);
449 p.setBrush(Colours[colour]);
451 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
454 if (a.start_sample() == a.end_sample())
455 draw_instant(a, p, h, start, y);
457 draw_range(a, p, h, start, end, y, pp,
461 void DecodeTrace::draw_annotation_block(
462 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
463 int y, size_t base_colour) const
465 using namespace pv::data::decode;
467 if (annotations.empty())
470 double samples_per_pixel, pixels_offset;
471 tie(pixels_offset, samples_per_pixel) =
472 get_pixels_offset_samples_per_pixel();
474 const double start = annotations.front().start_sample() /
475 samples_per_pixel - pixels_offset;
476 const double end = annotations.back().end_sample() /
477 samples_per_pixel - pixels_offset;
479 const double top = y + .5 - h / 2;
480 const double bottom = y + .5 + h / 2;
482 const size_t colour = (base_colour + annotations.front().format()) %
485 // Check if all annotations are of the same type (i.e. we can use one color)
486 // or if we should use a neutral color (i.e. gray)
487 const int format = annotations.front().format();
488 const bool single_format = std::all_of(
489 annotations.begin(), annotations.end(),
490 [&](const Annotation &a) { return a.format() == format; });
492 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
493 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
496 QRectF(start, top, end - start, bottom - top), h/4, h/4);
499 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
500 int h, double x, int y) const
502 const QString text = a.annotations().empty() ?
503 QString() : a.annotations().back();
504 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
506 const QRectF rect(x - w / 2, y - h / 2, w, h);
508 p.drawRoundedRect(rect, h / 2, h / 2);
511 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
514 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
515 int h, double start, double end, int y, const ViewItemPaintParams &pp,
516 int row_title_width) const
518 const double top = y + .5 - h / 2;
519 const double bottom = y + .5 + h / 2;
520 const vector<QString> annotations = a.annotations();
522 // If the two ends are within 1 pixel, draw a vertical line
523 if (start + 1.0 > end) {
524 p.drawLine(QPointF(start, top), QPointF(start, bottom));
528 const double cap_width = min((end - start) / 4, EndCapWidth);
531 QPointF(start, y + .5f),
532 QPointF(start + cap_width, top),
533 QPointF(end - cap_width, top),
534 QPointF(end, y + .5f),
535 QPointF(end - cap_width, bottom),
536 QPointF(start + cap_width, bottom)
539 p.drawConvexPolygon(pts, countof(pts));
541 if (annotations.empty())
544 const int ann_start = start + cap_width;
545 const int ann_end = end - cap_width;
547 const int real_start = std::max(ann_start, pp.left() + row_title_width);
548 const int real_end = std::min(ann_end, pp.right());
549 const int real_width = real_end - real_start;
551 QRectF rect(real_start, y - h / 2, real_width, h);
552 if (rect.width() <= 4)
557 // Try to find an annotation that will fit
558 QString best_annotation;
561 for (const QString &a : annotations) {
562 const int w = p.boundingRect(QRectF(), 0, a).width();
563 if (w <= rect.width() && w > best_width)
564 best_annotation = a, best_width = w;
567 if (best_annotation.isEmpty())
568 best_annotation = annotations.back();
570 // If not ellide the last in the list
571 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
572 best_annotation, Qt::ElideRight, rect.width()));
575 void DecodeTrace::draw_error(QPainter &p, const QString &message,
576 const ViewItemPaintParams &pp)
578 const int y = get_visual_y();
580 p.setPen(ErrorBgColour.darker());
581 p.setBrush(ErrorBgColour);
583 const QRectF bounding_rect =
584 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
585 const QRectF text_rect = p.boundingRect(bounding_rect,
586 Qt::AlignCenter, message);
587 const float r = text_rect.height() / 4;
589 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
593 p.drawText(text_rect, message);
596 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
599 using namespace pv::data;
600 using pv::data::decode::Decoder;
602 double samples_per_pixel, pixels_offset;
604 assert(decoder_stack_);
606 shared_ptr<Logic> data;
607 shared_ptr<data::SignalBase> signalbase;
609 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
611 // We get the logic data of the first channel in the list.
612 // This works because we are currently assuming all
613 // LogicSignals have the same data/segment
614 for (const shared_ptr<Decoder> &dec : stack)
615 if (dec && !dec->channels().empty() &&
616 ((signalbase = (*dec->channels().begin()).second)) &&
617 ((data = signalbase->logic_data())))
620 if (!data || data->logic_segments().empty())
623 const shared_ptr<LogicSegment> segment =
624 data->logic_segments().front();
626 const int64_t sample_count = (int64_t)segment->get_sample_count();
627 if (sample_count == 0)
630 const int64_t samples_decoded = decoder_stack_->samples_decoded();
631 if (sample_count == samples_decoded)
634 const int y = get_visual_y();
636 tie(pixels_offset, samples_per_pixel) =
637 get_pixels_offset_samples_per_pixel();
639 const double start = max(samples_decoded /
640 samples_per_pixel - pixels_offset, left - 1.0);
641 const double end = min(sample_count / samples_per_pixel -
642 pixels_offset, right + 1.0);
643 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
645 p.setPen(QPen(Qt::NoPen));
646 p.setBrush(Qt::white);
647 p.drawRect(no_decode_rect);
649 p.setPen(NoDecodeColour);
650 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
651 p.drawRect(no_decode_rect);
654 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
657 assert(decoder_stack_);
659 const View *view = owner_->view();
662 const double scale = view->scale();
665 const double pixels_offset =
666 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
668 double samplerate = decoder_stack_->samplerate();
670 // Show sample rate as 1Hz when it is unknown
671 if (samplerate == 0.0)
674 return make_pair(pixels_offset, samplerate * scale);
677 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
678 int x_start, int x_end) const
680 double samples_per_pixel, pixels_offset;
681 tie(pixels_offset, samples_per_pixel) =
682 get_pixels_offset_samples_per_pixel();
684 const uint64_t start = (uint64_t)max(
685 (x_start + pixels_offset) * samples_per_pixel, 0.0);
686 const uint64_t end = (uint64_t)max(
687 (x_end + pixels_offset) * samples_per_pixel, 0.0);
689 return make_pair(start, end);
692 int DecodeTrace::get_row_at_point(const QPoint &point)
697 const int y = (point.y() - get_visual_y() + row_height_ / 2);
699 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
703 const int row = y / row_height_;
705 if (row >= (int)visible_rows_.size())
711 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
713 using namespace pv::data::decode;
718 const pair<uint64_t, uint64_t> sample_range =
719 get_sample_range(point.x(), point.x() + 1);
720 const int row = get_row_at_point(point);
724 vector<pv::data::decode::Annotation> annotations;
726 assert(decoder_stack_);
727 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
728 sample_range.first, sample_range.second);
730 return (annotations.empty()) ?
731 QString() : annotations[0].annotations().front();
734 void DecodeTrace::hover_point_changed()
738 const View *const view = owner_->view();
741 QPoint hp = view->hover_point();
742 QString ann = get_annotation_at_point(hp);
746 if (!row_height_ || ann.isEmpty()) {
747 QToolTip::hideText();
751 const int hover_row = get_row_at_point(hp);
753 QFontMetrics m(QToolTip::font());
754 const QRect text_size = m.boundingRect(QRect(), 0, ann);
756 // This is OS-specific and unfortunately we can't query it, so
757 // use an approximation to at least try to minimize the error.
758 const int padding = 8;
760 // Make sure the tool tip doesn't overlap with the mouse cursor.
761 // If it did, the tool tip would constantly hide and re-appear.
762 // We also push it up by one row so that it appears above the
763 // decode trace, not below.
764 hp.setX(hp.x() - (text_size.width() / 2) - padding);
766 hp.setY(get_visual_y() - (row_height_ / 2) +
767 (hover_row * row_height_) -
768 row_height_ - text_size.height() - padding);
770 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
773 void DecodeTrace::create_decoder_form(int index,
774 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
780 const srd_decoder *const decoder = dec->decoder();
783 const bool decoder_deletable = index > 0;
785 pv::widgets::DecoderGroupBox *const group =
786 new pv::widgets::DecoderGroupBox(
787 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
788 group->set_decoder_visible(dec->shown());
790 if (decoder_deletable) {
791 delete_mapper_.setMapping(group, index);
792 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
795 show_hide_mapper_.setMapping(group, index);
796 connect(group, SIGNAL(show_hide_decoder()),
797 &show_hide_mapper_, SLOT(map()));
799 QFormLayout *const decoder_form = new QFormLayout;
800 group->add_layout(decoder_form);
802 // Add the mandatory channels
803 for (l = decoder->channels; l; l = l->next) {
804 const struct srd_channel *const pdch =
805 (struct srd_channel *)l->data;
806 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
807 connect(combo, SIGNAL(currentIndexChanged(int)),
808 this, SLOT(on_channel_selected(int)));
809 decoder_form->addRow(tr("<b>%1</b> (%2) *")
810 .arg(QString::fromUtf8(pdch->name),
811 QString::fromUtf8(pdch->desc)), combo);
813 const ChannelSelector s = {combo, dec, pdch};
814 channel_selectors_.push_back(s);
817 // Add the optional channels
818 for (l = decoder->opt_channels; l; l = l->next) {
819 const struct srd_channel *const pdch =
820 (struct srd_channel *)l->data;
821 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
822 connect(combo, SIGNAL(currentIndexChanged(int)),
823 this, SLOT(on_channel_selected(int)));
824 decoder_form->addRow(tr("<b>%1</b> (%2)")
825 .arg(QString::fromUtf8(pdch->name),
826 QString::fromUtf8(pdch->desc)), combo);
828 const ChannelSelector s = {combo, dec, pdch};
829 channel_selectors_.push_back(s);
833 shared_ptr<binding::Decoder> binding(
834 new binding::Decoder(decoder_stack_, dec));
835 binding->add_properties_to_form(decoder_form, true);
837 bindings_.push_back(binding);
840 decoder_forms_.push_back(group);
843 QComboBox* DecodeTrace::create_channel_selector(
844 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
845 const srd_channel *const pdch)
849 const auto sigs(session_.signals());
851 vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
852 std::sort(sig_list.begin(), sig_list.end(),
853 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
854 return strnatcasecmp(a->base()->name().toStdString(),
855 b->base()->name().toStdString()) < 0; });
857 assert(decoder_stack_);
858 const auto channel_iter = dec->channels().find(pdch);
860 QComboBox *selector = new QComboBox(parent);
862 selector->addItem("-", qVariantFromValue((void*)nullptr));
864 if (channel_iter == dec->channels().end())
865 selector->setCurrentIndex(0);
867 for (const shared_ptr<view::Signal> &s : sig_list) {
869 if (s->base()->type() == sigrok::ChannelType::LOGIC && s->enabled()) {
870 selector->addItem(s->base()->name(),
871 qVariantFromValue((void*)s->base().get()));
873 if (channel_iter != dec->channels().end() &&
874 (*channel_iter).second == s->base())
875 selector->setCurrentIndex(
876 selector->count() - 1);
883 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
887 map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
889 const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
891 for (const ChannelSelector &s : channel_selectors_) {
892 if (s.decoder_ != dec)
895 const data::SignalBase *const selection =
896 (data::SignalBase*)s.combo_->itemData(
897 s.combo_->currentIndex()).value<void*>();
899 for (shared_ptr<Signal> sig : sigs)
900 if (sig->base().get() == selection) {
901 channel_map[s.pdch_] = sig->base();
906 dec->set_channels(channel_map);
909 void DecodeTrace::commit_channels()
911 assert(decoder_stack_);
912 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
913 commit_decoder_channels(dec);
915 decoder_stack_->begin_decode();
918 void DecodeTrace::on_new_decode_data()
921 owner_->row_item_appearance_changed(false, true);
924 void DecodeTrace::delete_pressed()
929 void DecodeTrace::on_delete()
931 session_.remove_decode_signal(this);
934 void DecodeTrace::on_channel_selected(int)
939 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
942 assert(decoder_stack_);
943 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
944 new data::decode::Decoder(decoder)));
945 decoder_stack_->begin_decode();
950 void DecodeTrace::on_delete_decoder(int index)
952 decoder_stack_->remove(index);
957 decoder_stack_->begin_decode();
960 void DecodeTrace::on_show_hide_decoder(int index)
962 using pv::data::decode::Decoder;
964 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
966 // Find the decoder in the stack
967 auto iter = stack.cbegin();
968 for (int i = 0; i < index; i++, iter++)
969 assert(iter != stack.end());
971 shared_ptr<Decoder> dec = *iter;
974 const bool show = !dec->shown();
977 assert(index < (int)decoder_forms_.size());
978 decoder_forms_[index]->set_decoder_visible(show);
981 owner_->row_item_appearance_changed(false, true);