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