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