]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/decodetrace.cpp
DecodeTrace: With a rounded rect
[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 prev_ann_pos = 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 // Gather all annotations that form a visual "block" and draw them as such
347 for (const Annotation &a : annotations) {
348
349 const int end = a.end_sample() / samples_per_pixel - pixels_offset;
350 const int delta = end - prev_ann_pos;
351
352 // Some annotations are in reverse order, so we cannot
353 // simply check for delta > 1
354 if (abs(delta) > 1) {
355 // Block was broken, draw it
356 if (a_block.size() == 1)
357 draw_annotation(a_block.front(), p, h, pp, y, base_colour);
358 else
359 if (a_block.size() > 0)
360 draw_annotation_block(a_block, p, h, y, base_colour);
361
362 a_block.clear();
363 }
364
365 a_block.push_back(a);
366 prev_ann_pos = end;
367 }
368
369 if (a_block.size() == 1)
370 draw_annotation(a_block.front(), p, h, pp, y, base_colour);
371 else
372 draw_annotation_block(a_block, p, h, y, base_colour);
373}
374
375void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
376 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
377 size_t base_colour) const
378{
379 double samples_per_pixel, pixels_offset;
380 tie(pixels_offset, samples_per_pixel) =
381 get_pixels_offset_samples_per_pixel();
382
383 const double start = a.start_sample() / samples_per_pixel -
384 pixels_offset;
385 const double end = a.end_sample() / samples_per_pixel -
386 pixels_offset;
387
388 const size_t colour = (base_colour + a.format()) % countof(Colours);
389 const QColor &fill = Colours[colour];
390 const QColor &outline = OutlineColours[colour];
391
392 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
393 return;
394
395 if (a.start_sample() == a.end_sample())
396 draw_instant(a, p, fill, outline, h, start, y);
397 else
398 draw_range(a, p, fill, outline, h, start, end, y);
399}
400
401void DecodeTrace::draw_annotation_block(
402 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
403 int y, size_t base_colour) const
404{
405 using namespace pv::data::decode;
406
407 double samples_per_pixel, pixels_offset;
408 tie(pixels_offset, samples_per_pixel) =
409 get_pixels_offset_samples_per_pixel();
410
411 const double start = annotations.front().start_sample() /
412 samples_per_pixel - pixels_offset;
413 const double end = annotations.back().end_sample() /
414 samples_per_pixel - pixels_offset;
415
416 const double top = y + .5 - h / 2;
417 const double bottom = y + .5 + h / 2;
418
419 const size_t colour = (base_colour + annotations.front().format()) %
420 countof(Colours);
421
422 // Check if all annotations are of the same type (i.e. we can use one color)
423 // or if we should use a neutral color (i.e. gray)
424 const int format = annotations.front().format();
425 const bool single_format = std::all_of(
426 annotations.begin(), annotations.end(),
427 [&](const Annotation &a) { return a.format() == format; });
428
429 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
430 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
431 Qt::Dense4Pattern));
432 p.drawRoundedRect(
433 QRectF(start, top, end - start, bottom - top), h/4, h/4);
434}
435
436void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
437 QColor fill, QColor outline, int h, double x, int y) const
438{
439 const QString text = a.annotations().empty() ?
440 QString() : a.annotations().back();
441 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
442 0.0) + h;
443 const QRectF rect(x - w / 2, y - h / 2, w, h);
444
445 p.setPen(outline);
446 p.setBrush(fill);
447 p.drawRoundedRect(rect, h / 2, h / 2);
448
449 p.setPen(Qt::black);
450 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
451}
452
453void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
454 QColor fill, QColor outline, int h, double start,
455 double end, int y) const
456{
457 const double top = y + .5 - h / 2;
458 const double bottom = y + .5 + h / 2;
459 const vector<QString> annotations = a.annotations();
460
461 p.setPen(outline);
462 p.setBrush(fill);
463
464 // If the two ends are within 1 pixel, draw a vertical line
465 if (start + 1.0 > end) {
466 p.drawLine(QPointF(start, top), QPointF(start, bottom));
467 return;
468 }
469
470 const double cap_width = min((end - start) / 4, EndCapWidth);
471
472 QPointF pts[] = {
473 QPointF(start, y + .5f),
474 QPointF(start + cap_width, top),
475 QPointF(end - cap_width, top),
476 QPointF(end, y + .5f),
477 QPointF(end - cap_width, bottom),
478 QPointF(start + cap_width, bottom)
479 };
480
481 p.drawConvexPolygon(pts, countof(pts));
482
483 if (annotations.empty())
484 return;
485
486 QRectF rect(start + cap_width, y - h / 2,
487 end - start - cap_width * 2, h);
488 if (rect.width() <= 4)
489 return;
490
491 p.setPen(Qt::black);
492
493 // Try to find an annotation that will fit
494 QString best_annotation;
495 int best_width = 0;
496
497 for (const QString &a : annotations) {
498 const int w = p.boundingRect(QRectF(), 0, a).width();
499 if (w <= rect.width() && w > best_width)
500 best_annotation = a, best_width = w;
501 }
502
503 if (best_annotation.isEmpty())
504 best_annotation = annotations.back();
505
506 // If not ellide the last in the list
507 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
508 best_annotation, Qt::ElideRight, rect.width()));
509}
510
511void DecodeTrace::draw_error(QPainter &p, const QString &message,
512 const ViewItemPaintParams &pp)
513{
514 const int y = get_visual_y();
515
516 p.setPen(ErrorBgColour.darker());
517 p.setBrush(ErrorBgColour);
518
519 const QRectF bounding_rect =
520 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
521 const QRectF text_rect = p.boundingRect(bounding_rect,
522 Qt::AlignCenter, message);
523 const float r = text_rect.height() / 4;
524
525 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
526 Qt::AbsoluteSize);
527
528 p.setPen(Qt::black);
529 p.drawText(text_rect, message);
530}
531
532void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
533 int right) const
534{
535 using namespace pv::data;
536 using pv::data::decode::Decoder;
537
538 double samples_per_pixel, pixels_offset;
539
540 assert(decoder_stack_);
541
542 shared_ptr<Logic> data;
543 shared_ptr<LogicSignal> logic_signal;
544
545 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
546
547 // We get the logic data of the first channel in the list.
548 // This works because we are currently assuming all
549 // LogicSignals have the same data/segment
550 for (const shared_ptr<Decoder> &dec : stack)
551 if (dec && !dec->channels().empty() &&
552 ((logic_signal = (*dec->channels().begin()).second)) &&
553 ((data = logic_signal->logic_data())))
554 break;
555
556 if (!data || data->logic_segments().empty())
557 return;
558
559 const shared_ptr<LogicSegment> segment =
560 data->logic_segments().front();
561 assert(segment);
562 const int64_t sample_count = (int64_t)segment->get_sample_count();
563 if (sample_count == 0)
564 return;
565
566 const int64_t samples_decoded = decoder_stack_->samples_decoded();
567 if (sample_count == samples_decoded)
568 return;
569
570 const int y = get_visual_y();
571
572 tie(pixels_offset, samples_per_pixel) =
573 get_pixels_offset_samples_per_pixel();
574
575 const double start = max(samples_decoded /
576 samples_per_pixel - pixels_offset, left - 1.0);
577 const double end = min(sample_count / samples_per_pixel -
578 pixels_offset, right + 1.0);
579 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
580
581 p.setPen(QPen(Qt::NoPen));
582 p.setBrush(Qt::white);
583 p.drawRect(no_decode_rect);
584
585 p.setPen(NoDecodeColour);
586 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
587 p.drawRect(no_decode_rect);
588}
589
590pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
591{
592 assert(owner_);
593 assert(decoder_stack_);
594
595 const View *view = owner_->view();
596 assert(view);
597
598 const double scale = view->scale();
599 assert(scale > 0);
600
601 const double pixels_offset =
602 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
603
604 double samplerate = decoder_stack_->samplerate();
605
606 // Show sample rate as 1Hz when it is unknown
607 if (samplerate == 0.0)
608 samplerate = 1.0;
609
610 return make_pair(pixels_offset, samplerate * scale);
611}
612
613pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
614 int x_start, int x_end) const
615{
616 double samples_per_pixel, pixels_offset;
617 tie(pixels_offset, samples_per_pixel) =
618 get_pixels_offset_samples_per_pixel();
619
620 const uint64_t start = (uint64_t)max(
621 (x_start + pixels_offset) * samples_per_pixel, 0.0);
622 const uint64_t end = (uint64_t)max(
623 (x_end + pixels_offset) * samples_per_pixel, 0.0);
624
625 return make_pair(start, end);
626}
627
628int DecodeTrace::get_row_at_point(const QPoint &point)
629{
630 if (!row_height_)
631 return -1;
632
633 const int y = (point.y() - get_visual_y() + row_height_ / 2);
634
635 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
636 if (y < 0)
637 return -1;
638
639 const int row = y / row_height_;
640
641 if (row >= (int)visible_rows_.size())
642 return -1;
643
644 return row;
645}
646
647const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
648{
649 using namespace pv::data::decode;
650
651 if (!enabled())
652 return QString();
653
654 const pair<uint64_t, uint64_t> sample_range =
655 get_sample_range(point.x(), point.x() + 1);
656 const int row = get_row_at_point(point);
657 if (row < 0)
658 return QString();
659
660 vector<pv::data::decode::Annotation> annotations;
661
662 assert(decoder_stack_);
663 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
664 sample_range.first, sample_range.second);
665
666 return (annotations.empty()) ?
667 QString() : annotations[0].annotations().front();
668}
669
670void DecodeTrace::hover_point_changed()
671{
672 assert(owner_);
673
674 const View *const view = owner_->view();
675 assert(view);
676
677 QPoint hp = view->hover_point();
678 QString ann = get_annotation_at_point(hp);
679
680 assert(view);
681
682 if (!row_height_ || ann.isEmpty()) {
683 QToolTip::hideText();
684 return;
685 }
686
687 const int hover_row = get_row_at_point(hp);
688
689 QFontMetrics m(QToolTip::font());
690 const QRect text_size = m.boundingRect(QRect(), 0, ann);
691
692 // This is OS-specific and unfortunately we can't query it, so
693 // use an approximation to at least try to minimize the error.
694 const int padding = 8;
695
696 // Make sure the tool tip doesn't overlap with the mouse cursor.
697 // If it did, the tool tip would constantly hide and re-appear.
698 // We also push it up by one row so that it appears above the
699 // decode trace, not below.
700 hp.setX(hp.x() - (text_size.width() / 2) - padding);
701
702 hp.setY(get_visual_y() - (row_height_ / 2) +
703 (hover_row * row_height_) -
704 row_height_ - text_size.height() - padding);
705
706 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
707}
708
709void DecodeTrace::create_decoder_form(int index,
710 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
711 QFormLayout *form)
712{
713 const GSList *l;
714
715 assert(dec);
716 const srd_decoder *const decoder = dec->decoder();
717 assert(decoder);
718
719 const bool decoder_deletable = index > 0;
720
721 pv::widgets::DecoderGroupBox *const group =
722 new pv::widgets::DecoderGroupBox(
723 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
724 group->set_decoder_visible(dec->shown());
725
726 if (decoder_deletable) {
727 delete_mapper_.setMapping(group, index);
728 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
729 }
730
731 show_hide_mapper_.setMapping(group, index);
732 connect(group, SIGNAL(show_hide_decoder()),
733 &show_hide_mapper_, SLOT(map()));
734
735 QFormLayout *const decoder_form = new QFormLayout;
736 group->add_layout(decoder_form);
737
738 // Add the mandatory channels
739 for (l = decoder->channels; l; l = l->next) {
740 const struct srd_channel *const pdch =
741 (struct srd_channel *)l->data;
742 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
743 connect(combo, SIGNAL(currentIndexChanged(int)),
744 this, SLOT(on_channel_selected(int)));
745 decoder_form->addRow(tr("<b>%1</b> (%2) *")
746 .arg(QString::fromUtf8(pdch->name))
747 .arg(QString::fromUtf8(pdch->desc)), combo);
748
749 const ChannelSelector s = {combo, dec, pdch};
750 channel_selectors_.push_back(s);
751 }
752
753 // Add the optional channels
754 for (l = decoder->opt_channels; l; l = l->next) {
755 const struct srd_channel *const pdch =
756 (struct srd_channel *)l->data;
757 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
758 connect(combo, SIGNAL(currentIndexChanged(int)),
759 this, SLOT(on_channel_selected(int)));
760 decoder_form->addRow(tr("<b>%1</b> (%2)")
761 .arg(QString::fromUtf8(pdch->name))
762 .arg(QString::fromUtf8(pdch->desc)), combo);
763
764 const ChannelSelector s = {combo, dec, pdch};
765 channel_selectors_.push_back(s);
766 }
767
768 // Add the options
769 shared_ptr<binding::Decoder> binding(
770 new binding::Decoder(decoder_stack_, dec));
771 binding->add_properties_to_form(decoder_form, true);
772
773 bindings_.push_back(binding);
774
775 form->addRow(group);
776 decoder_forms_.push_back(group);
777}
778
779QComboBox* DecodeTrace::create_channel_selector(
780 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
781 const srd_channel *const pdch)
782{
783 assert(dec);
784
785 const auto sigs(session_.signals());
786
787 vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
788 std::sort(sig_list.begin(), sig_list.end(),
789 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
790 return a->name().compare(b->name()) < 0; });
791
792 assert(decoder_stack_);
793 const auto channel_iter = dec->channels().find(pdch);
794
795 QComboBox *selector = new QComboBox(parent);
796
797 selector->addItem("-", qVariantFromValue((void*)nullptr));
798
799 if (channel_iter == dec->channels().end())
800 selector->setCurrentIndex(0);
801
802 for (const shared_ptr<view::Signal> &s : sig_list) {
803 assert(s);
804 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled()) {
805 selector->addItem(s->name(),
806 qVariantFromValue((void*)s.get()));
807
808 if (channel_iter != dec->channels().end() &&
809 (*channel_iter).second == s)
810 selector->setCurrentIndex(
811 selector->count() - 1);
812 }
813 }
814
815 return selector;
816}
817
818void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
819{
820 assert(dec);
821
822 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
823
824 const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
825
826 for (const ChannelSelector &s : channel_selectors_) {
827 if (s.decoder_ != dec)
828 break;
829
830 const LogicSignal *const selection =
831 (LogicSignal*)s.combo_->itemData(
832 s.combo_->currentIndex()).value<void*>();
833
834 for (shared_ptr<Signal> sig : sigs)
835 if (sig.get() == selection) {
836 channel_map[s.pdch_] =
837 dynamic_pointer_cast<LogicSignal>(sig);
838 break;
839 }
840 }
841
842 dec->set_channels(channel_map);
843}
844
845void DecodeTrace::commit_channels()
846{
847 assert(decoder_stack_);
848 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
849 commit_decoder_channels(dec);
850
851 decoder_stack_->begin_decode();
852}
853
854void DecodeTrace::on_new_decode_data()
855{
856 if (owner_)
857 owner_->row_item_appearance_changed(false, true);
858}
859
860void DecodeTrace::delete_pressed()
861{
862 on_delete();
863}
864
865void DecodeTrace::on_delete()
866{
867 session_.remove_decode_signal(this);
868}
869
870void DecodeTrace::on_channel_selected(int)
871{
872 commit_channels();
873}
874
875void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
876{
877 assert(decoder);
878 assert(decoder_stack_);
879 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
880 new data::decode::Decoder(decoder)));
881 decoder_stack_->begin_decode();
882
883 create_popup_form();
884}
885
886void DecodeTrace::on_delete_decoder(int index)
887{
888 decoder_stack_->remove(index);
889
890 // Update the popup
891 create_popup_form();
892
893 decoder_stack_->begin_decode();
894}
895
896void DecodeTrace::on_show_hide_decoder(int index)
897{
898 using pv::data::decode::Decoder;
899
900 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
901
902 // Find the decoder in the stack
903 auto iter = stack.cbegin();
904 for (int i = 0; i < index; i++, iter++)
905 assert(iter != stack.end());
906
907 shared_ptr<Decoder> dec = *iter;
908 assert(dec);
909
910 const bool show = !dec->shown();
911 dec->show(show);
912
913 assert(index < (int)decoder_forms_.size());
914 decoder_forms_[index]->set_decoder_visible(show);
915
916 if (owner_)
917 owner_->row_item_appearance_changed(false, true);
918}
919
920} // namespace view
921} // namespace pv