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