]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/decodetrace.cpp
MainWindow: Fix session error slot declaration
[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/view.hpp>
54#include <pv/view/viewport.hpp>
55#include <pv/widgets/decodergroupbox.hpp>
56#include <pv/widgets/decodermenu.hpp>
57
58using boost::shared_lock;
59using boost::shared_mutex;
60using std::dynamic_pointer_cast;
61using std::list;
62using std::lock_guard;
63using std::make_pair;
64using std::max;
65using std::make_pair;
66using std::map;
67using std::min;
68using std::pair;
69using std::shared_ptr;
70using std::tie;
71using std::unordered_set;
72using std::vector;
73
74namespace pv {
75namespace views {
76namespace TraceView {
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, int index) :
133 Trace(signalbase),
134 session_(session),
135 signalbase_(signalbase),
136 row_height_(0),
137 max_visible_rows_(0),
138 delete_mapper_(this),
139 show_hide_mapper_(this)
140{
141 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
142 signalbase_->decoder_stack();
143
144 // Determine shortest string we want to see displayed in full
145 QFontMetrics m(QApplication::font());
146 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
147
148 signalbase_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
149 signalbase_->set_colour(DecodeColours[index % countof(DecodeColours)]);
150
151 connect(decoder_stack.get(), SIGNAL(new_decode_data()),
152 this, SLOT(on_new_decode_data()));
153 connect(&delete_mapper_, SIGNAL(mapped(int)),
154 this, SLOT(on_delete_decoder(int)));
155 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
156 this, SLOT(on_show_hide_decoder(int)));
157}
158
159bool DecodeTrace::enabled() const
160{
161 return true;
162}
163
164std::shared_ptr<data::SignalBase> DecodeTrace::base() const
165{
166 return base_;
167}
168
169pair<int, int> DecodeTrace::v_extents() const
170{
171 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
172
173 // Make an empty decode trace appear symmetrical
174 const int row_count = max(1, max_visible_rows_);
175
176 return make_pair(-row_height, row_height * row_count);
177}
178
179void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
180{
181 Trace::paint_back(p, pp);
182 paint_axis(p, pp, get_visual_y());
183}
184
185void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
186{
187 using namespace pv::data::decode;
188
189 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
190 signalbase_->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 (std::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_ = std::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 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
303 signalbase_->decoder_stack();
304
305 assert(form);
306 assert(parent);
307 assert(decoder_stack);
308
309 // Add the standard options
310 Trace::populate_popup_form(parent, form);
311
312 // Add the decoder options
313 bindings_.clear();
314 channel_selectors_.clear();
315 decoder_forms_.clear();
316
317 const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
318
319 if (stack.empty()) {
320 QLabel *const l = new QLabel(
321 tr("<p><i>No decoders in the stack</i></p>"));
322 l->setAlignment(Qt::AlignCenter);
323 form->addRow(l);
324 } else {
325 auto iter = stack.cbegin();
326 for (int i = 0; i < (int)stack.size(); i++, iter++) {
327 shared_ptr<Decoder> dec(*iter);
328 create_decoder_form(i, dec, parent, form);
329 }
330
331 form->addRow(new QLabel(
332 tr("<i>* Required channels</i>"), parent));
333 }
334
335 // Add stacking button
336 pv::widgets::DecoderMenu *const decoder_menu =
337 new pv::widgets::DecoderMenu(parent);
338 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
339 this, SLOT(on_stack_decoder(srd_decoder*)));
340
341 QPushButton *const stack_button =
342 new QPushButton(tr("Stack Decoder"), parent);
343 stack_button->setMenu(decoder_menu);
344
345 QHBoxLayout *stack_button_box = new QHBoxLayout;
346 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
347 form->addRow(stack_button_box);
348}
349
350QMenu* DecodeTrace::create_context_menu(QWidget *parent)
351{
352 QMenu *const menu = Trace::create_context_menu(parent);
353
354 menu->addSeparator();
355
356 QAction *const del = new QAction(tr("Delete"), this);
357 del->setShortcuts(QKeySequence::Delete);
358 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
359 menu->addAction(del);
360
361 return menu;
362}
363
364void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
365 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
366 size_t base_colour, int row_title_width)
367{
368 using namespace pv::data::decode;
369
370 vector<Annotation> a_block;
371 int p_end = INT_MIN;
372
373 double samples_per_pixel, pixels_offset;
374 tie(pixels_offset, samples_per_pixel) =
375 get_pixels_offset_samples_per_pixel();
376
377 // Sort the annotations by start sample so that decoders
378 // can't confuse us by creating annotations out of order
379 stable_sort(annotations.begin(), annotations.end(),
380 [](const Annotation &a, const Annotation &b) {
381 return a.start_sample() < b.start_sample(); });
382
383 // Gather all annotations that form a visual "block" and draw them as such
384 for (const Annotation &a : annotations) {
385
386 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
387 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
388 const int a_width = a_end - a_start;
389
390 const int delta = a_end - p_end;
391
392 bool a_is_separate = false;
393
394 // Annotation wider than the threshold for a useful label width?
395 if (a_width >= min_useful_label_width_) {
396 for (const QString &ann_text : a.annotations()) {
397 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
398 // Annotation wide enough to fit a label? Don't put it in a block then
399 if (w <= a_width) {
400 a_is_separate = true;
401 break;
402 }
403 }
404 }
405
406 // Were the previous and this annotation more than a pixel apart?
407 if ((abs(delta) > 1) || a_is_separate) {
408 // Block was broken, draw annotations that form the current block
409 if (a_block.size() == 1) {
410 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
411 row_title_width);
412 }
413 else
414 draw_annotation_block(a_block, p, h, y, base_colour);
415
416 a_block.clear();
417 }
418
419 if (a_is_separate) {
420 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
421 // Next annotation must start a new block. delta will be > 1
422 // because we set p_end to INT_MIN but that's okay since
423 // a_block will be empty, so nothing will be drawn
424 p_end = INT_MIN;
425 } else {
426 a_block.push_back(a);
427 p_end = a_end;
428 }
429 }
430
431 if (a_block.size() == 1)
432 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
433 row_title_width);
434 else
435 draw_annotation_block(a_block, p, h, y, base_colour);
436}
437
438void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
439 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
440 size_t base_colour, int row_title_width) const
441{
442 double samples_per_pixel, pixels_offset;
443 tie(pixels_offset, samples_per_pixel) =
444 get_pixels_offset_samples_per_pixel();
445
446 const double start = a.start_sample() / samples_per_pixel -
447 pixels_offset;
448 const double end = a.end_sample() / samples_per_pixel -
449 pixels_offset;
450
451 const size_t colour = (base_colour + a.format()) % countof(Colours);
452 p.setPen(OutlineColours[colour]);
453 p.setBrush(Colours[colour]);
454
455 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
456 return;
457
458 if (a.start_sample() == a.end_sample())
459 draw_instant(a, p, h, start, y);
460 else
461 draw_range(a, p, h, start, end, y, pp,
462 row_title_width);
463}
464
465void DecodeTrace::draw_annotation_block(
466 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
467 int y, size_t base_colour) const
468{
469 using namespace pv::data::decode;
470
471 if (annotations.empty())
472 return;
473
474 double samples_per_pixel, pixels_offset;
475 tie(pixels_offset, samples_per_pixel) =
476 get_pixels_offset_samples_per_pixel();
477
478 const double start = annotations.front().start_sample() /
479 samples_per_pixel - pixels_offset;
480 const double end = annotations.back().end_sample() /
481 samples_per_pixel - pixels_offset;
482
483 const double top = y + .5 - h / 2;
484 const double bottom = y + .5 + h / 2;
485
486 const size_t colour = (base_colour + annotations.front().format()) %
487 countof(Colours);
488
489 // Check if all annotations are of the same type (i.e. we can use one color)
490 // or if we should use a neutral color (i.e. gray)
491 const int format = annotations.front().format();
492 const bool single_format = std::all_of(
493 annotations.begin(), annotations.end(),
494 [&](const Annotation &a) { return a.format() == format; });
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(
500 QRectF(start, top, end - start, bottom - top), h/4, h/4);
501}
502
503void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
504 int h, double x, int y) const
505{
506 const QString text = a.annotations().empty() ?
507 QString() : a.annotations().back();
508 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
509 0.0) + h;
510 const QRectF rect(x - w / 2, y - h / 2, w, h);
511
512 p.drawRoundedRect(rect, h / 2, h / 2);
513
514 p.setPen(Qt::black);
515 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
516}
517
518void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
519 int h, double start, double end, int y, const ViewItemPaintParams &pp,
520 int row_title_width) const
521{
522 const double top = y + .5 - h / 2;
523 const double bottom = y + .5 + h / 2;
524 const vector<QString> annotations = a.annotations();
525
526 // If the two ends are within 1 pixel, draw a vertical line
527 if (start + 1.0 > end) {
528 p.drawLine(QPointF(start, top), QPointF(start, bottom));
529 return;
530 }
531
532 const double cap_width = min((end - start) / 4, EndCapWidth);
533
534 QPointF pts[] = {
535 QPointF(start, y + .5f),
536 QPointF(start + cap_width, top),
537 QPointF(end - cap_width, top),
538 QPointF(end, y + .5f),
539 QPointF(end - cap_width, bottom),
540 QPointF(start + cap_width, bottom)
541 };
542
543 p.drawConvexPolygon(pts, countof(pts));
544
545 if (annotations.empty())
546 return;
547
548 const int ann_start = start + cap_width;
549 const int ann_end = end - cap_width;
550
551 const int real_start = std::max(ann_start, pp.left() + row_title_width);
552 const int real_end = std::min(ann_end, pp.right());
553 const int real_width = real_end - real_start;
554
555 QRectF rect(real_start, y - h / 2, real_width, h);
556 if (rect.width() <= 4)
557 return;
558
559 p.setPen(Qt::black);
560
561 // Try to find an annotation that will fit
562 QString best_annotation;
563 int best_width = 0;
564
565 for (const QString &a : annotations) {
566 const int w = p.boundingRect(QRectF(), 0, a).width();
567 if (w <= rect.width() && w > best_width)
568 best_annotation = a, best_width = w;
569 }
570
571 if (best_annotation.isEmpty())
572 best_annotation = annotations.back();
573
574 // If not ellide the last in the list
575 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
576 best_annotation, Qt::ElideRight, rect.width()));
577}
578
579void DecodeTrace::draw_error(QPainter &p, const QString &message,
580 const ViewItemPaintParams &pp)
581{
582 const int y = get_visual_y();
583
584 p.setPen(ErrorBgColour.darker());
585 p.setBrush(ErrorBgColour);
586
587 const QRectF bounding_rect =
588 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
589 const QRectF text_rect = p.boundingRect(bounding_rect,
590 Qt::AlignCenter, message);
591 const float r = text_rect.height() / 4;
592
593 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
594 Qt::AbsoluteSize);
595
596 p.setPen(Qt::black);
597 p.drawText(text_rect, message);
598}
599
600void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
601 int right) const
602{
603 using namespace pv::data;
604 using pv::data::decode::Decoder;
605
606 double samples_per_pixel, pixels_offset;
607
608 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
609 signalbase_->decoder_stack();
610
611 assert(decoder_stack);
612
613 shared_ptr<Logic> data;
614 shared_ptr<data::SignalBase> signalbase;
615
616 const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
617
618 // We get the logic data of the first channel in the list.
619 // This works because we are currently assuming all
620 // LogicSignals have the same data/segment
621 for (const shared_ptr<Decoder> &dec : stack)
622 if (dec && !dec->channels().empty() &&
623 ((signalbase = (*dec->channels().begin()).second)) &&
624 ((data = signalbase->logic_data())))
625 break;
626
627 if (!data || data->logic_segments().empty())
628 return;
629
630 const shared_ptr<LogicSegment> segment =
631 data->logic_segments().front();
632 assert(segment);
633 const int64_t sample_count = (int64_t)segment->get_sample_count();
634 if (sample_count == 0)
635 return;
636
637 const int64_t samples_decoded = decoder_stack->samples_decoded();
638 if (sample_count == samples_decoded)
639 return;
640
641 const int y = get_visual_y();
642
643 tie(pixels_offset, samples_per_pixel) =
644 get_pixels_offset_samples_per_pixel();
645
646 const double start = max(samples_decoded /
647 samples_per_pixel - pixels_offset, left - 1.0);
648 const double end = min(sample_count / samples_per_pixel -
649 pixels_offset, right + 1.0);
650 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
651
652 p.setPen(QPen(Qt::NoPen));
653 p.setBrush(Qt::white);
654 p.drawRect(no_decode_rect);
655
656 p.setPen(NoDecodeColour);
657 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
658 p.drawRect(no_decode_rect);
659}
660
661pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
662{
663 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
664 signalbase_->decoder_stack();
665
666 assert(owner_);
667 assert(decoder_stack);
668
669 const View *view = owner_->view();
670 assert(view);
671
672 const double scale = view->scale();
673 assert(scale > 0);
674
675 const double pixels_offset =
676 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
677
678 double samplerate = decoder_stack->samplerate();
679
680 // Show sample rate as 1Hz when it is unknown
681 if (samplerate == 0.0)
682 samplerate = 1.0;
683
684 return make_pair(pixels_offset, samplerate * scale);
685}
686
687pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
688 int x_start, int x_end) const
689{
690 double samples_per_pixel, pixels_offset;
691 tie(pixels_offset, samples_per_pixel) =
692 get_pixels_offset_samples_per_pixel();
693
694 const uint64_t start = (uint64_t)max(
695 (x_start + pixels_offset) * samples_per_pixel, 0.0);
696 const uint64_t end = (uint64_t)max(
697 (x_end + pixels_offset) * samples_per_pixel, 0.0);
698
699 return make_pair(start, end);
700}
701
702int DecodeTrace::get_row_at_point(const QPoint &point)
703{
704 if (!row_height_)
705 return -1;
706
707 const int y = (point.y() - get_visual_y() + row_height_ / 2);
708
709 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
710 if (y < 0)
711 return -1;
712
713 const int row = y / row_height_;
714
715 if (row >= (int)visible_rows_.size())
716 return -1;
717
718 return row;
719}
720
721const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
722{
723 using namespace pv::data::decode;
724
725 if (!enabled())
726 return QString();
727
728 const pair<uint64_t, uint64_t> sample_range =
729 get_sample_range(point.x(), point.x() + 1);
730 const int row = get_row_at_point(point);
731 if (row < 0)
732 return QString();
733
734 vector<pv::data::decode::Annotation> annotations;
735
736 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
737 signalbase_->decoder_stack();
738
739 assert(decoder_stack);
740 decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
741 sample_range.first, sample_range.second);
742
743 return (annotations.empty()) ?
744 QString() : annotations[0].annotations().front();
745}
746
747void DecodeTrace::hover_point_changed()
748{
749 assert(owner_);
750
751 const View *const view = owner_->view();
752 assert(view);
753
754 QPoint hp = view->hover_point();
755 QString ann = get_annotation_at_point(hp);
756
757 assert(view);
758
759 if (!row_height_ || ann.isEmpty()) {
760 QToolTip::hideText();
761 return;
762 }
763
764 const int hover_row = get_row_at_point(hp);
765
766 QFontMetrics m(QToolTip::font());
767 const QRect text_size = m.boundingRect(QRect(), 0, ann);
768
769 // This is OS-specific and unfortunately we can't query it, so
770 // use an approximation to at least try to minimize the error.
771 const int padding = 8;
772
773 // Make sure the tool tip doesn't overlap with the mouse cursor.
774 // If it did, the tool tip would constantly hide and re-appear.
775 // We also push it up by one row so that it appears above the
776 // decode trace, not below.
777 hp.setX(hp.x() - (text_size.width() / 2) - padding);
778
779 hp.setY(get_visual_y() - (row_height_ / 2) +
780 (hover_row * row_height_) -
781 row_height_ - text_size.height() - padding);
782
783 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
784}
785
786void DecodeTrace::create_decoder_form(int index,
787 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
788 QFormLayout *form)
789{
790 const GSList *l;
791
792 assert(dec);
793 const srd_decoder *const decoder = dec->decoder();
794 assert(decoder);
795
796 const bool decoder_deletable = index > 0;
797
798 pv::widgets::DecoderGroupBox *const group =
799 new pv::widgets::DecoderGroupBox(
800 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
801 group->set_decoder_visible(dec->shown());
802
803 if (decoder_deletable) {
804 delete_mapper_.setMapping(group, index);
805 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
806 }
807
808 show_hide_mapper_.setMapping(group, index);
809 connect(group, SIGNAL(show_hide_decoder()),
810 &show_hide_mapper_, SLOT(map()));
811
812 QFormLayout *const decoder_form = new QFormLayout;
813 group->add_layout(decoder_form);
814
815 // Add the mandatory channels
816 for (l = decoder->channels; l; l = l->next) {
817 const struct srd_channel *const pdch =
818 (struct srd_channel *)l->data;
819 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
820 connect(combo, SIGNAL(currentIndexChanged(int)),
821 this, SLOT(on_channel_selected(int)));
822 decoder_form->addRow(tr("<b>%1</b> (%2) *")
823 .arg(QString::fromUtf8(pdch->name),
824 QString::fromUtf8(pdch->desc)), combo);
825
826 const ChannelSelector s = {combo, dec, pdch};
827 channel_selectors_.push_back(s);
828 }
829
830 // Add the optional channels
831 for (l = decoder->opt_channels; l; l = l->next) {
832 const struct srd_channel *const pdch =
833 (struct srd_channel *)l->data;
834 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
835 connect(combo, SIGNAL(currentIndexChanged(int)),
836 this, SLOT(on_channel_selected(int)));
837 decoder_form->addRow(tr("<b>%1</b> (%2)")
838 .arg(QString::fromUtf8(pdch->name),
839 QString::fromUtf8(pdch->desc)), combo);
840
841 const ChannelSelector s = {combo, dec, pdch};
842 channel_selectors_.push_back(s);
843 }
844
845 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
846 signalbase_->decoder_stack();
847
848 // Add the options
849 shared_ptr<binding::Decoder> binding(
850 new binding::Decoder(decoder_stack, dec));
851 binding->add_properties_to_form(decoder_form, true);
852
853 bindings_.push_back(binding);
854
855 form->addRow(group);
856 decoder_forms_.push_back(group);
857}
858
859QComboBox* DecodeTrace::create_channel_selector(
860 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
861 const srd_channel *const pdch)
862{
863 assert(dec);
864
865 const auto sigs(session_.signalbases());
866
867 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
868 std::sort(sig_list.begin(), sig_list.end(),
869 [](const shared_ptr<data::SignalBase> &a,
870 const shared_ptr<data::SignalBase> &b) {
871 return strnatcasecmp(a->name().toStdString(),
872 b->name().toStdString()) < 0; });
873
874 const auto channel_iter = dec->channels().find(pdch);
875
876 QComboBox *selector = new QComboBox(parent);
877
878 selector->addItem("-", qVariantFromValue((void*)nullptr));
879
880 if (channel_iter == dec->channels().end())
881 selector->setCurrentIndex(0);
882
883 for (const shared_ptr<data::SignalBase> &b : sig_list) {
884 assert(b);
885 if (b->type() == sigrok::ChannelType::LOGIC && b->enabled()) {
886 selector->addItem(b->name(),
887 qVariantFromValue((void*)b.get()));
888
889 if (channel_iter != dec->channels().end() &&
890 (*channel_iter).second == b)
891 selector->setCurrentIndex(
892 selector->count() - 1);
893 }
894 }
895
896 return selector;
897}
898
899void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
900{
901 assert(dec);
902
903 map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
904
905 const unordered_set< shared_ptr<data::SignalBase> >
906 sigs(session_.signalbases());
907
908 for (const ChannelSelector &s : channel_selectors_) {
909 if (s.decoder_ != dec)
910 break;
911
912 const data::SignalBase *const selection =
913 (data::SignalBase*)s.combo_->itemData(
914 s.combo_->currentIndex()).value<void*>();
915
916 for (shared_ptr<data::SignalBase> sig : sigs)
917 if (sig.get() == selection) {
918 channel_map[s.pdch_] = sig;
919 break;
920 }
921 }
922
923 dec->set_channels(channel_map);
924}
925
926void DecodeTrace::commit_channels()
927{
928 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
929 signalbase_->decoder_stack();
930
931 assert(decoder_stack);
932 for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
933 commit_decoder_channels(dec);
934
935 decoder_stack->begin_decode();
936}
937
938void DecodeTrace::on_new_decode_data()
939{
940 if (owner_)
941 owner_->row_item_appearance_changed(false, true);
942}
943
944void DecodeTrace::delete_pressed()
945{
946 on_delete();
947}
948
949void DecodeTrace::on_delete()
950{
951 session_.remove_decode_signal(base_);
952}
953
954void DecodeTrace::on_channel_selected(int)
955{
956 commit_channels();
957}
958
959void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
960{
961 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
962 signalbase_->decoder_stack();
963
964 assert(decoder);
965 assert(decoder_stack);
966 decoder_stack->push(shared_ptr<data::decode::Decoder>(
967 new data::decode::Decoder(decoder)));
968 decoder_stack->begin_decode();
969
970 create_popup_form();
971}
972
973void DecodeTrace::on_delete_decoder(int index)
974{
975 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
976 signalbase_->decoder_stack();
977
978 decoder_stack->remove(index);
979
980 // Update the popup
981 create_popup_form();
982
983 decoder_stack->begin_decode();
984}
985
986void DecodeTrace::on_show_hide_decoder(int index)
987{
988 using pv::data::decode::Decoder;
989
990 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
991 signalbase_->decoder_stack();
992
993 const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
994
995 // Find the decoder in the stack
996 auto iter = stack.cbegin();
997 for (int i = 0; i < index; i++, iter++)
998 assert(iter != stack.end());
999
1000 shared_ptr<Decoder> dec = *iter;
1001 assert(dec);
1002
1003 const bool show = !dec->shown();
1004 dec->show(show);
1005
1006 assert(index < (int)decoder_forms_.size());
1007 decoder_forms_[index]->set_decoder_visible(show);
1008
1009 if (owner_)
1010 owner_->row_item_appearance_changed(false, true);
1011}
1012
1013} // namespace TraceView
1014} // namespace views
1015} // namespace pv