]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/decodetrace.cpp
Fix 745 by improving the draw_annotations() block drawing algo
[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);
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
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 QRectF rect(start + cap_width, y - h / 2,
520 end - start - cap_width * 2, h);
521 if (rect.width() <= 4)
522 return;
523
524 p.setPen(Qt::black);
525
526 // Try to find an annotation that will fit
527 QString best_annotation;
528 int best_width = 0;
529
530 for (const QString &a : annotations) {
531 const int w = p.boundingRect(QRectF(), 0, a).width();
532 if (w <= rect.width() && w > best_width)
533 best_annotation = a, best_width = w;
534 }
535
536 if (best_annotation.isEmpty())
537 best_annotation = annotations.back();
538
539 // If not ellide the last in the list
540 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
541 best_annotation, Qt::ElideRight, rect.width()));
542}
543
544void DecodeTrace::draw_error(QPainter &p, const QString &message,
545 const ViewItemPaintParams &pp)
546{
547 const int y = get_visual_y();
548
549 p.setPen(ErrorBgColour.darker());
550 p.setBrush(ErrorBgColour);
551
552 const QRectF bounding_rect =
553 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
554 const QRectF text_rect = p.boundingRect(bounding_rect,
555 Qt::AlignCenter, message);
556 const float r = text_rect.height() / 4;
557
558 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
559 Qt::AbsoluteSize);
560
561 p.setPen(Qt::black);
562 p.drawText(text_rect, message);
563}
564
565void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
566 int right) const
567{
568 using namespace pv::data;
569 using pv::data::decode::Decoder;
570
571 double samples_per_pixel, pixels_offset;
572
573 assert(decoder_stack_);
574
575 shared_ptr<Logic> data;
576 shared_ptr<LogicSignal> logic_signal;
577
578 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
579
580 // We get the logic data of the first channel in the list.
581 // This works because we are currently assuming all
582 // LogicSignals have the same data/segment
583 for (const shared_ptr<Decoder> &dec : stack)
584 if (dec && !dec->channels().empty() &&
585 ((logic_signal = (*dec->channels().begin()).second)) &&
586 ((data = logic_signal->logic_data())))
587 break;
588
589 if (!data || data->logic_segments().empty())
590 return;
591
592 const shared_ptr<LogicSegment> segment =
593 data->logic_segments().front();
594 assert(segment);
595 const int64_t sample_count = (int64_t)segment->get_sample_count();
596 if (sample_count == 0)
597 return;
598
599 const int64_t samples_decoded = decoder_stack_->samples_decoded();
600 if (sample_count == samples_decoded)
601 return;
602
603 const int y = get_visual_y();
604
605 tie(pixels_offset, samples_per_pixel) =
606 get_pixels_offset_samples_per_pixel();
607
608 const double start = max(samples_decoded /
609 samples_per_pixel - pixels_offset, left - 1.0);
610 const double end = min(sample_count / samples_per_pixel -
611 pixels_offset, right + 1.0);
612 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
613
614 p.setPen(QPen(Qt::NoPen));
615 p.setBrush(Qt::white);
616 p.drawRect(no_decode_rect);
617
618 p.setPen(NoDecodeColour);
619 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
620 p.drawRect(no_decode_rect);
621}
622
623pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
624{
625 assert(owner_);
626 assert(decoder_stack_);
627
628 const View *view = owner_->view();
629 assert(view);
630
631 const double scale = view->scale();
632 assert(scale > 0);
633
634 const double pixels_offset =
635 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
636
637 double samplerate = decoder_stack_->samplerate();
638
639 // Show sample rate as 1Hz when it is unknown
640 if (samplerate == 0.0)
641 samplerate = 1.0;
642
643 return make_pair(pixels_offset, samplerate * scale);
644}
645
646pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
647 int x_start, int x_end) const
648{
649 double samples_per_pixel, pixels_offset;
650 tie(pixels_offset, samples_per_pixel) =
651 get_pixels_offset_samples_per_pixel();
652
653 const uint64_t start = (uint64_t)max(
654 (x_start + pixels_offset) * samples_per_pixel, 0.0);
655 const uint64_t end = (uint64_t)max(
656 (x_end + pixels_offset) * samples_per_pixel, 0.0);
657
658 return make_pair(start, end);
659}
660
661int DecodeTrace::get_row_at_point(const QPoint &point)
662{
663 if (!row_height_)
664 return -1;
665
666 const int y = (point.y() - get_visual_y() + row_height_ / 2);
667
668 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
669 if (y < 0)
670 return -1;
671
672 const int row = y / row_height_;
673
674 if (row >= (int)visible_rows_.size())
675 return -1;
676
677 return row;
678}
679
680const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
681{
682 using namespace pv::data::decode;
683
684 if (!enabled())
685 return QString();
686
687 const pair<uint64_t, uint64_t> sample_range =
688 get_sample_range(point.x(), point.x() + 1);
689 const int row = get_row_at_point(point);
690 if (row < 0)
691 return QString();
692
693 vector<pv::data::decode::Annotation> annotations;
694
695 assert(decoder_stack_);
696 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
697 sample_range.first, sample_range.second);
698
699 return (annotations.empty()) ?
700 QString() : annotations[0].annotations().front();
701}
702
703void DecodeTrace::hover_point_changed()
704{
705 assert(owner_);
706
707 const View *const view = owner_->view();
708 assert(view);
709
710 QPoint hp = view->hover_point();
711 QString ann = get_annotation_at_point(hp);
712
713 assert(view);
714
715 if (!row_height_ || ann.isEmpty()) {
716 QToolTip::hideText();
717 return;
718 }
719
720 const int hover_row = get_row_at_point(hp);
721
722 QFontMetrics m(QToolTip::font());
723 const QRect text_size = m.boundingRect(QRect(), 0, ann);
724
725 // This is OS-specific and unfortunately we can't query it, so
726 // use an approximation to at least try to minimize the error.
727 const int padding = 8;
728
729 // Make sure the tool tip doesn't overlap with the mouse cursor.
730 // If it did, the tool tip would constantly hide and re-appear.
731 // We also push it up by one row so that it appears above the
732 // decode trace, not below.
733 hp.setX(hp.x() - (text_size.width() / 2) - padding);
734
735 hp.setY(get_visual_y() - (row_height_ / 2) +
736 (hover_row * row_height_) -
737 row_height_ - text_size.height() - padding);
738
739 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
740}
741
742void DecodeTrace::create_decoder_form(int index,
743 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
744 QFormLayout *form)
745{
746 const GSList *l;
747
748 assert(dec);
749 const srd_decoder *const decoder = dec->decoder();
750 assert(decoder);
751
752 const bool decoder_deletable = index > 0;
753
754 pv::widgets::DecoderGroupBox *const group =
755 new pv::widgets::DecoderGroupBox(
756 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
757 group->set_decoder_visible(dec->shown());
758
759 if (decoder_deletable) {
760 delete_mapper_.setMapping(group, index);
761 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
762 }
763
764 show_hide_mapper_.setMapping(group, index);
765 connect(group, SIGNAL(show_hide_decoder()),
766 &show_hide_mapper_, SLOT(map()));
767
768 QFormLayout *const decoder_form = new QFormLayout;
769 group->add_layout(decoder_form);
770
771 // Add the mandatory channels
772 for (l = decoder->channels; l; l = l->next) {
773 const struct srd_channel *const pdch =
774 (struct srd_channel *)l->data;
775 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
776 connect(combo, SIGNAL(currentIndexChanged(int)),
777 this, SLOT(on_channel_selected(int)));
778 decoder_form->addRow(tr("<b>%1</b> (%2) *")
779 .arg(QString::fromUtf8(pdch->name))
780 .arg(QString::fromUtf8(pdch->desc)), combo);
781
782 const ChannelSelector s = {combo, dec, pdch};
783 channel_selectors_.push_back(s);
784 }
785
786 // Add the optional channels
787 for (l = decoder->opt_channels; l; l = l->next) {
788 const struct srd_channel *const pdch =
789 (struct srd_channel *)l->data;
790 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
791 connect(combo, SIGNAL(currentIndexChanged(int)),
792 this, SLOT(on_channel_selected(int)));
793 decoder_form->addRow(tr("<b>%1</b> (%2)")
794 .arg(QString::fromUtf8(pdch->name))
795 .arg(QString::fromUtf8(pdch->desc)), combo);
796
797 const ChannelSelector s = {combo, dec, pdch};
798 channel_selectors_.push_back(s);
799 }
800
801 // Add the options
802 shared_ptr<binding::Decoder> binding(
803 new binding::Decoder(decoder_stack_, dec));
804 binding->add_properties_to_form(decoder_form, true);
805
806 bindings_.push_back(binding);
807
808 form->addRow(group);
809 decoder_forms_.push_back(group);
810}
811
812QComboBox* DecodeTrace::create_channel_selector(
813 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
814 const srd_channel *const pdch)
815{
816 assert(dec);
817
818 const auto sigs(session_.signals());
819
820 vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
821 std::sort(sig_list.begin(), sig_list.end(),
822 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
823 return a->name().compare(b->name()) < 0; });
824
825 assert(decoder_stack_);
826 const auto channel_iter = dec->channels().find(pdch);
827
828 QComboBox *selector = new QComboBox(parent);
829
830 selector->addItem("-", qVariantFromValue((void*)nullptr));
831
832 if (channel_iter == dec->channels().end())
833 selector->setCurrentIndex(0);
834
835 for (const shared_ptr<view::Signal> &s : sig_list) {
836 assert(s);
837 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled()) {
838 selector->addItem(s->name(),
839 qVariantFromValue((void*)s.get()));
840
841 if (channel_iter != dec->channels().end() &&
842 (*channel_iter).second == s)
843 selector->setCurrentIndex(
844 selector->count() - 1);
845 }
846 }
847
848 return selector;
849}
850
851void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
852{
853 assert(dec);
854
855 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
856
857 const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
858
859 for (const ChannelSelector &s : channel_selectors_) {
860 if (s.decoder_ != dec)
861 break;
862
863 const LogicSignal *const selection =
864 (LogicSignal*)s.combo_->itemData(
865 s.combo_->currentIndex()).value<void*>();
866
867 for (shared_ptr<Signal> sig : sigs)
868 if (sig.get() == selection) {
869 channel_map[s.pdch_] =
870 dynamic_pointer_cast<LogicSignal>(sig);
871 break;
872 }
873 }
874
875 dec->set_channels(channel_map);
876}
877
878void DecodeTrace::commit_channels()
879{
880 assert(decoder_stack_);
881 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
882 commit_decoder_channels(dec);
883
884 decoder_stack_->begin_decode();
885}
886
887void DecodeTrace::on_new_decode_data()
888{
889 if (owner_)
890 owner_->row_item_appearance_changed(false, true);
891}
892
893void DecodeTrace::delete_pressed()
894{
895 on_delete();
896}
897
898void DecodeTrace::on_delete()
899{
900 session_.remove_decode_signal(this);
901}
902
903void DecodeTrace::on_channel_selected(int)
904{
905 commit_channels();
906}
907
908void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
909{
910 assert(decoder);
911 assert(decoder_stack_);
912 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
913 new data::decode::Decoder(decoder)));
914 decoder_stack_->begin_decode();
915
916 create_popup_form();
917}
918
919void DecodeTrace::on_delete_decoder(int index)
920{
921 decoder_stack_->remove(index);
922
923 // Update the popup
924 create_popup_form();
925
926 decoder_stack_->begin_decode();
927}
928
929void DecodeTrace::on_show_hide_decoder(int index)
930{
931 using pv::data::decode::Decoder;
932
933 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
934
935 // Find the decoder in the stack
936 auto iter = stack.cbegin();
937 for (int i = 0; i < index; i++, iter++)
938 assert(iter != stack.end());
939
940 shared_ptr<Decoder> dec = *iter;
941 assert(dec);
942
943 const bool show = !dec->shown();
944 dec->show(show);
945
946 assert(index < (int)decoder_forms_.size());
947 decoder_forms_[index]->set_decoder_visible(show);
948
949 if (owner_)
950 owner_->row_item_appearance_changed(false, true);
951}
952
953} // namespace view
954} // namespace pv