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/logic.hpp>
52 #include <pv/data/logicsegment.hpp>
53 #include <pv/widgets/decodergroupbox.hpp>
54 #include <pv/widgets/decodermenu.hpp>
60 using std::out_of_range;
62 using std::shared_ptr;
66 using pv::data::decode::Annotation;
67 using pv::data::decode::Row;
68 using pv::data::DecodeChannel;
69 using pv::data::DecodeSignal;
75 const QColor DecodeTrace::DecodeColours[4] = {
76 QColor(0xEF, 0x29, 0x29), // Red
77 QColor(0xFC, 0xE9, 0x4F), // Yellow
78 QColor(0x8A, 0xE2, 0x34), // Green
79 QColor(0x72, 0x9F, 0xCF) // Blue
82 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
83 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
85 const int DecodeTrace::ArrowSize = 4;
86 const double DecodeTrace::EndCapWidth = 5;
87 const int DecodeTrace::RowTitleMargin = 10;
88 const int DecodeTrace::DrawPadding = 100;
90 const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz
92 const QColor DecodeTrace::Colours[16] = {
93 QColor(0xEF, 0x29, 0x29),
94 QColor(0xF6, 0x6A, 0x32),
95 QColor(0xFC, 0xAE, 0x3E),
96 QColor(0xFB, 0xCA, 0x47),
97 QColor(0xFC, 0xE9, 0x4F),
98 QColor(0xCD, 0xF0, 0x40),
99 QColor(0x8A, 0xE2, 0x34),
100 QColor(0x4E, 0xDC, 0x44),
101 QColor(0x55, 0xD7, 0x95),
102 QColor(0x64, 0xD1, 0xD2),
103 QColor(0x72, 0x9F, 0xCF),
104 QColor(0xD4, 0x76, 0xC4),
105 QColor(0x9D, 0x79, 0xB9),
106 QColor(0xAD, 0x7F, 0xA8),
107 QColor(0xC2, 0x62, 0x9B),
108 QColor(0xD7, 0x47, 0x6F)
111 const QColor DecodeTrace::OutlineColours[16] = {
112 QColor(0x77, 0x14, 0x14),
113 QColor(0x7B, 0x35, 0x19),
114 QColor(0x7E, 0x57, 0x1F),
115 QColor(0x7D, 0x65, 0x23),
116 QColor(0x7E, 0x74, 0x27),
117 QColor(0x66, 0x78, 0x20),
118 QColor(0x45, 0x71, 0x1A),
119 QColor(0x27, 0x6E, 0x22),
120 QColor(0x2A, 0x6B, 0x4A),
121 QColor(0x32, 0x68, 0x69),
122 QColor(0x39, 0x4F, 0x67),
123 QColor(0x6A, 0x3B, 0x62),
124 QColor(0x4E, 0x3C, 0x5C),
125 QColor(0x56, 0x3F, 0x54),
126 QColor(0x61, 0x31, 0x4D),
127 QColor(0x6B, 0x23, 0x37)
130 DecodeTrace::DecodeTrace(pv::Session &session,
131 shared_ptr<data::SignalBase> signalbase, int index) :
135 max_visible_rows_(0),
136 delete_mapper_(this),
137 show_hide_mapper_(this)
139 decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
141 // Determine shortest string we want to see displayed in full
142 QFontMetrics m(QApplication::font());
143 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
145 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
147 connect(decode_signal_.get(), SIGNAL(new_annotations()),
148 this, SLOT(on_new_annotations()));
149 connect(decode_signal_.get(), SIGNAL(decode_finished()),
150 this, SLOT(on_decode_finished()));
151 connect(decode_signal_.get(), SIGNAL(channels_updated()),
152 this, SLOT(on_channels_updated()));
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)));
159 connect(&delayed_trace_updater_, SIGNAL(timeout()),
160 this, SLOT(on_delayed_trace_update()));
161 delayed_trace_updater_.setSingleShot(true);
162 delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
165 bool DecodeTrace::enabled() const
170 shared_ptr<data::SignalBase> DecodeTrace::base() const
175 pair<int, int> DecodeTrace::v_extents() const
177 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
179 // Make an empty decode trace appear symmetrical
180 const int row_count = max(1, max_visible_rows_);
182 return make_pair(-row_height, row_height * row_count);
185 void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
187 Trace::paint_back(p, pp);
188 paint_axis(p, pp, get_visual_y());
191 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
193 const int text_height = ViewItemPaintParams::text_height();
194 row_height_ = (text_height * 6) / 4;
195 const int annotation_height = (text_height * 5) / 4;
197 const QString err = decode_signal_->error_message();
198 if (!err.isEmpty()) {
199 draw_unresolved_period(
200 p, annotation_height, pp.left(), pp.right());
201 draw_error(p, err, pp);
205 // Set default pen to allow for text width calculation
208 // Iterate through the rows
209 int y = get_visual_y();
210 pair<uint64_t, uint64_t> sample_range = get_sample_range(
211 pp.left(), pp.right());
213 const vector<Row> rows = decode_signal_->visible_rows();
215 visible_rows_.clear();
216 for (const Row& row : rows) {
217 // Cache the row title widths
220 row_title_width = row_title_widths_.at(row);
221 } catch (out_of_range) {
222 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
224 row_title_widths_[row] = w;
228 // Determine the row's color
229 size_t base_colour = 0x13579BDF;
230 boost::hash_combine(base_colour, this);
231 boost::hash_combine(base_colour, row.decoder());
232 boost::hash_combine(base_colour, row.row());
235 vector<Annotation> annotations;
236 decode_signal_->get_annotation_subset(annotations, row,
237 sample_range.first, sample_range.second);
238 if (!annotations.empty()) {
239 draw_annotations(annotations, p, annotation_height, pp, y,
240 base_colour, row_title_width);
244 visible_rows_.push_back(row);
249 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
251 if ((int)visible_rows_.size() > max_visible_rows_) {
252 max_visible_rows_ = (int)visible_rows_.size();
254 // Call order is important, otherwise the lazy event handler won't work
255 owner_->extents_changed(false, true);
256 owner_->row_item_appearance_changed(false, true);
260 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
264 for (size_t i = 0; i < visible_rows_.size(); i++) {
265 const int y = i * row_height_ + get_visual_y();
267 p.setPen(QPen(Qt::NoPen));
268 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
271 const QPointF points[] = {
272 QPointF(pp.left(), y - ArrowSize),
273 QPointF(pp.left() + ArrowSize, y),
274 QPointF(pp.left(), y + ArrowSize)
276 p.drawPolygon(points, countof(points));
279 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
280 pp.right() - pp.left(), row_height_);
281 const QString h(visible_rows_[i].title());
282 const int f = Qt::AlignLeft | Qt::AlignVCenter |
286 p.setPen(QApplication::palette().color(QPalette::Base));
287 for (int dx = -1; dx <= 1; dx++)
288 for (int dy = -1; dy <= 1; dy++)
289 if (dx != 0 && dy != 0)
290 p.drawText(r.translated(dx, dy), f, h);
293 p.setPen(QApplication::palette().color(QPalette::WindowText));
298 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
300 using pv::data::decode::Decoder;
304 // Add the standard options
305 Trace::populate_popup_form(parent, form);
307 // Add the decoder options
309 channel_id_map_.clear();
310 init_state_map_.clear();
311 decoder_forms_.clear();
313 const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_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);
340 stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
342 QHBoxLayout *stack_button_box = new QHBoxLayout;
343 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
344 form->addRow(stack_button_box);
347 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
349 QMenu *const menu = Trace::create_context_menu(parent);
351 menu->addSeparator();
353 QAction *const del = new QAction(tr("Delete"), this);
354 del->setShortcuts(QKeySequence::Delete);
355 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
356 menu->addAction(del);
361 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
362 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
363 size_t base_colour, int row_title_width)
365 using namespace pv::data::decode;
367 vector<Annotation> a_block;
370 double samples_per_pixel, pixels_offset;
371 tie(pixels_offset, samples_per_pixel) =
372 get_pixels_offset_samples_per_pixel();
374 // Sort the annotations by start sample so that decoders
375 // can't confuse us by creating annotations out of order
376 stable_sort(annotations.begin(), annotations.end(),
377 [](const Annotation &a, const Annotation &b) {
378 return a.start_sample() < b.start_sample(); });
380 // Gather all annotations that form a visual "block" and draw them as such
381 for (const Annotation &a : annotations) {
383 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
384 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
385 const int a_width = a_end - a_start;
387 const int delta = a_end - p_end;
389 bool a_is_separate = false;
391 // Annotation wider than the threshold for a useful label width?
392 if (a_width >= min_useful_label_width_) {
393 for (const QString &ann_text : a.annotations()) {
394 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
395 // Annotation wide enough to fit a label? Don't put it in a block then
397 a_is_separate = true;
403 // Were the previous and this annotation more than a pixel apart?
404 if ((abs(delta) > 1) || a_is_separate) {
405 // Block was broken, draw annotations that form the current block
406 if (a_block.size() == 1) {
407 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
411 draw_annotation_block(a_block, p, h, y, base_colour);
417 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
418 // Next annotation must start a new block. delta will be > 1
419 // because we set p_end to INT_MIN but that's okay since
420 // a_block will be empty, so nothing will be drawn
423 a_block.push_back(a);
428 if (a_block.size() == 1)
429 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
432 draw_annotation_block(a_block, p, h, y, base_colour);
435 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
436 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
437 size_t base_colour, int row_title_width) const
439 double samples_per_pixel, pixels_offset;
440 tie(pixels_offset, samples_per_pixel) =
441 get_pixels_offset_samples_per_pixel();
443 const double start = a.start_sample() / samples_per_pixel -
445 const double end = a.end_sample() / samples_per_pixel - pixels_offset;
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, row_title_width);
460 void DecodeTrace::draw_annotation_block(
461 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
462 int y, size_t base_colour) const
464 using namespace pv::data::decode;
466 if (annotations.empty())
469 double samples_per_pixel, pixels_offset;
470 tie(pixels_offset, samples_per_pixel) =
471 get_pixels_offset_samples_per_pixel();
473 const double start = annotations.front().start_sample() /
474 samples_per_pixel - pixels_offset;
475 const double end = annotations.back().end_sample() /
476 samples_per_pixel - pixels_offset;
478 const double top = y + .5 - h / 2;
479 const double bottom = y + .5 + h / 2;
481 const size_t colour = (base_colour + annotations.front().format()) %
484 // Check if all annotations are of the same type (i.e. we can use one color)
485 // or if we should use a neutral color (i.e. gray)
486 const int format = annotations.front().format();
487 const bool single_format = all_of(
488 annotations.begin(), annotations.end(),
489 [&](const Annotation &a) { return a.format() == format; });
491 const QRectF rect(start, top, end - start, bottom - top);
494 p.setPen(QPen(Qt::NoPen));
495 p.setBrush(Qt::white);
496 p.drawRoundedRect(rect, r, r);
498 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
499 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
501 p.drawRoundedRect(rect, r, r);
504 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
505 int h, double x, int y) const
507 const QString text = a.annotations().empty() ?
508 QString() : a.annotations().back();
509 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
511 const QRectF rect(x - w / 2, y - h / 2, w, h);
513 p.drawRoundedRect(rect, h / 2, h / 2);
516 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
519 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
520 int h, double start, double end, int y, const ViewItemPaintParams &pp,
521 int row_title_width) const
523 const double top = y + .5 - h / 2;
524 const double bottom = y + .5 + h / 2;
525 const vector<QString> annotations = a.annotations();
527 // If the two ends are within 1 pixel, draw a vertical line
528 if (start + 1.0 > end) {
529 p.drawLine(QPointF(start, top), QPointF(start, bottom));
533 const double cap_width = min((end - start) / 4, EndCapWidth);
536 QPointF(start, y + .5f),
537 QPointF(start + cap_width, top),
538 QPointF(end - cap_width, top),
539 QPointF(end, y + .5f),
540 QPointF(end - cap_width, bottom),
541 QPointF(start + cap_width, bottom)
544 p.drawConvexPolygon(pts, countof(pts));
546 if (annotations.empty())
549 const int ann_start = start + cap_width;
550 const int ann_end = end - cap_width;
552 const int real_start = max(ann_start, pp.left() + row_title_width);
553 const int real_end = min(ann_end, pp.right());
554 const int real_width = real_end - real_start;
556 QRectF rect(real_start, y - h / 2, real_width, h);
557 if (rect.width() <= 4)
562 // Try to find an annotation that will fit
563 QString best_annotation;
566 for (const QString &a : annotations) {
567 const int w = p.boundingRect(QRectF(), 0, a).width();
568 if (w <= rect.width() && w > best_width)
569 best_annotation = a, best_width = w;
572 if (best_annotation.isEmpty())
573 best_annotation = annotations.back();
575 // If not ellide the last in the list
576 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
577 best_annotation, Qt::ElideRight, rect.width()));
580 void DecodeTrace::draw_error(QPainter &p, const QString &message,
581 const ViewItemPaintParams &pp)
583 const int y = get_visual_y();
585 p.setPen(ErrorBgColour.darker());
586 p.setBrush(ErrorBgColour);
588 const QRectF bounding_rect =
589 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
590 const QRectF text_rect = p.boundingRect(bounding_rect,
591 Qt::AlignCenter, message);
592 const float r = text_rect.height() / 4;
594 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
598 p.drawText(text_rect, message);
601 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
603 using namespace pv::data;
604 using pv::data::decode::Decoder;
606 double samples_per_pixel, pixels_offset;
608 const int64_t sample_count = decode_signal_->get_working_sample_count();
609 if (sample_count == 0)
612 const int64_t samples_decoded = decode_signal_->get_decoded_sample_count();
613 if (sample_count == samples_decoded)
616 const int y = get_visual_y();
618 tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
620 const double start = max(samples_decoded /
621 samples_per_pixel - pixels_offset, left - 1.0);
622 const double end = min(sample_count / samples_per_pixel -
623 pixels_offset, right + 1.0);
624 const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
626 p.setPen(QPen(Qt::NoPen));
627 p.setBrush(Qt::white);
628 p.drawRect(no_decode_rect);
630 p.setPen(NoDecodeColour);
631 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
632 p.drawRect(no_decode_rect);
635 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
639 const View *view = owner_->view();
642 const double scale = view->scale();
645 const double pixels_offset =
646 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
648 double samplerate = decode_signal_->samplerate();
650 // Show sample rate as 1Hz when it is unknown
651 if (samplerate == 0.0)
654 return make_pair(pixels_offset, samplerate * scale);
657 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
658 int x_start, int x_end) const
660 double samples_per_pixel, pixels_offset;
661 tie(pixels_offset, samples_per_pixel) =
662 get_pixels_offset_samples_per_pixel();
664 const uint64_t start = (uint64_t)max(
665 (x_start + pixels_offset) * samples_per_pixel, 0.0);
666 const uint64_t end = (uint64_t)max(
667 (x_end + pixels_offset) * samples_per_pixel, 0.0);
669 return make_pair(start, end);
672 int DecodeTrace::get_row_at_point(const QPoint &point)
677 const int y = (point.y() - get_visual_y() + row_height_ / 2);
679 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
683 const int row = y / row_height_;
685 if (row >= (int)visible_rows_.size())
691 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
693 using namespace pv::data::decode;
698 const pair<uint64_t, uint64_t> sample_range =
699 get_sample_range(point.x(), point.x() + 1);
700 const int row = get_row_at_point(point);
704 vector<pv::data::decode::Annotation> annotations;
706 decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
707 sample_range.first, sample_range.second);
709 return (annotations.empty()) ?
710 QString() : annotations[0].annotations().front();
713 void DecodeTrace::hover_point_changed()
717 const View *const view = owner_->view();
720 QPoint hp = view->hover_point();
723 QToolTip::hideText();
727 QString ann = get_annotation_at_point(hp);
731 if (!row_height_ || ann.isEmpty()) {
732 QToolTip::hideText();
736 const int hover_row = get_row_at_point(hp);
738 QFontMetrics m(QToolTip::font());
739 const QRect text_size = m.boundingRect(QRect(), 0, ann);
741 // This is OS-specific and unfortunately we can't query it, so
742 // use an approximation to at least try to minimize the error.
743 const int padding = 8;
745 // Make sure the tool tip doesn't overlap with the mouse cursor.
746 // If it did, the tool tip would constantly hide and re-appear.
747 // We also push it up by one row so that it appears above the
748 // decode trace, not below.
749 hp.setX(hp.x() - (text_size.width() / 2) - padding);
751 hp.setY(get_visual_y() - (row_height_ / 2) +
752 (hover_row * row_height_) -
753 row_height_ - text_size.height() - padding);
755 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
758 void DecodeTrace::create_decoder_form(int index,
759 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
762 GlobalSettings settings;
765 const srd_decoder *const decoder = dec->decoder();
768 const bool decoder_deletable = index > 0;
770 pv::widgets::DecoderGroupBox *const group =
771 new pv::widgets::DecoderGroupBox(
772 QString::fromUtf8(decoder->name),
773 tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
774 QString::fromUtf8(decoder->desc)),
775 nullptr, decoder_deletable);
776 group->set_decoder_visible(dec->shown());
778 if (decoder_deletable) {
779 delete_mapper_.setMapping(group, index);
780 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
783 show_hide_mapper_.setMapping(group, index);
784 connect(group, SIGNAL(show_hide_decoder()),
785 &show_hide_mapper_, SLOT(map()));
787 QFormLayout *const decoder_form = new QFormLayout;
788 group->add_layout(decoder_form);
790 const vector<DecodeChannel> channels = decode_signal_->get_channels();
793 for (DecodeChannel ch : channels) {
794 // Ignore channels not part of the decoder we create the form for
795 if (ch.decoder_ != dec)
798 QComboBox *const combo = create_channel_selector(parent, &ch);
799 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
801 channel_id_map_[combo] = ch.id;
802 init_state_map_[combo_init_state] = ch.id;
804 connect(combo, SIGNAL(currentIndexChanged(int)),
805 this, SLOT(on_channel_selected(int)));
806 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
807 this, SLOT(on_init_state_changed(int)));
809 QHBoxLayout *const hlayout = new QHBoxLayout;
810 hlayout->addWidget(combo);
811 hlayout->addWidget(combo_init_state);
813 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
814 combo_init_state->hide();
816 const QString required_flag = ch.is_optional ? QString() : QString("*");
817 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
818 .arg(ch.name, ch.desc, required_flag), hlayout);
822 shared_ptr<binding::Decoder> binding(
823 new binding::Decoder(decode_signal_, dec));
824 binding->add_properties_to_form(decoder_form, true);
826 bindings_.push_back(binding);
829 decoder_forms_.push_back(group);
832 QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
834 const auto sigs(session_.signalbases());
836 // Sort signals in natural order
837 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
838 sort(sig_list.begin(), sig_list.end(),
839 [](const shared_ptr<data::SignalBase> &a,
840 const shared_ptr<data::SignalBase> &b) {
841 return strnatcasecmp(a->name().toStdString(),
842 b->name().toStdString()) < 0; });
844 QComboBox *selector = new QComboBox(parent);
846 selector->addItem("-", qVariantFromValue((void*)nullptr));
848 if (!ch->assigned_signal)
849 selector->setCurrentIndex(0);
851 for (const shared_ptr<data::SignalBase> &b : sig_list) {
853 if (b->logic_data() && b->enabled()) {
854 selector->addItem(b->name(),
855 qVariantFromValue((void*)b.get()));
857 if (ch->assigned_signal == b.get())
858 selector->setCurrentIndex(selector->count() - 1);
865 QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
866 const DecodeChannel *ch)
868 QComboBox *selector = new QComboBox(parent);
870 selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
871 selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
872 selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
874 selector->setCurrentIndex(ch->initial_pin_state);
876 selector->setToolTip("Initial (assumed) pin value before the first sample");
881 void DecodeTrace::on_new_annotations()
883 if (!delayed_trace_updater_.isActive())
884 delayed_trace_updater_.start();
887 void DecodeTrace::on_delayed_trace_update()
890 owner_->row_item_appearance_changed(false, true);
893 void DecodeTrace::on_decode_finished()
896 owner_->row_item_appearance_changed(false, true);
899 void DecodeTrace::delete_pressed()
904 void DecodeTrace::on_delete()
906 session_.remove_decode_signal(decode_signal_);
909 void DecodeTrace::on_channel_selected(int)
911 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
913 // Determine signal that was selected
914 const data::SignalBase *signal =
915 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
917 // Determine decode channel ID this combo box is the channel selector for
918 const uint16_t id = channel_id_map_.at(cb);
920 decode_signal_->assign_signal(id, signal);
923 void DecodeTrace::on_channels_updated()
926 owner_->row_item_appearance_changed(false, true);
929 void DecodeTrace::on_init_state_changed(int)
931 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
933 // Determine inital pin state that was selected
934 int init_state = cb->itemData(cb->currentIndex()).value<int>();
936 // Determine decode channel ID this combo box is the channel selector for
937 const uint16_t id = init_state_map_.at(cb);
939 decode_signal_->set_initial_pin_state(id, init_state);
942 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
944 decode_signal_->stack_decoder(decoder);
949 void DecodeTrace::on_delete_decoder(int index)
951 decode_signal_->remove_decoder(index);
953 // Force re-calculation of the trace height, see paint_mid()
954 max_visible_rows_ = 0;
955 owner_->extents_changed(false, true);
961 void DecodeTrace::on_show_hide_decoder(int index)
963 const bool state = decode_signal_->toggle_decoder_visibility(index);
965 assert(index < (int)decoder_forms_.size());
966 decoder_forms_[index]->set_decoder_visible(state);
969 // Force re-calculation of the trace height, see paint_mid()
970 max_visible_rows_ = 0;
971 owner_->extents_changed(false, true);
975 owner_->row_item_appearance_changed(false, true);