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