]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/decodetrace.cpp
Introduce PV-internal channel types
[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, 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#include <boost/thread/locks.hpp>
32#include <boost/thread/shared_mutex.hpp>
33
34#include <QAction>
35#include <QApplication>
36#include <QComboBox>
37#include <QFormLayout>
38#include <QLabel>
39#include <QMenu>
40#include <QPushButton>
41#include <QToolTip>
42
43#include "decodetrace.hpp"
44
45#include <pv/session.hpp>
46#include <pv/strnatcmp.hpp>
47#include <pv/data/decoderstack.hpp>
48#include <pv/data/decode/decoder.hpp>
49#include <pv/data/logic.hpp>
50#include <pv/data/logicsegment.hpp>
51#include <pv/data/decode/annotation.hpp>
52#include <pv/view/view.hpp>
53#include <pv/view/viewport.hpp>
54#include <pv/widgets/decodergroupbox.hpp>
55#include <pv/widgets/decodermenu.hpp>
56
57using boost::shared_lock;
58using boost::shared_mutex;
59
60using std::all_of;
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::out_of_range;
70using std::pair;
71using std::shared_ptr;
72using std::make_shared;
73using std::tie;
74using std::unordered_set;
75using std::vector;
76
77namespace pv {
78namespace views {
79namespace TraceView {
80
81const QColor DecodeTrace::DecodeColours[4] = {
82 QColor(0xEF, 0x29, 0x29), // Red
83 QColor(0xFC, 0xE9, 0x4F), // Yellow
84 QColor(0x8A, 0xE2, 0x34), // Green
85 QColor(0x72, 0x9F, 0xCF) // Blue
86};
87
88const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
89const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
90
91const int DecodeTrace::ArrowSize = 4;
92const double DecodeTrace::EndCapWidth = 5;
93const int DecodeTrace::RowTitleMargin = 10;
94const int DecodeTrace::DrawPadding = 100;
95
96const QColor DecodeTrace::Colours[16] = {
97 QColor(0xEF, 0x29, 0x29),
98 QColor(0xF6, 0x6A, 0x32),
99 QColor(0xFC, 0xAE, 0x3E),
100 QColor(0xFB, 0xCA, 0x47),
101 QColor(0xFC, 0xE9, 0x4F),
102 QColor(0xCD, 0xF0, 0x40),
103 QColor(0x8A, 0xE2, 0x34),
104 QColor(0x4E, 0xDC, 0x44),
105 QColor(0x55, 0xD7, 0x95),
106 QColor(0x64, 0xD1, 0xD2),
107 QColor(0x72, 0x9F, 0xCF),
108 QColor(0xD4, 0x76, 0xC4),
109 QColor(0x9D, 0x79, 0xB9),
110 QColor(0xAD, 0x7F, 0xA8),
111 QColor(0xC2, 0x62, 0x9B),
112 QColor(0xD7, 0x47, 0x6F)
113};
114
115const QColor DecodeTrace::OutlineColours[16] = {
116 QColor(0x77, 0x14, 0x14),
117 QColor(0x7B, 0x35, 0x19),
118 QColor(0x7E, 0x57, 0x1F),
119 QColor(0x7D, 0x65, 0x23),
120 QColor(0x7E, 0x74, 0x27),
121 QColor(0x66, 0x78, 0x20),
122 QColor(0x45, 0x71, 0x1A),
123 QColor(0x27, 0x6E, 0x22),
124 QColor(0x2A, 0x6B, 0x4A),
125 QColor(0x32, 0x68, 0x69),
126 QColor(0x39, 0x4F, 0x67),
127 QColor(0x6A, 0x3B, 0x62),
128 QColor(0x4E, 0x3C, 0x5C),
129 QColor(0x56, 0x3F, 0x54),
130 QColor(0x61, 0x31, 0x4D),
131 QColor(0x6B, 0x23, 0x37)
132};
133
134DecodeTrace::DecodeTrace(pv::Session &session,
135 shared_ptr<data::SignalBase> signalbase, int index) :
136 Trace(signalbase),
137 session_(session),
138 row_height_(0),
139 max_visible_rows_(0),
140 delete_mapper_(this),
141 show_hide_mapper_(this)
142{
143 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->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 base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
150 base_->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
165shared_ptr<data::SignalBase> DecodeTrace::base() const
166{
167 return base_;
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 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
191
192 const int text_height = ViewItemPaintParams::text_height();
193 row_height_ = (text_height * 6) / 4;
194 const int annotation_height = (text_height * 5) / 4;
195
196 assert(decoder_stack);
197 const QString err = decoder_stack->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(decoder_stack->get_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 decoder_stack->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, const ViewItemPaintParams &pp)
259{
260 using namespace pv::data::decode;
261
262 assert(row_height_);
263
264 for (size_t i = 0; i < visible_rows_.size(); i++) {
265 const int y = i * row_height_ + get_visual_y();
266
267 p.setPen(QPen(Qt::NoPen));
268 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
269
270 if (i != 0) {
271 const QPointF points[] = {
272 QPointF(pp.left(), y - ArrowSize),
273 QPointF(pp.left() + ArrowSize, y),
274 QPointF(pp.left(), y + ArrowSize)
275 };
276 p.drawPolygon(points, countof(points));
277 }
278
279 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
280 pp.right() - pp.left(), row_height_);
281 const QString h(visible_rows_[i].title());
282 const int f = Qt::AlignLeft | Qt::AlignVCenter |
283 Qt::TextDontClip;
284
285 // Draw the outline
286 p.setPen(QApplication::palette().color(QPalette::Base));
287 for (int dx = -1; dx <= 1; dx++)
288 for (int dy = -1; dy <= 1; dy++)
289 if (dx != 0 && dy != 0)
290 p.drawText(r.translated(dx, dy), f, h);
291
292 // Draw the text
293 p.setPen(QApplication::palette().color(QPalette::WindowText));
294 p.drawText(r, f, h);
295 }
296}
297
298void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
299{
300 using pv::data::decode::Decoder;
301
302 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
303
304 assert(form);
305 assert(parent);
306 assert(decoder_stack);
307
308 // Add the standard options
309 Trace::populate_popup_form(parent, form);
310
311 // Add the decoder options
312 bindings_.clear();
313 channel_selectors_.clear();
314 decoder_forms_.clear();
315
316 const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
317
318 if (stack.empty()) {
319 QLabel *const l = new QLabel(
320 tr("<p><i>No decoders in the stack</i></p>"));
321 l->setAlignment(Qt::AlignCenter);
322 form->addRow(l);
323 } else {
324 auto iter = stack.cbegin();
325 for (int i = 0; i < (int)stack.size(); i++, iter++) {
326 shared_ptr<Decoder> dec(*iter);
327 create_decoder_form(i, dec, parent, form);
328 }
329
330 form->addRow(new QLabel(
331 tr("<i>* Required channels</i>"), parent));
332 }
333
334 // Add stacking button
335 pv::widgets::DecoderMenu *const decoder_menu =
336 new pv::widgets::DecoderMenu(parent);
337 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
338 this, SLOT(on_stack_decoder(srd_decoder*)));
339
340 QPushButton *const stack_button =
341 new QPushButton(tr("Stack Decoder"), parent);
342 stack_button->setMenu(decoder_menu);
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 -
448 pixels_offset;
449
450 const size_t colour = (base_colour + a.format()) % countof(Colours);
451 p.setPen(OutlineColours[colour]);
452 p.setBrush(Colours[colour]);
453
454 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
455 return;
456
457 if (a.start_sample() == a.end_sample())
458 draw_instant(a, p, h, start, y);
459 else
460 draw_range(a, p, h, start, end, y, pp,
461 row_title_width);
462}
463
464void DecodeTrace::draw_annotation_block(
465 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
466 int y, size_t base_colour) const
467{
468 using namespace pv::data::decode;
469
470 if (annotations.empty())
471 return;
472
473 double samples_per_pixel, pixels_offset;
474 tie(pixels_offset, samples_per_pixel) =
475 get_pixels_offset_samples_per_pixel();
476
477 const double start = annotations.front().start_sample() /
478 samples_per_pixel - pixels_offset;
479 const double end = annotations.back().end_sample() /
480 samples_per_pixel - pixels_offset;
481
482 const double top = y + .5 - h / 2;
483 const double bottom = y + .5 + h / 2;
484
485 const size_t colour = (base_colour + annotations.front().format()) %
486 countof(Colours);
487
488 // Check if all annotations are of the same type (i.e. we can use one color)
489 // or if we should use a neutral color (i.e. gray)
490 const int format = annotations.front().format();
491 const bool single_format = all_of(
492 annotations.begin(), annotations.end(),
493 [&](const Annotation &a) { return a.format() == format; });
494
495 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
496 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
497 Qt::Dense4Pattern));
498 p.drawRoundedRect(
499 QRectF(start, top, end - start, bottom - top), h/4, h/4);
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.width(), INT_MIN / 2 + y, pp.width(), 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,
600 int right) const
601{
602 using namespace pv::data;
603 using pv::data::decode::Decoder;
604
605 double samples_per_pixel, pixels_offset;
606
607 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
608
609 assert(decoder_stack);
610
611 shared_ptr<Logic> data;
612 shared_ptr<data::SignalBase> signalbase;
613
614 const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
615
616 // We get the logic data of the first channel in the list.
617 // This works because we are currently assuming all
618 // LogicSignals have the same data/segment
619 for (const shared_ptr<Decoder> &dec : stack)
620 if (dec && !dec->channels().empty() &&
621 ((signalbase = (*dec->channels().begin()).second)) &&
622 ((data = signalbase->logic_data())))
623 break;
624
625 if (!data || data->logic_segments().empty())
626 return;
627
628 const shared_ptr<LogicSegment> segment =
629 data->logic_segments().front();
630 assert(segment);
631 const int64_t sample_count = (int64_t)segment->get_sample_count();
632 if (sample_count == 0)
633 return;
634
635 const int64_t samples_decoded = decoder_stack->samples_decoded();
636 if (sample_count == samples_decoded)
637 return;
638
639 const int y = get_visual_y();
640
641 tie(pixels_offset, samples_per_pixel) =
642 get_pixels_offset_samples_per_pixel();
643
644 const double start = max(samples_decoded /
645 samples_per_pixel - pixels_offset, left - 1.0);
646 const double end = min(sample_count / samples_per_pixel -
647 pixels_offset, right + 1.0);
648 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
649
650 p.setPen(QPen(Qt::NoPen));
651 p.setBrush(Qt::white);
652 p.drawRect(no_decode_rect);
653
654 p.setPen(NoDecodeColour);
655 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
656 p.drawRect(no_decode_rect);
657}
658
659pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
660{
661 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
662
663 assert(owner_);
664 assert(decoder_stack);
665
666 const View *view = owner_->view();
667 assert(view);
668
669 const double scale = view->scale();
670 assert(scale > 0);
671
672 const double pixels_offset =
673 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
674
675 double samplerate = decoder_stack->samplerate();
676
677 // Show sample rate as 1Hz when it is unknown
678 if (samplerate == 0.0)
679 samplerate = 1.0;
680
681 return make_pair(pixels_offset, samplerate * scale);
682}
683
684pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
685 int x_start, int x_end) const
686{
687 double samples_per_pixel, pixels_offset;
688 tie(pixels_offset, samples_per_pixel) =
689 get_pixels_offset_samples_per_pixel();
690
691 const uint64_t start = (uint64_t)max(
692 (x_start + pixels_offset) * samples_per_pixel, 0.0);
693 const uint64_t end = (uint64_t)max(
694 (x_end + pixels_offset) * samples_per_pixel, 0.0);
695
696 return make_pair(start, end);
697}
698
699int DecodeTrace::get_row_at_point(const QPoint &point)
700{
701 if (!row_height_)
702 return -1;
703
704 const int y = (point.y() - get_visual_y() + row_height_ / 2);
705
706 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
707 if (y < 0)
708 return -1;
709
710 const int row = y / row_height_;
711
712 if (row >= (int)visible_rows_.size())
713 return -1;
714
715 return row;
716}
717
718const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
719{
720 using namespace pv::data::decode;
721
722 if (!enabled())
723 return QString();
724
725 const pair<uint64_t, uint64_t> sample_range =
726 get_sample_range(point.x(), point.x() + 1);
727 const int row = get_row_at_point(point);
728 if (row < 0)
729 return QString();
730
731 vector<pv::data::decode::Annotation> annotations;
732
733 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
734
735 assert(decoder_stack);
736 decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
737 sample_range.first, sample_range.second);
738
739 return (annotations.empty()) ?
740 QString() : annotations[0].annotations().front();
741}
742
743void DecodeTrace::hover_point_changed()
744{
745 assert(owner_);
746
747 const View *const view = owner_->view();
748 assert(view);
749
750 QPoint hp = view->hover_point();
751 QString ann = get_annotation_at_point(hp);
752
753 assert(view);
754
755 if (!row_height_ || ann.isEmpty()) {
756 QToolTip::hideText();
757 return;
758 }
759
760 const int hover_row = get_row_at_point(hp);
761
762 QFontMetrics m(QToolTip::font());
763 const QRect text_size = m.boundingRect(QRect(), 0, ann);
764
765 // This is OS-specific and unfortunately we can't query it, so
766 // use an approximation to at least try to minimize the error.
767 const int padding = 8;
768
769 // Make sure the tool tip doesn't overlap with the mouse cursor.
770 // If it did, the tool tip would constantly hide and re-appear.
771 // We also push it up by one row so that it appears above the
772 // decode trace, not below.
773 hp.setX(hp.x() - (text_size.width() / 2) - padding);
774
775 hp.setY(get_visual_y() - (row_height_ / 2) +
776 (hover_row * row_height_) -
777 row_height_ - text_size.height() - padding);
778
779 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
780}
781
782void DecodeTrace::create_decoder_form(int index,
783 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
784 QFormLayout *form)
785{
786 const GSList *l;
787
788 assert(dec);
789 const srd_decoder *const decoder = dec->decoder();
790 assert(decoder);
791
792 const bool decoder_deletable = index > 0;
793
794 pv::widgets::DecoderGroupBox *const group =
795 new pv::widgets::DecoderGroupBox(
796 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
797 group->set_decoder_visible(dec->shown());
798
799 if (decoder_deletable) {
800 delete_mapper_.setMapping(group, index);
801 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
802 }
803
804 show_hide_mapper_.setMapping(group, index);
805 connect(group, SIGNAL(show_hide_decoder()),
806 &show_hide_mapper_, SLOT(map()));
807
808 QFormLayout *const decoder_form = new QFormLayout;
809 group->add_layout(decoder_form);
810
811 // Add the mandatory channels
812 for (l = decoder->channels; l; l = l->next) {
813 const struct srd_channel *const pdch =
814 (struct srd_channel *)l->data;
815 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
816 connect(combo, SIGNAL(currentIndexChanged(int)),
817 this, SLOT(on_channel_selected(int)));
818 decoder_form->addRow(tr("<b>%1</b> (%2) *")
819 .arg(QString::fromUtf8(pdch->name),
820 QString::fromUtf8(pdch->desc)), combo);
821
822 const ChannelSelector s = {combo, dec, pdch};
823 channel_selectors_.push_back(s);
824 }
825
826 // Add the optional channels
827 for (l = decoder->opt_channels; l; l = l->next) {
828 const struct srd_channel *const pdch =
829 (struct srd_channel *)l->data;
830 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
831 connect(combo, SIGNAL(currentIndexChanged(int)),
832 this, SLOT(on_channel_selected(int)));
833 decoder_form->addRow(tr("<b>%1</b> (%2)")
834 .arg(QString::fromUtf8(pdch->name),
835 QString::fromUtf8(pdch->desc)), combo);
836
837 const ChannelSelector s = {combo, dec, pdch};
838 channel_selectors_.push_back(s);
839 }
840
841 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
842
843 // Add the options
844 shared_ptr<binding::Decoder> binding(
845 new binding::Decoder(decoder_stack, dec));
846 binding->add_properties_to_form(decoder_form, true);
847
848 bindings_.push_back(binding);
849
850 form->addRow(group);
851 decoder_forms_.push_back(group);
852}
853
854QComboBox* DecodeTrace::create_channel_selector(
855 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
856 const srd_channel *const pdch)
857{
858 assert(dec);
859
860 const auto sigs(session_.signalbases());
861
862 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
863 sort(sig_list.begin(), sig_list.end(),
864 [](const shared_ptr<data::SignalBase> &a,
865 const shared_ptr<data::SignalBase> &b) {
866 return strnatcasecmp(a->name().toStdString(),
867 b->name().toStdString()) < 0; });
868
869 const auto channel_iter = dec->channels().find(pdch);
870
871 QComboBox *selector = new QComboBox(parent);
872
873 selector->addItem("-", qVariantFromValue((void*)nullptr));
874
875 if (channel_iter == dec->channels().end())
876 selector->setCurrentIndex(0);
877
878 for (const shared_ptr<data::SignalBase> &b : sig_list) {
879 assert(b);
880 if (b->type() == data::SignalBase::LogicChannel && b->enabled()) {
881 selector->addItem(b->name(),
882 qVariantFromValue((void*)b.get()));
883
884 if (channel_iter != dec->channels().end() &&
885 (*channel_iter).second == b)
886 selector->setCurrentIndex(
887 selector->count() - 1);
888 }
889 }
890
891 return selector;
892}
893
894void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
895{
896 assert(dec);
897
898 map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
899
900 const unordered_set< shared_ptr<data::SignalBase> >
901 sigs(session_.signalbases());
902
903 for (const ChannelSelector &s : channel_selectors_) {
904 if (s.decoder_ != dec)
905 break;
906
907 const data::SignalBase *const selection =
908 (data::SignalBase*)s.combo_->itemData(
909 s.combo_->currentIndex()).value<void*>();
910
911 for (shared_ptr<data::SignalBase> sig : sigs)
912 if (sig.get() == selection) {
913 channel_map[s.pdch_] = sig;
914 break;
915 }
916 }
917
918 dec->set_channels(channel_map);
919}
920
921void DecodeTrace::commit_channels()
922{
923 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
924
925 assert(decoder_stack);
926 for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
927 commit_decoder_channels(dec);
928
929 decoder_stack->begin_decode();
930}
931
932void DecodeTrace::on_new_decode_data()
933{
934 if (owner_)
935 owner_->row_item_appearance_changed(false, true);
936}
937
938void DecodeTrace::delete_pressed()
939{
940 on_delete();
941}
942
943void DecodeTrace::on_delete()
944{
945 session_.remove_decode_signal(base_);
946}
947
948void DecodeTrace::on_channel_selected(int)
949{
950 commit_channels();
951}
952
953void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
954{
955 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
956
957 assert(decoder);
958 assert(decoder_stack);
959 decoder_stack->push(make_shared<data::decode::Decoder>(decoder));
960 decoder_stack->begin_decode();
961
962 create_popup_form();
963}
964
965void DecodeTrace::on_delete_decoder(int index)
966{
967 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
968
969 decoder_stack->remove(index);
970
971 // Update the popup
972 create_popup_form();
973
974 decoder_stack->begin_decode();
975}
976
977void DecodeTrace::on_show_hide_decoder(int index)
978{
979 using pv::data::decode::Decoder;
980
981 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
982
983 const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
984
985 // Find the decoder in the stack
986 auto iter = stack.cbegin();
987 for (int i = 0; i < index; i++, iter++)
988 assert(iter != stack.end());
989
990 shared_ptr<Decoder> dec = *iter;
991 assert(dec);
992
993 const bool show = !dec->shown();
994 dec->show(show);
995
996 assert(index < (int)decoder_forms_.size());
997 decoder_forms_[index]->set_decoder_visible(show);
998
999 if (owner_)
1000 owner_->row_item_appearance_changed(false, true);
1001}
1002
1003} // namespace TraceView
1004} // namespace views
1005} // namespace pv