]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/decodetrace.cpp
MainWindow, View::View: Fix two memory errors reported by valgrind
[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 row_height_(0),
136 max_visible_rows_(0),
137 delete_mapper_(this),
138 show_hide_mapper_(this)
139{
140 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
141 base_->decoder_stack();
142
143 // Determine shortest string we want to see displayed in full
144 QFontMetrics m(QApplication::font());
145 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
146
147 base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
148 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
149
150 connect(decoder_stack.get(), SIGNAL(new_decode_data()),
151 this, SLOT(on_new_decode_data()));
152 connect(&delete_mapper_, SIGNAL(mapped(int)),
153 this, SLOT(on_delete_decoder(int)));
154 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
155 this, SLOT(on_show_hide_decoder(int)));
156}
157
158bool DecodeTrace::enabled() const
159{
160 return true;
161}
162
163std::shared_ptr<data::SignalBase> DecodeTrace::base() const
164{
165 return base_;
166}
167
168pair<int, int> DecodeTrace::v_extents() const
169{
170 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
171
172 // Make an empty decode trace appear symmetrical
173 const int row_count = max(1, max_visible_rows_);
174
175 return make_pair(-row_height, row_height * row_count);
176}
177
178void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
179{
180 Trace::paint_back(p, pp);
181 paint_axis(p, pp, get_visual_y());
182}
183
184void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
185{
186 using namespace pv::data::decode;
187
188 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
189 base_->decoder_stack();
190
191 const int text_height = ViewItemPaintParams::text_height();
192 row_height_ = (text_height * 6) / 4;
193 const int annotation_height = (text_height * 5) / 4;
194
195 assert(decoder_stack);
196 const QString err = decoder_stack->error_message();
197 if (!err.isEmpty()) {
198 draw_unresolved_period(
199 p, annotation_height, pp.left(), pp.right());
200 draw_error(p, err, pp);
201 return;
202 }
203
204 // Set default pen to allow for text width calculation
205 p.setPen(Qt::black);
206
207 // Iterate through the rows
208 int y = get_visual_y();
209 pair<uint64_t, uint64_t> sample_range = get_sample_range(
210 pp.left(), pp.right());
211
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 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
302 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 = std::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 = std::max(ann_start, pp.left() + row_title_width);
551 const int real_end = std::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 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
608 base_->decoder_stack();
609
610 assert(decoder_stack);
611
612 shared_ptr<Logic> data;
613 shared_ptr<data::SignalBase> signalbase;
614
615 const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
616
617 // We get the logic data of the first channel in the list.
618 // This works because we are currently assuming all
619 // LogicSignals have the same data/segment
620 for (const shared_ptr<Decoder> &dec : stack)
621 if (dec && !dec->channels().empty() &&
622 ((signalbase = (*dec->channels().begin()).second)) &&
623 ((data = signalbase->logic_data())))
624 break;
625
626 if (!data || data->logic_segments().empty())
627 return;
628
629 const shared_ptr<LogicSegment> segment =
630 data->logic_segments().front();
631 assert(segment);
632 const int64_t sample_count = (int64_t)segment->get_sample_count();
633 if (sample_count == 0)
634 return;
635
636 const int64_t samples_decoded = decoder_stack->samples_decoded();
637 if (sample_count == samples_decoded)
638 return;
639
640 const int y = get_visual_y();
641
642 tie(pixels_offset, samples_per_pixel) =
643 get_pixels_offset_samples_per_pixel();
644
645 const double start = max(samples_decoded /
646 samples_per_pixel - pixels_offset, left - 1.0);
647 const double end = min(sample_count / samples_per_pixel -
648 pixels_offset, right + 1.0);
649 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
650
651 p.setPen(QPen(Qt::NoPen));
652 p.setBrush(Qt::white);
653 p.drawRect(no_decode_rect);
654
655 p.setPen(NoDecodeColour);
656 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
657 p.drawRect(no_decode_rect);
658}
659
660pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
661{
662 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
663 base_->decoder_stack();
664
665 assert(owner_);
666 assert(decoder_stack);
667
668 const View *view = owner_->view();
669 assert(view);
670
671 const double scale = view->scale();
672 assert(scale > 0);
673
674 const double pixels_offset =
675 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
676
677 double samplerate = decoder_stack->samplerate();
678
679 // Show sample rate as 1Hz when it is unknown
680 if (samplerate == 0.0)
681 samplerate = 1.0;
682
683 return make_pair(pixels_offset, samplerate * scale);
684}
685
686pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
687 int x_start, int x_end) const
688{
689 double samples_per_pixel, pixels_offset;
690 tie(pixels_offset, samples_per_pixel) =
691 get_pixels_offset_samples_per_pixel();
692
693 const uint64_t start = (uint64_t)max(
694 (x_start + pixels_offset) * samples_per_pixel, 0.0);
695 const uint64_t end = (uint64_t)max(
696 (x_end + pixels_offset) * samples_per_pixel, 0.0);
697
698 return make_pair(start, end);
699}
700
701int DecodeTrace::get_row_at_point(const QPoint &point)
702{
703 if (!row_height_)
704 return -1;
705
706 const int y = (point.y() - get_visual_y() + row_height_ / 2);
707
708 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
709 if (y < 0)
710 return -1;
711
712 const int row = y / row_height_;
713
714 if (row >= (int)visible_rows_.size())
715 return -1;
716
717 return row;
718}
719
720const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
721{
722 using namespace pv::data::decode;
723
724 if (!enabled())
725 return QString();
726
727 const pair<uint64_t, uint64_t> sample_range =
728 get_sample_range(point.x(), point.x() + 1);
729 const int row = get_row_at_point(point);
730 if (row < 0)
731 return QString();
732
733 vector<pv::data::decode::Annotation> annotations;
734
735 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
736 base_->decoder_stack();
737
738 assert(decoder_stack);
739 decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
740 sample_range.first, sample_range.second);
741
742 return (annotations.empty()) ?
743 QString() : annotations[0].annotations().front();
744}
745
746void DecodeTrace::hover_point_changed()
747{
748 assert(owner_);
749
750 const View *const view = owner_->view();
751 assert(view);
752
753 QPoint hp = view->hover_point();
754 QString ann = get_annotation_at_point(hp);
755
756 assert(view);
757
758 if (!row_height_ || ann.isEmpty()) {
759 QToolTip::hideText();
760 return;
761 }
762
763 const int hover_row = get_row_at_point(hp);
764
765 QFontMetrics m(QToolTip::font());
766 const QRect text_size = m.boundingRect(QRect(), 0, ann);
767
768 // This is OS-specific and unfortunately we can't query it, so
769 // use an approximation to at least try to minimize the error.
770 const int padding = 8;
771
772 // Make sure the tool tip doesn't overlap with the mouse cursor.
773 // If it did, the tool tip would constantly hide and re-appear.
774 // We also push it up by one row so that it appears above the
775 // decode trace, not below.
776 hp.setX(hp.x() - (text_size.width() / 2) - padding);
777
778 hp.setY(get_visual_y() - (row_height_ / 2) +
779 (hover_row * row_height_) -
780 row_height_ - text_size.height() - padding);
781
782 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
783}
784
785void DecodeTrace::create_decoder_form(int index,
786 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
787 QFormLayout *form)
788{
789 const GSList *l;
790
791 assert(dec);
792 const srd_decoder *const decoder = dec->decoder();
793 assert(decoder);
794
795 const bool decoder_deletable = index > 0;
796
797 pv::widgets::DecoderGroupBox *const group =
798 new pv::widgets::DecoderGroupBox(
799 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
800 group->set_decoder_visible(dec->shown());
801
802 if (decoder_deletable) {
803 delete_mapper_.setMapping(group, index);
804 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
805 }
806
807 show_hide_mapper_.setMapping(group, index);
808 connect(group, SIGNAL(show_hide_decoder()),
809 &show_hide_mapper_, SLOT(map()));
810
811 QFormLayout *const decoder_form = new QFormLayout;
812 group->add_layout(decoder_form);
813
814 // Add the mandatory channels
815 for (l = decoder->channels; l; l = l->next) {
816 const struct srd_channel *const pdch =
817 (struct srd_channel *)l->data;
818 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
819 connect(combo, SIGNAL(currentIndexChanged(int)),
820 this, SLOT(on_channel_selected(int)));
821 decoder_form->addRow(tr("<b>%1</b> (%2) *")
822 .arg(QString::fromUtf8(pdch->name),
823 QString::fromUtf8(pdch->desc)), combo);
824
825 const ChannelSelector s = {combo, dec, pdch};
826 channel_selectors_.push_back(s);
827 }
828
829 // Add the optional channels
830 for (l = decoder->opt_channels; l; l = l->next) {
831 const struct srd_channel *const pdch =
832 (struct srd_channel *)l->data;
833 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
834 connect(combo, SIGNAL(currentIndexChanged(int)),
835 this, SLOT(on_channel_selected(int)));
836 decoder_form->addRow(tr("<b>%1</b> (%2)")
837 .arg(QString::fromUtf8(pdch->name),
838 QString::fromUtf8(pdch->desc)), combo);
839
840 const ChannelSelector s = {combo, dec, pdch};
841 channel_selectors_.push_back(s);
842 }
843
844 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
845 base_->decoder_stack();
846
847 // Add the options
848 shared_ptr<binding::Decoder> binding(
849 new binding::Decoder(decoder_stack, dec));
850 binding->add_properties_to_form(decoder_form, true);
851
852 bindings_.push_back(binding);
853
854 form->addRow(group);
855 decoder_forms_.push_back(group);
856}
857
858QComboBox* DecodeTrace::create_channel_selector(
859 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
860 const srd_channel *const pdch)
861{
862 assert(dec);
863
864 const auto sigs(session_.signalbases());
865
866 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
867 std::sort(sig_list.begin(), sig_list.end(),
868 [](const shared_ptr<data::SignalBase> &a,
869 const shared_ptr<data::SignalBase> &b) {
870 return strnatcasecmp(a->name().toStdString(),
871 b->name().toStdString()) < 0; });
872
873 const auto channel_iter = dec->channels().find(pdch);
874
875 QComboBox *selector = new QComboBox(parent);
876
877 selector->addItem("-", qVariantFromValue((void*)nullptr));
878
879 if (channel_iter == dec->channels().end())
880 selector->setCurrentIndex(0);
881
882 for (const shared_ptr<data::SignalBase> &b : sig_list) {
883 assert(b);
884 if (b->type() == sigrok::ChannelType::LOGIC && b->enabled()) {
885 selector->addItem(b->name(),
886 qVariantFromValue((void*)b.get()));
887
888 if (channel_iter != dec->channels().end() &&
889 (*channel_iter).second == b)
890 selector->setCurrentIndex(
891 selector->count() - 1);
892 }
893 }
894
895 return selector;
896}
897
898void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
899{
900 assert(dec);
901
902 map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
903
904 const unordered_set< shared_ptr<data::SignalBase> >
905 sigs(session_.signalbases());
906
907 for (const ChannelSelector &s : channel_selectors_) {
908 if (s.decoder_ != dec)
909 break;
910
911 const data::SignalBase *const selection =
912 (data::SignalBase*)s.combo_->itemData(
913 s.combo_->currentIndex()).value<void*>();
914
915 for (shared_ptr<data::SignalBase> sig : sigs)
916 if (sig.get() == selection) {
917 channel_map[s.pdch_] = sig;
918 break;
919 }
920 }
921
922 dec->set_channels(channel_map);
923}
924
925void DecodeTrace::commit_channels()
926{
927 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
928 base_->decoder_stack();
929
930 assert(decoder_stack);
931 for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
932 commit_decoder_channels(dec);
933
934 decoder_stack->begin_decode();
935}
936
937void DecodeTrace::on_new_decode_data()
938{
939 if (owner_)
940 owner_->row_item_appearance_changed(false, true);
941}
942
943void DecodeTrace::delete_pressed()
944{
945 on_delete();
946}
947
948void DecodeTrace::on_delete()
949{
950 session_.remove_decode_signal(base_);
951}
952
953void DecodeTrace::on_channel_selected(int)
954{
955 commit_channels();
956}
957
958void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
959{
960 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
961 base_->decoder_stack();
962
963 assert(decoder);
964 assert(decoder_stack);
965 decoder_stack->push(shared_ptr<data::decode::Decoder>(
966 new data::decode::Decoder(decoder)));
967 decoder_stack->begin_decode();
968
969 create_popup_form();
970}
971
972void DecodeTrace::on_delete_decoder(int index)
973{
974 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
975 base_->decoder_stack();
976
977 decoder_stack->remove(index);
978
979 // Update the popup
980 create_popup_form();
981
982 decoder_stack->begin_decode();
983}
984
985void DecodeTrace::on_show_hide_decoder(int index)
986{
987 using pv::data::decode::Decoder;
988
989 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
990 base_->decoder_stack();
991
992 const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
993
994 // Find the decoder in the stack
995 auto iter = stack.cbegin();
996 for (int i = 0; i < index; i++, iter++)
997 assert(iter != stack.end());
998
999 shared_ptr<Decoder> dec = *iter;
1000 assert(dec);
1001
1002 const bool show = !dec->shown();
1003 dec->show(show);
1004
1005 assert(index < (int)decoder_forms_.size());
1006 decoder_forms_[index]->set_decoder_visible(show);
1007
1008 if (owner_)
1009 owner_->row_item_appearance_changed(false, true);
1010}
1011
1012} // namespace TraceView
1013} // namespace views
1014} // namespace pv