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 // Set default pen to allow for text width calculation
204 // Iterate through the rows
205 int y = get_visual_y();
206 pair<uint64_t, uint64_t> sample_range = get_sample_range(
207 pp.left(), pp.right());
209 assert(decoder_stack_);
210 const vector<Row> rows(decoder_stack_->get_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 (std::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 decoder_stack_->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_ = std::max(max_visible_rows_, (int)visible_rows_.size());
255 void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
257 using namespace pv::data::decode;
261 for (size_t i = 0; i < visible_rows_.size(); i++) {
262 const int y = i * row_height_ + get_visual_y();
264 p.setPen(QPen(Qt::NoPen));
265 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
268 const QPointF points[] = {
269 QPointF(pp.left(), y - ArrowSize),
270 QPointF(pp.left() + ArrowSize, y),
271 QPointF(pp.left(), y + ArrowSize)
273 p.drawPolygon(points, countof(points));
276 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
277 pp.right() - pp.left(), row_height_);
278 const QString h(visible_rows_[i].title());
279 const int f = Qt::AlignLeft | Qt::AlignVCenter |
283 p.setPen(QApplication::palette().color(QPalette::Base));
284 for (int dx = -1; dx <= 1; dx++)
285 for (int dy = -1; dy <= 1; dy++)
286 if (dx != 0 && dy != 0)
287 p.drawText(r.translated(dx, dy), f, h);
290 p.setPen(QApplication::palette().color(QPalette::WindowText));
295 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
297 using pv::data::decode::Decoder;
301 assert(decoder_stack_);
303 // Add the standard options
304 Trace::populate_popup_form(parent, form);
306 // Add the decoder options
308 channel_selectors_.clear();
309 decoder_forms_.clear();
311 const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
314 QLabel *const l = new QLabel(
315 tr("<p><i>No decoders in the stack</i></p>"));
316 l->setAlignment(Qt::AlignCenter);
319 auto iter = stack.cbegin();
320 for (int i = 0; i < (int)stack.size(); i++, iter++) {
321 shared_ptr<Decoder> dec(*iter);
322 create_decoder_form(i, dec, parent, form);
325 form->addRow(new QLabel(
326 tr("<i>* Required channels</i>"), parent));
329 // Add stacking button
330 pv::widgets::DecoderMenu *const decoder_menu =
331 new pv::widgets::DecoderMenu(parent);
332 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
333 this, SLOT(on_stack_decoder(srd_decoder*)));
335 QPushButton *const stack_button =
336 new QPushButton(tr("Stack Decoder"), parent);
337 stack_button->setMenu(decoder_menu);
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 -
445 const size_t colour = (base_colour + a.format()) % countof(Colours);
446 p.setPen(OutlineColours[colour]);
447 p.setBrush(Colours[colour]);
449 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
452 if (a.start_sample() == a.end_sample())
453 draw_instant(a, p, h, start, y);
455 draw_range(a, p, h, start, end, y, pp,
459 void DecodeTrace::draw_annotation_block(
460 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
461 int y, size_t base_colour) const
463 using namespace pv::data::decode;
465 if (annotations.empty())
468 double samples_per_pixel, pixels_offset;
469 tie(pixels_offset, samples_per_pixel) =
470 get_pixels_offset_samples_per_pixel();
472 const double start = annotations.front().start_sample() /
473 samples_per_pixel - pixels_offset;
474 const double end = annotations.back().end_sample() /
475 samples_per_pixel - pixels_offset;
477 const double top = y + .5 - h / 2;
478 const double bottom = y + .5 + h / 2;
480 const size_t colour = (base_colour + annotations.front().format()) %
483 // Check if all annotations are of the same type (i.e. we can use one color)
484 // or if we should use a neutral color (i.e. gray)
485 const int format = annotations.front().format();
486 const bool single_format = std::all_of(
487 annotations.begin(), annotations.end(),
488 [&](const Annotation &a) { return a.format() == format; });
490 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
491 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
494 QRectF(start, top, end - start, bottom - top), h/4, h/4);
497 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
498 int h, double x, int y) const
500 const QString text = a.annotations().empty() ?
501 QString() : a.annotations().back();
502 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
504 const QRectF rect(x - w / 2, y - h / 2, w, h);
506 p.drawRoundedRect(rect, h / 2, h / 2);
509 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
512 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
513 int h, double start, double end, int y, const ViewItemPaintParams &pp,
514 int row_title_width) const
516 const double top = y + .5 - h / 2;
517 const double bottom = y + .5 + h / 2;
518 const vector<QString> annotations = a.annotations();
520 // If the two ends are within 1 pixel, draw a vertical line
521 if (start + 1.0 > end) {
522 p.drawLine(QPointF(start, top), QPointF(start, bottom));
526 const double cap_width = min((end - start) / 4, EndCapWidth);
529 QPointF(start, y + .5f),
530 QPointF(start + cap_width, top),
531 QPointF(end - cap_width, top),
532 QPointF(end, y + .5f),
533 QPointF(end - cap_width, bottom),
534 QPointF(start + cap_width, bottom)
537 p.drawConvexPolygon(pts, countof(pts));
539 if (annotations.empty())
542 const int ann_start = start + cap_width;
543 const int ann_end = end - cap_width;
545 const int real_start = std::max(ann_start, pp.left() + row_title_width);
546 const int real_end = std::min(ann_end, pp.right());
547 const int real_width = real_end - real_start;
549 QRectF rect(real_start, y - h / 2, real_width, h);
550 if (rect.width() <= 4)
555 // Try to find an annotation that will fit
556 QString best_annotation;
559 for (const QString &a : annotations) {
560 const int w = p.boundingRect(QRectF(), 0, a).width();
561 if (w <= rect.width() && w > best_width)
562 best_annotation = a, best_width = w;
565 if (best_annotation.isEmpty())
566 best_annotation = annotations.back();
568 // If not ellide the last in the list
569 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
570 best_annotation, Qt::ElideRight, rect.width()));
573 void DecodeTrace::draw_error(QPainter &p, const QString &message,
574 const ViewItemPaintParams &pp)
576 const int y = get_visual_y();
578 p.setPen(ErrorBgColour.darker());
579 p.setBrush(ErrorBgColour);
581 const QRectF bounding_rect =
582 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
583 const QRectF text_rect = p.boundingRect(bounding_rect,
584 Qt::AlignCenter, message);
585 const float r = text_rect.height() / 4;
587 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
591 p.drawText(text_rect, message);
594 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
597 using namespace pv::data;
598 using pv::data::decode::Decoder;
600 double samples_per_pixel, pixels_offset;
602 assert(decoder_stack_);
604 shared_ptr<Logic> data;
605 shared_ptr<LogicSignal> logic_signal;
607 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
609 // We get the logic data of the first channel in the list.
610 // This works because we are currently assuming all
611 // LogicSignals have the same data/segment
612 for (const shared_ptr<Decoder> &dec : stack)
613 if (dec && !dec->channels().empty() &&
614 ((logic_signal = (*dec->channels().begin()).second)) &&
615 ((data = logic_signal->logic_data())))
618 if (!data || data->logic_segments().empty())
621 const shared_ptr<LogicSegment> segment =
622 data->logic_segments().front();
624 const int64_t sample_count = (int64_t)segment->get_sample_count();
625 if (sample_count == 0)
628 const int64_t samples_decoded = decoder_stack_->samples_decoded();
629 if (sample_count == samples_decoded)
632 const int y = get_visual_y();
634 tie(pixels_offset, samples_per_pixel) =
635 get_pixels_offset_samples_per_pixel();
637 const double start = max(samples_decoded /
638 samples_per_pixel - pixels_offset, left - 1.0);
639 const double end = min(sample_count / samples_per_pixel -
640 pixels_offset, right + 1.0);
641 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
643 p.setPen(QPen(Qt::NoPen));
644 p.setBrush(Qt::white);
645 p.drawRect(no_decode_rect);
647 p.setPen(NoDecodeColour);
648 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
649 p.drawRect(no_decode_rect);
652 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
655 assert(decoder_stack_);
657 const View *view = owner_->view();
660 const double scale = view->scale();
663 const double pixels_offset =
664 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
666 double samplerate = decoder_stack_->samplerate();
668 // Show sample rate as 1Hz when it is unknown
669 if (samplerate == 0.0)
672 return make_pair(pixels_offset, samplerate * scale);
675 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
676 int x_start, int x_end) const
678 double samples_per_pixel, pixels_offset;
679 tie(pixels_offset, samples_per_pixel) =
680 get_pixels_offset_samples_per_pixel();
682 const uint64_t start = (uint64_t)max(
683 (x_start + pixels_offset) * samples_per_pixel, 0.0);
684 const uint64_t end = (uint64_t)max(
685 (x_end + pixels_offset) * samples_per_pixel, 0.0);
687 return make_pair(start, end);
690 int DecodeTrace::get_row_at_point(const QPoint &point)
695 const int y = (point.y() - get_visual_y() + row_height_ / 2);
697 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
701 const int row = y / row_height_;
703 if (row >= (int)visible_rows_.size())
709 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
711 using namespace pv::data::decode;
716 const pair<uint64_t, uint64_t> sample_range =
717 get_sample_range(point.x(), point.x() + 1);
718 const int row = get_row_at_point(point);
722 vector<pv::data::decode::Annotation> annotations;
724 assert(decoder_stack_);
725 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
726 sample_range.first, sample_range.second);
728 return (annotations.empty()) ?
729 QString() : annotations[0].annotations().front();
732 void DecodeTrace::hover_point_changed()
736 const View *const view = owner_->view();
739 QPoint hp = view->hover_point();
740 QString ann = get_annotation_at_point(hp);
744 if (!row_height_ || ann.isEmpty()) {
745 QToolTip::hideText();
749 const int hover_row = get_row_at_point(hp);
751 QFontMetrics m(QToolTip::font());
752 const QRect text_size = m.boundingRect(QRect(), 0, ann);
754 // This is OS-specific and unfortunately we can't query it, so
755 // use an approximation to at least try to minimize the error.
756 const int padding = 8;
758 // Make sure the tool tip doesn't overlap with the mouse cursor.
759 // If it did, the tool tip would constantly hide and re-appear.
760 // We also push it up by one row so that it appears above the
761 // decode trace, not below.
762 hp.setX(hp.x() - (text_size.width() / 2) - padding);
764 hp.setY(get_visual_y() - (row_height_ / 2) +
765 (hover_row * row_height_) -
766 row_height_ - text_size.height() - padding);
768 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
771 void DecodeTrace::create_decoder_form(int index,
772 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
778 const srd_decoder *const decoder = dec->decoder();
781 const bool decoder_deletable = index > 0;
783 pv::widgets::DecoderGroupBox *const group =
784 new pv::widgets::DecoderGroupBox(
785 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
786 group->set_decoder_visible(dec->shown());
788 if (decoder_deletable) {
789 delete_mapper_.setMapping(group, index);
790 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
793 show_hide_mapper_.setMapping(group, index);
794 connect(group, SIGNAL(show_hide_decoder()),
795 &show_hide_mapper_, SLOT(map()));
797 QFormLayout *const decoder_form = new QFormLayout;
798 group->add_layout(decoder_form);
800 // Add the mandatory channels
801 for (l = decoder->channels; l; l = l->next) {
802 const struct srd_channel *const pdch =
803 (struct srd_channel *)l->data;
804 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
805 connect(combo, SIGNAL(currentIndexChanged(int)),
806 this, SLOT(on_channel_selected(int)));
807 decoder_form->addRow(tr("<b>%1</b> (%2) *")
808 .arg(QString::fromUtf8(pdch->name),
809 QString::fromUtf8(pdch->desc)), combo);
811 const ChannelSelector s = {combo, dec, pdch};
812 channel_selectors_.push_back(s);
815 // Add the optional channels
816 for (l = decoder->opt_channels; l; l = l->next) {
817 const struct srd_channel *const pdch =
818 (struct srd_channel *)l->data;
819 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
820 connect(combo, SIGNAL(currentIndexChanged(int)),
821 this, SLOT(on_channel_selected(int)));
822 decoder_form->addRow(tr("<b>%1</b> (%2)")
823 .arg(QString::fromUtf8(pdch->name),
824 QString::fromUtf8(pdch->desc)), combo);
826 const ChannelSelector s = {combo, dec, pdch};
827 channel_selectors_.push_back(s);
831 shared_ptr<binding::Decoder> binding(
832 new binding::Decoder(decoder_stack_, dec));
833 binding->add_properties_to_form(decoder_form, true);
835 bindings_.push_back(binding);
838 decoder_forms_.push_back(group);
841 QComboBox* DecodeTrace::create_channel_selector(
842 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
843 const srd_channel *const pdch)
847 const auto sigs(session_.signals());
849 vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
850 std::sort(sig_list.begin(), sig_list.end(),
851 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
852 return strnatcasecmp(a->name().toStdString(),
853 b->name().toStdString()) < 0; });
855 assert(decoder_stack_);
856 const auto channel_iter = dec->channels().find(pdch);
858 QComboBox *selector = new QComboBox(parent);
860 selector->addItem("-", qVariantFromValue((void*)nullptr));
862 if (channel_iter == dec->channels().end())
863 selector->setCurrentIndex(0);
865 for (const shared_ptr<view::Signal> &s : sig_list) {
867 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled()) {
868 selector->addItem(s->name(),
869 qVariantFromValue((void*)s.get()));
871 if (channel_iter != dec->channels().end() &&
872 (*channel_iter).second == s)
873 selector->setCurrentIndex(
874 selector->count() - 1);
881 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
885 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
887 const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
889 for (const ChannelSelector &s : channel_selectors_) {
890 if (s.decoder_ != dec)
893 const LogicSignal *const selection =
894 (LogicSignal*)s.combo_->itemData(
895 s.combo_->currentIndex()).value<void*>();
897 for (shared_ptr<Signal> sig : sigs)
898 if (sig.get() == selection) {
899 channel_map[s.pdch_] =
900 dynamic_pointer_cast<LogicSignal>(sig);
905 dec->set_channels(channel_map);
908 void DecodeTrace::commit_channels()
910 assert(decoder_stack_);
911 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
912 commit_decoder_channels(dec);
914 decoder_stack_->begin_decode();
917 void DecodeTrace::on_new_decode_data()
920 owner_->row_item_appearance_changed(false, true);
923 void DecodeTrace::delete_pressed()
928 void DecodeTrace::on_delete()
930 session_.remove_decode_signal(this);
933 void DecodeTrace::on_channel_selected(int)
938 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
941 assert(decoder_stack_);
942 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
943 new data::decode::Decoder(decoder)));
944 decoder_stack_->begin_decode();
949 void DecodeTrace::on_delete_decoder(int index)
951 decoder_stack_->remove(index);
956 decoder_stack_->begin_decode();
959 void DecodeTrace::on_show_hide_decoder(int index)
961 using pv::data::decode::Decoder;
963 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
965 // Find the decoder in the stack
966 auto iter = stack.cbegin();
967 for (int i = 0; i < index; i++, iter++)
968 assert(iter != stack.end());
970 shared_ptr<Decoder> dec = *iter;
973 const bool show = !dec->shown();
976 assert(index < (int)decoder_forms_.size());
977 decoder_forms_[index]->set_decoder_visible(show);
980 owner_->row_item_appearance_changed(false, true);