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