2 * This file is part of the PulseView project.
4 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
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.
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.
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
22 #include <libsigrokdecode/libsigrokdecode.h>
31 #include <boost/functional/hash.hpp>
32 #include <boost/thread/locks.hpp>
33 #include <boost/thread/shared_mutex.hpp>
36 #include <QApplication>
38 #include <QFormLayout>
41 #include <QPushButton>
44 #include "decodetrace.hpp"
46 #include <pv/session.hpp>
47 #include <pv/data/decoderstack.hpp>
48 #include <pv/data/decode/decoder.hpp>
49 #include <pv/data/logic.hpp>
50 #include <pv/data/logicsegment.hpp>
51 #include <pv/data/decode/annotation.hpp>
52 #include <pv/view/logicsignal.hpp>
53 #include <pv/view/view.hpp>
54 #include <pv/view/viewport.hpp>
55 #include <pv/widgets/decodergroupbox.hpp>
56 #include <pv/widgets/decodermenu.hpp>
58 using boost::shared_lock;
59 using boost::shared_mutex;
60 using std::dynamic_pointer_cast;
62 using std::lock_guard;
69 using std::shared_ptr;
71 using std::unordered_set;
77 const QColor DecodeTrace::DecodeColours[4] = {
78 QColor(0xEF, 0x29, 0x29), // Red
79 QColor(0xFC, 0xE9, 0x4F), // Yellow
80 QColor(0x8A, 0xE2, 0x34), // Green
81 QColor(0x72, 0x9F, 0xCF) // Blue
84 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
85 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
87 const int DecodeTrace::ArrowSize = 4;
88 const double DecodeTrace::EndCapWidth = 5;
89 const int DecodeTrace::DrawPadding = 100;
91 const QColor DecodeTrace::Colours[16] = {
92 QColor(0xEF, 0x29, 0x29),
93 QColor(0xF6, 0x6A, 0x32),
94 QColor(0xFC, 0xAE, 0x3E),
95 QColor(0xFB, 0xCA, 0x47),
96 QColor(0xFC, 0xE9, 0x4F),
97 QColor(0xCD, 0xF0, 0x40),
98 QColor(0x8A, 0xE2, 0x34),
99 QColor(0x4E, 0xDC, 0x44),
100 QColor(0x55, 0xD7, 0x95),
101 QColor(0x64, 0xD1, 0xD2),
102 QColor(0x72, 0x9F, 0xCF),
103 QColor(0xD4, 0x76, 0xC4),
104 QColor(0x9D, 0x79, 0xB9),
105 QColor(0xAD, 0x7F, 0xA8),
106 QColor(0xC2, 0x62, 0x9B),
107 QColor(0xD7, 0x47, 0x6F)
110 const QColor DecodeTrace::OutlineColours[16] = {
111 QColor(0x77, 0x14, 0x14),
112 QColor(0x7B, 0x35, 0x19),
113 QColor(0x7E, 0x57, 0x1F),
114 QColor(0x7D, 0x65, 0x23),
115 QColor(0x7E, 0x74, 0x27),
116 QColor(0x66, 0x78, 0x20),
117 QColor(0x45, 0x71, 0x1A),
118 QColor(0x27, 0x6E, 0x22),
119 QColor(0x2A, 0x6B, 0x4A),
120 QColor(0x32, 0x68, 0x69),
121 QColor(0x39, 0x4F, 0x67),
122 QColor(0x6A, 0x3B, 0x62),
123 QColor(0x4E, 0x3C, 0x5C),
124 QColor(0x56, 0x3F, 0x54),
125 QColor(0x61, 0x31, 0x4D),
126 QColor(0x6B, 0x23, 0x37)
129 DecodeTrace::DecodeTrace(pv::Session &session,
130 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
131 Trace(QString::fromUtf8(
132 decoder_stack->stack().front()->decoder()->name)),
134 decoder_stack_(decoder_stack),
136 delete_mapper_(this),
137 show_hide_mapper_(this)
139 assert(decoder_stack_);
141 colour_ = DecodeColours[index % countof(DecodeColours)];
143 connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
144 this, SLOT(on_new_decode_data()));
145 connect(&delete_mapper_, SIGNAL(mapped(int)),
146 this, SLOT(on_delete_decoder(int)));
147 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
148 this, SLOT(on_show_hide_decoder(int)));
151 bool DecodeTrace::enabled() const
156 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
158 return decoder_stack_;
161 pair<int, int> DecodeTrace::v_extents() const
163 /// @todo Replace this with an implementation that knows the true
164 /// height of the trace
165 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
166 return make_pair(-row_height / 2, row_height * 7 / 2);
169 void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
171 Trace::paint_back(p, pp);
172 paint_axis(p, pp, get_visual_y());
175 void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
177 using namespace pv::data::decode;
179 const int text_height = ViewItemPaintParams::text_height();
180 row_height_ = (text_height * 6) / 4;
181 const int annotation_height = (text_height * 5) / 4;
183 assert(decoder_stack_);
184 const QString err = decoder_stack_->error_message();
187 draw_unresolved_period(
188 p, annotation_height, pp.left(), pp.right());
189 draw_error(p, err, pp);
193 // Iterate through the rows
194 int y = get_visual_y();
195 pair<uint64_t, uint64_t> sample_range = get_sample_range(
196 pp.left(), pp.right());
198 assert(decoder_stack_);
199 const vector<Row> rows(decoder_stack_->get_visible_rows());
201 visible_rows_.clear();
202 for (size_t i = 0; i < rows.size(); i++)
204 const Row &row = rows[i];
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());
212 vector<Annotation> annotations;
213 decoder_stack_->get_annotation_subset(annotations, row,
214 sample_range.first, sample_range.second);
215 if (!annotations.empty()) {
216 for (const Annotation &a : annotations)
217 draw_annotation(a, p, annotation_height,
221 visible_rows_.push_back(rows[i]);
226 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
229 void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
231 using namespace pv::data::decode;
235 for (size_t i = 0; i < visible_rows_.size(); i++)
237 const int y = i * row_height_ + get_visual_y();
239 p.setPen(QPen(Qt::NoPen));
240 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
244 const QPointF points[] = {
245 QPointF(pp.left(), y - ArrowSize),
246 QPointF(pp.left() + ArrowSize, y),
247 QPointF(pp.left(), y + ArrowSize)
249 p.drawPolygon(points, countof(points));
252 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
253 pp.right() - pp.left(), row_height_);
254 const QString h(visible_rows_[i].title());
255 const int f = Qt::AlignLeft | Qt::AlignVCenter |
259 p.setPen(QApplication::palette().color(QPalette::Base));
260 for (int dx = -1; dx <= 1; dx++)
261 for (int dy = -1; dy <= 1; dy++)
262 if (dx != 0 && dy != 0)
263 p.drawText(r.translated(dx, dy), f, h);
266 p.setPen(QApplication::palette().color(QPalette::WindowText));
271 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
273 using pv::data::decode::Decoder;
277 assert(decoder_stack_);
279 // Add the standard options
280 Trace::populate_popup_form(parent, form);
282 // Add the decoder options
284 channel_selectors_.clear();
285 decoder_forms_.clear();
287 const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
291 QLabel *const l = new QLabel(
292 tr("<p><i>No decoders in the stack</i></p>"));
293 l->setAlignment(Qt::AlignCenter);
298 auto iter = stack.cbegin();
299 for (int i = 0; i < (int)stack.size(); i++, iter++) {
300 shared_ptr<Decoder> dec(*iter);
301 create_decoder_form(i, dec, parent, form);
304 form->addRow(new QLabel(
305 tr("<i>* Required channels</i>"), parent));
308 // Add stacking button
309 pv::widgets::DecoderMenu *const decoder_menu =
310 new pv::widgets::DecoderMenu(parent);
311 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
312 this, SLOT(on_stack_decoder(srd_decoder*)));
314 QPushButton *const stack_button =
315 new QPushButton(tr("Stack Decoder"), parent);
316 stack_button->setMenu(decoder_menu);
318 QHBoxLayout *stack_button_box = new QHBoxLayout;
319 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
320 form->addRow(stack_button_box);
323 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
325 QMenu *const menu = Trace::create_context_menu(parent);
327 menu->addSeparator();
329 QAction *const del = new QAction(tr("Delete"), this);
330 del->setShortcuts(QKeySequence::Delete);
331 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
332 menu->addAction(del);
337 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
338 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
339 size_t base_colour) const
341 double samples_per_pixel, pixels_offset;
342 tie(pixels_offset, samples_per_pixel) =
343 get_pixels_offset_samples_per_pixel();
345 const double start = a.start_sample() / samples_per_pixel -
347 const double end = a.end_sample() / samples_per_pixel -
350 const size_t colour = (base_colour + a.format()) % countof(Colours);
351 const QColor &fill = Colours[colour];
352 const QColor &outline = OutlineColours[colour];
354 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
357 if (a.start_sample() == a.end_sample())
358 draw_instant(a, p, fill, outline, h, start, y);
360 draw_range(a, p, fill, outline, h, start, end, y);
363 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
364 QColor fill, QColor outline, int h, double x, int y) const
366 const QString text = a.annotations().empty() ?
367 QString() : a.annotations().back();
368 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
370 const QRectF rect(x - w / 2, y - h / 2, w, h);
374 p.drawRoundedRect(rect, h / 2, h / 2);
377 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
380 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
381 QColor fill, QColor outline, int h, double start,
382 double end, int y) const
384 const double top = y + .5 - h / 2;
385 const double bottom = y + .5 + h / 2;
386 const vector<QString> annotations = a.annotations();
391 // If the two ends are within 1 pixel, draw a vertical line
392 if (start + 1.0 > end)
394 p.drawLine(QPointF(start, top), QPointF(start, bottom));
398 const double cap_width = min((end - start) / 4, EndCapWidth);
401 QPointF(start, y + .5f),
402 QPointF(start + cap_width, top),
403 QPointF(end - cap_width, top),
404 QPointF(end, y + .5f),
405 QPointF(end - cap_width, bottom),
406 QPointF(start + cap_width, bottom)
409 p.drawConvexPolygon(pts, countof(pts));
411 if (annotations.empty())
414 QRectF rect(start + cap_width, y - h / 2,
415 end - start - cap_width * 2, h);
416 if (rect.width() <= 4)
421 // Try to find an annotation that will fit
422 QString best_annotation;
425 for (const QString &a : annotations) {
426 const int w = p.boundingRect(QRectF(), 0, a).width();
427 if (w <= rect.width() && w > best_width)
428 best_annotation = a, best_width = w;
431 if (best_annotation.isEmpty())
432 best_annotation = annotations.back();
434 // If not ellide the last in the list
435 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
436 best_annotation, Qt::ElideRight, rect.width()));
439 void DecodeTrace::draw_error(QPainter &p, const QString &message,
440 const ViewItemPaintParams &pp)
442 const int y = get_visual_y();
444 p.setPen(ErrorBgColour.darker());
445 p.setBrush(ErrorBgColour);
447 const QRectF bounding_rect =
448 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
449 const QRectF text_rect = p.boundingRect(bounding_rect,
450 Qt::AlignCenter, message);
451 const float r = text_rect.height() / 4;
453 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
457 p.drawText(text_rect, message);
460 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
463 using namespace pv::data;
464 using pv::data::decode::Decoder;
466 double samples_per_pixel, pixels_offset;
468 assert(decoder_stack_);
470 shared_ptr<Logic> data;
471 shared_ptr<LogicSignal> logic_signal;
473 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
475 // We get the logic data of the first channel in the list.
476 // This works because we are currently assuming all
477 // LogicSignals have the same data/segment
478 for (const shared_ptr<Decoder> &dec : stack)
479 if (dec && !dec->channels().empty() &&
480 ((logic_signal = (*dec->channels().begin()).second)) &&
481 ((data = logic_signal->logic_data())))
484 if (!data || data->logic_segments().empty())
487 const shared_ptr<LogicSegment> segment =
488 data->logic_segments().front();
490 const int64_t sample_count = (int64_t)segment->get_sample_count();
491 if (sample_count == 0)
494 const int64_t samples_decoded = decoder_stack_->samples_decoded();
495 if (sample_count == samples_decoded)
498 const int y = get_visual_y();
500 tie(pixels_offset, samples_per_pixel) =
501 get_pixels_offset_samples_per_pixel();
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);
509 p.setPen(QPen(Qt::NoPen));
510 p.setBrush(Qt::white);
511 p.drawRect(no_decode_rect);
513 p.setPen(NoDecodeColour);
514 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
515 p.drawRect(no_decode_rect);
518 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
521 assert(decoder_stack_);
523 const View *view = owner_->view();
526 const double scale = view->scale();
529 const double pixels_offset =
530 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
532 double samplerate = decoder_stack_->samplerate();
534 // Show sample rate as 1Hz when it is unknown
535 if (samplerate == 0.0)
538 return make_pair(pixels_offset, samplerate * scale);
541 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
542 int x_start, int x_end) const
544 double samples_per_pixel, pixels_offset;
545 tie(pixels_offset, samples_per_pixel) =
546 get_pixels_offset_samples_per_pixel();
548 const uint64_t start = (uint64_t)max(
549 (x_start + pixels_offset) * samples_per_pixel, 0.0);
550 const uint64_t end = (uint64_t)max(
551 (x_end + pixels_offset) * samples_per_pixel, 0.0);
553 return make_pair(start, end);
556 int DecodeTrace::get_row_at_point(const QPoint &point)
561 const int y = (point.y() - get_visual_y() + row_height_ / 2);
563 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
567 const int row = y / row_height_;
569 if (row >= (int)visible_rows_.size())
575 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
577 using namespace pv::data::decode;
582 const pair<uint64_t, uint64_t> sample_range =
583 get_sample_range(point.x(), point.x() + 1);
584 const int row = get_row_at_point(point);
588 vector<pv::data::decode::Annotation> annotations;
590 assert(decoder_stack_);
591 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
592 sample_range.first, sample_range.second);
594 return (annotations.empty()) ?
595 QString() : annotations[0].annotations().front();
598 void DecodeTrace::hover_point_changed()
602 const View *const view = owner_->view();
605 QPoint hp = view->hover_point();
606 QString ann = get_annotation_at_point(hp);
610 if (!row_height_ || ann.isEmpty()) {
611 QToolTip::hideText();
615 const int hover_row = get_row_at_point(hp);
617 QFontMetrics m(QToolTip::font());
618 const QRect text_size = m.boundingRect(QRect(), 0, ann);
620 // This is OS-specific and unfortunately we can't query it, so
621 // use an approximation to at least try to minimize the error.
622 const int padding = 8;
624 // Make sure the tool tip doesn't overlap with the mouse cursor.
625 // If it did, the tool tip would constantly hide and re-appear.
626 // We also push it up by one row so that it appears above the
627 // decode trace, not below.
628 hp.setX(hp.x() - (text_size.width() / 2) - padding);
630 hp.setY(get_visual_y() - (row_height_ / 2) +
631 (hover_row * row_height_) -
632 row_height_ - text_size.height() - padding);
634 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
637 void DecodeTrace::create_decoder_form(int index,
638 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
644 const srd_decoder *const decoder = dec->decoder();
647 const bool decoder_deletable = index > 0;
649 pv::widgets::DecoderGroupBox *const group =
650 new pv::widgets::DecoderGroupBox(
651 QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
652 group->set_decoder_visible(dec->shown());
654 if (decoder_deletable) {
655 delete_mapper_.setMapping(group, index);
656 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
659 show_hide_mapper_.setMapping(group, index);
660 connect(group, SIGNAL(show_hide_decoder()),
661 &show_hide_mapper_, SLOT(map()));
663 QFormLayout *const decoder_form = new QFormLayout;
664 group->add_layout(decoder_form);
666 // Add the mandatory channels
667 for (l = decoder->channels; l; l = l->next) {
668 const struct srd_channel *const pdch =
669 (struct srd_channel *)l->data;
670 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
671 connect(combo, SIGNAL(currentIndexChanged(int)),
672 this, SLOT(on_channel_selected(int)));
673 decoder_form->addRow(tr("<b>%1</b> (%2) *")
674 .arg(QString::fromUtf8(pdch->name))
675 .arg(QString::fromUtf8(pdch->desc)), combo);
677 const ChannelSelector s = {combo, dec, pdch};
678 channel_selectors_.push_back(s);
681 // Add the optional channels
682 for (l = decoder->opt_channels; l; l = l->next) {
683 const struct srd_channel *const pdch =
684 (struct srd_channel *)l->data;
685 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
686 connect(combo, SIGNAL(currentIndexChanged(int)),
687 this, SLOT(on_channel_selected(int)));
688 decoder_form->addRow(tr("<b>%1</b> (%2)")
689 .arg(QString::fromUtf8(pdch->name))
690 .arg(QString::fromUtf8(pdch->desc)), combo);
692 const ChannelSelector s = {combo, dec, pdch};
693 channel_selectors_.push_back(s);
697 shared_ptr<binding::Decoder> binding(
698 new binding::Decoder(decoder_stack_, dec));
699 binding->add_properties_to_form(decoder_form, true);
701 bindings_.push_back(binding);
704 decoder_forms_.push_back(group);
707 QComboBox* DecodeTrace::create_channel_selector(
708 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
709 const srd_channel *const pdch)
713 const auto sigs(session_.signals());
715 vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
716 std::sort(sig_list.begin(), sig_list.end(),
717 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
718 return a->name().compare(b->name()) < 0; });
720 assert(decoder_stack_);
721 const auto channel_iter = dec->channels().find(pdch);
723 QComboBox *selector = new QComboBox(parent);
725 selector->addItem("-", qVariantFromValue((void*)nullptr));
727 if (channel_iter == dec->channels().end())
728 selector->setCurrentIndex(0);
730 for (const shared_ptr<view::Signal> &s : sig_list) {
732 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
734 selector->addItem(s->name(),
735 qVariantFromValue((void*)s.get()));
736 if ((*channel_iter).second == s)
737 selector->setCurrentIndex(
738 selector->count() - 1);
745 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
749 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
751 const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
753 for (const ChannelSelector &s : channel_selectors_)
755 if (s.decoder_ != dec)
758 const LogicSignal *const selection =
759 (LogicSignal*)s.combo_->itemData(
760 s.combo_->currentIndex()).value<void*>();
762 for (shared_ptr<Signal> sig : sigs)
763 if (sig.get() == selection) {
764 channel_map[s.pdch_] =
765 dynamic_pointer_cast<LogicSignal>(sig);
770 dec->set_channels(channel_map);
773 void DecodeTrace::commit_channels()
775 assert(decoder_stack_);
776 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
777 commit_decoder_channels(dec);
779 decoder_stack_->begin_decode();
782 void DecodeTrace::on_new_decode_data()
785 owner_->row_item_appearance_changed(false, true);
788 void DecodeTrace::delete_pressed()
793 void DecodeTrace::on_delete()
795 session_.remove_decode_signal(this);
798 void DecodeTrace::on_channel_selected(int)
803 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
806 assert(decoder_stack_);
807 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
808 new data::decode::Decoder(decoder)));
809 decoder_stack_->begin_decode();
814 void DecodeTrace::on_delete_decoder(int index)
816 decoder_stack_->remove(index);
821 decoder_stack_->begin_decode();
824 void DecodeTrace::on_show_hide_decoder(int index)
826 using pv::data::decode::Decoder;
828 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
830 // Find the decoder in the stack
831 auto iter = stack.cbegin();
832 for (int i = 0; i < index; i++, iter++)
833 assert(iter != stack.end());
835 shared_ptr<Decoder> dec = *iter;
838 const bool show = !dec->shown();
841 assert(index < (int)decoder_forms_.size());
842 decoder_forms_[index]->set_decoder_visible(show);
845 owner_->row_item_appearance_changed(false, true);