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