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