]> sigrok.org Git - pulseview.git/blobdiff - pv/views/trace/decodetrace.cpp
DecodeTrace: Highlight row expand markers when a class is hidden
[pulseview.git] / pv / views / trace / decodetrace.cpp
index 3555d57635ec28588abbaa6d5a83781320baffcf..0f625b1a3b4bd62ba37f6b309056d81fa9457773 100644 (file)
@@ -32,6 +32,7 @@ extern "C" {
 #include <QAction>
 #include <QApplication>
 #include <QClipboard>
+#include <QCheckBox>
 #include <QComboBox>
 #include <QFileDialog>
 #include <QFormLayout>
@@ -71,6 +72,7 @@ using std::tie;
 using std::vector;
 
 using pv::data::decode::Annotation;
+using pv::data::decode::AnnotationClass;
 using pv::data::decode::Row;
 using pv::data::decode::DecodeChannel;
 using pv::data::DecodeSignal;
@@ -85,6 +87,9 @@ namespace trace {
 
 const QColor DecodeTrace::ErrorBgColor = QColor(0xEF, 0x29, 0x29);
 const QColor DecodeTrace::NoDecodeColor = QColor(0x88, 0x8A, 0x85);
+const QColor DecodeTrace::ExpandMarkerWarnColor = QColor(0xFF, 0xA5, 0x00); // QColorConstants::Svg::orange
+const uint8_t DecodeTrace::ExpansionAreaHeaderAlpha = 10 * 255 / 100;
+const uint8_t DecodeTrace::ExpansionAreaAlpha = 5 * 255 / 100;
 
 const int DecodeTrace::ArrowSize = 6;
 const double DecodeTrace::EndCapWidth = 5;
@@ -100,7 +105,8 @@ DecodeTrace::DecodeTrace(pv::Session &session,
        session_(session),
        max_visible_rows_(0),
        delete_mapper_(this),
-       show_hide_mapper_(this)
+       show_hide_mapper_(this),
+       row_show_hide_mapper_(this)
 {
        decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
 
@@ -140,6 +146,10 @@ DecodeTrace::DecodeTrace(pv::Session &session,
                this, SLOT(on_delete_decoder(int)));
        connect(&show_hide_mapper_, SIGNAL(mapped(int)),
                this, SLOT(on_show_hide_decoder(int)));
+       connect(&row_show_hide_mapper_, SIGNAL(mapped(int)),
+               this, SLOT(on_show_hide_row(int)));
+       connect(&class_show_hide_mapper_, SIGNAL(mapped(QWidget*)),
+               this, SLOT(on_show_hide_class(QWidget*)));
 
        connect(&delayed_trace_updater_, SIGNAL(timeout()),
                this, SLOT(on_delayed_trace_update()));
@@ -159,8 +169,14 @@ DecodeTrace::~DecodeTrace()
 {
        GlobalSettings::remove_change_handler(this);
 
-       for (RowData& r : rows_)
+       for (DecodeTraceRow& r : rows_) {
+               for (QCheckBox* cb : r.selectors)
+                       delete cb;
+
+               delete r.selector_container;
+               delete r.header_container;
                delete r.container;
+       }
 }
 
 bool DecodeTrace::enabled() const
@@ -180,7 +196,7 @@ pair<int, int> DecodeTrace::v_extents() const
                return make_pair(-default_row_height_, default_row_height_);
 
        unsigned int height = 0;
-       for (const RowData& r : rows_)
+       for (const DecodeTraceRow& r : rows_)
                if (r.currently_visible)
                        height += r.height;
 
@@ -208,14 +224,14 @@ void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
        sample_range.second = min((int64_t)sample_range.second,
                decode_signal_->get_decoded_sample_count(current_segment_, false));
 
-       for (RowData& r : rows_)
-               r.currently_visible = false;
        visible_rows_ = 0;
        int y = get_visual_y();
 
-       for (RowData& r : rows_) {
-               // If an entire decoder is hidden, we don't want to fetch annotations
-               if (!r.decode_row.decoder()->shown()) {
+       for (DecodeTraceRow& r : rows_) {
+               // If the row is hidden, we don't want to fetch annotations
+               assert(r.decode_row);
+               assert(r.decode_row->decoder());
+               if ((!r.decode_row->decoder()->shown()) || (!r.decode_row->visible())) {
                        r.currently_visible = false;
                        continue;
                }
@@ -226,12 +242,15 @@ void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
 
                // Show row if there are visible annotations or when user wants to see
                // all rows that have annotations somewhere and this one is one of them
-               size_t ann_count = decode_signal_->get_annotation_count(r.decode_row, current_segment_);
-               r.currently_visible = !annotations.empty() || (always_show_all_rows_ && (ann_count > 0));
+               r.currently_visible = !annotations.empty();
+               if (!r.currently_visible) {
+                       size_t ann_count = decode_signal_->get_annotation_count(r.decode_row, current_segment_);
+                       r.currently_visible = always_show_all_rows_ && (ann_count > 0);
+               }
 
                if (r.currently_visible) {
                        draw_annotations(annotations, p, annotation_height_, pp, y,
-                               get_row_color(r.decode_row.index()), r.title_width);
+                               get_row_color(r.decode_row->index()), r.title_width);
                        y += r.height;
                        visible_rows_++;
                }
@@ -256,7 +275,7 @@ void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
 {
        unsigned int y = get_visual_y();
 
-       for (const RowData& r : rows_) {
+       for (const DecodeTraceRow& r : rows_) {
                if (!r.currently_visible)
                        continue;
 
@@ -264,6 +283,8 @@ void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
 
                if (r.expand_marker_highlighted)
                        p.setBrush(QApplication::palette().brush(QPalette::Highlight));
+               else if (r.has_hidden_classes)
+                       p.setBrush(ExpandMarkerWarnColor);
                else
                        p.setBrush(QApplication::palette().brush(QPalette::WindowText));
 
@@ -276,7 +297,7 @@ void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
 
                const QRect text_rect(pp.left() + ArrowSize * 2, y - r.height / 2,
                        pp.right() - pp.left(), r.height);
-               const QString h(r.decode_row.title());
+               const QString h(r.decode_row->title());
                const int f = Qt::AlignLeft | Qt::AlignVCenter |
                        Qt::TextDontClip;
 
@@ -304,7 +325,7 @@ void DecodeTrace::update_stack_button()
 
        // Only show decoders in the menu that can be stacked onto the last one in the stack
        if (!stack.empty()) {
-               const srd_decoder* d = stack.back()->decoder();
+               const srd_decoder* d = stack.back()->get_srd_decoder();
 
                if (d->outputs) {
                        pv::widgets::DecoderMenu *const decoder_menu =
@@ -402,9 +423,9 @@ QMenu* DecodeTrace::create_view_context_menu(QWidget *parent, QPoint &click_pos)
        }
 
        selected_row_ = nullptr;
-       const RowData* r = get_row_at_point(click_pos);
+       const DecodeTraceRow* r = get_row_at_point(click_pos);
        if (r)
-               selected_row_ = &(r->decode_row);
+               selected_row_ = r->decode_row;
 
        const View *const view = owner_->view();
        assert(view);
@@ -504,10 +525,10 @@ void DecodeTrace::hover_point_changed(const QPoint &hp)
 
        assert(owner_);
 
-       RowData* hover_row = get_row_at_point(hp);
+       DecodeTraceRow* hover_row = get_row_at_point(hp);
 
        // Row expansion marker handling
-       for (RowData& r : rows_)
+       for (DecodeTraceRow& r : rows_)
                r.expand_marker_highlighted = false;
 
        if (hover_row) {
@@ -553,7 +574,7 @@ void DecodeTrace::hover_point_changed(const QPoint &hp)
 void DecodeTrace::mouse_left_press_event(const QMouseEvent* event)
 {
        // Handle row expansion marker
-       for (RowData& r : rows_) {
+       for (DecodeTraceRow& r : rows_) {
                if (!r.expand_marker_highlighted)
                        continue;
 
@@ -570,14 +591,14 @@ void DecodeTrace::mouse_left_press_event(const QMouseEvent* event)
                                r.expanding = true;
                                r.anim_shape = 0;
                                r.container->setVisible(true);
+                               QApplication::processEvents();
+                               r.expanded_height = 5 * default_row_height_ + r.container->size().height();
                        }
 
                        r.animation_step = 0;
                        r.anim_height = r.height;
 
-                       r.container->move(2 * ArrowSize,
-                               get_row_y(&r) + default_row_height_);
-
+                       update_expanded_rows();
                        animation_timer_.start();
                }
        }
@@ -929,13 +950,13 @@ QColor DecodeTrace::get_annotation_color(QColor row_color, int annotation_index)
        return color;
 }
 
-unsigned int DecodeTrace::get_row_y(const RowData* row) const
+unsigned int DecodeTrace::get_row_y(const DecodeTraceRow* row) const
 {
        assert(row);
 
        unsigned int y = get_visual_y();
 
-       for (const RowData& r : rows_) {
+       for (const DecodeTraceRow& r : rows_) {
                if (!r.currently_visible)
                        continue;
 
@@ -948,11 +969,11 @@ unsigned int DecodeTrace::get_row_y(const RowData* row) const
        return y;
 }
 
-RowData* DecodeTrace::get_row_at_point(const QPoint &point)
+DecodeTraceRow* DecodeTrace::get_row_at_point(const QPoint &point)
 {
        int y = get_visual_y() - (default_row_height_ / 2);
 
-       for (RowData& r : rows_) {
+       for (DecodeTraceRow& r : rows_) {
                if (!r.currently_visible)
                        continue;
 
@@ -974,7 +995,7 @@ const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
 
        const pair<uint64_t, uint64_t> sample_range =
                get_view_sample_range(point.x(), point.x() + 1);
-       const RowData* r = get_row_at_point(point);
+       const DecodeTraceRow* r = get_row_at_point(point);
 
        if (!r)
                return QString();
@@ -998,7 +1019,7 @@ void DecodeTrace::create_decoder_form(int index,
        GlobalSettings settings;
 
        assert(dec);
-       const srd_decoder *const decoder = dec->decoder();
+       const srd_decoder *const decoder = dec->get_srd_decoder();
        assert(decoder);
 
        const bool decoder_deletable = index > 0;
@@ -1139,7 +1160,7 @@ void DecodeTrace::export_annotations(vector<Annotation> *annotations) const
                        const QString sample_range = QString("%1-%2") \
                                .arg(QString::number(ann.start_sample()), QString::number(ann.end_sample()));
 
-                       const QString class_name = quote + ann.row()->class_name() + quote;
+                       const QString row_name = quote + ann.row()->description() + quote;
 
                        QString all_ann_text;
                        for (const QString &s : ann.annotations())
@@ -1152,7 +1173,7 @@ void DecodeTrace::export_annotations(vector<Annotation> *annotations) const
                        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("%c", class_name);
+                       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';
@@ -1169,48 +1190,123 @@ void DecodeTrace::export_annotations(vector<Annotation> *annotations) const
        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_);
 
-       QFontMetrics m(QApplication::font());
-
-       for (RowData& r : rows_)
+       for (DecodeTraceRow& r : rows_)
                r.exists = false;
 
-       for (const Row& decode_row : decode_signal_->get_rows()) {
+       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(),
-                       [&](RowData& r){ return r.decode_row == decode_row; });
+                       [&](DecodeTraceRow& r){ return r.decode_row == decode_row; });
 
-               RowData* r = nullptr;
+               DecodeTraceRow* r = nullptr;
                if (r_it == rows_.end()) {
                        // Row doesn't exist yet, create and append it
-                       RowData nr;
+                       DecodeTraceRow nr;
                        nr.decode_row = decode_row;
                        nr.height = default_row_height_;
-                       nr.expanded_height = 5*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()->viewport());
+                       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;
-
-               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);
+               row_id++;
        }
 
        // Remove any rows that no longer exist, obeying that iterators are invalidated
@@ -1220,7 +1316,13 @@ void DecodeTrace::update_rows()
 
                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;
@@ -1228,6 +1330,46 @@ void DecodeTrace::update_rows()
        } 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_);
+       }
+}
+
 void DecodeTrace::on_setting_changed(const QString &key, const QVariant &value)
 {
        Trace::on_setting_changed(key, value);
@@ -1344,8 +1486,38 @@ void DecodeTrace::on_show_hide_decoder(int index)
                owner_->extents_changed(false, true);
        }
 
-       if (owner_)
-               owner_->row_item_appearance_changed(false, true);
+       owner_->row_item_appearance_changed(false, true);
+}
+
+void DecodeTrace::on_show_hide_row(int row_id)
+{
+       if (row_id >= (int)rows_.size())
+               return;
+
+       set_row_collapsed(&rows_[row_id]);
+       rows_[row_id].decode_row->set_visible(!rows_[row_id].decode_row->visible());
+
+       // Force re-calculation of the trace height, see paint_mid()
+       max_visible_rows_ = 0;
+       owner_->extents_changed(false, true);
+       owner_->row_item_appearance_changed(false, true);
+}
+
+void DecodeTrace::on_show_hide_class(QWidget* sender)
+{
+       void* ann_class_ptr = sender->property("ann_class_ptr").value<void*>();
+       assert(ann_class_ptr);
+       AnnotationClass* ann_class = (AnnotationClass*)ann_class_ptr;
+
+       ann_class->visible = !ann_class->visible;
+
+       void* row_ptr = sender->property("decode_trace_row_ptr").value<void*>();
+       assert(row_ptr);
+       DecodeTraceRow* row = (DecodeTraceRow*)row_ptr;
+
+       row->has_hidden_classes = row->decode_row->has_hidden_classes();
+
+       owner_->row_item_appearance_changed(false, true);
 }
 
 void DecodeTrace::on_copy_annotation_to_clipboard()
@@ -1357,7 +1529,7 @@ void DecodeTrace::on_copy_annotation_to_clipboard()
 
        vector<Annotation> *annotations = new vector<Annotation>();
 
-       decode_signal_->get_annotation_subset(*annotations, *selected_row_,
+       decode_signal_->get_annotation_subset(*annotations, selected_row_,
                current_segment_, selected_sample_range_.first, selected_sample_range_.first);
 
        if (annotations->empty())
@@ -1445,7 +1617,7 @@ void DecodeTrace::on_export_row_from_here()
 
        vector<Annotation> *annotations = new vector<Annotation>();
 
-       decode_signal_->get_annotation_subset(*annotations, *selected_row_,
+       decode_signal_->get_annotation_subset(*annotations, selected_row_,
                current_segment_, selected_sample_range_.first, selected_sample_range_.second);
 
        if (annotations->empty())
@@ -1474,7 +1646,7 @@ void DecodeTrace::on_animation_timer()
 {
        bool animation_finished = true;
 
-       for (RowData& r : rows_) {
+       for (DecodeTraceRow& r : rows_) {
                if (!(r.expanding || r.collapsing))
                        continue;
 
@@ -1486,11 +1658,8 @@ void DecodeTrace::on_animation_timer()
                                r.height = r.anim_height;
                                r.anim_shape += ArrowSize / (float)AnimationDurationInTicks;
                                animation_finished = false;
-                       } else {
-                               r.height = std::min(r.height, r.expanded_height);
-                               r.expanding = false;
-                               r.expanded = true;
-                       }
+                       } else
+                               set_row_expanded(&r);
                }
 
                if (r.collapsing) {
@@ -1499,13 +1668,8 @@ void DecodeTrace::on_animation_timer()
                                r.height = r.anim_height;
                                r.anim_shape -= ArrowSize / (float)AnimationDurationInTicks;
                                animation_finished = false;
-                       } else {
-                               r.height = std::max(r.height, default_row_height_);
-                               r.collapsing = false;
-                               r.expanded = false;
-                               r.expand_marker_shape = default_marker_shape_;
-                               r.container->setVisible(false);
-                       }
+                       } else
+                               set_row_collapsed(&r);
                }
 
                // The expansion marker shape switches between
@@ -1515,16 +1679,13 @@ void DecodeTrace::on_animation_timer()
                r.expand_marker_shape.setPoint(0, 0, -ArrowSize + r.anim_shape);
                r.expand_marker_shape.setPoint(1, ArrowSize, r.anim_shape);
                r.expand_marker_shape.setPoint(2, 2*r.anim_shape, ArrowSize - r.anim_shape);
-
-               r.container->resize(owner_->view()->viewport()->width() - r.container->pos().x(),
-                       r.height - 2 * default_row_height_);
        }
 
        if (animation_finished)
                animation_timer_.stop();
 
-       if (owner_)
-               owner_->row_item_appearance_changed(false, true);
+       owner_->extents_changed(false, true);
+       owner_->row_item_appearance_changed(false, true);
 }
 
 } // namespace trace