]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/decodetrace.cpp
TraceView: Center traces vertically after view creation
[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;
59using std::dynamic_pointer_cast;
60using std::list;
61using std::lock_guard;
62using std::make_pair;
63using std::max;
64using std::make_pair;
65using std::map;
66using std::min;
67using std::pair;
68using std::shared_ptr;
69using std::tie;
70using std::unordered_set;
71using std::vector;
72
73namespace pv {
74namespace views {
75namespace TraceView {
76
77const QColor DecodeTrace::DecodeColours[4] = {
78 QColor(0xEF, 0x29, 0x29), // Red
79 QColor(0xFC, 0xE9, 0x4F), // Yellow
80 QColor(0x8A, 0xE2, 0x34), // Green
81 QColor(0x72, 0x9F, 0xCF) // Blue
82};
83
84const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
85const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
86
87const int DecodeTrace::ArrowSize = 4;
88const double DecodeTrace::EndCapWidth = 5;
89const int DecodeTrace::RowTitleMargin = 10;
90const int DecodeTrace::DrawPadding = 100;
91
92const QColor DecodeTrace::Colours[16] = {
93 QColor(0xEF, 0x29, 0x29),
94 QColor(0xF6, 0x6A, 0x32),
95 QColor(0xFC, 0xAE, 0x3E),
96 QColor(0xFB, 0xCA, 0x47),
97 QColor(0xFC, 0xE9, 0x4F),
98 QColor(0xCD, 0xF0, 0x40),
99 QColor(0x8A, 0xE2, 0x34),
100 QColor(0x4E, 0xDC, 0x44),
101 QColor(0x55, 0xD7, 0x95),
102 QColor(0x64, 0xD1, 0xD2),
103 QColor(0x72, 0x9F, 0xCF),
104 QColor(0xD4, 0x76, 0xC4),
105 QColor(0x9D, 0x79, 0xB9),
106 QColor(0xAD, 0x7F, 0xA8),
107 QColor(0xC2, 0x62, 0x9B),
108 QColor(0xD7, 0x47, 0x6F)
109};
110
111const QColor DecodeTrace::OutlineColours[16] = {
112 QColor(0x77, 0x14, 0x14),
113 QColor(0x7B, 0x35, 0x19),
114 QColor(0x7E, 0x57, 0x1F),
115 QColor(0x7D, 0x65, 0x23),
116 QColor(0x7E, 0x74, 0x27),
117 QColor(0x66, 0x78, 0x20),
118 QColor(0x45, 0x71, 0x1A),
119 QColor(0x27, 0x6E, 0x22),
120 QColor(0x2A, 0x6B, 0x4A),
121 QColor(0x32, 0x68, 0x69),
122 QColor(0x39, 0x4F, 0x67),
123 QColor(0x6A, 0x3B, 0x62),
124 QColor(0x4E, 0x3C, 0x5C),
125 QColor(0x56, 0x3F, 0x54),
126 QColor(0x61, 0x31, 0x4D),
127 QColor(0x6B, 0x23, 0x37)
128};
129
130DecodeTrace::DecodeTrace(pv::Session &session,
131 shared_ptr<data::SignalBase> signalbase, int index) :
132 Trace(signalbase),
133 session_(session),
134 row_height_(0),
135 max_visible_rows_(0),
136 delete_mapper_(this),
137 show_hide_mapper_(this)
138{
139 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
140 base_->decoder_stack();
141
142 // Determine shortest string we want to see displayed in full
143 QFontMetrics m(QApplication::font());
144 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
145
146 base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
147 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
148
149 connect(decoder_stack.get(), SIGNAL(new_decode_data()),
150 this, SLOT(on_new_decode_data()));
151 connect(&delete_mapper_, SIGNAL(mapped(int)),
152 this, SLOT(on_delete_decoder(int)));
153 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
154 this, SLOT(on_show_hide_decoder(int)));
155}
156
157bool DecodeTrace::enabled() const
158{
159 return true;
160}
161
162std::shared_ptr<data::SignalBase> DecodeTrace::base() const
163{
164 return base_;
165}
166
167pair<int, int> DecodeTrace::v_extents() const
168{
169 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
170
171 // Make an empty decode trace appear symmetrical
172 const int row_count = max(1, max_visible_rows_);
173
174 return make_pair(-row_height, row_height * row_count);
175}
176
177void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
178{
179 Trace::paint_back(p, pp);
180 paint_axis(p, pp, get_visual_y());
181}
182
183void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
184{
185 using namespace pv::data::decode;
186
187 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
188 base_->decoder_stack();
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 const vector<Row> rows(decoder_stack->get_visible_rows());
212
213 visible_rows_.clear();
214 for (const Row& row : rows) {
215 // Cache the row title widths
216 int row_title_width;
217 try {
218 row_title_width = row_title_widths_.at(row);
219 } catch (std::out_of_range) {
220 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
221 RowTitleMargin;
222 row_title_widths_[row] = w;
223 row_title_width = w;
224 }
225
226 // Determine the row's color
227 size_t base_colour = 0x13579BDF;
228 boost::hash_combine(base_colour, this);
229 boost::hash_combine(base_colour, row.decoder());
230 boost::hash_combine(base_colour, row.row());
231 base_colour >>= 16;
232
233 vector<Annotation> annotations;
234 decoder_stack->get_annotation_subset(annotations, row,
235 sample_range.first, sample_range.second);
236 if (!annotations.empty()) {
237 draw_annotations(annotations, p, annotation_height, pp, y,
238 base_colour, row_title_width);
239
240 y += row_height_;
241
242 visible_rows_.push_back(row);
243 }
244 }
245
246 // Draw the hatching
247 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
248
249 if ((int)visible_rows_.size() > max_visible_rows_)
250 owner_->extents_changed(false, true);
251
252 // Update the maximum row count if needed
253 max_visible_rows_ = std::max(max_visible_rows_, (int)visible_rows_.size());
254}
255
256void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
257{
258 using namespace pv::data::decode;
259
260 assert(row_height_);
261
262 for (size_t i = 0; i < visible_rows_.size(); i++) {
263 const int y = i * row_height_ + get_visual_y();
264
265 p.setPen(QPen(Qt::NoPen));
266 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
267
268 if (i != 0) {
269 const QPointF points[] = {
270 QPointF(pp.left(), y - ArrowSize),
271 QPointF(pp.left() + ArrowSize, y),
272 QPointF(pp.left(), y + ArrowSize)
273 };
274 p.drawPolygon(points, countof(points));
275 }
276
277 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
278 pp.right() - pp.left(), row_height_);
279 const QString h(visible_rows_[i].title());
280 const int f = Qt::AlignLeft | Qt::AlignVCenter |
281 Qt::TextDontClip;
282
283 // Draw the outline
284 p.setPen(QApplication::palette().color(QPalette::Base));
285 for (int dx = -1; dx <= 1; dx++)
286 for (int dy = -1; dy <= 1; dy++)
287 if (dx != 0 && dy != 0)
288 p.drawText(r.translated(dx, dy), f, h);
289
290 // Draw the text
291 p.setPen(QApplication::palette().color(QPalette::WindowText));
292 p.drawText(r, f, h);
293 }
294}
295
296void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
297{
298 using pv::data::decode::Decoder;
299
300 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
301 base_->decoder_stack();
302
303 assert(form);
304 assert(parent);
305 assert(decoder_stack);
306
307 // Add the standard options
308 Trace::populate_popup_form(parent, form);
309
310 // Add the decoder options
311 bindings_.clear();
312 channel_selectors_.clear();
313 decoder_forms_.clear();
314
315 const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
316
317 if (stack.empty()) {
318 QLabel *const l = new QLabel(
319 tr("<p><i>No decoders in the stack</i></p>"));
320 l->setAlignment(Qt::AlignCenter);
321 form->addRow(l);
322 } else {
323 auto iter = stack.cbegin();
324 for (int i = 0; i < (int)stack.size(); i++, iter++) {
325 shared_ptr<Decoder> dec(*iter);
326 create_decoder_form(i, dec, parent, form);
327 }
328
329 form->addRow(new QLabel(
330 tr("<i>* Required channels</i>"), parent));
331 }
332
333 // Add stacking button
334 pv::widgets::DecoderMenu *const decoder_menu =
335 new pv::widgets::DecoderMenu(parent);
336 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
337 this, SLOT(on_stack_decoder(srd_decoder*)));
338
339 QPushButton *const stack_button =
340 new QPushButton(tr("Stack Decoder"), parent);
341 stack_button->setMenu(decoder_menu);
342
343 QHBoxLayout *stack_button_box = new QHBoxLayout;
344 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
345 form->addRow(stack_button_box);
346}
347
348QMenu* DecodeTrace::create_context_menu(QWidget *parent)
349{
350 QMenu *const menu = Trace::create_context_menu(parent);
351
352 menu->addSeparator();
353
354 QAction *const del = new QAction(tr("Delete"), this);
355 del->setShortcuts(QKeySequence::Delete);
356 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
357 menu->addAction(del);
358
359 return menu;
360}
361
362void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
363 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
364 size_t base_colour, int row_title_width)
365{
366 using namespace pv::data::decode;
367
368 vector<Annotation> a_block;
369 int p_end = INT_MIN;
370
371 double samples_per_pixel, pixels_offset;
372 tie(pixels_offset, samples_per_pixel) =
373 get_pixels_offset_samples_per_pixel();
374
375 // Sort the annotations by start sample so that decoders
376 // can't confuse us by creating annotations out of order
377 stable_sort(annotations.begin(), annotations.end(),
378 [](const Annotation &a, const Annotation &b) {
379 return a.start_sample() < b.start_sample(); });
380
381 // Gather all annotations that form a visual "block" and draw them as such
382 for (const Annotation &a : annotations) {
383
384 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
385 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
386 const int a_width = a_end - a_start;
387
388 const int delta = a_end - p_end;
389
390 bool a_is_separate = false;
391
392 // Annotation wider than the threshold for a useful label width?
393 if (a_width >= min_useful_label_width_) {
394 for (const QString &ann_text : a.annotations()) {
395 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
396 // Annotation wide enough to fit a label? Don't put it in a block then
397 if (w <= a_width) {
398 a_is_separate = true;
399 break;
400 }
401 }
402 }
403
404 // Were the previous and this annotation more than a pixel apart?
405 if ((abs(delta) > 1) || a_is_separate) {
406 // Block was broken, draw annotations that form the current block
407 if (a_block.size() == 1) {
408 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
409 row_title_width);
410 }
411 else
412 draw_annotation_block(a_block, p, h, y, base_colour);
413
414 a_block.clear();
415 }
416
417 if (a_is_separate) {
418 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
419 // Next annotation must start a new block. delta will be > 1
420 // because we set p_end to INT_MIN but that's okay since
421 // a_block will be empty, so nothing will be drawn
422 p_end = INT_MIN;
423 } else {
424 a_block.push_back(a);
425 p_end = a_end;
426 }
427 }
428
429 if (a_block.size() == 1)
430 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
431 row_title_width);
432 else
433 draw_annotation_block(a_block, p, h, y, base_colour);
434}
435
436void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
437 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
438 size_t base_colour, int row_title_width) const
439{
440 double samples_per_pixel, pixels_offset;
441 tie(pixels_offset, samples_per_pixel) =
442 get_pixels_offset_samples_per_pixel();
443
444 const double start = a.start_sample() / samples_per_pixel -
445 pixels_offset;
446 const double end = a.end_sample() / samples_per_pixel -
447 pixels_offset;
448
449 const size_t colour = (base_colour + a.format()) % countof(Colours);
450 p.setPen(OutlineColours[colour]);
451 p.setBrush(Colours[colour]);
452
453 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
454 return;
455
456 if (a.start_sample() == a.end_sample())
457 draw_instant(a, p, h, start, y);
458 else
459 draw_range(a, p, h, start, end, y, pp,
460 row_title_width);
461}
462
463void DecodeTrace::draw_annotation_block(
464 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
465 int y, size_t base_colour) const
466{
467 using namespace pv::data::decode;
468
469 if (annotations.empty())
470 return;
471
472 double samples_per_pixel, pixels_offset;
473 tie(pixels_offset, samples_per_pixel) =
474 get_pixels_offset_samples_per_pixel();
475
476 const double start = annotations.front().start_sample() /
477 samples_per_pixel - pixels_offset;
478 const double end = annotations.back().end_sample() /
479 samples_per_pixel - pixels_offset;
480
481 const double top = y + .5 - h / 2;
482 const double bottom = y + .5 + h / 2;
483
484 const size_t colour = (base_colour + annotations.front().format()) %
485 countof(Colours);
486
487 // Check if all annotations are of the same type (i.e. we can use one color)
488 // or if we should use a neutral color (i.e. gray)
489 const int format = annotations.front().format();
490 const bool single_format = std::all_of(
491 annotations.begin(), annotations.end(),
492 [&](const Annotation &a) { return a.format() == format; });
493
494 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
495 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
496 Qt::Dense4Pattern));
497 p.drawRoundedRect(
498 QRectF(start, top, end - start, bottom - top), h/4, h/4);
499}
500
501void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
502 int h, double x, int y) const
503{
504 const QString text = a.annotations().empty() ?
505 QString() : a.annotations().back();
506 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
507 0.0) + h;
508 const QRectF rect(x - w / 2, y - h / 2, w, h);
509
510 p.drawRoundedRect(rect, h / 2, h / 2);
511
512 p.setPen(Qt::black);
513 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
514}
515
516void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
517 int h, double start, double end, int y, const ViewItemPaintParams &pp,
518 int row_title_width) const
519{
520 const double top = y + .5 - h / 2;
521 const double bottom = y + .5 + h / 2;
522 const vector<QString> annotations = a.annotations();
523
524 // If the two ends are within 1 pixel, draw a vertical line
525 if (start + 1.0 > end) {
526 p.drawLine(QPointF(start, top), QPointF(start, bottom));
527 return;
528 }
529
530 const double cap_width = min((end - start) / 4, EndCapWidth);
531
532 QPointF pts[] = {
533 QPointF(start, y + .5f),
534 QPointF(start + cap_width, top),
535 QPointF(end - cap_width, top),
536 QPointF(end, y + .5f),
537 QPointF(end - cap_width, bottom),
538 QPointF(start + cap_width, bottom)
539 };
540
541 p.drawConvexPolygon(pts, countof(pts));
542
543 if (annotations.empty())
544 return;
545
546 const int ann_start = start + cap_width;
547 const int ann_end = end - cap_width;
548
549 const int real_start = std::max(ann_start, pp.left() + row_title_width);
550 const int real_end = std::min(ann_end, pp.right());
551 const int real_width = real_end - real_start;
552
553 QRectF rect(real_start, y - h / 2, real_width, h);
554 if (rect.width() <= 4)
555 return;
556
557 p.setPen(Qt::black);
558
559 // Try to find an annotation that will fit
560 QString best_annotation;
561 int best_width = 0;
562
563 for (const QString &a : annotations) {
564 const int w = p.boundingRect(QRectF(), 0, a).width();
565 if (w <= rect.width() && w > best_width)
566 best_annotation = a, best_width = w;
567 }
568
569 if (best_annotation.isEmpty())
570 best_annotation = annotations.back();
571
572 // If not ellide the last in the list
573 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
574 best_annotation, Qt::ElideRight, rect.width()));
575}
576
577void DecodeTrace::draw_error(QPainter &p, const QString &message,
578 const ViewItemPaintParams &pp)
579{
580 const int y = get_visual_y();
581
582 p.setPen(ErrorBgColour.darker());
583 p.setBrush(ErrorBgColour);
584
585 const QRectF bounding_rect =
586 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
587 const QRectF text_rect = p.boundingRect(bounding_rect,
588 Qt::AlignCenter, message);
589 const float r = text_rect.height() / 4;
590
591 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
592 Qt::AbsoluteSize);
593
594 p.setPen(Qt::black);
595 p.drawText(text_rect, message);
596}
597
598void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
599 int right) const
600{
601 using namespace pv::data;
602 using pv::data::decode::Decoder;
603
604 double samples_per_pixel, pixels_offset;
605
606 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
607 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 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
662 base_->decoder_stack();
663
664 assert(owner_);
665 assert(decoder_stack);
666
667 const View *view = owner_->view();
668 assert(view);
669
670 const double scale = view->scale();
671 assert(scale > 0);
672
673 const double pixels_offset =
674 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
675
676 double samplerate = decoder_stack->samplerate();
677
678 // Show sample rate as 1Hz when it is unknown
679 if (samplerate == 0.0)
680 samplerate = 1.0;
681
682 return make_pair(pixels_offset, samplerate * scale);
683}
684
685pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
686 int x_start, int x_end) const
687{
688 double samples_per_pixel, pixels_offset;
689 tie(pixels_offset, samples_per_pixel) =
690 get_pixels_offset_samples_per_pixel();
691
692 const uint64_t start = (uint64_t)max(
693 (x_start + pixels_offset) * samples_per_pixel, 0.0);
694 const uint64_t end = (uint64_t)max(
695 (x_end + pixels_offset) * samples_per_pixel, 0.0);
696
697 return make_pair(start, end);
698}
699
700int DecodeTrace::get_row_at_point(const QPoint &point)
701{
702 if (!row_height_)
703 return -1;
704
705 const int y = (point.y() - get_visual_y() + row_height_ / 2);
706
707 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
708 if (y < 0)
709 return -1;
710
711 const int row = y / row_height_;
712
713 if (row >= (int)visible_rows_.size())
714 return -1;
715
716 return row;
717}
718
719const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
720{
721 using namespace pv::data::decode;
722
723 if (!enabled())
724 return QString();
725
726 const pair<uint64_t, uint64_t> sample_range =
727 get_sample_range(point.x(), point.x() + 1);
728 const int row = get_row_at_point(point);
729 if (row < 0)
730 return QString();
731
732 vector<pv::data::decode::Annotation> annotations;
733
734 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
735 base_->decoder_stack();
736
737 assert(decoder_stack);
738 decoder_stack->get_annotation_subset(annotations, visible_rows_[row],
739 sample_range.first, sample_range.second);
740
741 return (annotations.empty()) ?
742 QString() : annotations[0].annotations().front();
743}
744
745void DecodeTrace::hover_point_changed()
746{
747 assert(owner_);
748
749 const View *const view = owner_->view();
750 assert(view);
751
752 QPoint hp = view->hover_point();
753 QString ann = get_annotation_at_point(hp);
754
755 assert(view);
756
757 if (!row_height_ || ann.isEmpty()) {
758 QToolTip::hideText();
759 return;
760 }
761
762 const int hover_row = get_row_at_point(hp);
763
764 QFontMetrics m(QToolTip::font());
765 const QRect text_size = m.boundingRect(QRect(), 0, ann);
766
767 // This is OS-specific and unfortunately we can't query it, so
768 // use an approximation to at least try to minimize the error.
769 const int padding = 8;
770
771 // Make sure the tool tip doesn't overlap with the mouse cursor.
772 // If it did, the tool tip would constantly hide and re-appear.
773 // We also push it up by one row so that it appears above the
774 // decode trace, not below.
775 hp.setX(hp.x() - (text_size.width() / 2) - padding);
776
777 hp.setY(get_visual_y() - (row_height_ / 2) +
778 (hover_row * row_height_) -
779 row_height_ - text_size.height() - padding);
780
781 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
782}
783
784void DecodeTrace::create_decoder_form(int index,
785 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
786 QFormLayout *form)
787{
788 const GSList *l;
789
790 assert(dec);
791 const srd_decoder *const decoder = dec->decoder();
792 assert(decoder);
793
794 const bool decoder_deletable = index > 0;
795
796 pv::widgets::DecoderGroupBox *const group =
797 new pv::widgets::DecoderGroupBox(
798 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
799 group->set_decoder_visible(dec->shown());
800
801 if (decoder_deletable) {
802 delete_mapper_.setMapping(group, index);
803 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
804 }
805
806 show_hide_mapper_.setMapping(group, index);
807 connect(group, SIGNAL(show_hide_decoder()),
808 &show_hide_mapper_, SLOT(map()));
809
810 QFormLayout *const decoder_form = new QFormLayout;
811 group->add_layout(decoder_form);
812
813 // Add the mandatory channels
814 for (l = decoder->channels; l; l = l->next) {
815 const struct srd_channel *const pdch =
816 (struct srd_channel *)l->data;
817 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
818 connect(combo, SIGNAL(currentIndexChanged(int)),
819 this, SLOT(on_channel_selected(int)));
820 decoder_form->addRow(tr("<b>%1</b> (%2) *")
821 .arg(QString::fromUtf8(pdch->name),
822 QString::fromUtf8(pdch->desc)), combo);
823
824 const ChannelSelector s = {combo, dec, pdch};
825 channel_selectors_.push_back(s);
826 }
827
828 // Add the optional channels
829 for (l = decoder->opt_channels; l; l = l->next) {
830 const struct srd_channel *const pdch =
831 (struct srd_channel *)l->data;
832 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
833 connect(combo, SIGNAL(currentIndexChanged(int)),
834 this, SLOT(on_channel_selected(int)));
835 decoder_form->addRow(tr("<b>%1</b> (%2)")
836 .arg(QString::fromUtf8(pdch->name),
837 QString::fromUtf8(pdch->desc)), combo);
838
839 const ChannelSelector s = {combo, dec, pdch};
840 channel_selectors_.push_back(s);
841 }
842
843 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
844 base_->decoder_stack();
845
846 // Add the options
847 shared_ptr<binding::Decoder> binding(
848 new binding::Decoder(decoder_stack, dec));
849 binding->add_properties_to_form(decoder_form, true);
850
851 bindings_.push_back(binding);
852
853 form->addRow(group);
854 decoder_forms_.push_back(group);
855}
856
857QComboBox* DecodeTrace::create_channel_selector(
858 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
859 const srd_channel *const pdch)
860{
861 assert(dec);
862
863 const auto sigs(session_.signalbases());
864
865 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
866 std::sort(sig_list.begin(), sig_list.end(),
867 [](const shared_ptr<data::SignalBase> &a,
868 const shared_ptr<data::SignalBase> &b) {
869 return strnatcasecmp(a->name().toStdString(),
870 b->name().toStdString()) < 0; });
871
872 const auto channel_iter = dec->channels().find(pdch);
873
874 QComboBox *selector = new QComboBox(parent);
875
876 selector->addItem("-", qVariantFromValue((void*)nullptr));
877
878 if (channel_iter == dec->channels().end())
879 selector->setCurrentIndex(0);
880
881 for (const shared_ptr<data::SignalBase> &b : sig_list) {
882 assert(b);
883 if (b->type() == sigrok::ChannelType::LOGIC && b->enabled()) {
884 selector->addItem(b->name(),
885 qVariantFromValue((void*)b.get()));
886
887 if (channel_iter != dec->channels().end() &&
888 (*channel_iter).second == b)
889 selector->setCurrentIndex(
890 selector->count() - 1);
891 }
892 }
893
894 return selector;
895}
896
897void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
898{
899 assert(dec);
900
901 map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
902
903 const unordered_set< shared_ptr<data::SignalBase> >
904 sigs(session_.signalbases());
905
906 for (const ChannelSelector &s : channel_selectors_) {
907 if (s.decoder_ != dec)
908 break;
909
910 const data::SignalBase *const selection =
911 (data::SignalBase*)s.combo_->itemData(
912 s.combo_->currentIndex()).value<void*>();
913
914 for (shared_ptr<data::SignalBase> sig : sigs)
915 if (sig.get() == selection) {
916 channel_map[s.pdch_] = sig;
917 break;
918 }
919 }
920
921 dec->set_channels(channel_map);
922}
923
924void DecodeTrace::commit_channels()
925{
926 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
927 base_->decoder_stack();
928
929 assert(decoder_stack);
930 for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
931 commit_decoder_channels(dec);
932
933 decoder_stack->begin_decode();
934}
935
936void DecodeTrace::on_new_decode_data()
937{
938 if (owner_)
939 owner_->row_item_appearance_changed(false, true);
940}
941
942void DecodeTrace::delete_pressed()
943{
944 on_delete();
945}
946
947void DecodeTrace::on_delete()
948{
949 session_.remove_decode_signal(base_);
950}
951
952void DecodeTrace::on_channel_selected(int)
953{
954 commit_channels();
955}
956
957void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
958{
959 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
960 base_->decoder_stack();
961
962 assert(decoder);
963 assert(decoder_stack);
964 decoder_stack->push(shared_ptr<data::decode::Decoder>(
965 new data::decode::Decoder(decoder)));
966 decoder_stack->begin_decode();
967
968 create_popup_form();
969}
970
971void DecodeTrace::on_delete_decoder(int index)
972{
973 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
974 base_->decoder_stack();
975
976 decoder_stack->remove(index);
977
978 // Update the popup
979 create_popup_form();
980
981 decoder_stack->begin_decode();
982}
983
984void DecodeTrace::on_show_hide_decoder(int index)
985{
986 using pv::data::decode::Decoder;
987
988 std::shared_ptr<pv::data::DecoderStack> decoder_stack =
989 base_->decoder_stack();
990
991 const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
992
993 // Find the decoder in the stack
994 auto iter = stack.cbegin();
995 for (int i = 0; i < index; i++, iter++)
996 assert(iter != stack.end());
997
998 shared_ptr<Decoder> dec = *iter;
999 assert(dec);
1000
1001 const bool show = !dec->shown();
1002 dec->show(show);
1003
1004 assert(index < (int)decoder_forms_.size());
1005 decoder_forms_[index]->set_decoder_visible(show);
1006
1007 if (owner_)
1008 owner_->row_item_appearance_changed(false, true);
1009}
1010
1011} // namespace TraceView
1012} // namespace views
1013} // namespace pv