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