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