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