]> sigrok.org Git - pulseview.git/blame_incremental - pv/views/trace/decodetrace.cpp
Re-use DecodeTrace::ChannelSelector as DecodeChannel
[pulseview.git] / pv / views / trace / 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#include "view.hpp"
43#include "viewport.hpp"
44
45#include <pv/globalsettings.hpp>
46#include <pv/session.hpp>
47#include <pv/strnatcmp.hpp>
48#include <pv/data/decodesignal.hpp>
49#include <pv/data/decode/annotation.hpp>
50#include <pv/data/decode/decoder.hpp>
51#include <pv/data/decoderstack.hpp>
52#include <pv/data/logic.hpp>
53#include <pv/data/logicsegment.hpp>
54#include <pv/widgets/decodergroupbox.hpp>
55#include <pv/widgets/decodermenu.hpp>
56
57using std::all_of;
58using std::list;
59using std::make_pair;
60using std::max;
61using std::make_pair;
62using std::map;
63using std::min;
64using std::out_of_range;
65using std::pair;
66using std::shared_ptr;
67using std::make_shared;
68using std::tie;
69using std::unordered_set;
70using std::vector;
71
72using pv::data::decode::Annotation;
73using pv::data::decode::Row;
74using pv::data::DecodeChannel;
75using pv::data::DecodeSignal;
76
77namespace pv {
78namespace views {
79namespace trace {
80
81const QColor DecodeTrace::DecodeColours[4] = {
82 QColor(0xEF, 0x29, 0x29), // Red
83 QColor(0xFC, 0xE9, 0x4F), // Yellow
84 QColor(0x8A, 0xE2, 0x34), // Green
85 QColor(0x72, 0x9F, 0xCF) // Blue
86};
87
88const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
89const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
90
91const int DecodeTrace::ArrowSize = 4;
92const double DecodeTrace::EndCapWidth = 5;
93const int DecodeTrace::RowTitleMargin = 10;
94const int DecodeTrace::DrawPadding = 100;
95
96const QColor DecodeTrace::Colours[16] = {
97 QColor(0xEF, 0x29, 0x29),
98 QColor(0xF6, 0x6A, 0x32),
99 QColor(0xFC, 0xAE, 0x3E),
100 QColor(0xFB, 0xCA, 0x47),
101 QColor(0xFC, 0xE9, 0x4F),
102 QColor(0xCD, 0xF0, 0x40),
103 QColor(0x8A, 0xE2, 0x34),
104 QColor(0x4E, 0xDC, 0x44),
105 QColor(0x55, 0xD7, 0x95),
106 QColor(0x64, 0xD1, 0xD2),
107 QColor(0x72, 0x9F, 0xCF),
108 QColor(0xD4, 0x76, 0xC4),
109 QColor(0x9D, 0x79, 0xB9),
110 QColor(0xAD, 0x7F, 0xA8),
111 QColor(0xC2, 0x62, 0x9B),
112 QColor(0xD7, 0x47, 0x6F)
113};
114
115const QColor DecodeTrace::OutlineColours[16] = {
116 QColor(0x77, 0x14, 0x14),
117 QColor(0x7B, 0x35, 0x19),
118 QColor(0x7E, 0x57, 0x1F),
119 QColor(0x7D, 0x65, 0x23),
120 QColor(0x7E, 0x74, 0x27),
121 QColor(0x66, 0x78, 0x20),
122 QColor(0x45, 0x71, 0x1A),
123 QColor(0x27, 0x6E, 0x22),
124 QColor(0x2A, 0x6B, 0x4A),
125 QColor(0x32, 0x68, 0x69),
126 QColor(0x39, 0x4F, 0x67),
127 QColor(0x6A, 0x3B, 0x62),
128 QColor(0x4E, 0x3C, 0x5C),
129 QColor(0x56, 0x3F, 0x54),
130 QColor(0x61, 0x31, 0x4D),
131 QColor(0x6B, 0x23, 0x37)
132};
133
134DecodeTrace::DecodeTrace(pv::Session &session,
135 shared_ptr<data::SignalBase> signalbase, int index) :
136 Trace(signalbase),
137 session_(session),
138 row_height_(0),
139 max_visible_rows_(0),
140 delete_mapper_(this),
141 show_hide_mapper_(this)
142{
143 decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
144
145 // Determine shortest string we want to see displayed in full
146 QFontMetrics m(QApplication::font());
147 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
148
149 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
150
151 connect(decode_signal_.get(), SIGNAL(new_annotations()),
152 this, SLOT(on_new_annotations()));
153 connect(decode_signal_.get(), SIGNAL(channels_updated()),
154 this, SLOT(on_channels_updated()));
155
156 connect(&delete_mapper_, SIGNAL(mapped(int)),
157 this, SLOT(on_delete_decoder(int)));
158 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
159 this, SLOT(on_show_hide_decoder(int)));
160}
161
162bool DecodeTrace::enabled() const
163{
164 return true;
165}
166
167shared_ptr<data::SignalBase> DecodeTrace::base() const
168{
169 return base_;
170}
171
172pair<int, int> DecodeTrace::v_extents() const
173{
174 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
175
176 // Make an empty decode trace appear symmetrical
177 const int row_count = max(1, max_visible_rows_);
178
179 return make_pair(-row_height, row_height * row_count);
180}
181
182void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
183{
184 Trace::paint_back(p, pp);
185 paint_axis(p, pp, get_visual_y());
186}
187
188void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
189{
190 const int text_height = ViewItemPaintParams::text_height();
191 row_height_ = (text_height * 6) / 4;
192 const int annotation_height = (text_height * 5) / 4;
193
194 const QString err = decode_signal_->error_message();
195 if (!err.isEmpty()) {
196 draw_unresolved_period(
197 p, annotation_height, pp.left(), pp.right());
198 draw_error(p, err, pp);
199 return;
200 }
201
202 // Set default pen to allow for text width calculation
203 p.setPen(Qt::black);
204
205 // Iterate through the rows
206 int y = get_visual_y();
207 pair<uint64_t, uint64_t> sample_range = get_sample_range(
208 pp.left(), pp.right());
209
210 const vector<Row> rows = decode_signal_->visible_rows();
211
212 visible_rows_.clear();
213 for (const Row& row : rows) {
214 // Cache the row title widths
215 int row_title_width;
216 try {
217 row_title_width = row_title_widths_.at(row);
218 } catch (out_of_range) {
219 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
220 RowTitleMargin;
221 row_title_widths_[row] = w;
222 row_title_width = w;
223 }
224
225 // Determine the row's color
226 size_t base_colour = 0x13579BDF;
227 boost::hash_combine(base_colour, this);
228 boost::hash_combine(base_colour, row.decoder());
229 boost::hash_combine(base_colour, row.row());
230 base_colour >>= 16;
231
232 vector<Annotation> annotations;
233 decode_signal_->get_annotation_subset(annotations, row,
234 sample_range.first, sample_range.second);
235 if (!annotations.empty()) {
236 draw_annotations(annotations, p, annotation_height, pp, y,
237 base_colour, row_title_width);
238
239 y += row_height_;
240
241 visible_rows_.push_back(row);
242 }
243 }
244
245 // Draw the hatching
246 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
247
248 if ((int)visible_rows_.size() > max_visible_rows_)
249 owner_->extents_changed(false, true);
250
251 // Update the maximum row count if needed
252 max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
253}
254
255void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
256{
257 assert(row_height_);
258
259 for (size_t i = 0; i < visible_rows_.size(); i++) {
260 const int y = i * row_height_ + get_visual_y();
261
262 p.setPen(QPen(Qt::NoPen));
263 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
264
265 if (i != 0) {
266 const QPointF points[] = {
267 QPointF(pp.left(), y - ArrowSize),
268 QPointF(pp.left() + ArrowSize, y),
269 QPointF(pp.left(), y + ArrowSize)
270 };
271 p.drawPolygon(points, countof(points));
272 }
273
274 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
275 pp.right() - pp.left(), row_height_);
276 const QString h(visible_rows_[i].title());
277 const int f = Qt::AlignLeft | Qt::AlignVCenter |
278 Qt::TextDontClip;
279
280 // Draw the outline
281 p.setPen(QApplication::palette().color(QPalette::Base));
282 for (int dx = -1; dx <= 1; dx++)
283 for (int dy = -1; dy <= 1; dy++)
284 if (dx != 0 && dy != 0)
285 p.drawText(r.translated(dx, dy), f, h);
286
287 // Draw the text
288 p.setPen(QApplication::palette().color(QPalette::WindowText));
289 p.drawText(r, f, h);
290 }
291}
292
293void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
294{
295 using pv::data::decode::Decoder;
296
297 assert(form);
298
299 // Add the standard options
300 Trace::populate_popup_form(parent, form);
301
302 // Add the decoder options
303 bindings_.clear();
304 channel_id_map_.clear();
305 init_state_map_.clear();
306 decoder_forms_.clear();
307
308 const list< shared_ptr<Decoder> >& stack =
309 decode_signal_->decoder_stack_list();
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.left(), INT_MIN / 2 + y, pp.right(), 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 GlobalSettings settings;
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 const list<DecodeChannel> channels = decode_signal_->get_channels();
812
813 // Add the channels
814 for (DecodeChannel ch : channels) {
815 // Ignore channels not part of the decoder we create the form for
816 if (ch.decoder_ != dec)
817 continue;
818
819 QComboBox *const combo = create_channel_selector(parent, &ch);
820 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
821
822 channel_id_map_[combo] = ch.id;
823 init_state_map_[combo_init_state] = ch.id;
824
825 connect(combo, SIGNAL(currentIndexChanged(int)),
826 this, SLOT(on_channel_selected(int)));
827 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
828 this, SLOT(on_init_state_changed(int)));
829
830 QHBoxLayout *const hlayout = new QHBoxLayout;
831 hlayout->addWidget(combo);
832 hlayout->addWidget(combo_init_state);
833
834 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
835 combo_init_state->hide();
836
837 const QString required_flag = ch.is_optional ? QString() : QString("*");
838 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
839 .arg(ch.name, ch.desc, required_flag), hlayout);
840 }
841
842 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
843
844 // Add the options
845 shared_ptr<binding::Decoder> binding(
846 new binding::Decoder(decoder_stack, dec));
847 binding->add_properties_to_form(decoder_form, true);
848
849 bindings_.push_back(binding);
850
851 form->addRow(group);
852 decoder_forms_.push_back(group);
853}
854
855QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
856{
857 const auto sigs(session_.signalbases());
858
859 // Sort signals in natural order
860 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
861 sort(sig_list.begin(), sig_list.end(),
862 [](const shared_ptr<data::SignalBase> &a,
863 const shared_ptr<data::SignalBase> &b) {
864 return strnatcasecmp(a->name().toStdString(),
865 b->name().toStdString()) < 0; });
866
867 QComboBox *selector = new QComboBox(parent);
868
869 selector->addItem("-", qVariantFromValue((void*)nullptr));
870
871 if (!ch->assigned_signal)
872 selector->setCurrentIndex(0);
873
874 for (const shared_ptr<data::SignalBase> &b : sig_list) {
875 assert(b);
876 if (b->logic_data() && b->enabled()) {
877 selector->addItem(b->name(),
878 qVariantFromValue((void*)b.get()));
879
880 if (ch->assigned_signal == b.get())
881 selector->setCurrentIndex(selector->count() - 1);
882 }
883 }
884
885 return selector;
886}
887
888QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
889 const DecodeChannel *ch)
890{
891 QComboBox *selector = new QComboBox(parent);
892
893 selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
894 selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
895 selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
896
897 selector->setCurrentIndex(ch->initial_pin_state);
898
899 selector->setToolTip("Initial (assumed) pin value before the first sample");
900
901 return selector;
902}
903
904void DecodeTrace::on_new_annotations()
905{
906 if (owner_)
907 owner_->row_item_appearance_changed(false, true);
908}
909
910void DecodeTrace::delete_pressed()
911{
912 on_delete();
913}
914
915void DecodeTrace::on_delete()
916{
917 session_.remove_decode_signal(decode_signal_);
918}
919
920void DecodeTrace::on_channel_selected(int)
921{
922 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
923
924 // Determine signal that was selected
925 const data::SignalBase *signal =
926 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
927
928 // Determine decode channel ID this combo box is the channel selector for
929 const uint16_t id = channel_id_map_.at(cb);
930
931 decode_signal_->assign_signal(id, signal);
932}
933
934void DecodeTrace::on_channels_updated()
935{
936 if (owner_)
937 owner_->row_item_appearance_changed(false, true);
938}
939
940void DecodeTrace::on_init_state_changed(int)
941{
942 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
943
944 // Determine inital pin state that was selected
945 int init_state = cb->itemData(cb->currentIndex()).value<int>();
946
947 // Determine decode channel ID this combo box is the channel selector for
948 const uint16_t id = init_state_map_.at(cb);
949
950 decode_signal_->set_initial_pin_state(id, init_state);
951}
952
953void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
954{
955 decode_signal_->stack_decoder(decoder);
956
957 create_popup_form();
958}
959
960void DecodeTrace::on_delete_decoder(int index)
961{
962 decode_signal_->remove_decoder(index);
963
964 // Update the popup
965 create_popup_form();
966}
967
968void DecodeTrace::on_show_hide_decoder(int index)
969{
970 const bool state = decode_signal_->toggle_decoder_visibility(index);
971
972 assert(index < (int)decoder_forms_.size());
973 decoder_forms_[index]->set_decoder_visible(state);
974
975 if (owner_)
976 owner_->row_item_appearance_changed(false, true);
977}
978
979} // namespace trace
980} // namespace views
981} // namespace pv