]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/decodetrace.cpp
Introduce pv::data::SignalBase
[pulseview.git] / pv / view / 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, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21extern "C" {
22#include <libsigrokdecode/libsigrokdecode.h>
23}
24
25#include <mutex>
26
27#include <extdef.h>
28
29#include <tuple>
30
31#include <boost/functional/hash.hpp>
32#include <boost/thread/locks.hpp>
33#include <boost/thread/shared_mutex.hpp>
34
35#include <QAction>
36#include <QApplication>
37#include <QComboBox>
38#include <QFormLayout>
39#include <QLabel>
40#include <QMenu>
41#include <QPushButton>
42#include <QToolTip>
43
44#include "decodetrace.hpp"
45
46#include <pv/session.hpp>
47#include <pv/strnatcmp.hpp>
48#include <pv/data/decoderstack.hpp>
49#include <pv/data/decode/decoder.hpp>
50#include <pv/data/logic.hpp>
51#include <pv/data/logicsegment.hpp>
52#include <pv/data/decode/annotation.hpp>
53#include <pv/view/logicsignal.hpp>
54#include <pv/view/view.hpp>
55#include <pv/view/viewport.hpp>
56#include <pv/widgets/decodergroupbox.hpp>
57#include <pv/widgets/decodermenu.hpp>
58
59using boost::shared_lock;
60using boost::shared_mutex;
61using std::dynamic_pointer_cast;
62using std::list;
63using std::lock_guard;
64using std::make_pair;
65using std::max;
66using std::make_pair;
67using std::map;
68using std::min;
69using std::pair;
70using std::shared_ptr;
71using std::tie;
72using std::unordered_set;
73using std::vector;
74
75namespace pv {
76namespace view {
77
78const QColor DecodeTrace::DecodeColours[4] = {
79 QColor(0xEF, 0x29, 0x29), // Red
80 QColor(0xFC, 0xE9, 0x4F), // Yellow
81 QColor(0x8A, 0xE2, 0x34), // Green
82 QColor(0x72, 0x9F, 0xCF) // Blue
83};
84
85const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
86const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
87
88const int DecodeTrace::ArrowSize = 4;
89const double DecodeTrace::EndCapWidth = 5;
90const int DecodeTrace::RowTitleMargin = 10;
91const int DecodeTrace::DrawPadding = 100;
92
93const QColor DecodeTrace::Colours[16] = {
94 QColor(0xEF, 0x29, 0x29),
95 QColor(0xF6, 0x6A, 0x32),
96 QColor(0xFC, 0xAE, 0x3E),
97 QColor(0xFB, 0xCA, 0x47),
98 QColor(0xFC, 0xE9, 0x4F),
99 QColor(0xCD, 0xF0, 0x40),
100 QColor(0x8A, 0xE2, 0x34),
101 QColor(0x4E, 0xDC, 0x44),
102 QColor(0x55, 0xD7, 0x95),
103 QColor(0x64, 0xD1, 0xD2),
104 QColor(0x72, 0x9F, 0xCF),
105 QColor(0xD4, 0x76, 0xC4),
106 QColor(0x9D, 0x79, 0xB9),
107 QColor(0xAD, 0x7F, 0xA8),
108 QColor(0xC2, 0x62, 0x9B),
109 QColor(0xD7, 0x47, 0x6F)
110};
111
112const QColor DecodeTrace::OutlineColours[16] = {
113 QColor(0x77, 0x14, 0x14),
114 QColor(0x7B, 0x35, 0x19),
115 QColor(0x7E, 0x57, 0x1F),
116 QColor(0x7D, 0x65, 0x23),
117 QColor(0x7E, 0x74, 0x27),
118 QColor(0x66, 0x78, 0x20),
119 QColor(0x45, 0x71, 0x1A),
120 QColor(0x27, 0x6E, 0x22),
121 QColor(0x2A, 0x6B, 0x4A),
122 QColor(0x32, 0x68, 0x69),
123 QColor(0x39, 0x4F, 0x67),
124 QColor(0x6A, 0x3B, 0x62),
125 QColor(0x4E, 0x3C, 0x5C),
126 QColor(0x56, 0x3F, 0x54),
127 QColor(0x61, 0x31, 0x4D),
128 QColor(0x6B, 0x23, 0x37)
129};
130
131DecodeTrace::DecodeTrace(pv::Session &session,
132 shared_ptr<data::SignalBase> signalbase,
133 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
134 Trace(signalbase),
135 session_(session),
136 signalbase_(signalbase),
137 decoder_stack_(decoder_stack),
138 row_height_(0),
139 max_visible_rows_(0),
140 delete_mapper_(this),
141 show_hide_mapper_(this)
142{
143 assert(decoder_stack_);
144
145 // Determine shortest string we want to see displayed in full
146 QFontMetrics m(QApplication::font());
147 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
148
149 signalbase_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
150 signalbase_->set_colour(DecodeColours[index % countof(DecodeColours)]);
151
152 connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
153 this, SLOT(on_new_decode_data()));
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
160bool DecodeTrace::enabled() const
161{
162 return true;
163}
164
165const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
166{
167 return decoder_stack_;
168}
169
170pair<int, int> DecodeTrace::v_extents() const
171{
172 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
173
174 // Make an empty decode trace appear symmetrical
175 const int row_count = max(1, max_visible_rows_);
176
177 return make_pair(-row_height, row_height * row_count);
178}
179
180void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
181{
182 Trace::paint_back(p, pp);
183 paint_axis(p, pp, get_visual_y());
184}
185
186void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
187{
188 using namespace pv::data::decode;
189
190 const int text_height = ViewItemPaintParams::text_height();
191 row_height_ = (text_height * 6) / 4;
192 const int annotation_height = (text_height * 5) / 4;
193
194 assert(decoder_stack_);
195 const QString err = decoder_stack_->error_message();
196 if (!err.isEmpty()) {
197 draw_unresolved_period(
198 p, annotation_height, pp.left(), pp.right());
199 draw_error(p, err, pp);
200 return;
201 }
202
203 // Set default pen to allow for text width calculation
204 p.setPen(Qt::black);
205
206 // Iterate through the rows
207 int y = get_visual_y();
208 pair<uint64_t, uint64_t> sample_range = get_sample_range(
209 pp.left(), pp.right());
210
211 assert(decoder_stack_);
212 const vector<Row> rows(decoder_stack_->get_visible_rows());
213
214 visible_rows_.clear();
215 for (const Row& row : rows) {
216 // Cache the row title widths
217 int row_title_width;
218 try {
219 row_title_width = row_title_widths_.at(row);
220 } catch (std::out_of_range) {
221 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
222 RowTitleMargin;
223 row_title_widths_[row] = w;
224 row_title_width = w;
225 }
226
227 // Determine the row's color
228 size_t base_colour = 0x13579BDF;
229 boost::hash_combine(base_colour, this);
230 boost::hash_combine(base_colour, row.decoder());
231 boost::hash_combine(base_colour, row.row());
232 base_colour >>= 16;
233
234 vector<Annotation> annotations;
235 decoder_stack_->get_annotation_subset(annotations, row,
236 sample_range.first, sample_range.second);
237 if (!annotations.empty()) {
238 draw_annotations(annotations, p, annotation_height, pp, y,
239 base_colour, row_title_width);
240
241 y += row_height_;
242
243 visible_rows_.push_back(row);
244 }
245 }
246
247 // Draw the hatching
248 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
249
250 if ((int)visible_rows_.size() > max_visible_rows_)
251 owner_->extents_changed(false, true);
252
253 // Update the maximum row count if needed
254 max_visible_rows_ = std::max(max_visible_rows_, (int)visible_rows_.size());
255}
256
257void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
258{
259 using namespace pv::data::decode;
260
261 assert(row_height_);
262
263 for (size_t i = 0; i < visible_rows_.size(); i++) {
264 const int y = i * row_height_ + get_visual_y();
265
266 p.setPen(QPen(Qt::NoPen));
267 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
268
269 if (i != 0) {
270 const QPointF points[] = {
271 QPointF(pp.left(), y - ArrowSize),
272 QPointF(pp.left() + ArrowSize, y),
273 QPointF(pp.left(), y + ArrowSize)
274 };
275 p.drawPolygon(points, countof(points));
276 }
277
278 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
279 pp.right() - pp.left(), row_height_);
280 const QString h(visible_rows_[i].title());
281 const int f = Qt::AlignLeft | Qt::AlignVCenter |
282 Qt::TextDontClip;
283
284 // Draw the outline
285 p.setPen(QApplication::palette().color(QPalette::Base));
286 for (int dx = -1; dx <= 1; dx++)
287 for (int dy = -1; dy <= 1; dy++)
288 if (dx != 0 && dy != 0)
289 p.drawText(r.translated(dx, dy), f, h);
290
291 // Draw the text
292 p.setPen(QApplication::palette().color(QPalette::WindowText));
293 p.drawText(r, f, h);
294 }
295}
296
297void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
298{
299 using pv::data::decode::Decoder;
300
301 assert(form);
302 assert(parent);
303 assert(decoder_stack_);
304
305 // Add the standard options
306 Trace::populate_popup_form(parent, form);
307
308 // Add the decoder options
309 bindings_.clear();
310 channel_selectors_.clear();
311 decoder_forms_.clear();
312
313 const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
314
315 if (stack.empty()) {
316 QLabel *const l = new QLabel(
317 tr("<p><i>No decoders in the stack</i></p>"));
318 l->setAlignment(Qt::AlignCenter);
319 form->addRow(l);
320 } else {
321 auto iter = stack.cbegin();
322 for (int i = 0; i < (int)stack.size(); i++, iter++) {
323 shared_ptr<Decoder> dec(*iter);
324 create_decoder_form(i, dec, parent, form);
325 }
326
327 form->addRow(new QLabel(
328 tr("<i>* Required channels</i>"), parent));
329 }
330
331 // Add stacking button
332 pv::widgets::DecoderMenu *const decoder_menu =
333 new pv::widgets::DecoderMenu(parent);
334 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
335 this, SLOT(on_stack_decoder(srd_decoder*)));
336
337 QPushButton *const stack_button =
338 new QPushButton(tr("Stack Decoder"), parent);
339 stack_button->setMenu(decoder_menu);
340
341 QHBoxLayout *stack_button_box = new QHBoxLayout;
342 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
343 form->addRow(stack_button_box);
344}
345
346QMenu* DecodeTrace::create_context_menu(QWidget *parent)
347{
348 QMenu *const menu = Trace::create_context_menu(parent);
349
350 menu->addSeparator();
351
352 QAction *const del = new QAction(tr("Delete"), this);
353 del->setShortcuts(QKeySequence::Delete);
354 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
355 menu->addAction(del);
356
357 return menu;
358}
359
360void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
361 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
362 size_t base_colour, int row_title_width)
363{
364 using namespace pv::data::decode;
365
366 vector<Annotation> a_block;
367 int p_end = INT_MIN;
368
369 double samples_per_pixel, pixels_offset;
370 tie(pixels_offset, samples_per_pixel) =
371 get_pixels_offset_samples_per_pixel();
372
373 // Sort the annotations by start sample so that decoders
374 // can't confuse us by creating annotations out of order
375 stable_sort(annotations.begin(), annotations.end(),
376 [](const Annotation &a, const Annotation &b) {
377 return a.start_sample() < b.start_sample(); });
378
379 // Gather all annotations that form a visual "block" and draw them as such
380 for (const Annotation &a : annotations) {
381
382 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
383 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
384 const int a_width = a_end - a_start;
385
386 const int delta = a_end - p_end;
387
388 bool a_is_separate = false;
389
390 // Annotation wider than the threshold for a useful label width?
391 if (a_width >= min_useful_label_width_) {
392 for (const QString &ann_text : a.annotations()) {
393 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
394 // Annotation wide enough to fit a label? Don't put it in a block then
395 if (w <= a_width) {
396 a_is_separate = true;
397 break;
398 }
399 }
400 }
401
402 // Were the previous and this annotation more than a pixel apart?
403 if ((abs(delta) > 1) || a_is_separate) {
404 // Block was broken, draw annotations that form the current block
405 if (a_block.size() == 1) {
406 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
407 row_title_width);
408 }
409 else
410 draw_annotation_block(a_block, p, h, y, base_colour);
411
412 a_block.clear();
413 }
414
415 if (a_is_separate) {
416 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
417 // Next annotation must start a new block. delta will be > 1
418 // because we set p_end to INT_MIN but that's okay since
419 // a_block will be empty, so nothing will be drawn
420 p_end = INT_MIN;
421 } else {
422 a_block.push_back(a);
423 p_end = a_end;
424 }
425 }
426
427 if (a_block.size() == 1)
428 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
429 row_title_width);
430 else
431 draw_annotation_block(a_block, p, h, y, base_colour);
432}
433
434void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
435 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
436 size_t base_colour, int row_title_width) const
437{
438 double samples_per_pixel, pixels_offset;
439 tie(pixels_offset, samples_per_pixel) =
440 get_pixels_offset_samples_per_pixel();
441
442 const double start = a.start_sample() / samples_per_pixel -
443 pixels_offset;
444 const double end = a.end_sample() / samples_per_pixel -
445 pixels_offset;
446
447 const size_t colour = (base_colour + a.format()) % countof(Colours);
448 p.setPen(OutlineColours[colour]);
449 p.setBrush(Colours[colour]);
450
451 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
452 return;
453
454 if (a.start_sample() == a.end_sample())
455 draw_instant(a, p, h, start, y);
456 else
457 draw_range(a, p, h, start, end, y, pp,
458 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 = std::all_of(
489 annotations.begin(), annotations.end(),
490 [&](const Annotation &a) { return a.format() == format; });
491
492 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
493 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
494 Qt::Dense4Pattern));
495 p.drawRoundedRect(
496 QRectF(start, top, end - start, bottom - top), h/4, h/4);
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 = std::max(ann_start, pp.left() + row_title_width);
548 const int real_end = std::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.width(), INT_MIN / 2 + y, pp.width(), 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,
597 int right) const
598{
599 using namespace pv::data;
600 using pv::data::decode::Decoder;
601
602 double samples_per_pixel, pixels_offset;
603
604 assert(decoder_stack_);
605
606 shared_ptr<Logic> data;
607 shared_ptr<LogicSignal> logic_signal;
608
609 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
610
611 // We get the logic data of the first channel in the list.
612 // This works because we are currently assuming all
613 // LogicSignals have the same data/segment
614 for (const shared_ptr<Decoder> &dec : stack)
615 if (dec && !dec->channels().empty() &&
616 ((logic_signal = (*dec->channels().begin()).second)) &&
617 ((data = logic_signal->logic_data())))
618 break;
619
620 if (!data || data->logic_segments().empty())
621 return;
622
623 const shared_ptr<LogicSegment> segment =
624 data->logic_segments().front();
625 assert(segment);
626 const int64_t sample_count = (int64_t)segment->get_sample_count();
627 if (sample_count == 0)
628 return;
629
630 const int64_t samples_decoded = decoder_stack_->samples_decoded();
631 if (sample_count == samples_decoded)
632 return;
633
634 const int y = get_visual_y();
635
636 tie(pixels_offset, samples_per_pixel) =
637 get_pixels_offset_samples_per_pixel();
638
639 const double start = max(samples_decoded /
640 samples_per_pixel - pixels_offset, left - 1.0);
641 const double end = min(sample_count / samples_per_pixel -
642 pixels_offset, right + 1.0);
643 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
644
645 p.setPen(QPen(Qt::NoPen));
646 p.setBrush(Qt::white);
647 p.drawRect(no_decode_rect);
648
649 p.setPen(NoDecodeColour);
650 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
651 p.drawRect(no_decode_rect);
652}
653
654pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
655{
656 assert(owner_);
657 assert(decoder_stack_);
658
659 const View *view = owner_->view();
660 assert(view);
661
662 const double scale = view->scale();
663 assert(scale > 0);
664
665 const double pixels_offset =
666 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
667
668 double samplerate = decoder_stack_->samplerate();
669
670 // Show sample rate as 1Hz when it is unknown
671 if (samplerate == 0.0)
672 samplerate = 1.0;
673
674 return make_pair(pixels_offset, samplerate * scale);
675}
676
677pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
678 int x_start, int x_end) const
679{
680 double samples_per_pixel, pixels_offset;
681 tie(pixels_offset, samples_per_pixel) =
682 get_pixels_offset_samples_per_pixel();
683
684 const uint64_t start = (uint64_t)max(
685 (x_start + pixels_offset) * samples_per_pixel, 0.0);
686 const uint64_t end = (uint64_t)max(
687 (x_end + pixels_offset) * samples_per_pixel, 0.0);
688
689 return make_pair(start, end);
690}
691
692int DecodeTrace::get_row_at_point(const QPoint &point)
693{
694 if (!row_height_)
695 return -1;
696
697 const int y = (point.y() - get_visual_y() + row_height_ / 2);
698
699 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
700 if (y < 0)
701 return -1;
702
703 const int row = y / row_height_;
704
705 if (row >= (int)visible_rows_.size())
706 return -1;
707
708 return row;
709}
710
711const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
712{
713 using namespace pv::data::decode;
714
715 if (!enabled())
716 return QString();
717
718 const pair<uint64_t, uint64_t> sample_range =
719 get_sample_range(point.x(), point.x() + 1);
720 const int row = get_row_at_point(point);
721 if (row < 0)
722 return QString();
723
724 vector<pv::data::decode::Annotation> annotations;
725
726 assert(decoder_stack_);
727 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
728 sample_range.first, sample_range.second);
729
730 return (annotations.empty()) ?
731 QString() : annotations[0].annotations().front();
732}
733
734void DecodeTrace::hover_point_changed()
735{
736 assert(owner_);
737
738 const View *const view = owner_->view();
739 assert(view);
740
741 QPoint hp = view->hover_point();
742 QString ann = get_annotation_at_point(hp);
743
744 assert(view);
745
746 if (!row_height_ || ann.isEmpty()) {
747 QToolTip::hideText();
748 return;
749 }
750
751 const int hover_row = get_row_at_point(hp);
752
753 QFontMetrics m(QToolTip::font());
754 const QRect text_size = m.boundingRect(QRect(), 0, ann);
755
756 // This is OS-specific and unfortunately we can't query it, so
757 // use an approximation to at least try to minimize the error.
758 const int padding = 8;
759
760 // Make sure the tool tip doesn't overlap with the mouse cursor.
761 // If it did, the tool tip would constantly hide and re-appear.
762 // We also push it up by one row so that it appears above the
763 // decode trace, not below.
764 hp.setX(hp.x() - (text_size.width() / 2) - padding);
765
766 hp.setY(get_visual_y() - (row_height_ / 2) +
767 (hover_row * row_height_) -
768 row_height_ - text_size.height() - padding);
769
770 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
771}
772
773void DecodeTrace::create_decoder_form(int index,
774 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
775 QFormLayout *form)
776{
777 const GSList *l;
778
779 assert(dec);
780 const srd_decoder *const decoder = dec->decoder();
781 assert(decoder);
782
783 const bool decoder_deletable = index > 0;
784
785 pv::widgets::DecoderGroupBox *const group =
786 new pv::widgets::DecoderGroupBox(
787 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
788 group->set_decoder_visible(dec->shown());
789
790 if (decoder_deletable) {
791 delete_mapper_.setMapping(group, index);
792 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
793 }
794
795 show_hide_mapper_.setMapping(group, index);
796 connect(group, SIGNAL(show_hide_decoder()),
797 &show_hide_mapper_, SLOT(map()));
798
799 QFormLayout *const decoder_form = new QFormLayout;
800 group->add_layout(decoder_form);
801
802 // Add the mandatory channels
803 for (l = decoder->channels; l; l = l->next) {
804 const struct srd_channel *const pdch =
805 (struct srd_channel *)l->data;
806 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
807 connect(combo, SIGNAL(currentIndexChanged(int)),
808 this, SLOT(on_channel_selected(int)));
809 decoder_form->addRow(tr("<b>%1</b> (%2) *")
810 .arg(QString::fromUtf8(pdch->name),
811 QString::fromUtf8(pdch->desc)), combo);
812
813 const ChannelSelector s = {combo, dec, pdch};
814 channel_selectors_.push_back(s);
815 }
816
817 // Add the optional channels
818 for (l = decoder->opt_channels; l; l = l->next) {
819 const struct srd_channel *const pdch =
820 (struct srd_channel *)l->data;
821 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
822 connect(combo, SIGNAL(currentIndexChanged(int)),
823 this, SLOT(on_channel_selected(int)));
824 decoder_form->addRow(tr("<b>%1</b> (%2)")
825 .arg(QString::fromUtf8(pdch->name),
826 QString::fromUtf8(pdch->desc)), combo);
827
828 const ChannelSelector s = {combo, dec, pdch};
829 channel_selectors_.push_back(s);
830 }
831
832 // Add the options
833 shared_ptr<binding::Decoder> binding(
834 new binding::Decoder(decoder_stack_, dec));
835 binding->add_properties_to_form(decoder_form, true);
836
837 bindings_.push_back(binding);
838
839 form->addRow(group);
840 decoder_forms_.push_back(group);
841}
842
843QComboBox* DecodeTrace::create_channel_selector(
844 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
845 const srd_channel *const pdch)
846{
847 assert(dec);
848
849 const auto sigs(session_.signals());
850
851 vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
852 std::sort(sig_list.begin(), sig_list.end(),
853 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
854 return strnatcasecmp(a->channel()->name().toStdString(),
855 b->channel()->name().toStdString()) < 0; });
856
857 assert(decoder_stack_);
858 const auto channel_iter = dec->channels().find(pdch);
859
860 QComboBox *selector = new QComboBox(parent);
861
862 selector->addItem("-", qVariantFromValue((void*)nullptr));
863
864 if (channel_iter == dec->channels().end())
865 selector->setCurrentIndex(0);
866
867 for (const shared_ptr<view::Signal> &s : sig_list) {
868 assert(s);
869 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled()) {
870 selector->addItem(s->channel()->name(),
871 qVariantFromValue((void*)s.get()));
872
873 if (channel_iter != dec->channels().end() &&
874 (*channel_iter).second == s)
875 selector->setCurrentIndex(
876 selector->count() - 1);
877 }
878 }
879
880 return selector;
881}
882
883void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
884{
885 assert(dec);
886
887 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
888
889 const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
890
891 for (const ChannelSelector &s : channel_selectors_) {
892 if (s.decoder_ != dec)
893 break;
894
895 const LogicSignal *const selection =
896 (LogicSignal*)s.combo_->itemData(
897 s.combo_->currentIndex()).value<void*>();
898
899 for (shared_ptr<Signal> sig : sigs)
900 if (sig.get() == selection) {
901 channel_map[s.pdch_] =
902 dynamic_pointer_cast<LogicSignal>(sig);
903 break;
904 }
905 }
906
907 dec->set_channels(channel_map);
908}
909
910void DecodeTrace::commit_channels()
911{
912 assert(decoder_stack_);
913 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
914 commit_decoder_channels(dec);
915
916 decoder_stack_->begin_decode();
917}
918
919void DecodeTrace::on_new_decode_data()
920{
921 if (owner_)
922 owner_->row_item_appearance_changed(false, true);
923}
924
925void DecodeTrace::delete_pressed()
926{
927 on_delete();
928}
929
930void DecodeTrace::on_delete()
931{
932 session_.remove_decode_signal(this);
933}
934
935void DecodeTrace::on_channel_selected(int)
936{
937 commit_channels();
938}
939
940void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
941{
942 assert(decoder);
943 assert(decoder_stack_);
944 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
945 new data::decode::Decoder(decoder)));
946 decoder_stack_->begin_decode();
947
948 create_popup_form();
949}
950
951void DecodeTrace::on_delete_decoder(int index)
952{
953 decoder_stack_->remove(index);
954
955 // Update the popup
956 create_popup_form();
957
958 decoder_stack_->begin_decode();
959}
960
961void DecodeTrace::on_show_hide_decoder(int index)
962{
963 using pv::data::decode::Decoder;
964
965 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
966
967 // Find the decoder in the stack
968 auto iter = stack.cbegin();
969 for (int i = 0; i < index; i++, iter++)
970 assert(iter != stack.end());
971
972 shared_ptr<Decoder> dec = *iter;
973 assert(dec);
974
975 const bool show = !dec->shown();
976 dec->show(show);
977
978 assert(index < (int)decoder_forms_.size());
979 decoder_forms_[index]->set_decoder_visible(show);
980
981 if (owner_)
982 owner_->row_item_appearance_changed(false, true);
983}
984
985} // namespace view
986} // namespace pv