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