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