]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/decodetrace.cpp
Fix a build issue with std::roundf() on Android.
[pulseview.git] / pv / view / decodetrace.cpp
... / ...
CommitLineData
1/*
2 * This file is part of the PulseView project.
3 *
4 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
5 *
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.
10 *
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.
15 *
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/>.
18 */
19
20extern "C" {
21#include <libsigrokdecode/libsigrokdecode.h>
22}
23
24#include <mutex>
25
26#include <extdef.h>
27
28#include <tuple>
29
30#include <boost/functional/hash.hpp>
31
32#include <QAction>
33#include <QApplication>
34#include <QComboBox>
35#include <QFormLayout>
36#include <QLabel>
37#include <QMenu>
38#include <QPushButton>
39#include <QToolTip>
40
41#include "decodetrace.hpp"
42
43#include <pv/data/decode/annotation.hpp>
44#include <pv/data/decode/decoder.hpp>
45#include <pv/data/decoderstack.hpp>
46#include <pv/data/logic.hpp>
47#include <pv/data/logicsegment.hpp>
48#include <pv/session.hpp>
49#include <pv/strnatcmp.hpp>
50#include <pv/view/view.hpp>
51#include <pv/view/viewport.hpp>
52#include <pv/widgets/decodergroupbox.hpp>
53#include <pv/widgets/decodermenu.hpp>
54
55using std::all_of;
56using std::list;
57using std::make_pair;
58using std::max;
59using std::make_pair;
60using std::map;
61using std::min;
62using std::out_of_range;
63using std::pair;
64using std::shared_ptr;
65using std::make_shared;
66using std::tie;
67using std::unordered_set;
68using std::vector;
69
70namespace pv {
71namespace views {
72namespace TraceView {
73
74const 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
79};
80
81const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
82const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
83
84const int DecodeTrace::ArrowSize = 4;
85const double DecodeTrace::EndCapWidth = 5;
86const int DecodeTrace::RowTitleMargin = 10;
87const int DecodeTrace::DrawPadding = 100;
88
89const QColor DecodeTrace::Colours[16] = {
90 QColor(0xEF, 0x29, 0x29),
91 QColor(0xF6, 0x6A, 0x32),
92 QColor(0xFC, 0xAE, 0x3E),
93 QColor(0xFB, 0xCA, 0x47),
94 QColor(0xFC, 0xE9, 0x4F),
95 QColor(0xCD, 0xF0, 0x40),
96 QColor(0x8A, 0xE2, 0x34),
97 QColor(0x4E, 0xDC, 0x44),
98 QColor(0x55, 0xD7, 0x95),
99 QColor(0x64, 0xD1, 0xD2),
100 QColor(0x72, 0x9F, 0xCF),
101 QColor(0xD4, 0x76, 0xC4),
102 QColor(0x9D, 0x79, 0xB9),
103 QColor(0xAD, 0x7F, 0xA8),
104 QColor(0xC2, 0x62, 0x9B),
105 QColor(0xD7, 0x47, 0x6F)
106};
107
108const QColor DecodeTrace::OutlineColours[16] = {
109 QColor(0x77, 0x14, 0x14),
110 QColor(0x7B, 0x35, 0x19),
111 QColor(0x7E, 0x57, 0x1F),
112 QColor(0x7D, 0x65, 0x23),
113 QColor(0x7E, 0x74, 0x27),
114 QColor(0x66, 0x78, 0x20),
115 QColor(0x45, 0x71, 0x1A),
116 QColor(0x27, 0x6E, 0x22),
117 QColor(0x2A, 0x6B, 0x4A),
118 QColor(0x32, 0x68, 0x69),
119 QColor(0x39, 0x4F, 0x67),
120 QColor(0x6A, 0x3B, 0x62),
121 QColor(0x4E, 0x3C, 0x5C),
122 QColor(0x56, 0x3F, 0x54),
123 QColor(0x61, 0x31, 0x4D),
124 QColor(0x6B, 0x23, 0x37)
125};
126
127DecodeTrace::DecodeTrace(pv::Session &session,
128 shared_ptr<data::SignalBase> signalbase, int index) :
129 Trace(signalbase),
130 session_(session),
131 row_height_(0),
132 max_visible_rows_(0),
133 delete_mapper_(this),
134 show_hide_mapper_(this)
135{
136 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
137
138 // Determine shortest string we want to see displayed in full
139 QFontMetrics m(QApplication::font());
140 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
141
142 base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
143 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
144
145 connect(decoder_stack.get(), SIGNAL(new_decode_data()),
146 this, SLOT(on_new_decode_data()));
147 connect(&delete_mapper_, SIGNAL(mapped(int)),
148 this, SLOT(on_delete_decoder(int)));
149 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
150 this, SLOT(on_show_hide_decoder(int)));
151}
152
153bool DecodeTrace::enabled() const
154{
155 return true;
156}
157
158shared_ptr<data::SignalBase> DecodeTrace::base() const
159{
160 return base_;
161}
162
163pair<int, int> DecodeTrace::v_extents() const
164{
165 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
166
167 // Make an empty decode trace appear symmetrical
168 const int row_count = max(1, max_visible_rows_);
169
170 return make_pair(-row_height, row_height * row_count);
171}
172
173void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
174{
175 Trace::paint_back(p, pp);
176 paint_axis(p, pp, get_visual_y());
177}
178
179void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
180{
181 using namespace pv::data::decode;
182
183 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
184
185 const int text_height = ViewItemPaintParams::text_height();
186 row_height_ = (text_height * 6) / 4;
187 const int annotation_height = (text_height * 5) / 4;
188
189 assert(decoder_stack);
190 const QString err = decoder_stack->error_message();
191 if (!err.isEmpty()) {
192 draw_unresolved_period(
193 p, annotation_height, pp.left(), pp.right());
194 draw_error(p, err, pp);
195 return;
196 }
197
198 // Set default pen to allow for text width calculation
199 p.setPen(Qt::black);
200
201 // Iterate through the rows
202 int y = get_visual_y();
203 pair<uint64_t, uint64_t> sample_range = get_sample_range(
204 pp.left(), pp.right());
205
206 const vector<Row> rows(decoder_stack->get_visible_rows());
207
208 visible_rows_.clear();
209 for (const Row& row : rows) {
210 // Cache the row title widths
211 int row_title_width;
212 try {
213 row_title_width = row_title_widths_.at(row);
214 } catch (out_of_range) {
215 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
216 RowTitleMargin;
217 row_title_widths_[row] = w;
218 row_title_width = w;
219 }
220
221 // Determine the row's color
222 size_t base_colour = 0x13579BDF;
223 boost::hash_combine(base_colour, this);
224 boost::hash_combine(base_colour, row.decoder());
225 boost::hash_combine(base_colour, row.row());
226 base_colour >>= 16;
227
228 vector<Annotation> annotations;
229 decoder_stack->get_annotation_subset(annotations, row,
230 sample_range.first, sample_range.second);
231 if (!annotations.empty()) {
232 draw_annotations(annotations, p, annotation_height, pp, y,
233 base_colour, row_title_width);
234
235 y += row_height_;
236
237 visible_rows_.push_back(row);
238 }
239 }
240
241 // Draw the hatching
242 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
243
244 if ((int)visible_rows_.size() > max_visible_rows_)
245 owner_->extents_changed(false, true);
246
247 // Update the maximum row count if needed
248 max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
249}
250
251void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
252{
253 using namespace pv::data::decode;
254
255 assert(row_height_);
256
257 for (size_t i = 0; i < visible_rows_.size(); i++) {
258 const int y = i * row_height_ + get_visual_y();
259
260 p.setPen(QPen(Qt::NoPen));
261 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
262
263 if (i != 0) {
264 const QPointF points[] = {
265 QPointF(pp.left(), y - ArrowSize),
266 QPointF(pp.left() + ArrowSize, y),
267 QPointF(pp.left(), y + ArrowSize)
268 };
269 p.drawPolygon(points, countof(points));
270 }
271
272 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
273 pp.right() - pp.left(), row_height_);
274 const QString h(visible_rows_[i].title());
275 const int f = Qt::AlignLeft | Qt::AlignVCenter |
276 Qt::TextDontClip;
277
278 // Draw the outline
279 p.setPen(QApplication::palette().color(QPalette::Base));
280 for (int dx = -1; dx <= 1; dx++)
281 for (int dy = -1; dy <= 1; dy++)
282 if (dx != 0 && dy != 0)
283 p.drawText(r.translated(dx, dy), f, h);
284
285 // Draw the text
286 p.setPen(QApplication::palette().color(QPalette::WindowText));
287 p.drawText(r, f, h);
288 }
289}
290
291void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
292{
293 using pv::data::decode::Decoder;
294
295 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
296
297 assert(form);
298 assert(parent);
299 assert(decoder_stack);
300
301 // Add the standard options
302 Trace::populate_popup_form(parent, form);
303
304 // Add the decoder options
305 bindings_.clear();
306 channel_selectors_.clear();
307 decoder_forms_.clear();
308
309 const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
310
311 if (stack.empty()) {
312 QLabel *const l = new QLabel(
313 tr("<p><i>No decoders in the stack</i></p>"));
314 l->setAlignment(Qt::AlignCenter);
315 form->addRow(l);
316 } else {
317 auto iter = stack.cbegin();
318 for (int i = 0; i < (int)stack.size(); i++, iter++) {
319 shared_ptr<Decoder> dec(*iter);
320 create_decoder_form(i, dec, parent, form);
321 }
322
323 form->addRow(new QLabel(
324 tr("<i>* Required channels</i>"), parent));
325 }
326
327 // Add stacking button
328 pv::widgets::DecoderMenu *const decoder_menu =
329 new pv::widgets::DecoderMenu(parent);
330 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
331 this, SLOT(on_stack_decoder(srd_decoder*)));
332
333 QPushButton *const stack_button =
334 new QPushButton(tr("Stack Decoder"), parent);
335 stack_button->setMenu(decoder_menu);
336 stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
337
338 QHBoxLayout *stack_button_box = new QHBoxLayout;
339 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
340 form->addRow(stack_button_box);
341}
342
343QMenu* DecodeTrace::create_context_menu(QWidget *parent)
344{
345 QMenu *const menu = Trace::create_context_menu(parent);
346
347 menu->addSeparator();
348
349 QAction *const del = new QAction(tr("Delete"), this);
350 del->setShortcuts(QKeySequence::Delete);
351 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
352 menu->addAction(del);
353
354 return menu;
355}
356
357void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
358 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
359 size_t base_colour, int row_title_width)
360{
361 using namespace pv::data::decode;
362
363 vector<Annotation> a_block;
364 int p_end = INT_MIN;
365
366 double samples_per_pixel, pixels_offset;
367 tie(pixels_offset, samples_per_pixel) =
368 get_pixels_offset_samples_per_pixel();
369
370 // Sort the annotations by start sample so that decoders
371 // can't confuse us by creating annotations out of order
372 stable_sort(annotations.begin(), annotations.end(),
373 [](const Annotation &a, const Annotation &b) {
374 return a.start_sample() < b.start_sample(); });
375
376 // Gather all annotations that form a visual "block" and draw them as such
377 for (const Annotation &a : annotations) {
378
379 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
380 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
381 const int a_width = a_end - a_start;
382
383 const int delta = a_end - p_end;
384
385 bool a_is_separate = false;
386
387 // Annotation wider than the threshold for a useful label width?
388 if (a_width >= min_useful_label_width_) {
389 for (const QString &ann_text : a.annotations()) {
390 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
391 // Annotation wide enough to fit a label? Don't put it in a block then
392 if (w <= a_width) {
393 a_is_separate = true;
394 break;
395 }
396 }
397 }
398
399 // Were the previous and this annotation more than a pixel apart?
400 if ((abs(delta) > 1) || a_is_separate) {
401 // Block was broken, draw annotations that form the current block
402 if (a_block.size() == 1) {
403 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
404 row_title_width);
405 }
406 else
407 draw_annotation_block(a_block, p, h, y, base_colour);
408
409 a_block.clear();
410 }
411
412 if (a_is_separate) {
413 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
414 // Next annotation must start a new block. delta will be > 1
415 // because we set p_end to INT_MIN but that's okay since
416 // a_block will be empty, so nothing will be drawn
417 p_end = INT_MIN;
418 } else {
419 a_block.push_back(a);
420 p_end = a_end;
421 }
422 }
423
424 if (a_block.size() == 1)
425 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
426 row_title_width);
427 else
428 draw_annotation_block(a_block, p, h, y, base_colour);
429}
430
431void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
432 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
433 size_t base_colour, int row_title_width) const
434{
435 double samples_per_pixel, pixels_offset;
436 tie(pixels_offset, samples_per_pixel) =
437 get_pixels_offset_samples_per_pixel();
438
439 const double start = a.start_sample() / samples_per_pixel -
440 pixels_offset;
441 const double end = a.end_sample() / samples_per_pixel - pixels_offset;
442
443 const size_t colour = (base_colour + a.format()) % countof(Colours);
444 p.setPen(OutlineColours[colour]);
445 p.setBrush(Colours[colour]);
446
447 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
448 return;
449
450 if (a.start_sample() == a.end_sample())
451 draw_instant(a, p, h, start, y);
452 else
453 draw_range(a, p, h, start, end, y, pp, row_title_width);
454}
455
456void DecodeTrace::draw_annotation_block(
457 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
458 int y, size_t base_colour) const
459{
460 using namespace pv::data::decode;
461
462 if (annotations.empty())
463 return;
464
465 double samples_per_pixel, pixels_offset;
466 tie(pixels_offset, samples_per_pixel) =
467 get_pixels_offset_samples_per_pixel();
468
469 const double start = annotations.front().start_sample() /
470 samples_per_pixel - pixels_offset;
471 const double end = annotations.back().end_sample() /
472 samples_per_pixel - pixels_offset;
473
474 const double top = y + .5 - h / 2;
475 const double bottom = y + .5 + h / 2;
476
477 const size_t colour = (base_colour + annotations.front().format()) %
478 countof(Colours);
479
480 // Check if all annotations are of the same type (i.e. we can use one color)
481 // or if we should use a neutral color (i.e. gray)
482 const int format = annotations.front().format();
483 const bool single_format = all_of(
484 annotations.begin(), annotations.end(),
485 [&](const Annotation &a) { return a.format() == format; });
486
487 const QRectF rect(start, top, end - start, bottom - top);
488 const int r = h / 4;
489
490 p.setPen(QPen(Qt::NoPen));
491 p.setBrush(Qt::white);
492 p.drawRoundedRect(rect, r, r);
493
494 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
495 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
496 Qt::Dense4Pattern));
497 p.drawRoundedRect(rect, r, r);
498}
499
500void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
501 int h, double x, int y) const
502{
503 const QString text = a.annotations().empty() ?
504 QString() : a.annotations().back();
505 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
506 0.0) + h;
507 const QRectF rect(x - w / 2, y - h / 2, w, h);
508
509 p.drawRoundedRect(rect, h / 2, h / 2);
510
511 p.setPen(Qt::black);
512 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
513}
514
515void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
516 int h, double start, double end, int y, const ViewItemPaintParams &pp,
517 int row_title_width) const
518{
519 const double top = y + .5 - h / 2;
520 const double bottom = y + .5 + h / 2;
521 const vector<QString> annotations = a.annotations();
522
523 // If the two ends are within 1 pixel, draw a vertical line
524 if (start + 1.0 > end) {
525 p.drawLine(QPointF(start, top), QPointF(start, bottom));
526 return;
527 }
528
529 const double cap_width = min((end - start) / 4, EndCapWidth);
530
531 QPointF pts[] = {
532 QPointF(start, y + .5f),
533 QPointF(start + cap_width, top),
534 QPointF(end - cap_width, top),
535 QPointF(end, y + .5f),
536 QPointF(end - cap_width, bottom),
537 QPointF(start + cap_width, bottom)
538 };
539
540 p.drawConvexPolygon(pts, countof(pts));
541
542 if (annotations.empty())
543 return;
544
545 const int ann_start = start + cap_width;
546 const int ann_end = end - cap_width;
547
548 const int real_start = max(ann_start, pp.left() + row_title_width);
549 const int real_end = min(ann_end, pp.right());
550 const int real_width = real_end - real_start;
551
552 QRectF rect(real_start, y - h / 2, real_width, h);
553 if (rect.width() <= 4)
554 return;
555
556 p.setPen(Qt::black);
557
558 // Try to find an annotation that will fit
559 QString best_annotation;
560 int best_width = 0;
561
562 for (const QString &a : annotations) {
563 const int w = p.boundingRect(QRectF(), 0, a).width();
564 if (w <= rect.width() && w > best_width)
565 best_annotation = a, best_width = w;
566 }
567
568 if (best_annotation.isEmpty())
569 best_annotation = annotations.back();
570
571 // If not ellide the last in the list
572 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
573 best_annotation, Qt::ElideRight, rect.width()));
574}
575
576void DecodeTrace::draw_error(QPainter &p, const QString &message,
577 const ViewItemPaintParams &pp)
578{
579 const int y = get_visual_y();
580
581 p.setPen(ErrorBgColour.darker());
582 p.setBrush(ErrorBgColour);
583
584 const QRectF bounding_rect =
585 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
586 const QRectF text_rect = p.boundingRect(bounding_rect,
587 Qt::AlignCenter, message);
588 const float r = text_rect.height() / 4;
589
590 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
591 Qt::AbsoluteSize);
592
593 p.setPen(Qt::black);
594 p.drawText(text_rect, message);
595}
596
597void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
598 int right) const
599{
600 using namespace pv::data;
601 using pv::data::decode::Decoder;
602
603 double samples_per_pixel, pixels_offset;
604
605 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
606
607 assert(decoder_stack);
608
609 shared_ptr<Logic> data;
610 shared_ptr<data::SignalBase> signalbase;
611
612 const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
613
614 // We get the logic data of the first channel in the list.
615 // This works because we are currently assuming all
616 // LogicSignals have the same data/segment
617 for (const shared_ptr<Decoder> &dec : stack)
618 if (dec && !dec->channels().empty() &&
619 ((signalbase = (*dec->channels().begin()).second)) &&
620 ((data = signalbase->logic_data())))
621 break;
622
623 if (!data || data->logic_segments().empty())
624 return;
625
626 const shared_ptr<LogicSegment> segment = data->logic_segments().front();
627 assert(segment);
628 const int64_t sample_count = (int64_t)segment->get_sample_count();
629 if (sample_count == 0)
630 return;
631
632 const int64_t samples_decoded = decoder_stack->samples_decoded();
633 if (sample_count == samples_decoded)
634 return;
635
636 const int y = get_visual_y();
637
638 tie(pixels_offset, samples_per_pixel) =
639 get_pixels_offset_samples_per_pixel();
640
641 const double start = max(samples_decoded /
642 samples_per_pixel - pixels_offset, left - 1.0);
643 const double end = min(sample_count / samples_per_pixel -
644 pixels_offset, right + 1.0);
645 const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
646
647 p.setPen(QPen(Qt::NoPen));
648 p.setBrush(Qt::white);
649 p.drawRect(no_decode_rect);
650
651 p.setPen(NoDecodeColour);
652 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
653 p.drawRect(no_decode_rect);
654}
655
656pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
657{
658 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
659
660 assert(owner_);
661 assert(decoder_stack);
662
663 const View *view = owner_->view();
664 assert(view);
665
666 const double scale = view->scale();
667 assert(scale > 0);
668
669 const double pixels_offset =
670 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
671
672 double samplerate = decoder_stack->samplerate();
673
674 // Show sample rate as 1Hz when it is unknown
675 if (samplerate == 0.0)
676 samplerate = 1.0;
677
678 return make_pair(pixels_offset, samplerate * scale);
679}
680
681pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
682 int x_start, int x_end) const
683{
684 double samples_per_pixel, pixels_offset;
685 tie(pixels_offset, samples_per_pixel) =
686 get_pixels_offset_samples_per_pixel();
687
688 const uint64_t start = (uint64_t)max(
689 (x_start + pixels_offset) * samples_per_pixel, 0.0);
690 const uint64_t end = (uint64_t)max(
691 (x_end + pixels_offset) * samples_per_pixel, 0.0);
692
693 return make_pair(start, end);
694}
695
696int DecodeTrace::get_row_at_point(const QPoint &point)
697{
698 if (!row_height_)
699 return -1;
700
701 const int y = (point.y() - get_visual_y() + row_height_ / 2);
702
703 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
704 if (y < 0)
705 return -1;
706
707 const int row = y / row_height_;
708
709 if (row >= (int)visible_rows_.size())
710 return -1;
711
712 return row;
713}
714
715const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
716{
717 using namespace pv::data::decode;
718
719 if (!enabled())
720 return QString();
721
722 const pair<uint64_t, uint64_t> sample_range =
723 get_sample_range(point.x(), point.x() + 1);
724 const int row = get_row_at_point(point);
725 if (row < 0)
726 return QString();
727
728 vector<pv::data::decode::Annotation> annotations;
729
730 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
731
732 assert(decoder_stack);
733 decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
734 sample_range.first, sample_range.second);
735
736 return (annotations.empty()) ?
737 QString() : annotations[0].annotations().front();
738}
739
740void DecodeTrace::hover_point_changed()
741{
742 assert(owner_);
743
744 const View *const view = owner_->view();
745 assert(view);
746
747 QPoint hp = view->hover_point();
748 QString ann = get_annotation_at_point(hp);
749
750 assert(view);
751
752 if (!row_height_ || ann.isEmpty()) {
753 QToolTip::hideText();
754 return;
755 }
756
757 const int hover_row = get_row_at_point(hp);
758
759 QFontMetrics m(QToolTip::font());
760 const QRect text_size = m.boundingRect(QRect(), 0, ann);
761
762 // This is OS-specific and unfortunately we can't query it, so
763 // use an approximation to at least try to minimize the error.
764 const int padding = 8;
765
766 // Make sure the tool tip doesn't overlap with the mouse cursor.
767 // If it did, the tool tip would constantly hide and re-appear.
768 // We also push it up by one row so that it appears above the
769 // decode trace, not below.
770 hp.setX(hp.x() - (text_size.width() / 2) - padding);
771
772 hp.setY(get_visual_y() - (row_height_ / 2) +
773 (hover_row * row_height_) -
774 row_height_ - text_size.height() - padding);
775
776 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
777}
778
779void DecodeTrace::create_decoder_form(int index,
780 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
781 QFormLayout *form)
782{
783 const GSList *l;
784
785 assert(dec);
786 const srd_decoder *const decoder = dec->decoder();
787 assert(decoder);
788
789 const bool decoder_deletable = index > 0;
790
791 pv::widgets::DecoderGroupBox *const group =
792 new pv::widgets::DecoderGroupBox(
793 QString::fromUtf8(decoder->name),
794 tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
795 QString::fromUtf8(decoder->desc)),
796 nullptr, decoder_deletable);
797 group->set_decoder_visible(dec->shown());
798
799 if (decoder_deletable) {
800 delete_mapper_.setMapping(group, index);
801 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
802 }
803
804 show_hide_mapper_.setMapping(group, index);
805 connect(group, SIGNAL(show_hide_decoder()),
806 &show_hide_mapper_, SLOT(map()));
807
808 QFormLayout *const decoder_form = new QFormLayout;
809 group->add_layout(decoder_form);
810
811 // Add the mandatory channels
812 for (l = decoder->channels; l; l = l->next) {
813 const struct srd_channel *const pdch =
814 (struct srd_channel *)l->data;
815 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
816 connect(combo, SIGNAL(currentIndexChanged(int)),
817 this, SLOT(on_channel_selected(int)));
818 decoder_form->addRow(tr("<b>%1</b> (%2) *")
819 .arg(QString::fromUtf8(pdch->name),
820 QString::fromUtf8(pdch->desc)), combo);
821
822 const ChannelSelector s = {combo, dec, pdch};
823 channel_selectors_.push_back(s);
824 }
825
826 // Add the optional channels
827 for (l = decoder->opt_channels; l; l = l->next) {
828 const struct srd_channel *const pdch =
829 (struct srd_channel *)l->data;
830 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
831 connect(combo, SIGNAL(currentIndexChanged(int)),
832 this, SLOT(on_channel_selected(int)));
833 decoder_form->addRow(tr("<b>%1</b> (%2)")
834 .arg(QString::fromUtf8(pdch->name),
835 QString::fromUtf8(pdch->desc)), combo);
836
837 const ChannelSelector s = {combo, dec, pdch};
838 channel_selectors_.push_back(s);
839 }
840
841 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
842
843 // Add the options
844 shared_ptr<binding::Decoder> binding(
845 new binding::Decoder(decoder_stack, dec));
846 binding->add_properties_to_form(decoder_form, true);
847
848 bindings_.push_back(binding);
849
850 form->addRow(group);
851 decoder_forms_.push_back(group);
852}
853
854QComboBox* DecodeTrace::create_channel_selector(
855 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
856 const srd_channel *const pdch)
857{
858 assert(dec);
859
860 const auto sigs(session_.signalbases());
861
862 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
863 sort(sig_list.begin(), sig_list.end(),
864 [](const shared_ptr<data::SignalBase> &a,
865 const shared_ptr<data::SignalBase> &b) {
866 return strnatcasecmp(a->name().toStdString(),
867 b->name().toStdString()) < 0; });
868
869 const auto channel_iter = dec->channels().find(pdch);
870
871 QComboBox *selector = new QComboBox(parent);
872
873 selector->addItem("-", qVariantFromValue((void*)nullptr));
874
875 if (channel_iter == dec->channels().end())
876 selector->setCurrentIndex(0);
877
878 for (const shared_ptr<data::SignalBase> &b : sig_list) {
879 assert(b);
880 if (b->logic_data() && b->enabled()) {
881 selector->addItem(b->name(),
882 qVariantFromValue((void*)b.get()));
883
884 if (channel_iter != dec->channels().end() &&
885 (*channel_iter).second == b)
886 selector->setCurrentIndex(
887 selector->count() - 1);
888 }
889 }
890
891 return selector;
892}
893
894void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
895{
896 assert(dec);
897
898 map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
899
900 const unordered_set< shared_ptr<data::SignalBase> >
901 sigs(session_.signalbases());
902
903 for (const ChannelSelector &s : channel_selectors_) {
904 if (s.decoder_ != dec)
905 break;
906
907 const data::SignalBase *const selection =
908 (data::SignalBase*)s.combo_->itemData(
909 s.combo_->currentIndex()).value<void*>();
910
911 for (shared_ptr<data::SignalBase> sig : sigs)
912 if (sig.get() == selection) {
913 channel_map[s.pdch_] = sig;
914 break;
915 }
916 }
917
918 dec->set_channels(channel_map);
919}
920
921void DecodeTrace::commit_channels()
922{
923 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
924
925 assert(decoder_stack);
926 for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
927 commit_decoder_channels(dec);
928
929 decoder_stack->begin_decode();
930}
931
932void DecodeTrace::on_new_decode_data()
933{
934 if (owner_)
935 owner_->row_item_appearance_changed(false, true);
936}
937
938void DecodeTrace::delete_pressed()
939{
940 on_delete();
941}
942
943void DecodeTrace::on_delete()
944{
945 session_.remove_decode_signal(base_);
946}
947
948void DecodeTrace::on_channel_selected(int)
949{
950 commit_channels();
951}
952
953void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
954{
955 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
956
957 assert(decoder);
958 assert(decoder_stack);
959 decoder_stack->push(make_shared<data::decode::Decoder>(decoder));
960 decoder_stack->begin_decode();
961
962 create_popup_form();
963}
964
965void DecodeTrace::on_delete_decoder(int index)
966{
967 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
968
969 decoder_stack->remove(index);
970
971 // Update the popup
972 create_popup_form();
973
974 decoder_stack->begin_decode();
975}
976
977void DecodeTrace::on_show_hide_decoder(int index)
978{
979 using pv::data::decode::Decoder;
980
981 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
982
983 const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
984
985 // Find the decoder in the stack
986 auto iter = stack.cbegin();
987 for (int i = 0; i < index; i++, iter++)
988 assert(iter != stack.end());
989
990 shared_ptr<Decoder> dec = *iter;
991 assert(dec);
992
993 const bool show = !dec->shown();
994 dec->show(show);
995
996 assert(index < (int)decoder_forms_.size());
997 decoder_forms_[index]->set_decoder_visible(show);
998
999 if (owner_)
1000 owner_->row_item_appearance_changed(false, true);
1001}
1002
1003} // namespace TraceView
1004} // namespace views
1005} // namespace pv