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