]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
Fix #719 by calculating the decoder trace height dynamically
[pulseview.git] / pv / view / decodetrace.cpp
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
21 extern "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
58 using boost::shared_lock;
59 using boost::shared_mutex;
60 using std::dynamic_pointer_cast;
61 using std::list;
62 using std::lock_guard;
63 using std::make_pair;
64 using std::max;
65 using std::make_pair;
66 using std::map;
67 using std::min;
68 using std::pair;
69 using std::shared_ptr;
70 using std::tie;
71 using std::unordered_set;
72 using std::vector;
73
74 namespace pv {
75 namespace view {
76
77 const 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
84 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
85 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
86
87 const int DecodeTrace::ArrowSize = 4;
88 const double DecodeTrace::EndCapWidth = 5;
89 const int DecodeTrace::DrawPadding = 100;
90
91 const 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
110 const 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
129 DecodeTrace::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         delete_mapper_(this),
137         show_hide_mapper_(this)
138 {
139         assert(decoder_stack_);
140
141         set_colour(DecodeColours[index % countof(DecodeColours)]);
142
143         connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
144                 this, SLOT(on_new_decode_data()));
145         connect(&delete_mapper_, SIGNAL(mapped(int)),
146                 this, SLOT(on_delete_decoder(int)));
147         connect(&show_hide_mapper_, SIGNAL(mapped(int)),
148                 this, SLOT(on_show_hide_decoder(int)));
149 }
150
151 bool DecodeTrace::enabled() const
152 {
153         return true;
154 }
155
156 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
157 {
158         return decoder_stack_;
159 }
160
161 pair<int, int> DecodeTrace::v_extents() const
162 {
163         const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
164         const int rows = visible_rows_.size();
165
166         return make_pair(-row_height, row_height * rows);
167 }
168
169 void 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
175 void 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
227 void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
228 {
229         using namespace pv::data::decode;
230
231         assert(row_height_);
232
233         for (size_t i = 0; i < visible_rows_.size(); i++) {
234                 const int y = i * row_height_ + get_visual_y();
235
236                 p.setPen(QPen(Qt::NoPen));
237                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
238
239                 if (i != 0) {
240                         const QPointF points[] = {
241                                 QPointF(pp.left(), y - ArrowSize),
242                                 QPointF(pp.left() + ArrowSize, y),
243                                 QPointF(pp.left(), y + ArrowSize)
244                         };
245                         p.drawPolygon(points, countof(points));
246                 }
247
248                 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
249                         pp.right() - pp.left(), row_height_);
250                 const QString h(visible_rows_[i].title());
251                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
252                         Qt::TextDontClip;
253
254                 // Draw the outline
255                 p.setPen(QApplication::palette().color(QPalette::Base));
256                 for (int dx = -1; dx <= 1; dx++)
257                         for (int dy = -1; dy <= 1; dy++)
258                                 if (dx != 0 && dy != 0)
259                                         p.drawText(r.translated(dx, dy), f, h);
260
261                 // Draw the text
262                 p.setPen(QApplication::palette().color(QPalette::WindowText));
263                 p.drawText(r, f, h);
264         }
265 }
266
267 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
268 {
269         using pv::data::decode::Decoder;
270
271         assert(form);
272         assert(parent);
273         assert(decoder_stack_);
274
275         // Add the standard options
276         Trace::populate_popup_form(parent, form);
277
278         // Add the decoder options
279         bindings_.clear();
280         channel_selectors_.clear();
281         decoder_forms_.clear();
282
283         const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
284
285         if (stack.empty()) {
286                 QLabel *const l = new QLabel(
287                         tr("<p><i>No decoders in the stack</i></p>"));
288                 l->setAlignment(Qt::AlignCenter);
289                 form->addRow(l);
290         } else {
291                 auto iter = stack.cbegin();
292                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
293                         shared_ptr<Decoder> dec(*iter);
294                         create_decoder_form(i, dec, parent, form);
295                 }
296
297                 form->addRow(new QLabel(
298                         tr("<i>* Required channels</i>"), parent));
299         }
300
301         // Add stacking button
302         pv::widgets::DecoderMenu *const decoder_menu =
303                 new pv::widgets::DecoderMenu(parent);
304         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
305                 this, SLOT(on_stack_decoder(srd_decoder*)));
306
307         QPushButton *const stack_button =
308                 new QPushButton(tr("Stack Decoder"), parent);
309         stack_button->setMenu(decoder_menu);
310
311         QHBoxLayout *stack_button_box = new QHBoxLayout;
312         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
313         form->addRow(stack_button_box);
314 }
315
316 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
317 {
318         QMenu *const menu = Trace::create_context_menu(parent);
319
320         menu->addSeparator();
321
322         QAction *const del = new QAction(tr("Delete"), this);
323         del->setShortcuts(QKeySequence::Delete);
324         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
325         menu->addAction(del);
326
327         return menu;
328 }
329
330 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
331                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
332                 size_t base_colour)
333 {
334         using namespace pv::data::decode;
335
336         vector<Annotation> a_block;
337         int prev_ann_pos = INT_MIN;
338
339         double samples_per_pixel, pixels_offset;
340         tie(pixels_offset, samples_per_pixel) =
341                 get_pixels_offset_samples_per_pixel();
342
343         // Gather all annotations that form a visual "block" and draw them as such
344         for (const Annotation &a : annotations) {
345
346                 const int end = a.end_sample() / samples_per_pixel - pixels_offset;
347                 const int delta = end - prev_ann_pos;
348
349                 // Some annotations are in reverse order, so we cannot
350                 // simply check for delta > 1
351                 if (abs(delta) > 1) {
352                         // Block was broken, draw it
353                         if (a_block.size() == 1)
354                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour);
355                         else
356                                 if (a_block.size() > 0)
357                                         draw_annotation_block(a_block, p, h, pp, y, base_colour);
358
359                         a_block.clear();
360                 }
361
362                 a_block.push_back(a);
363                 prev_ann_pos = end;
364         }
365
366         if (a_block.size() == 1)
367                 draw_annotation(a_block.front(), p, h, pp, y, base_colour);
368         else
369                 draw_annotation_block(a_block, p, h, pp, y, base_colour);
370 }
371
372 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
373         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
374         size_t base_colour) const
375 {
376         double samples_per_pixel, pixels_offset;
377         tie(pixels_offset, samples_per_pixel) =
378                 get_pixels_offset_samples_per_pixel();
379
380         const double start = a.start_sample() / samples_per_pixel -
381                 pixels_offset;
382         const double end = a.end_sample() / samples_per_pixel -
383                 pixels_offset;
384
385         const size_t colour = (base_colour + a.format()) % countof(Colours);
386         const QColor &fill = Colours[colour];
387         const QColor &outline = OutlineColours[colour];
388
389         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
390                 return;
391
392         if (a.start_sample() == a.end_sample())
393                 draw_instant(a, p, fill, outline, h, start, y);
394         else
395                 draw_range(a, p, fill, outline, h, start, end, y);
396 }
397
398 void DecodeTrace::draw_annotation_block(vector<pv::data::decode::Annotation> a,
399         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
400         size_t base_colour) const
401 {
402         double samples_per_pixel, pixels_offset;
403         tie(pixels_offset, samples_per_pixel) =
404                 get_pixels_offset_samples_per_pixel();
405
406         const size_t colour =
407                 (base_colour + a.front().format()) % countof(Colours);
408
409         const int start = a.front().start_sample() / samples_per_pixel -
410                 pixels_offset;
411         const int end = a.back().end_sample() / samples_per_pixel -
412                 pixels_offset;
413
414         const QRectF rect(
415                 std::max(pp.left(), start),
416                 y - h/2 + 0.5,
417                 std::min(pp.right(), end) - std::max(pp.left(), start) + 1,
418                 h);
419
420         p.setPen(OutlineColours[colour]);
421         p.setBrush(Colours[colour]);
422         p.drawRect(rect);
423 }
424
425 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
426         QColor fill, QColor outline, int h, double x, int y) const
427 {
428         const QString text = a.annotations().empty() ?
429                 QString() : a.annotations().back();
430         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
431                 0.0) + h;
432         const QRectF rect(x - w / 2, y - h / 2, w, h);
433
434         p.setPen(outline);
435         p.setBrush(fill);
436         p.drawRoundedRect(rect, h / 2, h / 2);
437
438         p.setPen(Qt::black);
439         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
440 }
441
442 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
443         QColor fill, QColor outline, int h, double start,
444         double end, int y) const
445 {
446         const double top = y + .5 - h / 2;
447         const double bottom = y + .5 + h / 2;
448         const vector<QString> annotations = a.annotations();
449
450         p.setPen(outline);
451         p.setBrush(fill);
452
453         // If the two ends are within 1 pixel, draw a vertical line
454         if (start + 1.0 > end) {
455                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
456                 return;
457         }
458
459         const double cap_width = min((end - start) / 4, EndCapWidth);
460
461         QPointF pts[] = {
462                 QPointF(start, y + .5f),
463                 QPointF(start + cap_width, top),
464                 QPointF(end - cap_width, top),
465                 QPointF(end, y + .5f),
466                 QPointF(end - cap_width, bottom),
467                 QPointF(start + cap_width, bottom)
468         };
469
470         p.drawConvexPolygon(pts, countof(pts));
471
472         if (annotations.empty())
473                 return;
474
475         QRectF rect(start + cap_width, y - h / 2,
476                 end - start - cap_width * 2, h);
477         if (rect.width() <= 4)
478                 return;
479
480         p.setPen(Qt::black);
481
482         // Try to find an annotation that will fit
483         QString best_annotation;
484         int best_width = 0;
485
486         for (const QString &a : annotations) {
487                 const int w = p.boundingRect(QRectF(), 0, a).width();
488                 if (w <= rect.width() && w > best_width)
489                         best_annotation = a, best_width = w;
490         }
491
492         if (best_annotation.isEmpty())
493                 best_annotation = annotations.back();
494
495         // If not ellide the last in the list
496         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
497                 best_annotation, Qt::ElideRight, rect.width()));
498 }
499
500 void DecodeTrace::draw_error(QPainter &p, const QString &message,
501         const ViewItemPaintParams &pp)
502 {
503         const int y = get_visual_y();
504
505         p.setPen(ErrorBgColour.darker());
506         p.setBrush(ErrorBgColour);
507
508         const QRectF bounding_rect =
509                 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
510         const QRectF text_rect = p.boundingRect(bounding_rect,
511                 Qt::AlignCenter, message);
512         const float r = text_rect.height() / 4;
513
514         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
515                 Qt::AbsoluteSize);
516
517         p.setPen(Qt::black);
518         p.drawText(text_rect, message);
519 }
520
521 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
522         int right) const
523 {
524         using namespace pv::data;
525         using pv::data::decode::Decoder;
526
527         double samples_per_pixel, pixels_offset;
528
529         assert(decoder_stack_); 
530
531         shared_ptr<Logic> data;
532         shared_ptr<LogicSignal> logic_signal;
533
534         const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
535
536         // We get the logic data of the first channel in the list.
537         // This works because we are currently assuming all
538         // LogicSignals have the same data/segment
539         for (const shared_ptr<Decoder> &dec : stack)
540                 if (dec && !dec->channels().empty() &&
541                         ((logic_signal = (*dec->channels().begin()).second)) &&
542                         ((data = logic_signal->logic_data())))
543                         break;
544
545         if (!data || data->logic_segments().empty())
546                 return;
547
548         const shared_ptr<LogicSegment> segment =
549                 data->logic_segments().front();
550         assert(segment);
551         const int64_t sample_count = (int64_t)segment->get_sample_count();
552         if (sample_count == 0)
553                 return;
554
555         const int64_t samples_decoded = decoder_stack_->samples_decoded();
556         if (sample_count == samples_decoded)
557                 return;
558
559         const int y = get_visual_y();
560
561         tie(pixels_offset, samples_per_pixel) =
562                 get_pixels_offset_samples_per_pixel();
563
564         const double start = max(samples_decoded /
565                 samples_per_pixel - pixels_offset, left - 1.0);
566         const double end = min(sample_count / samples_per_pixel -
567                 pixels_offset, right + 1.0);
568         const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
569
570         p.setPen(QPen(Qt::NoPen));
571         p.setBrush(Qt::white);
572         p.drawRect(no_decode_rect);
573
574         p.setPen(NoDecodeColour);
575         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
576         p.drawRect(no_decode_rect);
577 }
578
579 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
580 {
581         assert(owner_);
582         assert(decoder_stack_);
583
584         const View *view = owner_->view();
585         assert(view);
586
587         const double scale = view->scale();
588         assert(scale > 0);
589
590         const double pixels_offset =
591                 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
592
593         double samplerate = decoder_stack_->samplerate();
594
595         // Show sample rate as 1Hz when it is unknown
596         if (samplerate == 0.0)
597                 samplerate = 1.0;
598
599         return make_pair(pixels_offset, samplerate * scale);
600 }
601
602 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
603         int x_start, int x_end) const
604 {
605         double samples_per_pixel, pixels_offset;
606         tie(pixels_offset, samples_per_pixel) =
607                 get_pixels_offset_samples_per_pixel();
608
609         const uint64_t start = (uint64_t)max(
610                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
611         const uint64_t end = (uint64_t)max(
612                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
613
614         return make_pair(start, end);
615 }
616
617 int DecodeTrace::get_row_at_point(const QPoint &point)
618 {
619         if (!row_height_)
620                 return -1;
621
622         const int y = (point.y() - get_visual_y() + row_height_ / 2);
623
624         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
625         if (y < 0)
626                 return -1;
627
628         const int row = y / row_height_;
629
630         if (row >= (int)visible_rows_.size())
631                 return -1;
632
633         return row;
634 }
635
636 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
637 {
638         using namespace pv::data::decode;
639
640         if (!enabled())
641                 return QString();
642
643         const pair<uint64_t, uint64_t> sample_range =
644                 get_sample_range(point.x(), point.x() + 1);
645         const int row = get_row_at_point(point);
646         if (row < 0)
647                 return QString();
648
649         vector<pv::data::decode::Annotation> annotations;
650
651         assert(decoder_stack_);
652         decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
653                 sample_range.first, sample_range.second);
654
655         return (annotations.empty()) ?
656                 QString() : annotations[0].annotations().front();
657 }
658
659 void DecodeTrace::hover_point_changed()
660 {
661         assert(owner_);
662
663         const View *const view = owner_->view();
664         assert(view);
665
666         QPoint hp = view->hover_point();
667         QString ann = get_annotation_at_point(hp);
668
669         assert(view);
670
671         if (!row_height_ || ann.isEmpty()) {
672                 QToolTip::hideText();
673                 return;
674         }
675
676         const int hover_row = get_row_at_point(hp);
677
678         QFontMetrics m(QToolTip::font());
679         const QRect text_size = m.boundingRect(QRect(), 0, ann);
680
681         // This is OS-specific and unfortunately we can't query it, so
682         // use an approximation to at least try to minimize the error.
683         const int padding = 8;
684
685         // Make sure the tool tip doesn't overlap with the mouse cursor.
686         // If it did, the tool tip would constantly hide and re-appear.
687         // We also push it up by one row so that it appears above the
688         // decode trace, not below.
689         hp.setX(hp.x() - (text_size.width() / 2) - padding);
690
691         hp.setY(get_visual_y() - (row_height_ / 2) +
692                 (hover_row * row_height_) -
693                 row_height_ - text_size.height() - padding);
694
695         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
696 }
697
698 void DecodeTrace::create_decoder_form(int index,
699         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
700         QFormLayout *form)
701 {
702         const GSList *l;
703
704         assert(dec);
705         const srd_decoder *const decoder = dec->decoder();
706         assert(decoder);
707
708         const bool decoder_deletable = index > 0;
709
710         pv::widgets::DecoderGroupBox *const group =
711                 new pv::widgets::DecoderGroupBox(
712                         QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
713         group->set_decoder_visible(dec->shown());
714
715         if (decoder_deletable) {
716                 delete_mapper_.setMapping(group, index);
717                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
718         }
719
720         show_hide_mapper_.setMapping(group, index);
721         connect(group, SIGNAL(show_hide_decoder()),
722                 &show_hide_mapper_, SLOT(map()));
723
724         QFormLayout *const decoder_form = new QFormLayout;
725         group->add_layout(decoder_form);
726
727         // Add the mandatory channels
728         for (l = decoder->channels; l; l = l->next) {
729                 const struct srd_channel *const pdch =
730                         (struct srd_channel *)l->data;
731                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
732                 connect(combo, SIGNAL(currentIndexChanged(int)),
733                         this, SLOT(on_channel_selected(int)));
734                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
735                         .arg(QString::fromUtf8(pdch->name))
736                         .arg(QString::fromUtf8(pdch->desc)), combo);
737
738                 const ChannelSelector s = {combo, dec, pdch};
739                 channel_selectors_.push_back(s);
740         }
741
742         // Add the optional channels
743         for (l = decoder->opt_channels; l; l = l->next) {
744                 const struct srd_channel *const pdch =
745                         (struct srd_channel *)l->data;
746                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
747                 connect(combo, SIGNAL(currentIndexChanged(int)),
748                         this, SLOT(on_channel_selected(int)));
749                 decoder_form->addRow(tr("<b>%1</b> (%2)")
750                         .arg(QString::fromUtf8(pdch->name))
751                         .arg(QString::fromUtf8(pdch->desc)), combo);
752
753                 const ChannelSelector s = {combo, dec, pdch};
754                 channel_selectors_.push_back(s);
755         }
756
757         // Add the options
758         shared_ptr<binding::Decoder> binding(
759                 new binding::Decoder(decoder_stack_, dec));
760         binding->add_properties_to_form(decoder_form, true);
761
762         bindings_.push_back(binding);
763
764         form->addRow(group);
765         decoder_forms_.push_back(group);
766 }
767
768 QComboBox* DecodeTrace::create_channel_selector(
769         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
770         const srd_channel *const pdch)
771 {
772         assert(dec);
773
774         const auto sigs(session_.signals());
775
776         vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
777         std::sort(sig_list.begin(), sig_list.end(),
778                 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
779                         return a->name().compare(b->name()) < 0; });
780
781         assert(decoder_stack_);
782         const auto channel_iter = dec->channels().find(pdch);
783
784         QComboBox *selector = new QComboBox(parent);
785
786         selector->addItem("-", qVariantFromValue((void*)nullptr));
787
788         if (channel_iter == dec->channels().end())
789                 selector->setCurrentIndex(0);
790
791         for (const shared_ptr<view::Signal> &s : sig_list) {
792                 assert(s);
793                 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled()) {
794                         selector->addItem(s->name(),
795                                 qVariantFromValue((void*)s.get()));
796
797                         if (channel_iter != dec->channels().end() &&
798                                 (*channel_iter).second == s)
799                                 selector->setCurrentIndex(
800                                         selector->count() - 1);
801                 }
802         }
803
804         return selector;
805 }
806
807 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
808 {
809         assert(dec);
810
811         map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
812
813         const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
814
815         for (const ChannelSelector &s : channel_selectors_) {
816                 if (s.decoder_ != dec)
817                         break;
818
819                 const LogicSignal *const selection =
820                         (LogicSignal*)s.combo_->itemData(
821                                 s.combo_->currentIndex()).value<void*>();
822
823                 for (shared_ptr<Signal> sig : sigs)
824                         if (sig.get() == selection) {
825                                 channel_map[s.pdch_] =
826                                         dynamic_pointer_cast<LogicSignal>(sig);
827                                 break;
828                         }
829         }
830
831         dec->set_channels(channel_map);
832 }
833
834 void DecodeTrace::commit_channels()
835 {
836         assert(decoder_stack_);
837         for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
838                 commit_decoder_channels(dec);
839
840         decoder_stack_->begin_decode();
841 }
842
843 void DecodeTrace::on_new_decode_data()
844 {
845         if (owner_)
846                 owner_->row_item_appearance_changed(false, true);
847 }
848
849 void DecodeTrace::delete_pressed()
850 {
851         on_delete();
852 }
853
854 void DecodeTrace::on_delete()
855 {
856         session_.remove_decode_signal(this);
857 }
858
859 void DecodeTrace::on_channel_selected(int)
860 {
861         commit_channels();
862 }
863
864 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
865 {
866         assert(decoder);
867         assert(decoder_stack_);
868         decoder_stack_->push(shared_ptr<data::decode::Decoder>(
869                 new data::decode::Decoder(decoder)));
870         decoder_stack_->begin_decode();
871
872         create_popup_form();
873 }
874
875 void DecodeTrace::on_delete_decoder(int index)
876 {
877         decoder_stack_->remove(index);
878
879         // Update the popup
880         create_popup_form();    
881
882         decoder_stack_->begin_decode();
883 }
884
885 void DecodeTrace::on_show_hide_decoder(int index)
886 {
887         using pv::data::decode::Decoder;
888
889         const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
890
891         // Find the decoder in the stack
892         auto iter = stack.cbegin();
893         for (int i = 0; i < index; i++, iter++)
894                 assert(iter != stack.end());
895
896         shared_ptr<Decoder> dec = *iter;
897         assert(dec);
898
899         const bool show = !dec->shown();
900         dec->show(show);
901
902         assert(index < (int)decoder_forms_.size());
903         decoder_forms_[index]->set_decoder_visible(show);
904
905         if (owner_)
906                 owner_->row_item_appearance_changed(false, true);
907 }
908
909 } // namespace view
910 } // namespace pv