+void DecodeTrace::export_annotations(vector<Annotation> *annotations) const
+{
+ using namespace pv::data::decode;
+
+ GlobalSettings settings;
+ const QString dir = settings.value("MainWindow/SaveDirectory").toString();
+
+ const QString file_name = QFileDialog::getSaveFileName(
+ owner_->view(), tr("Export annotations"), dir, tr("Text Files (*.txt);;All Files (*)"));
+
+ if (file_name.isEmpty())
+ return;
+
+ QString format = settings.value(GlobalSettings::Key_Dec_ExportFormat).toString();
+ const QString quote = format.contains("%q") ? "\"" : "";
+ format = format.remove("%q");
+
+ QFile file(file_name);
+ if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
+ QTextStream out_stream(&file);
+
+ for (Annotation &ann : *annotations) {
+ const QString sample_range = QString("%1-%2") \
+ .arg(QString::number(ann.start_sample()), QString::number(ann.end_sample()));
+
+ const QString row_name = quote + ann.row()->description() + quote;
+
+ QString all_ann_text;
+ for (const QString &s : ann.annotations())
+ all_ann_text = all_ann_text + quote + s + quote + ",";
+ all_ann_text.chop(1);
+
+ const QString first_ann_text = quote + ann.annotations().front() + quote;
+
+ QString out_text = format;
+ out_text = out_text.replace("%s", sample_range);
+ out_text = out_text.replace("%d",
+ quote + QString::fromUtf8(ann.row()->decoder()->name()) + quote);
+ out_text = out_text.replace("%r", row_name);
+ out_text = out_text.replace("%1", first_ann_text);
+ out_text = out_text.replace("%a", all_ann_text);
+ out_stream << out_text << '\n';
+ }
+
+ if (out_stream.status() == QTextStream::Ok)
+ return;
+ }
+
+ QMessageBox msg(owner_->view());
+ msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
+ msg.setStandardButtons(QMessageBox::Ok);
+ msg.setIcon(QMessageBox::Warning);
+ msg.exec();
+}
+
+void DecodeTrace::initialize_row_widgets(DecodeTraceRow* r, unsigned int row_id)
+{
+ QFontMetrics m(QApplication::font());
+
+ QPalette header_palette = owner_->view()->palette();
+ QPalette selector_palette = owner_->view()->palette();
+
+ if (GlobalSettings::current_theme_is_dark()) {
+ header_palette.setColor(QPalette::Background,
+ QColor(255, 255, 255, ExpansionAreaHeaderAlpha));
+ selector_palette.setColor(QPalette::Background,
+ QColor(255, 255, 255, ExpansionAreaAlpha));
+ } else {
+ header_palette.setColor(QPalette::Background,
+ QColor(0, 0, 0, ExpansionAreaHeaderAlpha));
+ selector_palette.setColor(QPalette::Background,
+ QColor(0, 0, 0, ExpansionAreaAlpha));
+ }
+
+ const int w = m.boundingRect(r->decode_row->title()).width() + RowTitleMargin;
+ r->title_width = w;
+
+ r->container->resize(owner_->view()->viewport()->width() - r->container->pos().x(),
+ r->expanded_height - 2 * default_row_height_);
+ r->container->setVisible(false);
+
+ QVBoxLayout* vlayout = new QVBoxLayout();
+ r->container->setLayout(vlayout);
+
+ // Add header container with checkbox for this row
+ vlayout->addWidget(r->header_container);
+ vlayout->setContentsMargins(0, 0, 0, 0);
+ vlayout->setSpacing(0);
+ r->header_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ r->header_container->setMinimumSize(0, default_row_height_);
+ r->header_container->setLayout(new QVBoxLayout());
+ r->header_container->layout()->setContentsMargins(10, 2, 0, 2);
+
+ r->header_container->setAutoFillBackground(true);
+ r->header_container->setPalette(header_palette);
+
+ QCheckBox* cb = new QCheckBox();
+ r->header_container->layout()->addWidget(cb);
+ cb->setText(tr("Show this row"));
+ cb->setChecked(r->decode_row->visible());
+
+ row_show_hide_mapper_.setMapping(cb, row_id);
+ connect(cb, SIGNAL(stateChanged(int)),
+ &row_show_hide_mapper_, SLOT(map()));
+
+ // Add selector container
+ vlayout->addWidget(r->selector_container);
+ r->selector_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ r->selector_container->setMinimumSize(0, 3 * default_row_height_); // FIXME
+ r->selector_container->setLayout(new QHBoxLayout());
+
+ r->selector_container->setAutoFillBackground(true);
+ r->selector_container->setPalette(selector_palette);
+
+ // Add all classes that can be toggled
+ vector<AnnotationClass*> ann_classes = r->decode_row->ann_classes();
+
+ for (const AnnotationClass* ann_class : ann_classes) {
+ cb = new QCheckBox();
+ cb->setText(tr(ann_class->description));
+ cb->setChecked(ann_class->visible);
+
+ r->selector_container->layout()->addWidget(cb);
+
+ cb->setProperty("ann_class_ptr", QVariant::fromValue((void*)ann_class));
+ cb->setProperty("decode_trace_row_ptr", QVariant::fromValue((void*)r));
+
+ class_show_hide_mapper_.setMapping(cb, cb);
+ connect(cb, SIGNAL(stateChanged(int)),
+ &class_show_hide_mapper_, SLOT(map()));
+ }
+}
+
+void DecodeTrace::update_rows()
+{
+ lock_guard<mutex> lock(row_modification_mutex_);
+
+ for (DecodeTraceRow& r : rows_)
+ r.exists = false;
+
+ unsigned int row_id = 0;
+ for (Row* decode_row : decode_signal_->get_rows()) {
+ // Find row in our list
+ auto r_it = find_if(rows_.begin(), rows_.end(),
+ [&](DecodeTraceRow& r){ return r.decode_row == decode_row; });
+
+ DecodeTraceRow* r = nullptr;
+ if (r_it == rows_.end()) {
+ // Row doesn't exist yet, create and append it
+ DecodeTraceRow nr;
+ nr.decode_row = decode_row;
+ nr.height = default_row_height_;
+ nr.expanded_height = default_row_height_;
+ nr.currently_visible = false;
+ nr.has_hidden_classes = decode_row->has_hidden_classes();
+ nr.expand_marker_highlighted = false;
+ nr.expanding = false;
+ nr.expanded = false;
+ nr.collapsing = false;
+ nr.expand_marker_shape = default_marker_shape_;
+ nr.container = new QWidget(owner_->view()->scrollarea());
+ nr.header_container = new QWidget(nr.container);
+ nr.selector_container = new QWidget(nr.container);
+
+ rows_.push_back(nr);
+ r = &rows_.back();
+ initialize_row_widgets(r, row_id);
+ } else
+ r = &(*r_it);
+
+ r->exists = true;
+ row_id++;
+ }
+
+ // Remove any rows that no longer exist, obeying that iterators are invalidated
+ bool any_exists;
+ do {
+ any_exists = false;
+
+ for (unsigned int i = 0; i < rows_.size(); i++)
+ if (!rows_[i].exists) {
+ for (QCheckBox* cb : rows_[i].selectors)
+ delete cb;
+
+ delete rows_[i].selector_container;
+ delete rows_[i].header_container;
+ delete rows_[i].container;
+
+ rows_.erase(rows_.begin() + i);
+ any_exists = true;
+ break;
+ }
+ } while (any_exists);
+}
+
+void DecodeTrace::set_row_expanded(DecodeTraceRow* r)
+{
+ r->height = r->expanded_height;
+ r->expanding = false;
+ r->expanded = true;
+
+ // For details on this, see on_animation_timer()
+ r->expand_marker_shape.setPoint(0, 0, 0);
+ r->expand_marker_shape.setPoint(1, ArrowSize, ArrowSize);
+ r->expand_marker_shape.setPoint(2, 2*ArrowSize, 0);
+
+ r->container->resize(owner_->view()->viewport()->width() - r->container->pos().x(),
+ r->height - 2 * default_row_height_);
+
+ max_visible_rows_ = 0;
+}
+
+void DecodeTrace::set_row_collapsed(DecodeTraceRow* r)
+{
+ r->height = default_row_height_;
+ r->collapsing = false;
+ r->expanded = false;
+ r->expand_marker_shape = default_marker_shape_;
+ r->container->setVisible(false);
+
+ r->container->resize(owner_->view()->viewport()->width() - r->container->pos().x(),
+ r->height - 2 * default_row_height_);
+
+ max_visible_rows_ = 0;
+}
+
+void DecodeTrace::update_expanded_rows()
+{
+ for (DecodeTraceRow& r : rows_) {
+ r.container->move(2 * ArrowSize,
+ get_row_y(&r) + default_row_height_);
+
+ r.container->resize(owner_->view()->viewport()->width() - r.container->pos().x(),
+ r.height - 2 * default_row_height_);
+ }
+}
+
+void DecodeTrace::on_setting_changed(const QString &key, const QVariant &value)
+{
+ Trace::on_setting_changed(key, value);
+
+ if (key == GlobalSettings::Key_Dec_AlwaysShowAllRows) {
+ max_visible_rows_ = 0;
+ always_show_all_rows_ = value.toBool();
+ }
+}
+