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/logicsignal.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 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
133 Trace(QString::fromUtf8(
134 decoder_stack->stack().front()->decoder()->name)),
136 decoder_stack_(decoder_stack),
138 max_visible_rows_(0),
139 delete_mapper_(this),
140 show_hide_mapper_(this)
142 assert(decoder_stack_);
144 // Determine shortest string we want to see displayed in full
145 QFontMetrics m(QApplication::font());
146 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
148 set_colour(DecodeColours[index % countof(DecodeColours)]);
150 connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
151 this, SLOT(on_new_decode_data()));
152 connect(&delete_mapper_, SIGNAL(mapped(int)),
153 this, SLOT(on_delete_decoder(int)));
154 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
155 this, SLOT(on_show_hide_decoder(int)));
158 bool DecodeTrace::enabled() const
163 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
165 return decoder_stack_;
168 pair<int, int> DecodeTrace::v_extents() const
170 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
172 // Make an empty decode trace appear symmetrical
173 const int row_count = max(1, max_visible_rows_);
175 return make_pair(-row_height, row_height * row_count);
178 void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
180 Trace::paint_back(p, pp);
181 paint_axis(p, pp, get_visual_y());
184 void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
186 using namespace pv::data::decode;
188 const int text_height = ViewItemPaintParams::text_height();
189 row_height_ = (text_height * 6) / 4;
190 const int annotation_height = (text_height * 5) / 4;
192 assert(decoder_stack_);
193 const QString err = decoder_stack_->error_message();
194 if (!err.isEmpty()) {
195 draw_unresolved_period(
196 p, annotation_height, pp.left(), pp.right());
197 draw_error(p, err, pp);
201 // Iterate through the rows
202 int y = get_visual_y();
203 pair<uint64_t, uint64_t> sample_range = get_sample_range(
204 pp.left(), pp.right());
206 assert(decoder_stack_);
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 (std::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 // Update the maximum row count if needed
246 max_visible_rows_ = std::max(max_visible_rows_, (int)visible_rows_.size());
249 void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
251 using namespace pv::data::decode;
255 for (size_t i = 0; i < visible_rows_.size(); i++) {
256 const int y = i * row_height_ + get_visual_y();
258 p.setPen(QPen(Qt::NoPen));
259 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
262 const QPointF points[] = {
263 QPointF(pp.left(), y - ArrowSize),
264 QPointF(pp.left() + ArrowSize, y),
265 QPointF(pp.left(), y + ArrowSize)
267 p.drawPolygon(points, countof(points));
270 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
271 pp.right() - pp.left(), row_height_);
272 const QString h(visible_rows_[i].title());
273 const int f = Qt::AlignLeft | Qt::AlignVCenter |
277 p.setPen(QApplication::palette().color(QPalette::Base));
278 for (int dx = -1; dx <= 1; dx++)
279 for (int dy = -1; dy <= 1; dy++)
280 if (dx != 0 && dy != 0)
281 p.drawText(r.translated(dx, dy), f, h);
284 p.setPen(QApplication::palette().color(QPalette::WindowText));
289 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
291 using pv::data::decode::Decoder;
295 assert(decoder_stack_);
297 // Add the standard options
298 Trace::populate_popup_form(parent, form);
300 // Add the decoder options
302 channel_selectors_.clear();
303 decoder_forms_.clear();
305 const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
308 QLabel *const l = new QLabel(
309 tr("<p><i>No decoders in the stack</i></p>"));
310 l->setAlignment(Qt::AlignCenter);
313 auto iter = stack.cbegin();
314 for (int i = 0; i < (int)stack.size(); i++, iter++) {
315 shared_ptr<Decoder> dec(*iter);
316 create_decoder_form(i, dec, parent, form);
319 form->addRow(new QLabel(
320 tr("<i>* Required channels</i>"), parent));
323 // Add stacking button
324 pv::widgets::DecoderMenu *const decoder_menu =
325 new pv::widgets::DecoderMenu(parent);
326 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
327 this, SLOT(on_stack_decoder(srd_decoder*)));
329 QPushButton *const stack_button =
330 new QPushButton(tr("Stack Decoder"), parent);
331 stack_button->setMenu(decoder_menu);
333 QHBoxLayout *stack_button_box = new QHBoxLayout;
334 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
335 form->addRow(stack_button_box);
338 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
340 QMenu *const menu = Trace::create_context_menu(parent);
342 menu->addSeparator();
344 QAction *const del = new QAction(tr("Delete"), this);
345 del->setShortcuts(QKeySequence::Delete);
346 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
347 menu->addAction(del);
352 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
353 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
354 size_t base_colour, int row_title_width)
356 using namespace pv::data::decode;
358 vector<Annotation> a_block;
361 double samples_per_pixel, pixels_offset;
362 tie(pixels_offset, samples_per_pixel) =
363 get_pixels_offset_samples_per_pixel();
365 // Sort the annotations by start sample so that decoders
366 // can't confuse us by creating annotations out of order
367 stable_sort(annotations.begin(), annotations.end(),
368 [](const Annotation &a, const Annotation &b) {
369 return a.start_sample() < b.start_sample(); });
371 // Gather all annotations that form a visual "block" and draw them as such
372 for (const Annotation &a : annotations) {
374 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
375 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
376 const int a_width = a_end - a_start;
378 const int delta = a_end - p_end;
380 bool a_is_separate = false;
382 // Annotation wider than the threshold for a useful label width?
383 if (a_width >= min_useful_label_width_) {
384 for (const QString &ann_text : a.annotations()) {
385 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
386 // Annotation wide enough to fit a label? Don't put it in a block then
388 a_is_separate = true;
394 // Were the previous and this annotation more than a pixel apart?
395 if ((abs(delta) > 1) || a_is_separate) {
396 // Block was broken, draw annotations that form the current block
397 if (a_block.size() == 1) {
398 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
402 draw_annotation_block(a_block, p, h, y, base_colour);
408 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
409 // Next annotation must start a new block. delta will be > 1
410 // because we set p_end to INT_MIN but that's okay since
411 // a_block will be empty, so nothing will be drawn
414 a_block.push_back(a);
419 if (a_block.size() == 1)
420 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
423 draw_annotation_block(a_block, p, h, y, base_colour);
426 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
427 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
428 size_t base_colour, int row_title_width) const
430 double samples_per_pixel, pixels_offset;
431 tie(pixels_offset, samples_per_pixel) =
432 get_pixels_offset_samples_per_pixel();
434 const double start = a.start_sample() / samples_per_pixel -
436 const double end = a.end_sample() / samples_per_pixel -
439 const size_t colour = (base_colour + a.format()) % countof(Colours);
440 p.setPen(OutlineColours[colour]);
441 p.setBrush(Colours[colour]);
443 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
446 if (a.start_sample() == a.end_sample())
447 draw_instant(a, p, h, start, y);
449 draw_range(a, p, h, start, end, y, pp,
453 void DecodeTrace::draw_annotation_block(
454 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
455 int y, size_t base_colour) const
457 using namespace pv::data::decode;
459 if (annotations.empty())
462 double samples_per_pixel, pixels_offset;
463 tie(pixels_offset, samples_per_pixel) =
464 get_pixels_offset_samples_per_pixel();
466 const double start = annotations.front().start_sample() /
467 samples_per_pixel - pixels_offset;
468 const double end = annotations.back().end_sample() /
469 samples_per_pixel - pixels_offset;
471 const double top = y + .5 - h / 2;
472 const double bottom = y + .5 + h / 2;
474 const size_t colour = (base_colour + annotations.front().format()) %
477 // Check if all annotations are of the same type (i.e. we can use one color)
478 // or if we should use a neutral color (i.e. gray)
479 const int format = annotations.front().format();
480 const bool single_format = std::all_of(
481 annotations.begin(), annotations.end(),
482 [&](const Annotation &a) { return a.format() == format; });
484 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
485 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
488 QRectF(start, top, end - start, bottom - top), h/4, h/4);
491 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
492 int h, double x, int y) const
494 const QString text = a.annotations().empty() ?
495 QString() : a.annotations().back();
496 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
498 const QRectF rect(x - w / 2, y - h / 2, w, h);
500 p.drawRoundedRect(rect, h / 2, h / 2);
503 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
506 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
507 int h, double start, double end, int y, const ViewItemPaintParams &pp,
508 int row_title_width) const
510 const double top = y + .5 - h / 2;
511 const double bottom = y + .5 + h / 2;
512 const vector<QString> annotations = a.annotations();
514 // If the two ends are within 1 pixel, draw a vertical line
515 if (start + 1.0 > end) {
516 p.drawLine(QPointF(start, top), QPointF(start, bottom));
520 const double cap_width = min((end - start) / 4, EndCapWidth);
523 QPointF(start, y + .5f),
524 QPointF(start + cap_width, top),
525 QPointF(end - cap_width, top),
526 QPointF(end, y + .5f),
527 QPointF(end - cap_width, bottom),
528 QPointF(start + cap_width, bottom)
531 p.drawConvexPolygon(pts, countof(pts));
533 if (annotations.empty())
536 const int ann_start = start + cap_width;
537 const int ann_end = end - cap_width;
539 const int real_start = std::max(ann_start, pp.left() + row_title_width);
540 const int real_end = std::min(ann_end, pp.right());
541 const int real_width = real_end - real_start;
543 QRectF rect(real_start, y - h / 2, real_width, h);
544 if (rect.width() <= 4)
549 // Try to find an annotation that will fit
550 QString best_annotation;
553 for (const QString &a : annotations) {
554 const int w = p.boundingRect(QRectF(), 0, a).width();
555 if (w <= rect.width() && w > best_width)
556 best_annotation = a, best_width = w;
559 if (best_annotation.isEmpty())
560 best_annotation = annotations.back();
562 // If not ellide the last in the list
563 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
564 best_annotation, Qt::ElideRight, rect.width()));
567 void DecodeTrace::draw_error(QPainter &p, const QString &message,
568 const ViewItemPaintParams &pp)
570 const int y = get_visual_y();
572 p.setPen(ErrorBgColour.darker());
573 p.setBrush(ErrorBgColour);
575 const QRectF bounding_rect =
576 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
577 const QRectF text_rect = p.boundingRect(bounding_rect,
578 Qt::AlignCenter, message);
579 const float r = text_rect.height() / 4;
581 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
585 p.drawText(text_rect, message);
588 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
591 using namespace pv::data;
592 using pv::data::decode::Decoder;
594 double samples_per_pixel, pixels_offset;
596 assert(decoder_stack_);
598 shared_ptr<Logic> data;
599 shared_ptr<LogicSignal> logic_signal;
601 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
603 // We get the logic data of the first channel in the list.
604 // This works because we are currently assuming all
605 // LogicSignals have the same data/segment
606 for (const shared_ptr<Decoder> &dec : stack)
607 if (dec && !dec->channels().empty() &&
608 ((logic_signal = (*dec->channels().begin()).second)) &&
609 ((data = logic_signal->logic_data())))
612 if (!data || data->logic_segments().empty())
615 const shared_ptr<LogicSegment> segment =
616 data->logic_segments().front();
618 const int64_t sample_count = (int64_t)segment->get_sample_count();
619 if (sample_count == 0)
622 const int64_t samples_decoded = decoder_stack_->samples_decoded();
623 if (sample_count == samples_decoded)
626 const int y = get_visual_y();
628 tie(pixels_offset, samples_per_pixel) =
629 get_pixels_offset_samples_per_pixel();
631 const double start = max(samples_decoded /
632 samples_per_pixel - pixels_offset, left - 1.0);
633 const double end = min(sample_count / samples_per_pixel -
634 pixels_offset, right + 1.0);
635 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
637 p.setPen(QPen(Qt::NoPen));
638 p.setBrush(Qt::white);
639 p.drawRect(no_decode_rect);
641 p.setPen(NoDecodeColour);
642 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
643 p.drawRect(no_decode_rect);
646 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
649 assert(decoder_stack_);
651 const View *view = owner_->view();
654 const double scale = view->scale();
657 const double pixels_offset =
658 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
660 double samplerate = decoder_stack_->samplerate();
662 // Show sample rate as 1Hz when it is unknown
663 if (samplerate == 0.0)
666 return make_pair(pixels_offset, samplerate * scale);
669 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
670 int x_start, int x_end) const
672 double samples_per_pixel, pixels_offset;
673 tie(pixels_offset, samples_per_pixel) =
674 get_pixels_offset_samples_per_pixel();
676 const uint64_t start = (uint64_t)max(
677 (x_start + pixels_offset) * samples_per_pixel, 0.0);
678 const uint64_t end = (uint64_t)max(
679 (x_end + pixels_offset) * samples_per_pixel, 0.0);
681 return make_pair(start, end);
684 int DecodeTrace::get_row_at_point(const QPoint &point)
689 const int y = (point.y() - get_visual_y() + row_height_ / 2);
691 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
695 const int row = y / row_height_;
697 if (row >= (int)visible_rows_.size())
703 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
705 using namespace pv::data::decode;
710 const pair<uint64_t, uint64_t> sample_range =
711 get_sample_range(point.x(), point.x() + 1);
712 const int row = get_row_at_point(point);
716 vector<pv::data::decode::Annotation> annotations;
718 assert(decoder_stack_);
719 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
720 sample_range.first, sample_range.second);
722 return (annotations.empty()) ?
723 QString() : annotations[0].annotations().front();
726 void DecodeTrace::hover_point_changed()
730 const View *const view = owner_->view();
733 QPoint hp = view->hover_point();
734 QString ann = get_annotation_at_point(hp);
738 if (!row_height_ || ann.isEmpty()) {
739 QToolTip::hideText();
743 const int hover_row = get_row_at_point(hp);
745 QFontMetrics m(QToolTip::font());
746 const QRect text_size = m.boundingRect(QRect(), 0, ann);
748 // This is OS-specific and unfortunately we can't query it, so
749 // use an approximation to at least try to minimize the error.
750 const int padding = 8;
752 // Make sure the tool tip doesn't overlap with the mouse cursor.
753 // If it did, the tool tip would constantly hide and re-appear.
754 // We also push it up by one row so that it appears above the
755 // decode trace, not below.
756 hp.setX(hp.x() - (text_size.width() / 2) - padding);
758 hp.setY(get_visual_y() - (row_height_ / 2) +
759 (hover_row * row_height_) -
760 row_height_ - text_size.height() - padding);
762 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
765 void DecodeTrace::create_decoder_form(int index,
766 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
772 const srd_decoder *const decoder = dec->decoder();
775 const bool decoder_deletable = index > 0;
777 pv::widgets::DecoderGroupBox *const group =
778 new pv::widgets::DecoderGroupBox(
779 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
780 group->set_decoder_visible(dec->shown());
782 if (decoder_deletable) {
783 delete_mapper_.setMapping(group, index);
784 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
787 show_hide_mapper_.setMapping(group, index);
788 connect(group, SIGNAL(show_hide_decoder()),
789 &show_hide_mapper_, SLOT(map()));
791 QFormLayout *const decoder_form = new QFormLayout;
792 group->add_layout(decoder_form);
794 // Add the mandatory channels
795 for (l = decoder->channels; l; l = l->next) {
796 const struct srd_channel *const pdch =
797 (struct srd_channel *)l->data;
798 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
799 connect(combo, SIGNAL(currentIndexChanged(int)),
800 this, SLOT(on_channel_selected(int)));
801 decoder_form->addRow(tr("<b>%1</b> (%2) *")
802 .arg(QString::fromUtf8(pdch->name),
803 QString::fromUtf8(pdch->desc)), combo);
805 const ChannelSelector s = {combo, dec, pdch};
806 channel_selectors_.push_back(s);
809 // Add the optional channels
810 for (l = decoder->opt_channels; l; l = l->next) {
811 const struct srd_channel *const pdch =
812 (struct srd_channel *)l->data;
813 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
814 connect(combo, SIGNAL(currentIndexChanged(int)),
815 this, SLOT(on_channel_selected(int)));
816 decoder_form->addRow(tr("<b>%1</b> (%2)")
817 .arg(QString::fromUtf8(pdch->name),
818 QString::fromUtf8(pdch->desc)), combo);
820 const ChannelSelector s = {combo, dec, pdch};
821 channel_selectors_.push_back(s);
825 shared_ptr<binding::Decoder> binding(
826 new binding::Decoder(decoder_stack_, dec));
827 binding->add_properties_to_form(decoder_form, true);
829 bindings_.push_back(binding);
832 decoder_forms_.push_back(group);
835 QComboBox* DecodeTrace::create_channel_selector(
836 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
837 const srd_channel *const pdch)
841 const auto sigs(session_.signals());
843 vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
844 std::sort(sig_list.begin(), sig_list.end(),
845 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
846 return strnatcasecmp(a->name().toStdString(),
847 b->name().toStdString()) < 0; });
849 assert(decoder_stack_);
850 const auto channel_iter = dec->channels().find(pdch);
852 QComboBox *selector = new QComboBox(parent);
854 selector->addItem("-", qVariantFromValue((void*)nullptr));
856 if (channel_iter == dec->channels().end())
857 selector->setCurrentIndex(0);
859 for (const shared_ptr<view::Signal> &s : sig_list) {
861 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled()) {
862 selector->addItem(s->name(),
863 qVariantFromValue((void*)s.get()));
865 if (channel_iter != dec->channels().end() &&
866 (*channel_iter).second == s)
867 selector->setCurrentIndex(
868 selector->count() - 1);
875 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
879 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
881 const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
883 for (const ChannelSelector &s : channel_selectors_) {
884 if (s.decoder_ != dec)
887 const LogicSignal *const selection =
888 (LogicSignal*)s.combo_->itemData(
889 s.combo_->currentIndex()).value<void*>();
891 for (shared_ptr<Signal> sig : sigs)
892 if (sig.get() == selection) {
893 channel_map[s.pdch_] =
894 dynamic_pointer_cast<LogicSignal>(sig);
899 dec->set_channels(channel_map);
902 void DecodeTrace::commit_channels()
904 assert(decoder_stack_);
905 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
906 commit_decoder_channels(dec);
908 decoder_stack_->begin_decode();
911 void DecodeTrace::on_new_decode_data()
914 owner_->row_item_appearance_changed(false, true);
917 void DecodeTrace::delete_pressed()
922 void DecodeTrace::on_delete()
924 session_.remove_decode_signal(this);
927 void DecodeTrace::on_channel_selected(int)
932 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
935 assert(decoder_stack_);
936 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
937 new data::decode::Decoder(decoder)));
938 decoder_stack_->begin_decode();
943 void DecodeTrace::on_delete_decoder(int index)
945 decoder_stack_->remove(index);
950 decoder_stack_->begin_decode();
953 void DecodeTrace::on_show_hide_decoder(int index)
955 using pv::data::decode::Decoder;
957 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
959 // Find the decoder in the stack
960 auto iter = stack.cbegin();
961 for (int i = 0; i < index; i++, iter++)
962 assert(iter != stack.end());
964 shared_ptr<Decoder> dec = *iter;
967 const bool show = !dec->shown();
970 assert(index < (int)decoder_forms_.size());
971 decoder_forms_[index]->set_decoder_visible(show);
974 owner_->row_item_appearance_changed(false, true);