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