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