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 QFontMetrics m(QApplication::font());
164 const int text_height = m.boundingRect(QRect(), 0, "Tg").height();
165 const int row_height = (text_height * 6) / 4;
166 return make_pair(-row_height / 2, row_height * 7 / 2);
169 void DecodeTrace::paint_back(QPainter &p, int left, int right)
171 Trace::paint_back(p, left, right);
172 paint_axis(p, get_visual_y(), left, right);
175 void DecodeTrace::paint_mid(QPainter &p, int left, int right)
177 using namespace pv::data::decode;
179 QFontMetrics m(QApplication::font());
180 text_height_ = m.boundingRect(QRect(), 0, "Tg").height();
181 row_height_ = (text_height_ * 6) / 4;
182 const int annotation_height = (text_height_ * 5) / 4;
184 assert(decoder_stack_);
185 const QString err = decoder_stack_->error_message();
188 draw_unresolved_period(p, annotation_height, left, right);
189 draw_error(p, err, left, right);
193 // Iterate through the rows
194 int y = get_visual_y();
195 pair<uint64_t, uint64_t> sample_range = get_sample_range(left, right);
197 assert(decoder_stack_);
198 const vector<Row> rows(decoder_stack_->get_visible_rows());
200 visible_rows_.clear();
201 for (size_t i = 0; i < rows.size(); i++)
203 const Row &row = rows[i];
205 size_t base_colour = 0x13579BDF;
206 boost::hash_combine(base_colour, this);
207 boost::hash_combine(base_colour, row.decoder());
208 boost::hash_combine(base_colour, row.row());
211 vector<Annotation> annotations;
212 decoder_stack_->get_annotation_subset(annotations, row,
213 sample_range.first, sample_range.second);
214 if (!annotations.empty()) {
215 for (const Annotation &a : annotations)
216 draw_annotation(a, p, get_text_colour(),
217 annotation_height, left, right, y,
221 visible_rows_.push_back(rows[i]);
226 draw_unresolved_period(p, annotation_height, left, right);
229 void DecodeTrace::paint_fore(QPainter &p, int left, int right)
231 using namespace pv::data::decode;
237 for (size_t i = 0; i < visible_rows_.size(); i++)
239 const int y = i * row_height_ + get_visual_y();
241 p.setPen(QPen(Qt::NoPen));
242 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
246 const QPointF points[] = {
247 QPointF(left, y - ArrowSize),
248 QPointF(left + ArrowSize, y),
249 QPointF(left, y + ArrowSize)
251 p.drawPolygon(points, countof(points));
254 const QRect r(left + ArrowSize * 2, y - row_height_ / 2,
255 right - left, row_height_);
256 const QString h(visible_rows_[i].title());
257 const int f = Qt::AlignLeft | Qt::AlignVCenter |
261 p.setPen(QApplication::palette().color(QPalette::Base));
262 for (int dx = -1; dx <= 1; dx++)
263 for (int dy = -1; dy <= 1; dy++)
264 if (dx != 0 && dy != 0)
265 p.drawText(r.translated(dx, dy), f, h);
268 p.setPen(QApplication::palette().color(QPalette::WindowText));
273 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
275 using pv::data::decode::Decoder;
279 assert(decoder_stack_);
281 // Add the standard options
282 Trace::populate_popup_form(parent, form);
284 // Add the decoder options
286 channel_selectors_.clear();
287 decoder_forms_.clear();
289 const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
293 QLabel *const l = new QLabel(
294 tr("<p><i>No decoders in the stack</i></p>"));
295 l->setAlignment(Qt::AlignCenter);
300 auto iter = stack.cbegin();
301 for (int i = 0; i < (int)stack.size(); i++, iter++) {
302 shared_ptr<Decoder> dec(*iter);
303 create_decoder_form(i, dec, parent, form);
306 form->addRow(new QLabel(
307 tr("<i>* Required channels</i>"), parent));
310 // Add stacking button
311 pv::widgets::DecoderMenu *const decoder_menu =
312 new pv::widgets::DecoderMenu(parent);
313 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
314 this, SLOT(on_stack_decoder(srd_decoder*)));
316 QPushButton *const stack_button =
317 new QPushButton(tr("Stack Decoder"), parent);
318 stack_button->setMenu(decoder_menu);
320 QHBoxLayout *stack_button_box = new QHBoxLayout;
321 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
322 form->addRow(stack_button_box);
325 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
327 QMenu *const menu = Trace::create_context_menu(parent);
329 menu->addSeparator();
331 QAction *const del = new QAction(tr("Delete"), this);
332 del->setShortcuts(QKeySequence::Delete);
333 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
334 menu->addAction(del);
339 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
340 QPainter &p, QColor text_color, int h, int left, int right, int y,
341 size_t base_colour) const
343 double samples_per_pixel, pixels_offset;
344 tie(pixels_offset, samples_per_pixel) =
345 get_pixels_offset_samples_per_pixel();
347 const double start = a.start_sample() / samples_per_pixel -
349 const double end = a.end_sample() / samples_per_pixel -
352 const size_t colour = (base_colour + a.format()) % countof(Colours);
353 const QColor &fill = Colours[colour];
354 const QColor &outline = OutlineColours[colour];
356 if (start > right + DrawPadding || end < left - DrawPadding)
359 if (a.start_sample() == a.end_sample())
360 draw_instant(a, p, fill, outline, text_color, h,
363 draw_range(a, p, fill, outline, text_color, h,
367 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
368 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
370 const QString text = a.annotations().empty() ?
371 QString() : a.annotations().back();
372 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
374 const QRectF rect(x - w / 2, y - h / 2, w, h);
378 p.drawRoundedRect(rect, h / 2, h / 2);
380 p.setPen(text_color);
381 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
384 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
385 QColor fill, QColor outline, QColor text_color, int h, double start,
386 double end, int y) const
388 const double top = y + .5 - h / 2;
389 const double bottom = y + .5 + h / 2;
390 const vector<QString> annotations = a.annotations();
395 // If the two ends are within 1 pixel, draw a vertical line
396 if (start + 1.0 > end)
398 p.drawLine(QPointF(start, top), QPointF(start, bottom));
402 const double cap_width = min((end - start) / 4, EndCapWidth);
405 QPointF(start, y + .5f),
406 QPointF(start + cap_width, top),
407 QPointF(end - cap_width, top),
408 QPointF(end, y + .5f),
409 QPointF(end - cap_width, bottom),
410 QPointF(start + cap_width, bottom)
413 p.drawConvexPolygon(pts, countof(pts));
415 if (annotations.empty())
418 QRectF rect(start + cap_width, y - h / 2,
419 end - start - cap_width * 2, h);
420 if (rect.width() <= 4)
423 p.setPen(text_color);
425 // Try to find an annotation that will fit
426 QString best_annotation;
429 for (const QString &a : annotations) {
430 const int w = p.boundingRect(QRectF(), 0, a).width();
431 if (w <= rect.width() && w > best_width)
432 best_annotation = a, best_width = w;
435 if (best_annotation.isEmpty())
436 best_annotation = annotations.back();
438 // If not ellide the last in the list
439 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
440 best_annotation, Qt::ElideRight, rect.width()));
443 void DecodeTrace::draw_error(QPainter &p, const QString &message,
446 const int y = get_visual_y();
448 p.setPen(ErrorBgColour.darker());
449 p.setBrush(ErrorBgColour);
451 const QRectF bounding_rect =
452 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
453 const QRectF text_rect = p.boundingRect(bounding_rect,
454 Qt::AlignCenter, message);
455 const float r = text_rect.height() / 4;
457 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
460 p.setPen(get_text_colour());
461 p.drawText(text_rect, message);
464 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
467 using namespace pv::data;
468 using pv::data::decode::Decoder;
470 double samples_per_pixel, pixels_offset;
472 assert(decoder_stack_);
474 shared_ptr<Logic> data;
475 shared_ptr<LogicSignal> logic_signal;
477 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
479 // We get the logic data of the first channel in the list.
480 // This works because we are currently assuming all
481 // LogicSignals have the same data/snapshot
482 for (const shared_ptr<Decoder> &dec : stack)
483 if (dec && !dec->channels().empty() &&
484 ((logic_signal = (*dec->channels().begin()).second)) &&
485 ((data = logic_signal->logic_data())))
488 if (!data || data->get_snapshots().empty())
491 const shared_ptr<LogicSnapshot> snapshot =
492 data->get_snapshots().front();
494 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
495 if (sample_count == 0)
498 const int64_t samples_decoded = decoder_stack_->samples_decoded();
499 if (sample_count == samples_decoded)
502 const int y = get_visual_y();
504 tie(pixels_offset, samples_per_pixel) =
505 get_pixels_offset_samples_per_pixel();
507 const double start = max(samples_decoded /
508 samples_per_pixel - pixels_offset, left - 1.0);
509 const double end = min(sample_count / samples_per_pixel -
510 pixels_offset, right + 1.0);
511 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
513 p.setPen(QPen(Qt::NoPen));
514 p.setBrush(Qt::white);
515 p.drawRect(no_decode_rect);
517 p.setPen(NoDecodeColour);
518 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
519 p.drawRect(no_decode_rect);
522 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
525 assert(decoder_stack_);
527 const View *view = owner_->view();
530 const double scale = view->scale();
533 const double pixels_offset =
534 (view->offset() - decoder_stack_->get_start_time()) / scale;
536 double samplerate = decoder_stack_->samplerate();
538 // Show sample rate as 1Hz when it is unknown
539 if (samplerate == 0.0)
542 return make_pair(pixels_offset, samplerate * scale);
545 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
546 int x_start, int x_end) const
548 double samples_per_pixel, pixels_offset;
549 tie(pixels_offset, samples_per_pixel) =
550 get_pixels_offset_samples_per_pixel();
552 const uint64_t start = (uint64_t)max(
553 (x_start + pixels_offset) * samples_per_pixel, 0.0);
554 const uint64_t end = (uint64_t)max(
555 (x_end + pixels_offset) * samples_per_pixel, 0.0);
557 return make_pair(start, end);
560 int DecodeTrace::get_row_at_point(const QPoint &point)
565 const int row = (point.y() - get_visual_y() + row_height_ / 2) /
567 if (row < 0 || row >= (int)visible_rows_.size())
573 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
575 using namespace pv::data::decode;
580 const pair<uint64_t, uint64_t> sample_range =
581 get_sample_range(point.x(), point.x() + 1);
582 const int row = get_row_at_point(point);
586 vector<pv::data::decode::Annotation> annotations;
588 assert(decoder_stack_);
589 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
590 sample_range.first, sample_range.second);
592 return (annotations.empty()) ?
593 QString() : annotations[0].annotations().front();
596 void DecodeTrace::hide_hover_annotation()
598 QToolTip::hideText();
601 void DecodeTrace::hover_point_changed()
605 const View *const view = owner_->view();
608 QPoint hp = view->hover_point();
609 QString ann = get_annotation_at_point(hp);
613 if (!row_height_ || ann.isEmpty()) {
614 hide_hover_annotation();
618 const int hover_row = get_row_at_point(hp);
620 QFontMetrics m(QToolTip::font());
621 const QRect text_size = m.boundingRect(QRect(), 0, ann);
623 // This is OS-specific and unfortunately we can't query it, so
624 // use an approximation to at least try to minimize the error.
625 const int padding = 8;
627 // Make sure the tool tip doesn't overlap with the mouse cursor.
628 // If it did, the tool tip would constantly hide and re-appear.
629 // We also push it up by one row so that it appears above the
630 // decode trace, not below.
631 hp.setX(hp.x() - (text_size.width() / 2) - padding);
633 hp.setY(get_visual_y() - (row_height_ / 2) +
634 (hover_row * row_height_) -
635 row_height_ - text_size.height());
637 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
640 void DecodeTrace::create_decoder_form(int index,
641 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
647 const srd_decoder *const decoder = dec->decoder();
650 pv::widgets::DecoderGroupBox *const group =
651 new pv::widgets::DecoderGroupBox(
652 QString::fromUtf8(decoder->name));
653 group->set_decoder_visible(dec->shown());
655 delete_mapper_.setMapping(group, index);
656 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
658 show_hide_mapper_.setMapping(group, index);
659 connect(group, SIGNAL(show_hide_decoder()),
660 &show_hide_mapper_, SLOT(map()));
662 QFormLayout *const decoder_form = new QFormLayout;
663 group->add_layout(decoder_form);
665 // Add the mandatory channels
666 for(l = decoder->channels; l; l = l->next) {
667 const struct srd_channel *const pdch =
668 (struct srd_channel *)l->data;
669 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
670 connect(combo, SIGNAL(currentIndexChanged(int)),
671 this, SLOT(on_channel_selected(int)));
672 decoder_form->addRow(tr("<b>%1</b> (%2) *")
673 .arg(QString::fromUtf8(pdch->name))
674 .arg(QString::fromUtf8(pdch->desc)), combo);
676 const ChannelSelector s = {combo, dec, pdch};
677 channel_selectors_.push_back(s);
680 // Add the optional channels
681 for(l = decoder->opt_channels; l; l = l->next) {
682 const struct srd_channel *const pdch =
683 (struct srd_channel *)l->data;
684 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
685 connect(combo, SIGNAL(currentIndexChanged(int)),
686 this, SLOT(on_channel_selected(int)));
687 decoder_form->addRow(tr("<b>%1</b> (%2)")
688 .arg(QString::fromUtf8(pdch->name))
689 .arg(QString::fromUtf8(pdch->desc)), combo);
691 const ChannelSelector s = {combo, dec, pdch};
692 channel_selectors_.push_back(s);
696 shared_ptr<prop::binding::DecoderOptions> binding(
697 new prop::binding::DecoderOptions(decoder_stack_, dec));
698 binding->add_properties_to_form(decoder_form, true);
700 bindings_.push_back(binding);
703 decoder_forms_.push_back(group);
706 QComboBox* DecodeTrace::create_channel_selector(
707 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
708 const srd_channel *const pdch)
712 shared_lock<shared_mutex> lock(session_.signals_mutex());
713 const vector< shared_ptr<Signal> > &sigs(session_.signals());
715 assert(decoder_stack_);
716 const auto channel_iter = dec->channels().find(pdch);
718 QComboBox *selector = new QComboBox(parent);
720 selector->addItem("-", qVariantFromValue((void*)NULL));
722 if (channel_iter == dec->channels().end())
723 selector->setCurrentIndex(0);
725 for(size_t i = 0; i < sigs.size(); i++) {
726 const shared_ptr<view::Signal> s(sigs[i]);
729 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
731 selector->addItem(s->name(),
732 qVariantFromValue((void*)s.get()));
733 if ((*channel_iter).second == s)
734 selector->setCurrentIndex(i + 1);
741 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
745 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
747 shared_lock<shared_mutex> lock(session_.signals_mutex());
748 const vector< shared_ptr<Signal> > &sigs(session_.signals());
750 for (const ChannelSelector &s : channel_selectors_)
752 if(s.decoder_ != dec)
755 const LogicSignal *const selection =
756 (LogicSignal*)s.combo_->itemData(
757 s.combo_->currentIndex()).value<void*>();
759 for (shared_ptr<Signal> sig : sigs)
760 if(sig.get() == selection) {
761 channel_map[s.pdch_] =
762 dynamic_pointer_cast<LogicSignal>(sig);
767 dec->set_channels(channel_map);
770 void DecodeTrace::commit_channels()
772 assert(decoder_stack_);
773 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
774 commit_decoder_channels(dec);
776 decoder_stack_->begin_decode();
779 void DecodeTrace::on_new_decode_data()
782 owner_->appearance_changed(false, true);
785 void DecodeTrace::delete_pressed()
790 void DecodeTrace::on_delete()
792 session_.remove_decode_signal(this);
795 void DecodeTrace::on_channel_selected(int)
800 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
803 assert(decoder_stack_);
804 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
805 new data::decode::Decoder(decoder)));
806 decoder_stack_->begin_decode();
811 void DecodeTrace::on_delete_decoder(int index)
813 decoder_stack_->remove(index);
818 decoder_stack_->begin_decode();
821 void DecodeTrace::on_show_hide_decoder(int index)
823 using pv::data::decode::Decoder;
825 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
827 // Find the decoder in the stack
828 auto iter = stack.cbegin();
829 for(int i = 0; i < index; i++, iter++)
830 assert(iter != stack.end());
832 shared_ptr<Decoder> dec = *iter;
835 const bool show = !dec->shown();
838 assert(index < (int)decoder_forms_.size());
839 decoder_forms_[index]->set_decoder_visible(show);
842 owner_->appearance_changed(false, true);