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/logicsegment.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),
133 delete_mapper_(this),
134 show_hide_mapper_(this)
136 assert(decoder_stack_);
138 colour_ = DecodeColours[index % countof(DecodeColours)];
140 connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
141 this, SLOT(on_new_decode_data()));
142 connect(&delete_mapper_, SIGNAL(mapped(int)),
143 this, SLOT(on_delete_decoder(int)));
144 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
145 this, SLOT(on_show_hide_decoder(int)));
148 bool DecodeTrace::enabled() const
153 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
155 return decoder_stack_;
158 pair<int, int> DecodeTrace::v_extents() const
160 /// @todo Replace this with an implementation that knows the true
161 /// height of the trace
162 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
163 return make_pair(-row_height / 2, row_height * 7 / 2);
166 void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
168 Trace::paint_back(p, pp);
169 paint_axis(p, pp, get_visual_y());
172 void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
174 using namespace pv::data::decode;
176 const int text_height = ViewItemPaintParams::text_height();
177 row_height_ = (text_height * 6) / 4;
178 const int annotation_height = (text_height * 5) / 4;
180 assert(decoder_stack_);
181 const QString err = decoder_stack_->error_message();
184 draw_unresolved_period(
185 p, annotation_height, pp.left(), pp.right());
186 draw_error(p, err, pp);
190 // Iterate through the rows
191 int y = get_visual_y();
192 pair<uint64_t, uint64_t> sample_range = get_sample_range(
193 pp.left(), pp.right());
195 assert(decoder_stack_);
196 const vector<Row> rows(decoder_stack_->get_visible_rows());
198 visible_rows_.clear();
199 for (size_t i = 0; i < rows.size(); i++)
201 const Row &row = rows[i];
203 size_t base_colour = 0x13579BDF;
204 boost::hash_combine(base_colour, this);
205 boost::hash_combine(base_colour, row.decoder());
206 boost::hash_combine(base_colour, row.row());
209 vector<Annotation> annotations;
210 decoder_stack_->get_annotation_subset(annotations, row,
211 sample_range.first, sample_range.second);
212 if (!annotations.empty()) {
213 for (const Annotation &a : annotations)
214 draw_annotation(a, p, annotation_height,
218 visible_rows_.push_back(rows[i]);
223 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
226 void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
228 using namespace pv::data::decode;
232 for (size_t i = 0; i < visible_rows_.size(); i++)
234 const int y = i * row_height_ + get_visual_y();
236 p.setPen(QPen(Qt::NoPen));
237 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
241 const QPointF points[] = {
242 QPointF(pp.left(), y - ArrowSize),
243 QPointF(pp.left() + ArrowSize, y),
244 QPointF(pp.left(), y + ArrowSize)
246 p.drawPolygon(points, countof(points));
249 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
250 pp.right() - pp.left(), row_height_);
251 const QString h(visible_rows_[i].title());
252 const int f = Qt::AlignLeft | Qt::AlignVCenter |
256 p.setPen(QApplication::palette().color(QPalette::Base));
257 for (int dx = -1; dx <= 1; dx++)
258 for (int dy = -1; dy <= 1; dy++)
259 if (dx != 0 && dy != 0)
260 p.drawText(r.translated(dx, dy), f, h);
263 p.setPen(QApplication::palette().color(QPalette::WindowText));
268 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
270 using pv::data::decode::Decoder;
274 assert(decoder_stack_);
276 // Add the standard options
277 Trace::populate_popup_form(parent, form);
279 // Add the decoder options
281 channel_selectors_.clear();
282 decoder_forms_.clear();
284 const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
288 QLabel *const l = new QLabel(
289 tr("<p><i>No decoders in the stack</i></p>"));
290 l->setAlignment(Qt::AlignCenter);
295 auto iter = stack.cbegin();
296 for (int i = 0; i < (int)stack.size(); i++, iter++) {
297 shared_ptr<Decoder> dec(*iter);
298 create_decoder_form(i, dec, parent, form);
301 form->addRow(new QLabel(
302 tr("<i>* Required channels</i>"), parent));
305 // Add stacking button
306 pv::widgets::DecoderMenu *const decoder_menu =
307 new pv::widgets::DecoderMenu(parent);
308 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
309 this, SLOT(on_stack_decoder(srd_decoder*)));
311 QPushButton *const stack_button =
312 new QPushButton(tr("Stack Decoder"), parent);
313 stack_button->setMenu(decoder_menu);
315 QHBoxLayout *stack_button_box = new QHBoxLayout;
316 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
317 form->addRow(stack_button_box);
320 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
322 QMenu *const menu = Trace::create_context_menu(parent);
324 menu->addSeparator();
326 QAction *const del = new QAction(tr("Delete"), this);
327 del->setShortcuts(QKeySequence::Delete);
328 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
329 menu->addAction(del);
334 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
335 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
336 size_t base_colour) const
338 double samples_per_pixel, pixels_offset;
339 tie(pixels_offset, samples_per_pixel) =
340 get_pixels_offset_samples_per_pixel();
342 const double start = a.start_sample() / samples_per_pixel -
344 const double end = a.end_sample() / samples_per_pixel -
347 const size_t colour = (base_colour + a.format()) % countof(Colours);
348 const QColor &fill = Colours[colour];
349 const QColor &outline = OutlineColours[colour];
351 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
354 if (a.start_sample() == a.end_sample())
355 draw_instant(a, p, fill, outline, h, start, y);
357 draw_range(a, p, fill, outline, h, start, end, y);
360 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
361 QColor fill, QColor outline, int h, double x, int y) const
363 const QString text = a.annotations().empty() ?
364 QString() : a.annotations().back();
365 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
367 const QRectF rect(x - w / 2, y - h / 2, w, h);
371 p.drawRoundedRect(rect, h / 2, h / 2);
374 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
377 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
378 QColor fill, QColor outline, int h, double start,
379 double end, int y) const
381 const double top = y + .5 - h / 2;
382 const double bottom = y + .5 + h / 2;
383 const vector<QString> annotations = a.annotations();
388 // If the two ends are within 1 pixel, draw a vertical line
389 if (start + 1.0 > end)
391 p.drawLine(QPointF(start, top), QPointF(start, bottom));
395 const double cap_width = min((end - start) / 4, EndCapWidth);
398 QPointF(start, y + .5f),
399 QPointF(start + cap_width, top),
400 QPointF(end - cap_width, top),
401 QPointF(end, y + .5f),
402 QPointF(end - cap_width, bottom),
403 QPointF(start + cap_width, bottom)
406 p.drawConvexPolygon(pts, countof(pts));
408 if (annotations.empty())
411 QRectF rect(start + cap_width, y - h / 2,
412 end - start - cap_width * 2, h);
413 if (rect.width() <= 4)
418 // Try to find an annotation that will fit
419 QString best_annotation;
422 for (const QString &a : annotations) {
423 const int w = p.boundingRect(QRectF(), 0, a).width();
424 if (w <= rect.width() && w > best_width)
425 best_annotation = a, best_width = w;
428 if (best_annotation.isEmpty())
429 best_annotation = annotations.back();
431 // If not ellide the last in the list
432 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
433 best_annotation, Qt::ElideRight, rect.width()));
436 void DecodeTrace::draw_error(QPainter &p, const QString &message,
437 const ViewItemPaintParams &pp)
439 const int y = get_visual_y();
441 p.setPen(ErrorBgColour.darker());
442 p.setBrush(ErrorBgColour);
444 const QRectF bounding_rect =
445 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
446 const QRectF text_rect = p.boundingRect(bounding_rect,
447 Qt::AlignCenter, message);
448 const float r = text_rect.height() / 4;
450 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
454 p.drawText(text_rect, message);
457 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
460 using namespace pv::data;
461 using pv::data::decode::Decoder;
463 double samples_per_pixel, pixels_offset;
465 assert(decoder_stack_);
467 shared_ptr<Logic> data;
468 shared_ptr<LogicSignal> logic_signal;
470 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
472 // We get the logic data of the first channel in the list.
473 // This works because we are currently assuming all
474 // LogicSignals have the same data/segment
475 for (const shared_ptr<Decoder> &dec : stack)
476 if (dec && !dec->channels().empty() &&
477 ((logic_signal = (*dec->channels().begin()).second)) &&
478 ((data = logic_signal->logic_data())))
481 if (!data || data->logic_segments().empty())
484 const shared_ptr<LogicSegment> segment =
485 data->logic_segments().front();
487 const int64_t sample_count = (int64_t)segment->get_sample_count();
488 if (sample_count == 0)
491 const int64_t samples_decoded = decoder_stack_->samples_decoded();
492 if (sample_count == samples_decoded)
495 const int y = get_visual_y();
497 tie(pixels_offset, samples_per_pixel) =
498 get_pixels_offset_samples_per_pixel();
500 const double start = max(samples_decoded /
501 samples_per_pixel - pixels_offset, left - 1.0);
502 const double end = min(sample_count / samples_per_pixel -
503 pixels_offset, right + 1.0);
504 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
506 p.setPen(QPen(Qt::NoPen));
507 p.setBrush(Qt::white);
508 p.drawRect(no_decode_rect);
510 p.setPen(NoDecodeColour);
511 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
512 p.drawRect(no_decode_rect);
515 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
518 assert(decoder_stack_);
520 const View *view = owner_->view();
523 const double scale = view->scale();
526 const double pixels_offset =
527 (view->offset() - decoder_stack_->start_time()) / scale;
529 double samplerate = decoder_stack_->samplerate();
531 // Show sample rate as 1Hz when it is unknown
532 if (samplerate == 0.0)
535 return make_pair(pixels_offset, samplerate * scale);
538 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
539 int x_start, int x_end) const
541 double samples_per_pixel, pixels_offset;
542 tie(pixels_offset, samples_per_pixel) =
543 get_pixels_offset_samples_per_pixel();
545 const uint64_t start = (uint64_t)max(
546 (x_start + pixels_offset) * samples_per_pixel, 0.0);
547 const uint64_t end = (uint64_t)max(
548 (x_end + pixels_offset) * samples_per_pixel, 0.0);
550 return make_pair(start, end);
553 int DecodeTrace::get_row_at_point(const QPoint &point)
558 const int y = (point.y() - get_visual_y() + row_height_ / 2);
560 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
564 const int row = y / row_height_;
566 if (row >= (int)visible_rows_.size())
572 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
574 using namespace pv::data::decode;
579 const pair<uint64_t, uint64_t> sample_range =
580 get_sample_range(point.x(), point.x() + 1);
581 const int row = get_row_at_point(point);
585 vector<pv::data::decode::Annotation> annotations;
587 assert(decoder_stack_);
588 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
589 sample_range.first, sample_range.second);
591 return (annotations.empty()) ?
592 QString() : annotations[0].annotations().front();
595 void DecodeTrace::hover_point_changed()
599 const View *const view = owner_->view();
602 QPoint hp = view->hover_point();
603 QString ann = get_annotation_at_point(hp);
607 if (!row_height_ || ann.isEmpty()) {
608 QToolTip::hideText();
612 const int hover_row = get_row_at_point(hp);
614 QFontMetrics m(QToolTip::font());
615 const QRect text_size = m.boundingRect(QRect(), 0, ann);
617 // This is OS-specific and unfortunately we can't query it, so
618 // use an approximation to at least try to minimize the error.
619 const int padding = 8;
621 // Make sure the tool tip doesn't overlap with the mouse cursor.
622 // If it did, the tool tip would constantly hide and re-appear.
623 // We also push it up by one row so that it appears above the
624 // decode trace, not below.
625 hp.setX(hp.x() - (text_size.width() / 2) - padding);
627 hp.setY(get_visual_y() - (row_height_ / 2) +
628 (hover_row * row_height_) -
629 row_height_ - text_size.height() - padding);
631 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
634 void DecodeTrace::create_decoder_form(int index,
635 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
641 const srd_decoder *const decoder = dec->decoder();
644 pv::widgets::DecoderGroupBox *const group =
645 new pv::widgets::DecoderGroupBox(
646 QString::fromUtf8(decoder->name));
647 group->set_decoder_visible(dec->shown());
649 delete_mapper_.setMapping(group, index);
650 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
652 show_hide_mapper_.setMapping(group, index);
653 connect(group, SIGNAL(show_hide_decoder()),
654 &show_hide_mapper_, SLOT(map()));
656 QFormLayout *const decoder_form = new QFormLayout;
657 group->add_layout(decoder_form);
659 // Add the mandatory channels
660 for(l = decoder->channels; l; l = l->next) {
661 const struct srd_channel *const pdch =
662 (struct srd_channel *)l->data;
663 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
664 connect(combo, SIGNAL(currentIndexChanged(int)),
665 this, SLOT(on_channel_selected(int)));
666 decoder_form->addRow(tr("<b>%1</b> (%2) *")
667 .arg(QString::fromUtf8(pdch->name))
668 .arg(QString::fromUtf8(pdch->desc)), combo);
670 const ChannelSelector s = {combo, dec, pdch};
671 channel_selectors_.push_back(s);
674 // Add the optional channels
675 for(l = decoder->opt_channels; l; l = l->next) {
676 const struct srd_channel *const pdch =
677 (struct srd_channel *)l->data;
678 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
679 connect(combo, SIGNAL(currentIndexChanged(int)),
680 this, SLOT(on_channel_selected(int)));
681 decoder_form->addRow(tr("<b>%1</b> (%2)")
682 .arg(QString::fromUtf8(pdch->name))
683 .arg(QString::fromUtf8(pdch->desc)), combo);
685 const ChannelSelector s = {combo, dec, pdch};
686 channel_selectors_.push_back(s);
690 shared_ptr<binding::Decoder> binding(
691 new binding::Decoder(decoder_stack_, dec));
692 binding->add_properties_to_form(decoder_form, true);
694 bindings_.push_back(binding);
697 decoder_forms_.push_back(group);
700 QComboBox* DecodeTrace::create_channel_selector(
701 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
702 const srd_channel *const pdch)
706 shared_lock<shared_mutex> lock(session_.signals_mutex());
707 const vector< shared_ptr<Signal> > &sigs(session_.signals());
709 assert(decoder_stack_);
710 const auto channel_iter = dec->channels().find(pdch);
712 QComboBox *selector = new QComboBox(parent);
714 selector->addItem("-", qVariantFromValue((void*)NULL));
716 if (channel_iter == dec->channels().end())
717 selector->setCurrentIndex(0);
719 for(size_t i = 0; i < sigs.size(); i++) {
720 const shared_ptr<view::Signal> s(sigs[i]);
723 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
725 selector->addItem(s->name(),
726 qVariantFromValue((void*)s.get()));
727 if ((*channel_iter).second == s)
728 selector->setCurrentIndex(i + 1);
735 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
739 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
741 shared_lock<shared_mutex> lock(session_.signals_mutex());
742 const vector< shared_ptr<Signal> > &sigs(session_.signals());
744 for (const ChannelSelector &s : channel_selectors_)
746 if(s.decoder_ != dec)
749 const LogicSignal *const selection =
750 (LogicSignal*)s.combo_->itemData(
751 s.combo_->currentIndex()).value<void*>();
753 for (shared_ptr<Signal> sig : sigs)
754 if(sig.get() == selection) {
755 channel_map[s.pdch_] =
756 dynamic_pointer_cast<LogicSignal>(sig);
761 dec->set_channels(channel_map);
764 void DecodeTrace::commit_channels()
766 assert(decoder_stack_);
767 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
768 commit_decoder_channels(dec);
770 decoder_stack_->begin_decode();
773 void DecodeTrace::on_new_decode_data()
776 owner_->row_item_appearance_changed(false, true);
779 void DecodeTrace::delete_pressed()
784 void DecodeTrace::on_delete()
786 session_.remove_decode_signal(this);
789 void DecodeTrace::on_channel_selected(int)
794 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
797 assert(decoder_stack_);
798 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
799 new data::decode::Decoder(decoder)));
800 decoder_stack_->begin_decode();
805 void DecodeTrace::on_delete_decoder(int index)
807 decoder_stack_->remove(index);
812 decoder_stack_->begin_decode();
815 void DecodeTrace::on_show_hide_decoder(int index)
817 using pv::data::decode::Decoder;
819 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
821 // Find the decoder in the stack
822 auto iter = stack.cbegin();
823 for(int i = 0; i < index; i++, iter++)
824 assert(iter != stack.end());
826 shared_ptr<Decoder> dec = *iter;
829 const bool show = !dec->shown();
832 assert(index < (int)decoder_forms_.size());
833 decoder_forms_[index]->set_decoder_visible(show);
836 owner_->row_item_appearance_changed(false, true);