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