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>
34 #include <QApplication>
36 #include <QFormLayout>
39 #include <QPushButton>
42 #include "decodetrace.hpp"
44 #include <pv/session.hpp>
45 #include <pv/data/decoderstack.hpp>
46 #include <pv/data/decode/decoder.hpp>
47 #include <pv/data/logic.hpp>
48 #include <pv/data/logicsnapshot.hpp>
49 #include <pv/data/decode/annotation.hpp>
50 #include <pv/view/logicsignal.hpp>
51 #include <pv/view/view.hpp>
52 #include <pv/view/viewport.hpp>
53 #include <pv/widgets/decodergroupbox.hpp>
54 #include <pv/widgets/decodermenu.hpp>
56 using boost::shared_lock;
57 using boost::shared_mutex;
58 using std::dynamic_pointer_cast;
60 using std::lock_guard;
67 using std::shared_ptr;
74 const QColor DecodeTrace::DecodeColours[4] = {
75 QColor(0xEF, 0x29, 0x29), // Red
76 QColor(0xFC, 0xE9, 0x4F), // Yellow
77 QColor(0x8A, 0xE2, 0x34), // Green
78 QColor(0x72, 0x9F, 0xCF) // Blue
81 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
82 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
84 const int DecodeTrace::ArrowSize = 4;
85 const double DecodeTrace::EndCapWidth = 5;
86 const int DecodeTrace::DrawPadding = 100;
88 const QColor DecodeTrace::Colours[16] = {
89 QColor(0xEF, 0x29, 0x29),
90 QColor(0xF6, 0x6A, 0x32),
91 QColor(0xFC, 0xAE, 0x3E),
92 QColor(0xFB, 0xCA, 0x47),
93 QColor(0xFC, 0xE9, 0x4F),
94 QColor(0xCD, 0xF0, 0x40),
95 QColor(0x8A, 0xE2, 0x34),
96 QColor(0x4E, 0xDC, 0x44),
97 QColor(0x55, 0xD7, 0x95),
98 QColor(0x64, 0xD1, 0xD2),
99 QColor(0x72, 0x9F, 0xCF),
100 QColor(0xD4, 0x76, 0xC4),
101 QColor(0x9D, 0x79, 0xB9),
102 QColor(0xAD, 0x7F, 0xA8),
103 QColor(0xC2, 0x62, 0x9B),
104 QColor(0xD7, 0x47, 0x6F)
107 const QColor DecodeTrace::OutlineColours[16] = {
108 QColor(0x77, 0x14, 0x14),
109 QColor(0x7B, 0x35, 0x19),
110 QColor(0x7E, 0x57, 0x1F),
111 QColor(0x7D, 0x65, 0x23),
112 QColor(0x7E, 0x74, 0x27),
113 QColor(0x66, 0x78, 0x20),
114 QColor(0x45, 0x71, 0x1A),
115 QColor(0x27, 0x6E, 0x22),
116 QColor(0x2A, 0x6B, 0x4A),
117 QColor(0x32, 0x68, 0x69),
118 QColor(0x39, 0x4F, 0x67),
119 QColor(0x6A, 0x3B, 0x62),
120 QColor(0x4E, 0x3C, 0x5C),
121 QColor(0x56, 0x3F, 0x54),
122 QColor(0x61, 0x31, 0x4D),
123 QColor(0x6B, 0x23, 0x37)
126 DecodeTrace::DecodeTrace(pv::Session &session,
127 std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
128 Trace(QString::fromUtf8(
129 decoder_stack->stack().front()->decoder()->name)),
131 decoder_stack_(decoder_stack),
134 delete_mapper_(this),
135 show_hide_mapper_(this)
137 assert(decoder_stack_);
139 colour_ = DecodeColours[index % countof(DecodeColours)];
141 connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
142 this, SLOT(on_new_decode_data()));
143 connect(&delete_mapper_, SIGNAL(mapped(int)),
144 this, SLOT(on_delete_decoder(int)));
145 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
146 this, SLOT(on_show_hide_decoder(int)));
149 bool DecodeTrace::enabled() const
154 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
156 return decoder_stack_;
159 pair<int, int> DecodeTrace::v_extents() const
161 /// @todo Replace this with an implementation that knows the true
162 /// height of the trace
163 QFontMetrics m(QApplication::font());
164 const int text_height = m.boundingRect(QRect(), 0, "Tg").height();
165 const int row_height = (text_height * 6) / 4;
166 return make_pair(-row_height / 2, row_height * 7 / 2);
169 void DecodeTrace::paint_back(QPainter &p, const RowItemPaintParams &pp)
171 Trace::paint_back(p, pp);
172 paint_axis(p, pp, get_visual_y());
175 void DecodeTrace::paint_mid(QPainter &p, const RowItemPaintParams &pp)
177 using namespace pv::data::decode;
179 QFontMetrics m(QApplication::font());
180 text_height_ = m.boundingRect(QRect(), 0, "Tg").height();
181 row_height_ = (text_height_ * 6) / 4;
182 const int annotation_height = (text_height_ * 5) / 4;
184 assert(decoder_stack_);
185 const QString err = decoder_stack_->error_message();
188 draw_unresolved_period(
189 p, annotation_height, pp.left(), pp.right());
190 draw_error(p, err, pp);
194 // Iterate through the rows
195 int y = get_visual_y();
196 pair<uint64_t, uint64_t> sample_range = get_sample_range(
197 pp.left(), pp.right());
199 assert(decoder_stack_);
200 const vector<Row> rows(decoder_stack_->get_visible_rows());
202 visible_rows_.clear();
203 for (size_t i = 0; i < rows.size(); i++)
205 const Row &row = rows[i];
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());
213 vector<Annotation> annotations;
214 decoder_stack_->get_annotation_subset(annotations, row,
215 sample_range.first, sample_range.second);
216 if (!annotations.empty()) {
217 for (const Annotation &a : annotations)
218 draw_annotation(a, p, get_text_colour(),
219 annotation_height, pp, y, base_colour);
222 visible_rows_.push_back(rows[i]);
227 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
230 void DecodeTrace::paint_fore(QPainter &p, const RowItemPaintParams &pp)
232 using namespace pv::data::decode;
236 for (size_t i = 0; i < visible_rows_.size(); i++)
238 const int y = i * row_height_ + get_visual_y();
240 p.setPen(QPen(Qt::NoPen));
241 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
245 const QPointF points[] = {
246 QPointF(pp.left(), y - ArrowSize),
247 QPointF(pp.left() + ArrowSize, y),
248 QPointF(pp.left(), y + ArrowSize)
250 p.drawPolygon(points, countof(points));
253 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
254 pp.right() - pp.left(), row_height_);
255 const QString h(visible_rows_[i].title());
256 const int f = Qt::AlignLeft | Qt::AlignVCenter |
260 p.setPen(QApplication::palette().color(QPalette::Base));
261 for (int dx = -1; dx <= 1; dx++)
262 for (int dy = -1; dy <= 1; dy++)
263 if (dx != 0 && dy != 0)
264 p.drawText(r.translated(dx, dy), f, h);
267 p.setPen(QApplication::palette().color(QPalette::WindowText));
272 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
274 using pv::data::decode::Decoder;
278 assert(decoder_stack_);
280 // Add the standard options
281 Trace::populate_popup_form(parent, form);
283 // Add the decoder options
285 channel_selectors_.clear();
286 decoder_forms_.clear();
288 const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
292 QLabel *const l = new QLabel(
293 tr("<p><i>No decoders in the stack</i></p>"));
294 l->setAlignment(Qt::AlignCenter);
299 auto iter = stack.cbegin();
300 for (int i = 0; i < (int)stack.size(); i++, iter++) {
301 shared_ptr<Decoder> dec(*iter);
302 create_decoder_form(i, dec, parent, form);
305 form->addRow(new QLabel(
306 tr("<i>* Required channels</i>"), parent));
309 // Add stacking button
310 pv::widgets::DecoderMenu *const decoder_menu =
311 new pv::widgets::DecoderMenu(parent);
312 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
313 this, SLOT(on_stack_decoder(srd_decoder*)));
315 QPushButton *const stack_button =
316 new QPushButton(tr("Stack Decoder"), parent);
317 stack_button->setMenu(decoder_menu);
319 QHBoxLayout *stack_button_box = new QHBoxLayout;
320 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
321 form->addRow(stack_button_box);
324 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
326 QMenu *const menu = Trace::create_context_menu(parent);
328 menu->addSeparator();
330 QAction *const del = new QAction(tr("Delete"), this);
331 del->setShortcuts(QKeySequence::Delete);
332 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
333 menu->addAction(del);
338 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
339 QPainter &p, QColor text_color, int h, const RowItemPaintParams &pp, int y,
340 size_t base_colour) const
342 double samples_per_pixel, pixels_offset;
343 tie(pixels_offset, samples_per_pixel) =
344 get_pixels_offset_samples_per_pixel();
346 const double start = a.start_sample() / samples_per_pixel -
348 const double end = a.end_sample() / samples_per_pixel -
351 const size_t colour = (base_colour + a.format()) % countof(Colours);
352 const QColor &fill = Colours[colour];
353 const QColor &outline = OutlineColours[colour];
355 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
358 if (a.start_sample() == a.end_sample())
359 draw_instant(a, p, fill, outline, text_color, h,
362 draw_range(a, p, fill, outline, text_color, h,
366 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
367 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
369 const QString text = a.annotations().empty() ?
370 QString() : a.annotations().back();
371 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
373 const QRectF rect(x - w / 2, y - h / 2, w, h);
377 p.drawRoundedRect(rect, h / 2, h / 2);
379 p.setPen(text_color);
380 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
383 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
384 QColor fill, QColor outline, QColor text_color, int h, double start,
385 double end, int y) const
387 const double top = y + .5 - h / 2;
388 const double bottom = y + .5 + h / 2;
389 const vector<QString> annotations = a.annotations();
394 // If the two ends are within 1 pixel, draw a vertical line
395 if (start + 1.0 > end)
397 p.drawLine(QPointF(start, top), QPointF(start, bottom));
401 const double cap_width = min((end - start) / 4, EndCapWidth);
404 QPointF(start, y + .5f),
405 QPointF(start + cap_width, top),
406 QPointF(end - cap_width, top),
407 QPointF(end, y + .5f),
408 QPointF(end - cap_width, bottom),
409 QPointF(start + cap_width, bottom)
412 p.drawConvexPolygon(pts, countof(pts));
414 if (annotations.empty())
417 QRectF rect(start + cap_width, y - h / 2,
418 end - start - cap_width * 2, h);
419 if (rect.width() <= 4)
422 p.setPen(text_color);
424 // Try to find an annotation that will fit
425 QString best_annotation;
428 for (const QString &a : annotations) {
429 const int w = p.boundingRect(QRectF(), 0, a).width();
430 if (w <= rect.width() && w > best_width)
431 best_annotation = a, best_width = w;
434 if (best_annotation.isEmpty())
435 best_annotation = annotations.back();
437 // If not ellide the last in the list
438 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
439 best_annotation, Qt::ElideRight, rect.width()));
442 void DecodeTrace::draw_error(QPainter &p, const QString &message,
443 const RowItemPaintParams &pp)
445 const int y = get_visual_y();
447 p.setPen(ErrorBgColour.darker());
448 p.setBrush(ErrorBgColour);
450 const QRectF bounding_rect =
451 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
452 const QRectF text_rect = p.boundingRect(bounding_rect,
453 Qt::AlignCenter, message);
454 const float r = text_rect.height() / 4;
456 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
459 p.setPen(get_text_colour());
460 p.drawText(text_rect, message);
463 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
466 using namespace pv::data;
467 using pv::data::decode::Decoder;
469 double samples_per_pixel, pixels_offset;
471 assert(decoder_stack_);
473 shared_ptr<Logic> data;
474 shared_ptr<LogicSignal> logic_signal;
476 const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
478 // We get the logic data of the first channel in the list.
479 // This works because we are currently assuming all
480 // LogicSignals have the same data/snapshot
481 for (const shared_ptr<Decoder> &dec : stack)
482 if (dec && !dec->channels().empty() &&
483 ((logic_signal = (*dec->channels().begin()).second)) &&
484 ((data = logic_signal->logic_data())))
487 if (!data || data->get_snapshots().empty())
490 const shared_ptr<LogicSnapshot> snapshot =
491 data->get_snapshots().front();
493 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
494 if (sample_count == 0)
497 const int64_t samples_decoded = decoder_stack_->samples_decoded();
498 if (sample_count == samples_decoded)
501 const int y = get_visual_y();
503 tie(pixels_offset, samples_per_pixel) =
504 get_pixels_offset_samples_per_pixel();
506 const double start = max(samples_decoded /
507 samples_per_pixel - pixels_offset, left - 1.0);
508 const double end = min(sample_count / samples_per_pixel -
509 pixels_offset, right + 1.0);
510 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
512 p.setPen(QPen(Qt::NoPen));
513 p.setBrush(Qt::white);
514 p.drawRect(no_decode_rect);
516 p.setPen(NoDecodeColour);
517 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
518 p.drawRect(no_decode_rect);
521 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
524 assert(decoder_stack_);
526 const View *view = owner_->view();
529 const double scale = view->scale();
532 const double pixels_offset =
533 (view->offset() - decoder_stack_->get_start_time()) / scale;
535 double samplerate = decoder_stack_->samplerate();
537 // Show sample rate as 1Hz when it is unknown
538 if (samplerate == 0.0)
541 return make_pair(pixels_offset, samplerate * scale);
544 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
545 int x_start, int x_end) const
547 double samples_per_pixel, pixels_offset;
548 tie(pixels_offset, samples_per_pixel) =
549 get_pixels_offset_samples_per_pixel();
551 const uint64_t start = (uint64_t)max(
552 (x_start + pixels_offset) * samples_per_pixel, 0.0);
553 const uint64_t end = (uint64_t)max(
554 (x_end + pixels_offset) * samples_per_pixel, 0.0);
556 return make_pair(start, end);
559 int DecodeTrace::get_row_at_point(const QPoint &point)
564 const int row = (point.y() - get_visual_y() + row_height_ / 2) /
566 if (row < 0 || row >= (int)visible_rows_.size())
572 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
574 using namespace pv::data::decode;
579 const pair<uint64_t, uint64_t> sample_range =
580 get_sample_range(point.x(), point.x() + 1);
581 const int row = get_row_at_point(point);
585 vector<pv::data::decode::Annotation> annotations;
587 assert(decoder_stack_);
588 decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
589 sample_range.first, sample_range.second);
591 return (annotations.empty()) ?
592 QString() : annotations[0].annotations().front();
595 void DecodeTrace::hide_hover_annotation()
597 QToolTip::hideText();
600 void DecodeTrace::hover_point_changed()
604 const View *const view = owner_->view();
607 QPoint hp = view->hover_point();
608 QString ann = get_annotation_at_point(hp);
612 if (!row_height_ || ann.isEmpty()) {
613 hide_hover_annotation();
617 const int hover_row = get_row_at_point(hp);
619 QFontMetrics m(QToolTip::font());
620 const QRect text_size = m.boundingRect(QRect(), 0, ann);
622 // This is OS-specific and unfortunately we can't query it, so
623 // use an approximation to at least try to minimize the error.
624 const int padding = 8;
626 // Make sure the tool tip doesn't overlap with the mouse cursor.
627 // If it did, the tool tip would constantly hide and re-appear.
628 // We also push it up by one row so that it appears above the
629 // decode trace, not below.
630 hp.setX(hp.x() - (text_size.width() / 2) - padding);
632 hp.setY(get_visual_y() - (row_height_ / 2) +
633 (hover_row * row_height_) -
634 row_height_ - text_size.height());
636 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
639 void DecodeTrace::create_decoder_form(int index,
640 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
646 const srd_decoder *const decoder = dec->decoder();
649 pv::widgets::DecoderGroupBox *const group =
650 new pv::widgets::DecoderGroupBox(
651 QString::fromUtf8(decoder->name));
652 group->set_decoder_visible(dec->shown());
654 delete_mapper_.setMapping(group, index);
655 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
657 show_hide_mapper_.setMapping(group, index);
658 connect(group, SIGNAL(show_hide_decoder()),
659 &show_hide_mapper_, SLOT(map()));
661 QFormLayout *const decoder_form = new QFormLayout;
662 group->add_layout(decoder_form);
664 // Add the mandatory channels
665 for(l = decoder->channels; l; l = l->next) {
666 const struct srd_channel *const pdch =
667 (struct srd_channel *)l->data;
668 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
669 connect(combo, SIGNAL(currentIndexChanged(int)),
670 this, SLOT(on_channel_selected(int)));
671 decoder_form->addRow(tr("<b>%1</b> (%2) *")
672 .arg(QString::fromUtf8(pdch->name))
673 .arg(QString::fromUtf8(pdch->desc)), combo);
675 const ChannelSelector s = {combo, dec, pdch};
676 channel_selectors_.push_back(s);
679 // Add the optional channels
680 for(l = decoder->opt_channels; l; l = l->next) {
681 const struct srd_channel *const pdch =
682 (struct srd_channel *)l->data;
683 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
684 connect(combo, SIGNAL(currentIndexChanged(int)),
685 this, SLOT(on_channel_selected(int)));
686 decoder_form->addRow(tr("<b>%1</b> (%2)")
687 .arg(QString::fromUtf8(pdch->name))
688 .arg(QString::fromUtf8(pdch->desc)), combo);
690 const ChannelSelector s = {combo, dec, pdch};
691 channel_selectors_.push_back(s);
695 shared_ptr<prop::binding::DecoderOptions> binding(
696 new prop::binding::DecoderOptions(decoder_stack_, dec));
697 binding->add_properties_to_form(decoder_form, true);
699 bindings_.push_back(binding);
702 decoder_forms_.push_back(group);
705 QComboBox* DecodeTrace::create_channel_selector(
706 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
707 const srd_channel *const pdch)
711 shared_lock<shared_mutex> lock(session_.signals_mutex());
712 const vector< shared_ptr<Signal> > &sigs(session_.signals());
714 assert(decoder_stack_);
715 const auto channel_iter = dec->channels().find(pdch);
717 QComboBox *selector = new QComboBox(parent);
719 selector->addItem("-", qVariantFromValue((void*)NULL));
721 if (channel_iter == dec->channels().end())
722 selector->setCurrentIndex(0);
724 for(size_t i = 0; i < sigs.size(); i++) {
725 const shared_ptr<view::Signal> s(sigs[i]);
728 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
730 selector->addItem(s->name(),
731 qVariantFromValue((void*)s.get()));
732 if ((*channel_iter).second == s)
733 selector->setCurrentIndex(i + 1);
740 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
744 map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
746 shared_lock<shared_mutex> lock(session_.signals_mutex());
747 const vector< shared_ptr<Signal> > &sigs(session_.signals());
749 for (const ChannelSelector &s : channel_selectors_)
751 if(s.decoder_ != dec)
754 const LogicSignal *const selection =
755 (LogicSignal*)s.combo_->itemData(
756 s.combo_->currentIndex()).value<void*>();
758 for (shared_ptr<Signal> sig : sigs)
759 if(sig.get() == selection) {
760 channel_map[s.pdch_] =
761 dynamic_pointer_cast<LogicSignal>(sig);
766 dec->set_channels(channel_map);
769 void DecodeTrace::commit_channels()
771 assert(decoder_stack_);
772 for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
773 commit_decoder_channels(dec);
775 decoder_stack_->begin_decode();
778 void DecodeTrace::on_new_decode_data()
781 owner_->appearance_changed(false, true);
784 void DecodeTrace::delete_pressed()
789 void DecodeTrace::on_delete()
791 session_.remove_decode_signal(this);
794 void DecodeTrace::on_channel_selected(int)
799 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
802 assert(decoder_stack_);
803 decoder_stack_->push(shared_ptr<data::decode::Decoder>(
804 new data::decode::Decoder(decoder)));
805 decoder_stack_->begin_decode();
810 void DecodeTrace::on_delete_decoder(int index)
812 decoder_stack_->remove(index);
817 decoder_stack_->begin_decode();
820 void DecodeTrace::on_show_hide_decoder(int index)
822 using pv::data::decode::Decoder;
824 const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
826 // Find the decoder in the stack
827 auto iter = stack.cbegin();
828 for(int i = 0; i < index; i++, iter++)
829 assert(iter != stack.end());
831 shared_ptr<Decoder> dec = *iter;
834 const bool show = !dec->shown();
837 assert(index < (int)decoder_forms_.size());
838 decoder_forms_[index]->set_decoder_visible(show);
841 owner_->appearance_changed(false, true);