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