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