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>
34 #include <QApplication>
36 #include <QFormLayout>
39 #include <QPushButton>
42 #include "decodetrace.hpp"
44 #include <pv/session.hpp>
45 #include <pv/data/decoderstack.hpp>
46 #include <pv/data/decode/decoder.hpp>
47 #include <pv/data/logic.hpp>
48 #include <pv/data/logicsnapshot.hpp>
49 #include <pv/data/decode/annotation.hpp>
50 #include <pv/view/logicsignal.hpp>
51 #include <pv/view/view.hpp>
52 #include <pv/view/viewport.hpp>
53 #include <pv/widgets/decodergroupbox.hpp>
54 #include <pv/widgets/decodermenu.hpp>
56 using boost::shared_lock;
57 using boost::shared_mutex;
58 using std::dynamic_pointer_cast;
60 using std::lock_guard;
67 using std::shared_ptr;
74 const QColor DecodeTrace::DecodeColours[4] = {
75 QColor(0xEF, 0x29, 0x29), // Red
76 QColor(0xFC, 0xE9, 0x4F), // Yellow
77 QColor(0x8A, 0xE2, 0x34), // Green
78 QColor(0x72, 0x9F, 0xCF) // Blue
81 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
82 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
84 const int DecodeTrace::ArrowSize = 4;
85 const double DecodeTrace::EndCapWidth = 5;
86 const int DecodeTrace::DrawPadding = 100;
88 const QColor DecodeTrace::Colours[16] = {
89 QColor(0xEF, 0x29, 0x29),
90 QColor(0xF6, 0x6A, 0x32),
91 QColor(0xFC, 0xAE, 0x3E),
92 QColor(0xFB, 0xCA, 0x47),
93 QColor(0xFC, 0xE9, 0x4F),
94 QColor(0xCD, 0xF0, 0x40),
95 QColor(0x8A, 0xE2, 0x34),
96 QColor(0x4E, 0xDC, 0x44),
97 QColor(0x55, 0xD7, 0x95),
98 QColor(0x64, 0xD1, 0xD2),
99 QColor(0x72, 0x9F, 0xCF),
100 QColor(0xD4, 0x76, 0xC4),
101 QColor(0x9D, 0x79, 0xB9),
102 QColor(0xAD, 0x7F, 0xA8),
103 QColor(0xC2, 0x62, 0x9B),
104 QColor(0xD7, 0x47, 0x6F)
107 const QColor DecodeTrace::OutlineColours[16] = {
108 QColor(0x77, 0x14, 0x14),
109 QColor(0x7B, 0x35, 0x19),
110 QColor(0x7E, 0x57, 0x1F),
111 QColor(0x7D, 0x65, 0x23),
112 QColor(0x7E, 0x74, 0x27),
113 QColor(0x66, 0x78, 0x20),
114 QColor(0x45, 0x71, 0x1A),
115 QColor(0x27, 0x6E, 0x22),
116 QColor(0x2A, 0x6B, 0x4A),
117 QColor(0x32, 0x68, 0x69),
118 QColor(0x39, 0x4F, 0x67),
119 QColor(0x6A, 0x3B, 0x62),
120 QColor(0x4E, 0x3C, 0x5C),
121 QColor(0x56, 0x3F, 0x54),
122 QColor(0x61, 0x31, 0x4D),
123 QColor(0x6B, 0x23, 0x37)
126 DecodeTrace::DecodeTrace(pv::Session &session,
127 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
128 Trace(QString::fromUtf8(
129 decoder_stack->stack().front()->decoder()->name)),
131 decoder_stack_(decoder_stack),
134 delete_mapper_(this),
135 show_hide_mapper_(this)
137 assert(decoder_stack_);
139 colour_ = DecodeColours[index % countof(DecodeColours)];
141 connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
142 this, SLOT(on_new_decode_data()));
143 connect(&delete_mapper_, SIGNAL(mapped(int)),
144 this, SLOT(on_delete_decoder(int)));
145 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
146 this, SLOT(on_show_hide_decoder(int)));
149 bool DecodeTrace::enabled() const
154 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
156 return decoder_stack_;
159 pair<int, int> DecodeTrace::v_extents() const
161 /// @todo Replace this with an implementation that knows the true
162 /// height of the trace
163 const int row_height = (RowItemPaintParams::text_height() * 6) / 4;
164 return make_pair(-row_height / 2, row_height * 7 / 2);
167 void DecodeTrace::paint_back(QPainter &p, const RowItemPaintParams &pp)
169 Trace::paint_back(p, pp);
170 paint_axis(p, pp, get_visual_y());
173 void DecodeTrace::paint_mid(QPainter &p, const RowItemPaintParams &pp)
175 using namespace pv::data::decode;
177 text_height_ = RowItemPaintParams::text_height();
178 row_height_ = (text_height_ * 6) / 4;
179 const int annotation_height = (text_height_ * 5) / 4;
181 assert(decoder_stack_);
182 const QString err = decoder_stack_->error_message();
185 draw_unresolved_period(
186 p, annotation_height, pp.left(), pp.right());
187 draw_error(p, err, pp);
191 // Iterate through the rows
192 int y = get_visual_y();
193 pair<uint64_t, uint64_t> sample_range = get_sample_range(
194 pp.left(), pp.right());
196 assert(decoder_stack_);
197 const vector<Row> rows(decoder_stack_->get_visible_rows());
199 visible_rows_.clear();
200 for (size_t i = 0; i < rows.size(); i++)
202 const Row &row = rows[i];
204 size_t base_colour = 0x13579BDF;
205 boost::hash_combine(base_colour, this);
206 boost::hash_combine(base_colour, row.decoder());
207 boost::hash_combine(base_colour, row.row());
210 vector<Annotation> annotations;
211 decoder_stack_->get_annotation_subset(annotations, row,
212 sample_range.first, sample_range.second);
213 if (!annotations.empty()) {
214 for (const Annotation &a : annotations)
215 draw_annotation(a, p, get_text_colour(),
216 annotation_height, pp, y, base_colour);
219 visible_rows_.push_back(rows[i]);
224 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
227 void DecodeTrace::paint_fore(QPainter &p, const RowItemPaintParams &pp)
229 using namespace pv::data::decode;
233 for (size_t i = 0; i < visible_rows_.size(); i++)
235 const int y = i * row_height_ + get_visual_y();
237 p.setPen(QPen(Qt::NoPen));
238 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
242 const QPointF points[] = {
243 QPointF(pp.left(), y - ArrowSize),
244 QPointF(pp.left() + ArrowSize, y),
245 QPointF(pp.left(), y + ArrowSize)
247 p.drawPolygon(points, countof(points));
250 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
251 pp.right() - pp.left(), row_height_);
252 const QString h(visible_rows_[i].title());
253 const int f = Qt::AlignLeft | Qt::AlignVCenter |
257 p.setPen(QApplication::palette().color(QPalette::Base));
258 for (int dx = -1; dx <= 1; dx++)
259 for (int dy = -1; dy <= 1; dy++)
260 if (dx != 0 && dy != 0)
261 p.drawText(r.translated(dx, dy), f, h);
264 p.setPen(QApplication::palette().color(QPalette::WindowText));
269 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
271 using pv::data::decode::Decoder;
275 assert(decoder_stack_);
277 // Add the standard options
278 Trace::populate_popup_form(parent, form);
280 // Add the decoder options
282 channel_selectors_.clear();
283 decoder_forms_.clear();
285 const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
289 QLabel *const l = new QLabel(
290 tr("<p><i>No decoders in the stack</i></p>"));
291 l->setAlignment(Qt::AlignCenter);
296 auto iter = stack.cbegin();
297 for (int i = 0; i < (int)stack.size(); i++, iter++) {
298 shared_ptr<Decoder> dec(*iter);
299 create_decoder_form(i, dec, parent, form);
302 form->addRow(new QLabel(
303 tr("<i>* Required channels</i>"), parent));
306 // Add stacking button
307 pv::widgets::DecoderMenu *const decoder_menu =
308 new pv::widgets::DecoderMenu(parent);
309 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
310 this, SLOT(on_stack_decoder(srd_decoder*)));
312 QPushButton *const stack_button =
313 new QPushButton(tr("Stack Decoder"), parent);
314 stack_button->setMenu(decoder_menu);
316 QHBoxLayout *stack_button_box = new QHBoxLayout;
317 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
318 form->addRow(stack_button_box);
321 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
323 QMenu *const menu = Trace::create_context_menu(parent);
325 menu->addSeparator();
327 QAction *const del = new QAction(tr("Delete"), this);
328 del->setShortcuts(QKeySequence::Delete);
329 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
330 menu->addAction(del);
335 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
336 QPainter &p, QColor text_color, int h, const RowItemPaintParams &pp, int y,
337 size_t base_colour) const
339 double samples_per_pixel, pixels_offset;
340 tie(pixels_offset, samples_per_pixel) =
341 get_pixels_offset_samples_per_pixel();
343 const double start = a.start_sample() / samples_per_pixel -
345 const double end = a.end_sample() / samples_per_pixel -
348 const size_t colour = (base_colour + a.format()) % countof(Colours);
349 const QColor &fill = Colours[colour];
350 const QColor &outline = OutlineColours[colour];
352 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
355 if (a.start_sample() == a.end_sample())
356 draw_instant(a, p, fill, outline, text_color, h,
359 draw_range(a, p, fill, outline, text_color, h,
363 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
364 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
366 const QString text = a.annotations().empty() ?
367 QString() : a.annotations().back();
368 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
370 const QRectF rect(x - w / 2, y - h / 2, w, h);
374 p.drawRoundedRect(rect, h / 2, h / 2);
376 p.setPen(text_color);
377 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
380 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
381 QColor fill, QColor outline, QColor text_color, int h, double start,
382 double end, int y) const
384 const double top = y + .5 - h / 2;
385 const double bottom = y + .5 + h / 2;
386 const vector<QString> annotations = a.annotations();
391 // If the two ends are within 1 pixel, draw a vertical line
392 if (start + 1.0 > end)
394 p.drawLine(QPointF(start, top), QPointF(start, bottom));
398 const double cap_width = min((end - start) / 4, EndCapWidth);
401 QPointF(start, y + .5f),
402 QPointF(start + cap_width, top),
403 QPointF(end - cap_width, top),
404 QPointF(end, y + .5f),
405 QPointF(end - cap_width, bottom),
406 QPointF(start + cap_width, bottom)
409 p.drawConvexPolygon(pts, countof(pts));
411 if (annotations.empty())
414 QRectF rect(start + cap_width, y - h / 2,
415 end - start - cap_width * 2, h);
416 if (rect.width() <= 4)
419 p.setPen(text_color);
421 // Try to find an annotation that will fit
422 QString best_annotation;
425 for (const QString &a : annotations) {
426 const int w = p.boundingRect(QRectF(), 0, a).width();
427 if (w <= rect.width() && w > best_width)
428 best_annotation = a, best_width = w;
431 if (best_annotation.isEmpty())
432 best_annotation = annotations.back();
434 // If not ellide the last in the list
435 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
436 best_annotation, Qt::ElideRight, rect.width()));
439 void DecodeTrace::draw_error(QPainter &p, const QString &message,
440 const RowItemPaintParams &pp)
442 const int y = get_visual_y();
444 p.setPen(ErrorBgColour.darker());
445 p.setBrush(ErrorBgColour);
447 const QRectF bounding_rect =
448 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
449 const QRectF text_rect = p.boundingRect(bounding_rect,
450 Qt::AlignCenter, message);
451 const float r = text_rect.height() / 4;
453 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
456 p.setPen(get_text_colour());
457 p.drawText(text_rect, message);
460 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
463 using namespace pv::data;
464 using pv::data::decode::Decoder;
466 double samples_per_pixel, pixels_offset;
468 assert(decoder_stack_);
470 shared_ptr<Logic> data;
471 shared_ptr<LogicSignal> logic_signal;
473 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
475 // We get the logic data of the first channel in the list.
476 // This works because we are currently assuming all
477 // LogicSignals have the same data/snapshot
478 for (const shared_ptr<Decoder> &dec : stack)
479 if (dec && !dec->channels().empty() &&
480 ((logic_signal = (*dec->channels().begin()).second)) &&
481 ((data = logic_signal->logic_data())))
484 if (!data || data->logic_snapshots().empty())
487 const shared_ptr<LogicSnapshot> snapshot =
488 data->logic_snapshots().front();
490 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
491 if (sample_count == 0)
494 const int64_t samples_decoded = decoder_stack_->samples_decoded();
495 if (sample_count == samples_decoded)
498 const int y = get_visual_y();
500 tie(pixels_offset, samples_per_pixel) =
501 get_pixels_offset_samples_per_pixel();
503 const double start = max(samples_decoded /
504 samples_per_pixel - pixels_offset, left - 1.0);
505 const double end = min(sample_count / samples_per_pixel -
506 pixels_offset, right + 1.0);
507 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
509 p.setPen(QPen(Qt::NoPen));
510 p.setBrush(Qt::white);
511 p.drawRect(no_decode_rect);
513 p.setPen(NoDecodeColour);
514 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
515 p.drawRect(no_decode_rect);
518 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
521 assert(decoder_stack_);
523 const View *view = owner_->view();
526 const double scale = view->scale();
529 const double pixels_offset =
530 (view->offset() - decoder_stack_->start_time()) / scale;
532 double samplerate = decoder_stack_->samplerate();
534 // Show sample rate as 1Hz when it is unknown
535 if (samplerate == 0.0)
538 return make_pair(pixels_offset, samplerate * scale);
541 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
542 int x_start, int x_end) const
544 double samples_per_pixel, pixels_offset;
545 tie(pixels_offset, samples_per_pixel) =
546 get_pixels_offset_samples_per_pixel();
548 const uint64_t start = (uint64_t)max(
549 (x_start + pixels_offset) * samples_per_pixel, 0.0);
550 const uint64_t end = (uint64_t)max(
551 (x_end + pixels_offset) * samples_per_pixel, 0.0);
553 return make_pair(start, end);
556 int DecodeTrace::get_row_at_point(const QPoint &point)
561 const int row = (point.y() - get_visual_y() + row_height_ / 2) /
563 if (row < 0 || row >= (int)visible_rows_.size())
569 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
571 using namespace pv::data::decode;
576 const pair<uint64_t, uint64_t> sample_range =
577 get_sample_range(point.x(), point.x() + 1);
578 const int row = get_row_at_point(point);
582 vector<pv::data::decode::Annotation> annotations;
584 assert(decoder_stack_);
585 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
586 sample_range.first, sample_range.second);
588 return (annotations.empty()) ?
589 QString() : annotations[0].annotations().front();
592 void DecodeTrace::hide_hover_annotation()
594 QToolTip::hideText();
597 void DecodeTrace::hover_point_changed()
601 const View *const view = owner_->view();
604 QPoint hp = view->hover_point();
605 QString ann = get_annotation_at_point(hp);
609 if (!row_height_ || ann.isEmpty()) {
610 hide_hover_annotation();
614 const int hover_row = get_row_at_point(hp);
616 QFontMetrics m(QToolTip::font());
617 const QRect text_size = m.boundingRect(QRect(), 0, ann);
619 // This is OS-specific and unfortunately we can't query it, so
620 // use an approximation to at least try to minimize the error.
621 const int padding = 8;
623 // Make sure the tool tip doesn't overlap with the mouse cursor.
624 // If it did, the tool tip would constantly hide and re-appear.
625 // We also push it up by one row so that it appears above the
626 // decode trace, not below.
627 hp.setX(hp.x() - (text_size.width() / 2) - padding);
629 hp.setY(get_visual_y() - (row_height_ / 2) +
630 (hover_row * row_height_) -
631 row_height_ - text_size.height());
633 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
636 void DecodeTrace::create_decoder_form(int index,
637 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
643 const srd_decoder *const decoder = dec->decoder();
646 pv::widgets::DecoderGroupBox *const group =
647 new pv::widgets::DecoderGroupBox(
648 QString::fromUtf8(decoder->name));
649 group->set_decoder_visible(dec->shown());
651 delete_mapper_.setMapping(group, index);
652 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
654 show_hide_mapper_.setMapping(group, index);
655 connect(group, SIGNAL(show_hide_decoder()),
656 &show_hide_mapper_, SLOT(map()));
658 QFormLayout *const decoder_form = new QFormLayout;
659 group->add_layout(decoder_form);
661 // Add the mandatory channels
662 for(l = decoder->channels; l; l = l->next) {
663 const struct srd_channel *const pdch =
664 (struct srd_channel *)l->data;
665 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
666 connect(combo, SIGNAL(currentIndexChanged(int)),
667 this, SLOT(on_channel_selected(int)));
668 decoder_form->addRow(tr("<b>%1</b> (%2) *")
669 .arg(QString::fromUtf8(pdch->name))
670 .arg(QString::fromUtf8(pdch->desc)), combo);
672 const ChannelSelector s = {combo, dec, pdch};
673 channel_selectors_.push_back(s);
676 // Add the optional channels
677 for(l = decoder->opt_channels; l; l = l->next) {
678 const struct srd_channel *const pdch =
679 (struct srd_channel *)l->data;
680 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
681 connect(combo, SIGNAL(currentIndexChanged(int)),
682 this, SLOT(on_channel_selected(int)));
683 decoder_form->addRow(tr("<b>%1</b> (%2)")
684 .arg(QString::fromUtf8(pdch->name))
685 .arg(QString::fromUtf8(pdch->desc)), combo);
687 const ChannelSelector s = {combo, dec, pdch};
688 channel_selectors_.push_back(s);
692 shared_ptr<prop::binding::DecoderOptions> binding(
693 new prop::binding::DecoderOptions(decoder_stack_, dec));
694 binding->add_properties_to_form(decoder_form, true);
696 bindings_.push_back(binding);
699 decoder_forms_.push_back(group);
702 QComboBox* DecodeTrace::create_channel_selector(
703 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
704 const srd_channel *const pdch)
708 shared_lock<shared_mutex> lock(session_.signals_mutex());
709 const vector< shared_ptr<Signal> > &sigs(session_.signals());
711 assert(decoder_stack_);
712 const auto channel_iter = dec->channels().find(pdch);
714 QComboBox *selector = new QComboBox(parent);
716 selector->addItem("-", qVariantFromValue((void*)NULL));
718 if (channel_iter == dec->channels().end())
719 selector->setCurrentIndex(0);
721 for(size_t i = 0; i < sigs.size(); i++) {
722 const shared_ptr<view::Signal> s(sigs[i]);
725 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
727 selector->addItem(s->name(),
728 qVariantFromValue((void*)s.get()));
729 if ((*channel_iter).second == s)
730 selector->setCurrentIndex(i + 1);
737 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
741 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
743 shared_lock<shared_mutex> lock(session_.signals_mutex());
744 const vector< shared_ptr<Signal> > &sigs(session_.signals());
746 for (const ChannelSelector &s : channel_selectors_)
748 if(s.decoder_ != dec)
751 const LogicSignal *const selection =
752 (LogicSignal*)s.combo_->itemData(
753 s.combo_->currentIndex()).value<void*>();
755 for (shared_ptr<Signal> sig : sigs)
756 if(sig.get() == selection) {
757 channel_map[s.pdch_] =
758 dynamic_pointer_cast<LogicSignal>(sig);
763 dec->set_channels(channel_map);
766 void DecodeTrace::commit_channels()
768 assert(decoder_stack_);
769 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
770 commit_decoder_channels(dec);
772 decoder_stack_->begin_decode();
775 void DecodeTrace::on_new_decode_data()
778 owner_->appearance_changed(false, true);
781 void DecodeTrace::delete_pressed()
786 void DecodeTrace::on_delete()
788 session_.remove_decode_signal(this);
791 void DecodeTrace::on_channel_selected(int)
796 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
799 assert(decoder_stack_);
800 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
801 new data::decode::Decoder(decoder)));
802 decoder_stack_->begin_decode();
807 void DecodeTrace::on_delete_decoder(int index)
809 decoder_stack_->remove(index);
814 decoder_stack_->begin_decode();
817 void DecodeTrace::on_show_hide_decoder(int index)
819 using pv::data::decode::Decoder;
821 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
823 // Find the decoder in the stack
824 auto iter = stack.cbegin();
825 for(int i = 0; i < index; i++, iter++)
826 assert(iter != stack.end());
828 shared_ptr<Decoder> dec = *iter;
831 const bool show = !dec->shown();
834 assert(index < (int)decoder_forms_.size());
835 decoder_forms_[index]->set_decoder_visible(show);
838 owner_->appearance_changed(false, true);