]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
Replaced lengthy iterator types with the auto keyword
[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 <extdef.h>
26
27 #include <boost/foreach.hpp>
28 #include <boost/functional/hash.hpp>
29
30 #include <QAction>
31 #include <QApplication>
32 #include <QComboBox>
33 #include <QFormLayout>
34 #include <QLabel>
35 #include <QMenu>
36 #include <QPushButton>
37
38 #include "decodetrace.h"
39
40 #include <pv/sigsession.h>
41 #include <pv/data/decoderstack.h>
42 #include <pv/data/decode/decoder.h>
43 #include <pv/data/logic.h>
44 #include <pv/data/logicsnapshot.h>
45 #include <pv/data/decode/annotation.h>
46 #include <pv/view/logicsignal.h>
47 #include <pv/view/view.h>
48 #include <pv/widgets/decodergroupbox.h>
49 #include <pv/widgets/decodermenu.h>
50
51 using boost::dynamic_pointer_cast;
52 using boost::shared_ptr;
53 using std::list;
54 using std::max;
55 using std::map;
56 using std::min;
57 using std::vector;
58
59 namespace pv {
60 namespace view {
61
62 const QColor DecodeTrace::DecodeColours[4] = {
63         QColor(0xEF, 0x29, 0x29),       // Red
64         QColor(0xFC, 0xE9, 0x4F),       // Yellow
65         QColor(0x8A, 0xE2, 0x34),       // Green
66         QColor(0x72, 0x9F, 0xCF)        // Blue
67 };
68
69 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
70 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
71
72 const int DecodeTrace::ArrowSize = 4;
73 const double DecodeTrace::EndCapWidth = 5;
74 const int DecodeTrace::DrawPadding = 100;
75
76 const QColor DecodeTrace::Colours[16] = {
77         QColor(0xEF, 0x29, 0x29),
78         QColor(0xF6, 0x6A, 0x32),
79         QColor(0xFC, 0xAE, 0x3E),
80         QColor(0xFB, 0xCA, 0x47),
81         QColor(0xFC, 0xE9, 0x4F),
82         QColor(0xCD, 0xF0, 0x40),
83         QColor(0x8A, 0xE2, 0x34),
84         QColor(0x4E, 0xDC, 0x44),
85         QColor(0x55, 0xD7, 0x95),
86         QColor(0x64, 0xD1, 0xD2),
87         QColor(0x72, 0x9F, 0xCF),
88         QColor(0xD4, 0x76, 0xC4),
89         QColor(0x9D, 0x79, 0xB9),
90         QColor(0xAD, 0x7F, 0xA8),
91         QColor(0xC2, 0x62, 0x9B),
92         QColor(0xD7, 0x47, 0x6F)
93 };
94
95 const QColor DecodeTrace::OutlineColours[16] = {
96         QColor(0x77, 0x14, 0x14),
97         QColor(0x7B, 0x35, 0x19),
98         QColor(0x7E, 0x57, 0x1F),
99         QColor(0x7D, 0x65, 0x23),
100         QColor(0x7E, 0x74, 0x27),
101         QColor(0x66, 0x78, 0x20),
102         QColor(0x45, 0x71, 0x1A),
103         QColor(0x27, 0x6E, 0x22),
104         QColor(0x2A, 0x6B, 0x4A),
105         QColor(0x32, 0x68, 0x69),
106         QColor(0x39, 0x4F, 0x67),
107         QColor(0x6A, 0x3B, 0x62),
108         QColor(0x4E, 0x3C, 0x5C),
109         QColor(0x56, 0x3F, 0x54),
110         QColor(0x61, 0x31, 0x4D),
111         QColor(0x6B, 0x23, 0x37)
112 };
113
114 DecodeTrace::DecodeTrace(pv::SigSession &session,
115         boost::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
116         Trace(QString::fromUtf8(
117                 decoder_stack->stack().front()->decoder()->name)),
118         _session(session),
119         _decoder_stack(decoder_stack),
120         _delete_mapper(this),
121         _show_hide_mapper(this)
122 {
123         assert(_decoder_stack);
124
125         _colour = DecodeColours[index % countof(DecodeColours)];
126
127         connect(_decoder_stack.get(), SIGNAL(new_decode_data()),
128                 this, SLOT(on_new_decode_data()));
129         connect(&_delete_mapper, SIGNAL(mapped(int)),
130                 this, SLOT(on_delete_decoder(int)));
131         connect(&_show_hide_mapper, SIGNAL(mapped(int)),
132                 this, SLOT(on_show_hide_decoder(int)));
133 }
134
135 bool DecodeTrace::enabled() const
136 {
137         return true;
138 }
139
140 const boost::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
141 {
142         return _decoder_stack;
143 }
144
145 void DecodeTrace::set_view(pv::view::View *view)
146 {
147         assert(view);
148         Trace::set_view(view);
149 }
150
151 void DecodeTrace::paint_back(QPainter &p, int left, int right)
152 {
153         Trace::paint_back(p, left, right);
154         paint_axis(p, get_y(), left, right);
155 }
156
157 void DecodeTrace::paint_mid(QPainter &p, int left, int right)
158 {
159         using namespace pv::data::decode;
160
161         const double scale = _view->scale();
162         assert(scale > 0);
163
164         double samplerate = _decoder_stack->samplerate();
165
166         _cur_row_headings.clear();
167
168         // Show sample rate as 1Hz when it is unknown
169         if (samplerate == 0.0)
170                 samplerate = 1.0;
171
172         const double pixels_offset = (_view->offset() -
173                 _decoder_stack->get_start_time()) / scale;
174         const double samples_per_pixel = samplerate * scale;
175
176         const uint64_t start_sample = (uint64_t)max((left + pixels_offset) *
177                 samples_per_pixel, 0.0);
178         const uint64_t end_sample = (uint64_t)max((right + pixels_offset) *
179                 samples_per_pixel, 0.0);
180
181         QFontMetrics m(QApplication::font());
182         const int text_height =  m.boundingRect(QRect(), 0, "Tg").height();
183         const int annotation_height = (text_height * 5) / 4;
184         const int row_height = (text_height * 6) / 4;
185
186         assert(_decoder_stack);
187         const QString err = _decoder_stack->error_message();
188         if (!err.isEmpty())
189         {
190                 draw_unresolved_period(p, annotation_height, left, right,
191                         samples_per_pixel, pixels_offset);
192                 draw_error(p, err, left, right);
193                 return;
194         }
195
196         // Iterate through the rows
197         assert(_view);
198         int y = get_y();
199
200         assert(_decoder_stack);
201
202         const vector<Row> rows(_decoder_stack->get_visible_rows());
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                         start_sample, end_sample);
216                 if (!annotations.empty()) {
217                         BOOST_FOREACH(const Annotation &a, annotations)
218                                 draw_annotation(a, p, get_text_colour(),
219                                         annotation_height, left, right,
220                                         samples_per_pixel, pixels_offset, y,
221                                         base_colour);
222                         y += row_height;
223
224                         _cur_row_headings.push_back(row.title());
225                 }
226         }
227
228         // Draw the hatching
229         draw_unresolved_period(p, annotation_height, left, right,
230                 samples_per_pixel, pixels_offset);
231 }
232
233 void DecodeTrace::paint_fore(QPainter &p, int left, int right)
234 {
235         using namespace pv::data::decode;
236
237         (void)right;
238
239         QFontMetrics m(QApplication::font());
240         const int text_height =  m.boundingRect(QRect(), 0, "Tg").height();
241         const int row_height = (text_height * 6) / 4;
242
243         for (size_t i = 0; i < _cur_row_headings.size(); i++)
244         {
245                 const int y = i * row_height + get_y();
246
247                 p.setPen(QPen(Qt::NoPen));
248                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
249
250                 if (i != 0)
251                 {
252                         const QPointF points[] = {
253                                 QPointF(left, y - ArrowSize),
254                                 QPointF(left + ArrowSize, y),
255                                 QPointF(left, y + ArrowSize)
256                         };
257                         p.drawPolygon(points, countof(points));
258                 }
259
260                 const QRect r(left + ArrowSize * 2, y - row_height / 2,
261                         right - left, row_height);
262                 const QString h(_cur_row_headings[i]);
263                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
264                         Qt::TextDontClip;
265
266                 // Draw the outline
267                 p.setPen(QApplication::palette().color(QPalette::Base));
268                 for (int dx = -1; dx <= 1; dx++)
269                         for (int dy = -1; dy <= 1; dy++)
270                                 if (dx != 0 && dy != 0)
271                                         p.drawText(r.translated(dx, dy), f, h);
272
273                 // Draw the text
274                 p.setPen(QApplication::palette().color(QPalette::WindowText));
275                 p.drawText(r, f, h);
276         }
277 }
278
279 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
280 {
281         using pv::data::decode::Decoder;
282
283         assert(form);
284         assert(parent);
285         assert(_decoder_stack);
286
287         // Add the standard options
288         Trace::populate_popup_form(parent, form);
289
290         // Add the decoder options
291         _bindings.clear();
292         _probe_selectors.clear();
293         _decoder_forms.clear();
294
295         const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
296
297         if (stack.empty())
298         {
299                 QLabel *const l = new QLabel(
300                         tr("<p><i>No decoders in the stack</i></p>"));
301                 l->setAlignment(Qt::AlignCenter);
302                 form->addRow(l);
303         }
304         else
305         {
306                 auto iter = stack.cbegin();
307                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
308                         shared_ptr<Decoder> dec(*iter);
309                         create_decoder_form(i, dec, parent, form);
310                 }
311
312                 form->addRow(new QLabel(
313                         tr("<i>* Required channels</i>"), parent));
314         }
315
316         // Add stacking button
317         pv::widgets::DecoderMenu *const decoder_menu =
318                 new pv::widgets::DecoderMenu(parent);
319         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
320                 this, SLOT(on_stack_decoder(srd_decoder*)));
321
322         QPushButton *const stack_button =
323                 new QPushButton(tr("Stack Decoder"), parent);
324         stack_button->setMenu(decoder_menu);
325
326         QHBoxLayout *stack_button_box = new QHBoxLayout;
327         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
328         form->addRow(stack_button_box);
329 }
330
331 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
332 {
333         QMenu *const menu = Trace::create_context_menu(parent);
334
335         menu->addSeparator();
336
337         QAction *const del = new QAction(tr("Delete"), this);
338         del->setShortcuts(QKeySequence::Delete);
339         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
340         menu->addAction(del);
341
342         return menu;
343 }
344
345 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
346         QPainter &p, QColor text_color, int h, int left, int right,
347         double samples_per_pixel, double pixels_offset, int y,
348         size_t base_colour) const
349 {
350         const double start = a.start_sample() / samples_per_pixel -
351                 pixels_offset;
352         const double end = a.end_sample() / samples_per_pixel -
353                 pixels_offset;
354
355         const size_t colour = (base_colour + a.format()) % countof(Colours);
356         const QColor &fill = Colours[colour];
357         const QColor &outline = OutlineColours[colour];
358
359         if (start > right + DrawPadding || end < left - DrawPadding)
360                 return;
361
362         if (a.start_sample() == a.end_sample())
363                 draw_instant(a, p, fill, outline, text_color, h,
364                         start, y);
365         else
366                 draw_range(a, p, fill, outline, text_color, h,
367                         start, end, y);
368 }
369
370 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
371         QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
372 {
373         const QString text = a.annotations().empty() ?
374                 QString() : a.annotations().back();
375         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
376                 0.0) + h;
377         const QRectF rect(x - w / 2, y - h / 2, w, h);
378
379         p.setPen(outline);
380         p.setBrush(fill);
381         p.drawRoundedRect(rect, h / 2, h / 2);
382
383         p.setPen(text_color);
384         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
385 }
386
387 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
388         QColor fill, QColor outline, QColor text_color, int h, double start,
389         double end, int y) const
390 {
391         const double top = y + .5 - h / 2;
392         const double bottom = y + .5 + h / 2;
393         const vector<QString> annotations = a.annotations();
394
395         p.setPen(outline);
396         p.setBrush(fill);
397
398         // If the two ends are within 1 pixel, draw a vertical line
399         if (start + 1.0 > end)
400         {
401                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
402                 return;
403         }
404
405         const double cap_width = min((end - start) / 4, EndCapWidth);
406
407         QPointF pts[] = {
408                 QPointF(start, y + .5f),
409                 QPointF(start + cap_width, top),
410                 QPointF(end - cap_width, top),
411                 QPointF(end, y + .5f),
412                 QPointF(end - cap_width, bottom),
413                 QPointF(start + cap_width, bottom)
414         };
415
416         p.drawConvexPolygon(pts, countof(pts));
417
418         if (annotations.empty())
419                 return;
420
421         QRectF rect(start + cap_width, y - h / 2,
422                 end - start - cap_width * 2, h);
423         if (rect.width() <= 4)
424                 return;
425
426         p.setPen(text_color);
427
428         // Try to find an annotation that will fit
429         QString best_annotation;
430         int best_width = 0;
431
432         BOOST_FOREACH(const QString &a, annotations) {
433                 const int w = p.boundingRect(QRectF(), 0, a).width();
434                 if (w <= rect.width() && w > best_width)
435                         best_annotation = a, best_width = w;
436         }
437
438         if (best_annotation.isEmpty())
439                 best_annotation = annotations.back();
440
441         // If not ellide the last in the list
442         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
443                 best_annotation, Qt::ElideRight, rect.width()));
444 }
445
446 void DecodeTrace::draw_error(QPainter &p, const QString &message,
447         int left, int right)
448 {
449         const int y = get_y();
450
451         p.setPen(ErrorBgColour.darker());
452         p.setBrush(ErrorBgColour);
453
454         const QRectF bounding_rect =
455                 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
456         const QRectF text_rect = p.boundingRect(bounding_rect,
457                 Qt::AlignCenter, message);
458         const float r = text_rect.height() / 4;
459
460         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
461                 Qt::AbsoluteSize);
462
463         p.setPen(get_text_colour());
464         p.drawText(text_rect, message);
465 }
466
467 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
468         int right, double samples_per_pixel, double pixels_offset) 
469 {
470         using namespace pv::data;
471         using pv::data::decode::Decoder;
472
473         assert(_decoder_stack); 
474
475         shared_ptr<Logic> data;
476         shared_ptr<LogicSignal> logic_signal;
477
478         const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
479
480         // We get the logic data of the first probe in the list.
481         // This works because we are currently assuming all
482         // LogicSignals have the same data/snapshot
483         BOOST_FOREACH (const shared_ptr<Decoder> &dec, stack)
484                 if (dec && !dec->channels().empty() &&
485                         ((logic_signal = (*dec->channels().begin()).second)) &&
486                         ((data = logic_signal->logic_data())))
487                         break;
488
489         if (!data || data->get_snapshots().empty())
490                 return;
491
492         const shared_ptr<LogicSnapshot> snapshot =
493                 data->get_snapshots().front();
494         assert(snapshot);
495         const int64_t sample_count = (int64_t)snapshot->get_sample_count();
496         if (sample_count == 0)
497                 return;
498
499         const int64_t samples_decoded = _decoder_stack->samples_decoded();
500         if (sample_count == samples_decoded)
501                 return;
502
503         const int y = get_y();
504         const double start = max(samples_decoded /
505                 samples_per_pixel - pixels_offset, left - 1.0);
506         const double end = min(sample_count / samples_per_pixel -
507                 pixels_offset, right + 1.0);
508         const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
509
510         p.setPen(QPen(Qt::NoPen));
511         p.setBrush(Qt::white);
512         p.drawRect(no_decode_rect);
513
514         p.setPen(NoDecodeColour);
515         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
516         p.drawRect(no_decode_rect);
517 }
518
519 void DecodeTrace::create_decoder_form(int index,
520         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
521         QFormLayout *form)
522 {
523         const GSList *l;
524
525         assert(dec);
526         const srd_decoder *const decoder = dec->decoder();
527         assert(decoder);
528
529         pv::widgets::DecoderGroupBox *const group =
530                 new pv::widgets::DecoderGroupBox(
531                         QString::fromUtf8(decoder->name));
532         group->set_decoder_visible(dec->shown());
533
534         _delete_mapper.setMapping(group, index);
535         connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
536
537         _show_hide_mapper.setMapping(group, index);
538         connect(group, SIGNAL(show_hide_decoder()),
539                 &_show_hide_mapper, SLOT(map()));
540
541         QFormLayout *const decoder_form = new QFormLayout;
542         group->add_layout(decoder_form);
543
544         // Add the mandatory channels
545         for(l = decoder->channels; l; l = l->next) {
546                 const struct srd_channel *const pdch =
547                         (struct srd_channel *)l->data;
548                 QComboBox *const combo = create_probe_selector(parent, dec, pdch);
549                 connect(combo, SIGNAL(currentIndexChanged(int)),
550                         this, SLOT(on_probe_selected(int)));
551                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
552                         .arg(QString::fromUtf8(pdch->name))
553                         .arg(QString::fromUtf8(pdch->desc)), combo);
554
555                 const ProbeSelector s = {combo, dec, pdch};
556                 _probe_selectors.push_back(s);
557         }
558
559         // Add the optional channels
560         for(l = decoder->opt_channels; l; l = l->next) {
561                 const struct srd_channel *const pdch =
562                         (struct srd_channel *)l->data;
563                 QComboBox *const combo = create_probe_selector(parent, dec, pdch);
564                 connect(combo, SIGNAL(currentIndexChanged(int)),
565                         this, SLOT(on_probe_selected(int)));
566                 decoder_form->addRow(tr("<b>%1</b> (%2)")
567                         .arg(QString::fromUtf8(pdch->name))
568                         .arg(QString::fromUtf8(pdch->desc)), combo);
569
570                 const ProbeSelector s = {combo, dec, pdch};
571                 _probe_selectors.push_back(s);
572         }
573
574         // Add the options
575         shared_ptr<prop::binding::DecoderOptions> binding(
576                 new prop::binding::DecoderOptions(_decoder_stack, dec));
577         binding->add_properties_to_form(decoder_form, true);
578
579         _bindings.push_back(binding);
580
581         form->addRow(group);
582         _decoder_forms.push_back(group);
583 }
584
585 QComboBox* DecodeTrace::create_probe_selector(
586         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
587         const srd_channel *const pdch)
588 {
589         assert(dec);
590
591         const vector< shared_ptr<Signal> > sigs = _session.get_signals();
592
593         assert(_decoder_stack);
594         const auto probe_iter = dec->channels().find(pdch);
595
596         QComboBox *selector = new QComboBox(parent);
597
598         selector->addItem("-", qVariantFromValue((void*)NULL));
599
600         if (probe_iter == dec->channels().end())
601                 selector->setCurrentIndex(0);
602
603         for(size_t i = 0; i < sigs.size(); i++) {
604                 const shared_ptr<view::Signal> s(sigs[i]);
605                 assert(s);
606
607                 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
608                 {
609                         selector->addItem(s->get_name(),
610                                 qVariantFromValue((void*)s.get()));
611                         if ((*probe_iter).second == s)
612                                 selector->setCurrentIndex(i + 1);
613                 }
614         }
615
616         return selector;
617 }
618
619 void DecodeTrace::commit_decoder_probes(shared_ptr<data::decode::Decoder> &dec)
620 {
621         assert(dec);
622
623         map<const srd_channel*, shared_ptr<LogicSignal> > probe_map;
624         const vector< shared_ptr<Signal> > sigs = _session.get_signals();
625
626         BOOST_FOREACH(const ProbeSelector &s, _probe_selectors)
627         {
628                 if(s._decoder != dec)
629                         break;
630
631                 const LogicSignal *const selection =
632                         (LogicSignal*)s._combo->itemData(
633                                 s._combo->currentIndex()).value<void*>();
634
635                 BOOST_FOREACH(shared_ptr<Signal> sig, sigs)
636                         if(sig.get() == selection) {
637                                 probe_map[s._pdch] =
638                                         dynamic_pointer_cast<LogicSignal>(sig);
639                                 break;
640                         }
641         }
642
643         dec->set_probes(probe_map);
644 }
645
646 void DecodeTrace::commit_probes()
647 {
648         assert(_decoder_stack);
649         BOOST_FOREACH(shared_ptr<data::decode::Decoder> dec,
650                 _decoder_stack->stack())
651                 commit_decoder_probes(dec);
652
653         _decoder_stack->begin_decode();
654 }
655
656 void DecodeTrace::on_new_decode_data()
657 {
658         if (_view)
659                 _view->update_viewport();
660 }
661
662 void DecodeTrace::delete_pressed()
663 {
664         on_delete();
665 }
666
667 void DecodeTrace::on_delete()
668 {
669         _session.remove_decode_signal(this);
670 }
671
672 void DecodeTrace::on_probe_selected(int)
673 {
674         commit_probes();
675 }
676
677 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
678 {
679         assert(decoder);
680         assert(_decoder_stack);
681         _decoder_stack->push(shared_ptr<data::decode::Decoder>(
682                 new data::decode::Decoder(decoder)));
683         _decoder_stack->begin_decode();
684
685         create_popup_form();
686 }
687
688 void DecodeTrace::on_delete_decoder(int index)
689 {
690         _decoder_stack->remove(index);
691
692         // Update the popup
693         create_popup_form();    
694
695         _decoder_stack->begin_decode();
696 }
697
698 void DecodeTrace::on_show_hide_decoder(int index)
699 {
700         using pv::data::decode::Decoder;
701
702         const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
703
704         // Find the decoder in the stack
705         auto iter = stack.cbegin();
706         for(int i = 0; i < index; i++, iter++)
707                 assert(iter != stack.end());
708
709         shared_ptr<Decoder> dec = *iter;
710         assert(dec);
711
712         const bool show = !dec->shown();
713         dec->show(show);
714
715         assert(index < (int)_decoder_forms.size());
716         _decoder_forms[index]->set_decoder_visible(show);
717
718         _view->update_viewport();
719 }
720
721 } // namespace view
722 } // namespace pv