]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
9f8306ea8ef9c7f0983e99cb321978fdc7eb2bf9
[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                 list< shared_ptr<Decoder> >::const_iterator iter =
307                         stack.begin();
308                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
309                         shared_ptr<Decoder> dec(*iter);
310                         create_decoder_form(i, dec, parent, form);
311                 }
312
313                 form->addRow(new QLabel(
314                         tr("<i>* Required Probes</i>"), parent));
315         }
316
317         // Add stacking button
318         pv::widgets::DecoderMenu *const decoder_menu =
319                 new pv::widgets::DecoderMenu(parent);
320         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
321                 this, SLOT(on_stack_decoder(srd_decoder*)));
322
323         QPushButton *const stack_button =
324                 new QPushButton(tr("Stack Decoder"), parent);
325         stack_button->setMenu(decoder_menu);
326
327         QHBoxLayout *stack_button_box = new QHBoxLayout;
328         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
329         form->addRow(stack_button_box);
330 }
331
332 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
333 {
334         QMenu *const menu = Trace::create_context_menu(parent);
335
336         menu->addSeparator();
337
338         QAction *const del = new QAction(tr("Delete"), this);
339         del->setShortcuts(QKeySequence::Delete);
340         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
341         menu->addAction(del);
342
343         return menu;
344 }
345
346 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
347         QPainter &p, QColor text_color, int h, int left, int right,
348         double samples_per_pixel, double pixels_offset, int y,
349         size_t base_colour) const
350 {
351         const double start = a.start_sample() / samples_per_pixel -
352                 pixels_offset;
353         const double end = a.end_sample() / samples_per_pixel -
354                 pixels_offset;
355
356         const size_t colour = (base_colour + a.format()) % countof(Colours);
357         const QColor &fill = Colours[colour];
358         const QColor &outline = OutlineColours[colour];
359
360         if (start > right + DrawPadding || end < left - DrawPadding)
361                 return;
362
363         if (a.start_sample() == a.end_sample())
364                 draw_instant(a, p, fill, outline, text_color, h,
365                         start, y);
366         else
367                 draw_range(a, p, fill, outline, text_color, h,
368                         start, end, y);
369 }
370
371 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
372         QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
373 {
374         const QString text = a.annotations().empty() ?
375                 QString() : a.annotations().back();
376         const double w = min(p.boundingRect(QRectF(), 0, text).width(),
377                 0.0) + h;
378         const QRectF rect(x - w / 2, y - h / 2, w, h);
379
380         p.setPen(outline);
381         p.setBrush(fill);
382         p.drawRoundedRect(rect, h / 2, h / 2);
383
384         p.setPen(text_color);
385         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
386 }
387
388 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
389         QColor fill, QColor outline, QColor text_color, int h, double start,
390         double end, int y) const
391 {
392         const double top = y + .5 - h / 2;
393         const double bottom = y + .5 + h / 2;
394         const vector<QString> annotations = a.annotations();
395
396         p.setPen(outline);
397         p.setBrush(fill);
398
399         // If the two ends are within 1 pixel, draw a vertical line
400         if (start + 1.0 > end)
401         {
402                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
403                 return;
404         }
405
406         const double cap_width = min((end - start) / 4, EndCapWidth);
407
408         QPointF pts[] = {
409                 QPointF(start, y + .5f),
410                 QPointF(start + cap_width, top),
411                 QPointF(end - cap_width, top),
412                 QPointF(end, y + .5f),
413                 QPointF(end - cap_width, bottom),
414                 QPointF(start + cap_width, bottom)
415         };
416
417         p.drawConvexPolygon(pts, countof(pts));
418
419         if (annotations.empty())
420                 return;
421
422         QRectF rect(start + cap_width, y - h / 2,
423                 end - start - cap_width * 2, h);
424         p.setPen(text_color);
425
426         // Try to find an annotation that will fit
427         QString best_annotation;
428         int best_width = 0;
429
430         BOOST_FOREACH(const QString &a, annotations) {
431                 const int w = p.boundingRect(QRectF(), 0, a).width();
432                 if (w <= rect.width() && w > best_width)
433                         best_annotation = a, best_width = w;
434         }
435
436         if (best_annotation.isEmpty())
437                 best_annotation = annotations.back();
438
439         // If not ellide the last in the list
440         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
441                 best_annotation, Qt::ElideRight, rect.width()));
442 }
443
444 void DecodeTrace::draw_error(QPainter &p, const QString &message,
445         int left, int right)
446 {
447         const int y = get_y();
448
449         p.setPen(ErrorBgColour.darker());
450         p.setBrush(ErrorBgColour);
451
452         const QRectF bounding_rect =
453                 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
454         const QRectF text_rect = p.boundingRect(bounding_rect,
455                 Qt::AlignCenter, message);
456         const float r = text_rect.height() / 4;
457
458         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
459                 Qt::AbsoluteSize);
460
461         p.setPen(get_text_colour());
462         p.drawText(text_rect, message);
463 }
464
465 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
466         int right, double samples_per_pixel, double pixels_offset) 
467 {
468         using namespace pv::data;
469         using pv::data::decode::Decoder;
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 probe in the list.
479         // This works because we are currently assuming all
480         // LogicSignals have the same data/snapshot
481         BOOST_FOREACH (const shared_ptr<Decoder> &dec, stack)
482                 if (dec && !dec->probes().empty() &&
483                         ((logic_signal = (*dec->probes().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_y();
502         const double start = max(samples_decoded /
503                 samples_per_pixel - pixels_offset, left - 1.0);
504         const double end = min(sample_count / samples_per_pixel -
505                 pixels_offset, right + 1.0);
506         const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
507
508         p.setPen(QPen(Qt::NoPen));
509         p.setBrush(Qt::white);
510         p.drawRect(no_decode_rect);
511
512         p.setPen(NoDecodeColour);
513         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
514         p.drawRect(no_decode_rect);
515 }
516
517 void DecodeTrace::create_decoder_form(int index,
518         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
519         QFormLayout *form)
520 {
521         const GSList *probe;
522
523         assert(dec);
524         const srd_decoder *const decoder = dec->decoder();
525         assert(decoder);
526
527         pv::widgets::DecoderGroupBox *const group =
528                 new pv::widgets::DecoderGroupBox(
529                         QString::fromUtf8(decoder->name));
530         group->set_decoder_visible(dec->shown());
531
532         _delete_mapper.setMapping(group, index);
533         connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
534
535         _show_hide_mapper.setMapping(group, index);
536         connect(group, SIGNAL(show_hide_decoder()),
537                 &_show_hide_mapper, SLOT(map()));
538
539         QFormLayout *const decoder_form = new QFormLayout;
540         group->add_layout(decoder_form);
541
542         // Add the mandatory probes
543         for(probe = decoder->probes; probe; probe = probe->next) {
544                 const struct srd_probe *const p =
545                         (struct srd_probe *)probe->data;
546                 QComboBox *const combo = create_probe_selector(parent, dec, p);
547                 connect(combo, SIGNAL(currentIndexChanged(int)),
548                         this, SLOT(on_probe_selected(int)));
549                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
550                         .arg(QString::fromUtf8(p->name))
551                         .arg(QString::fromUtf8(p->desc)), combo);
552
553                 const ProbeSelector s = {combo, dec, p};
554                 _probe_selectors.push_back(s);
555         }
556
557         // Add the optional probes
558         for(probe = decoder->opt_probes; probe; probe = probe->next) {
559                 const struct srd_probe *const p =
560                         (struct srd_probe *)probe->data;
561                 QComboBox *const combo = create_probe_selector(parent, dec, p);
562                 connect(combo, SIGNAL(currentIndexChanged(int)),
563                         this, SLOT(on_probe_selected(int)));
564                 decoder_form->addRow(tr("<b>%1</b> (%2)")
565                         .arg(QString::fromUtf8(p->name))
566                         .arg(QString::fromUtf8(p->desc)), combo);
567
568                 const ProbeSelector s = {combo, dec, p};
569                 _probe_selectors.push_back(s);
570         }
571
572         // Add the options
573         shared_ptr<prop::binding::DecoderOptions> binding(
574                 new prop::binding::DecoderOptions(_decoder_stack, dec));
575         binding->add_properties_to_form(decoder_form, true);
576
577         _bindings.push_back(binding);
578
579         form->addRow(group);
580         _decoder_forms.push_back(group);
581 }
582
583 QComboBox* DecodeTrace::create_probe_selector(
584         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
585         const srd_probe *const probe)
586 {
587         assert(dec);
588
589         const vector< shared_ptr<Signal> > sigs = _session.get_signals();
590
591         assert(_decoder_stack);
592         const map<const srd_probe*,
593                 shared_ptr<LogicSignal> >::const_iterator probe_iter =
594                 dec->probes().find(probe);
595
596         QComboBox *selector = new QComboBox(parent);
597
598         selector->addItem("-", qVariantFromValue((void*)NULL));
599
600         if (probe_iter == dec->probes().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_probe*, 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._probe] =
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         list< shared_ptr<Decoder> >::const_iterator iter = stack.begin();
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