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_reset()),
150 this, SLOT(on_decode_reset()));
151 connect(decode_signal_.get(), SIGNAL(decode_finished()),
152 this, SLOT(on_decode_finished()));
153 connect(decode_signal_.get(), SIGNAL(channels_updated()),
154 this, SLOT(on_channels_updated()));
156 connect(&delete_mapper_, SIGNAL(mapped(int)),
157 this, SLOT(on_delete_decoder(int)));
158 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
159 this, SLOT(on_show_hide_decoder(int)));
161 connect(&delayed_trace_updater_, SIGNAL(timeout()),
162 this, SLOT(on_delayed_trace_update()));
163 delayed_trace_updater_.setSingleShot(true);
164 delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
167 bool DecodeTrace::enabled() const
172 shared_ptr<data::SignalBase> DecodeTrace::base() const
177 pair<int, int> DecodeTrace::v_extents() const
179 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
181 // Make an empty decode trace appear symmetrical
182 const int row_count = max(1, max_visible_rows_);
184 return make_pair(-row_height, row_height * row_count);
187 void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
189 Trace::paint_back(p, pp);
190 paint_axis(p, pp, get_visual_y());
193 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
195 const int text_height = ViewItemPaintParams::text_height();
196 row_height_ = (text_height * 6) / 4;
197 const int annotation_height = (text_height * 5) / 4;
199 const QString err = decode_signal_->error_message();
200 if (!err.isEmpty()) {
201 draw_unresolved_period(
202 p, annotation_height, pp.left(), pp.right());
203 draw_error(p, err, pp);
207 // Set default pen to allow for text width calculation
210 // Iterate through the rows
211 int y = get_visual_y();
212 pair<uint64_t, uint64_t> sample_range = get_sample_range(
213 pp.left(), pp.right());
215 const vector<Row> rows = decode_signal_->visible_rows();
217 visible_rows_.clear();
218 for (const Row& row : rows) {
219 // Cache the row title widths
222 row_title_width = row_title_widths_.at(row);
223 } catch (out_of_range) {
224 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
226 row_title_widths_[row] = w;
230 // Determine the row's color
231 size_t base_colour = 0x13579BDF;
232 boost::hash_combine(base_colour, this);
233 boost::hash_combine(base_colour, row.decoder());
234 boost::hash_combine(base_colour, row.row());
237 vector<Annotation> annotations;
238 decode_signal_->get_annotation_subset(annotations, row,
239 current_segment_, sample_range.first, sample_range.second);
240 if (!annotations.empty()) {
241 draw_annotations(annotations, p, annotation_height, pp, y,
242 base_colour, row_title_width);
246 visible_rows_.push_back(row);
251 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
253 if ((int)visible_rows_.size() > max_visible_rows_) {
254 max_visible_rows_ = (int)visible_rows_.size();
256 // Call order is important, otherwise the lazy event handler won't work
257 owner_->extents_changed(false, true);
258 owner_->row_item_appearance_changed(false, true);
262 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
266 for (size_t i = 0; i < visible_rows_.size(); i++) {
267 const int y = i * row_height_ + get_visual_y();
269 p.setPen(QPen(Qt::NoPen));
270 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
273 const QPointF points[] = {
274 QPointF(pp.left(), y - ArrowSize),
275 QPointF(pp.left() + ArrowSize, y),
276 QPointF(pp.left(), y + ArrowSize)
278 p.drawPolygon(points, countof(points));
281 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
282 pp.right() - pp.left(), row_height_);
283 const QString h(visible_rows_[i].title());
284 const int f = Qt::AlignLeft | Qt::AlignVCenter |
288 p.setPen(QApplication::palette().color(QPalette::Base));
289 for (int dx = -1; dx <= 1; dx++)
290 for (int dy = -1; dy <= 1; dy++)
291 if (dx != 0 && dy != 0)
292 p.drawText(r.translated(dx, dy), f, h);
295 p.setPen(QApplication::palette().color(QPalette::WindowText));
300 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
302 using pv::data::decode::Decoder;
306 // Add the standard options
307 Trace::populate_popup_form(parent, form);
309 // Add the decoder options
311 channel_id_map_.clear();
312 init_state_map_.clear();
313 decoder_forms_.clear();
315 const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_stack();
318 QLabel *const l = new QLabel(
319 tr("<p><i>No decoders in the stack</i></p>"));
320 l->setAlignment(Qt::AlignCenter);
323 auto iter = stack.cbegin();
324 for (int i = 0; i < (int)stack.size(); i++, iter++) {
325 shared_ptr<Decoder> dec(*iter);
326 create_decoder_form(i, dec, parent, form);
329 form->addRow(new QLabel(
330 tr("<i>* Required channels</i>"), parent));
333 // Add stacking button
334 pv::widgets::DecoderMenu *const decoder_menu =
335 new pv::widgets::DecoderMenu(parent);
336 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
337 this, SLOT(on_stack_decoder(srd_decoder*)));
339 QPushButton *const stack_button =
340 new QPushButton(tr("Stack Decoder"), parent);
341 stack_button->setMenu(decoder_menu);
342 stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
344 QHBoxLayout *stack_button_box = new QHBoxLayout;
345 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
346 form->addRow(stack_button_box);
349 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
351 QMenu *const menu = Trace::create_context_menu(parent);
353 menu->addSeparator();
355 QAction *const del = new QAction(tr("Delete"), this);
356 del->setShortcuts(QKeySequence::Delete);
357 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
358 menu->addAction(del);
363 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
364 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
365 size_t base_colour, int row_title_width)
367 using namespace pv::data::decode;
369 vector<Annotation> a_block;
372 double samples_per_pixel, pixels_offset;
373 tie(pixels_offset, samples_per_pixel) =
374 get_pixels_offset_samples_per_pixel();
376 // Sort the annotations by start sample so that decoders
377 // can't confuse us by creating annotations out of order
378 stable_sort(annotations.begin(), annotations.end(),
379 [](const Annotation &a, const Annotation &b) {
380 return a.start_sample() < b.start_sample(); });
382 // Gather all annotations that form a visual "block" and draw them as such
383 for (const Annotation &a : annotations) {
385 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
386 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
387 const int a_width = a_end - a_start;
389 const int delta = a_end - p_end;
391 bool a_is_separate = false;
393 // Annotation wider than the threshold for a useful label width?
394 if (a_width >= min_useful_label_width_) {
395 for (const QString &ann_text : a.annotations()) {
396 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
397 // Annotation wide enough to fit a label? Don't put it in a block then
399 a_is_separate = true;
405 // Were the previous and this annotation more than a pixel apart?
406 if ((abs(delta) > 1) || a_is_separate) {
407 // Block was broken, draw annotations that form the current block
408 if (a_block.size() == 1) {
409 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
413 draw_annotation_block(a_block, p, h, y, base_colour);
419 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
420 // Next annotation must start a new block. delta will be > 1
421 // because we set p_end to INT_MIN but that's okay since
422 // a_block will be empty, so nothing will be drawn
425 a_block.push_back(a);
430 if (a_block.size() == 1)
431 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
434 draw_annotation_block(a_block, p, h, y, base_colour);
437 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
438 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
439 size_t base_colour, int row_title_width) const
441 double samples_per_pixel, pixels_offset;
442 tie(pixels_offset, samples_per_pixel) =
443 get_pixels_offset_samples_per_pixel();
445 const double start = a.start_sample() / samples_per_pixel -
447 const double end = a.end_sample() / samples_per_pixel - pixels_offset;
449 const size_t colour = (base_colour + a.format()) % countof(Colours);
450 p.setPen(OutlineColours[colour]);
451 p.setBrush(Colours[colour]);
453 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
456 if (a.start_sample() == a.end_sample())
457 draw_instant(a, p, h, start, y);
459 draw_range(a, p, h, start, end, y, pp, row_title_width);
462 void DecodeTrace::draw_annotation_block(
463 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
464 int y, size_t base_colour) const
466 using namespace pv::data::decode;
468 if (annotations.empty())
471 double samples_per_pixel, pixels_offset;
472 tie(pixels_offset, samples_per_pixel) =
473 get_pixels_offset_samples_per_pixel();
475 const double start = annotations.front().start_sample() /
476 samples_per_pixel - pixels_offset;
477 const double end = annotations.back().end_sample() /
478 samples_per_pixel - pixels_offset;
480 const double top = y + .5 - h / 2;
481 const double bottom = y + .5 + h / 2;
483 const size_t colour = (base_colour + annotations.front().format()) %
486 // Check if all annotations are of the same type (i.e. we can use one color)
487 // or if we should use a neutral color (i.e. gray)
488 const int format = annotations.front().format();
489 const bool single_format = all_of(
490 annotations.begin(), annotations.end(),
491 [&](const Annotation &a) { return a.format() == format; });
493 const QRectF rect(start, top, end - start, bottom - top);
496 p.setPen(QPen(Qt::NoPen));
497 p.setBrush(Qt::white);
498 p.drawRoundedRect(rect, r, r);
500 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
501 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
503 p.drawRoundedRect(rect, r, r);
506 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
507 int h, double x, int y) const
509 const QString text = a.annotations().empty() ?
510 QString() : a.annotations().back();
511 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
513 const QRectF rect(x - w / 2, y - h / 2, w, h);
515 p.drawRoundedRect(rect, h / 2, h / 2);
518 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
521 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
522 int h, double start, double end, int y, const ViewItemPaintParams &pp,
523 int row_title_width) const
525 const double top = y + .5 - h / 2;
526 const double bottom = y + .5 + h / 2;
527 const vector<QString> annotations = a.annotations();
529 // If the two ends are within 1 pixel, draw a vertical line
530 if (start + 1.0 > end) {
531 p.drawLine(QPointF(start, top), QPointF(start, bottom));
535 const double cap_width = min((end - start) / 4, EndCapWidth);
538 QPointF(start, y + .5f),
539 QPointF(start + cap_width, top),
540 QPointF(end - cap_width, top),
541 QPointF(end, y + .5f),
542 QPointF(end - cap_width, bottom),
543 QPointF(start + cap_width, bottom)
546 p.drawConvexPolygon(pts, countof(pts));
548 if (annotations.empty())
551 const int ann_start = start + cap_width;
552 const int ann_end = end - cap_width;
554 const int real_start = max(ann_start, pp.left() + row_title_width);
555 const int real_end = min(ann_end, pp.right());
556 const int real_width = real_end - real_start;
558 QRectF rect(real_start, y - h / 2, real_width, h);
559 if (rect.width() <= 4)
564 // Try to find an annotation that will fit
565 QString best_annotation;
568 for (const QString &a : annotations) {
569 const int w = p.boundingRect(QRectF(), 0, a).width();
570 if (w <= rect.width() && w > best_width)
571 best_annotation = a, best_width = w;
574 if (best_annotation.isEmpty())
575 best_annotation = annotations.back();
577 // If not ellide the last in the list
578 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
579 best_annotation, Qt::ElideRight, rect.width()));
582 void DecodeTrace::draw_error(QPainter &p, const QString &message,
583 const ViewItemPaintParams &pp)
585 const int y = get_visual_y();
587 p.setPen(ErrorBgColour.darker());
588 p.setBrush(ErrorBgColour);
590 const QRectF bounding_rect =
591 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
592 const QRectF text_rect = p.boundingRect(bounding_rect,
593 Qt::AlignCenter, message);
594 const float r = text_rect.height() / 4;
596 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
600 p.drawText(text_rect, message);
603 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
605 using namespace pv::data;
606 using pv::data::decode::Decoder;
608 double samples_per_pixel, pixels_offset;
610 const int64_t sample_count = decode_signal_->get_working_sample_count(current_segment_);
611 if (sample_count == 0)
614 const int64_t samples_decoded = decode_signal_->get_decoded_sample_count(current_segment_);
615 if (sample_count == samples_decoded)
618 const int y = get_visual_y();
620 tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
622 const double start = max(samples_decoded /
623 samples_per_pixel - pixels_offset, left - 1.0);
624 const double end = min(sample_count / samples_per_pixel -
625 pixels_offset, right + 1.0);
626 const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
628 p.setPen(QPen(Qt::NoPen));
629 p.setBrush(Qt::white);
630 p.drawRect(no_decode_rect);
632 p.setPen(NoDecodeColour);
633 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
634 p.drawRect(no_decode_rect);
637 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
641 const View *view = owner_->view();
644 const double scale = view->scale();
647 const double pixels_offset =
648 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
650 double samplerate = decode_signal_->samplerate();
652 // Show sample rate as 1Hz when it is unknown
653 if (samplerate == 0.0)
656 return make_pair(pixels_offset, samplerate * scale);
659 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
660 int x_start, int x_end) const
662 double samples_per_pixel, pixels_offset;
663 tie(pixels_offset, samples_per_pixel) =
664 get_pixels_offset_samples_per_pixel();
666 const uint64_t start = (uint64_t)max(
667 (x_start + pixels_offset) * samples_per_pixel, 0.0);
668 const uint64_t end = (uint64_t)max(
669 (x_end + pixels_offset) * samples_per_pixel, 0.0);
671 return make_pair(start, end);
674 int DecodeTrace::get_row_at_point(const QPoint &point)
679 const int y = (point.y() - get_visual_y() + row_height_ / 2);
681 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
685 const int row = y / row_height_;
687 if (row >= (int)visible_rows_.size())
693 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
695 using namespace pv::data::decode;
700 const pair<uint64_t, uint64_t> sample_range =
701 get_sample_range(point.x(), point.x() + 1);
702 const int row = get_row_at_point(point);
706 vector<pv::data::decode::Annotation> annotations;
708 decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
709 current_segment_, sample_range.first, sample_range.second);
711 return (annotations.empty()) ?
712 QString() : annotations[0].annotations().front();
715 void DecodeTrace::hover_point_changed(const QPoint &hp)
719 const View *const view = owner_->view();
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.
750 p.setX(hp.x() - (text_size.width() / 2) - padding);
752 p.setY(get_visual_y() - (row_height_ / 2) +
753 (hover_row * row_height_) -
754 row_height_ - text_size.height() - padding);
756 QToolTip::showText(view->viewport()->mapToGlobal(p), ann);
759 void DecodeTrace::create_decoder_form(int index,
760 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
763 GlobalSettings settings;
766 const srd_decoder *const decoder = dec->decoder();
769 const bool decoder_deletable = index > 0;
771 pv::widgets::DecoderGroupBox *const group =
772 new pv::widgets::DecoderGroupBox(
773 QString::fromUtf8(decoder->name),
774 tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
775 QString::fromUtf8(decoder->desc)),
776 nullptr, decoder_deletable);
777 group->set_decoder_visible(dec->shown());
779 if (decoder_deletable) {
780 delete_mapper_.setMapping(group, index);
781 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
784 show_hide_mapper_.setMapping(group, index);
785 connect(group, SIGNAL(show_hide_decoder()),
786 &show_hide_mapper_, SLOT(map()));
788 QFormLayout *const decoder_form = new QFormLayout;
789 group->add_layout(decoder_form);
791 const vector<DecodeChannel> channels = decode_signal_->get_channels();
794 for (DecodeChannel ch : channels) {
795 // Ignore channels not part of the decoder we create the form for
796 if (ch.decoder_ != dec)
799 QComboBox *const combo = create_channel_selector(parent, &ch);
800 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
802 channel_id_map_[combo] = ch.id;
803 init_state_map_[combo_init_state] = ch.id;
805 connect(combo, SIGNAL(currentIndexChanged(int)),
806 this, SLOT(on_channel_selected(int)));
807 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
808 this, SLOT(on_init_state_changed(int)));
810 QHBoxLayout *const hlayout = new QHBoxLayout;
811 hlayout->addWidget(combo);
812 hlayout->addWidget(combo_init_state);
814 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
815 combo_init_state->hide();
817 const QString required_flag = ch.is_optional ? QString() : QString("*");
818 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
819 .arg(ch.name, ch.desc, required_flag), hlayout);
823 shared_ptr<binding::Decoder> binding(
824 new binding::Decoder(decode_signal_, dec));
825 binding->add_properties_to_form(decoder_form, true);
827 bindings_.push_back(binding);
830 decoder_forms_.push_back(group);
833 QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
835 const auto sigs(session_.signalbases());
837 // Sort signals in natural order
838 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
839 sort(sig_list.begin(), sig_list.end(),
840 [](const shared_ptr<data::SignalBase> &a,
841 const shared_ptr<data::SignalBase> &b) {
842 return strnatcasecmp(a->name().toStdString(),
843 b->name().toStdString()) < 0; });
845 QComboBox *selector = new QComboBox(parent);
847 selector->addItem("-", qVariantFromValue((void*)nullptr));
849 if (!ch->assigned_signal)
850 selector->setCurrentIndex(0);
852 for (const shared_ptr<data::SignalBase> &b : sig_list) {
854 if (b->logic_data() && b->enabled()) {
855 selector->addItem(b->name(),
856 qVariantFromValue((void*)b.get()));
858 if (ch->assigned_signal == b.get())
859 selector->setCurrentIndex(selector->count() - 1);
866 QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
867 const DecodeChannel *ch)
869 QComboBox *selector = new QComboBox(parent);
871 selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
872 selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
873 selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
875 selector->setCurrentIndex(ch->initial_pin_state);
877 selector->setToolTip("Initial (assumed) pin value before the first sample");
882 void DecodeTrace::on_new_annotations()
884 if (!delayed_trace_updater_.isActive())
885 delayed_trace_updater_.start();
888 void DecodeTrace::on_delayed_trace_update()
891 owner_->row_item_appearance_changed(false, true);
894 void DecodeTrace::on_decode_reset()
896 visible_rows_.clear();
897 max_visible_rows_ = 0;
900 owner_->row_item_appearance_changed(false, true);
903 void DecodeTrace::on_decode_finished()
906 owner_->row_item_appearance_changed(false, true);
909 void DecodeTrace::delete_pressed()
914 void DecodeTrace::on_delete()
916 session_.remove_decode_signal(decode_signal_);
919 void DecodeTrace::on_channel_selected(int)
921 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
923 // Determine signal that was selected
924 const data::SignalBase *signal =
925 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
927 // Determine decode channel ID this combo box is the channel selector for
928 const uint16_t id = channel_id_map_.at(cb);
930 decode_signal_->assign_signal(id, signal);
933 void DecodeTrace::on_channels_updated()
936 owner_->row_item_appearance_changed(false, true);
939 void DecodeTrace::on_init_state_changed(int)
941 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
943 // Determine inital pin state that was selected
944 int init_state = cb->itemData(cb->currentIndex()).value<int>();
946 // Determine decode channel ID this combo box is the channel selector for
947 const uint16_t id = init_state_map_.at(cb);
949 decode_signal_->set_initial_pin_state(id, init_state);
952 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
954 decode_signal_->stack_decoder(decoder);
959 void DecodeTrace::on_delete_decoder(int index)
961 decode_signal_->remove_decoder(index);
963 // Force re-calculation of the trace height, see paint_mid()
964 max_visible_rows_ = 0;
965 owner_->extents_changed(false, true);
971 void DecodeTrace::on_show_hide_decoder(int index)
973 const bool state = decode_signal_->toggle_decoder_visibility(index);
975 assert(index < (int)decoder_forms_.size());
976 decoder_forms_[index]->set_decoder_visible(state);
979 // Force re-calculation of the trace height, see paint_mid()
980 max_visible_rows_ = 0;
981 owner_->extents_changed(false, true);
985 owner_->row_item_appearance_changed(false, true);