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