From: Soeren Apel Date: Tue, 14 Aug 2018 08:01:34 +0000 (+0200) Subject: Enum: Use slider instead of combo box for contiguous ranges X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=73078ef7c82758194a92c185131df980c7220cbd;p=pulseview.git Enum: Use slider instead of combo box for contiguous ranges --- diff --git a/pv/prop/enum.cpp b/pv/prop/enum.cpp index 1f542b74..563f5384 100644 --- a/pv/prop/enum.cpp +++ b/pv/prop/enum.cpp @@ -20,12 +20,21 @@ #include #include #include +#include +#include #include +#include +#include +#include +#include #include "enum.hpp" using std::abs; +// Note that "using std::isnan;" is _not_ put here since that would break +// compilation on some platforms. Use "std::isnan()" instead in checks below. +using std::numeric_limits; using std::pair; using std::vector; @@ -37,15 +46,60 @@ Enum::Enum(QString name, QString desc, Getter getter, Setter setter) : Property(name, desc, getter, setter), values_(values), - selector_(nullptr) + is_range_(false), + selector_(nullptr), + slider_layout_widget_(nullptr), + slider_(nullptr), + slider_label_(nullptr) { + // Try to determine whether the values make up a range, created by e.g. + // std_gvar_min_max_step_thresholds() + + vector deltas; + double prev_value; + + for (const pair &v : values_) { + gdouble value; + if (v.first.is_of_type(Glib::VariantType("d"))) { + g_variant_get((GVariant*)(v.first.gobj()), "d", &value); + } else if (v.first.is_of_type(Glib::VariantType("(dd)"))) { + gdouble dummy; + g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &value, &dummy); + } else + break; // Type not d or (dd), so not a range that we can handle + deltas.push_back(value - prev_value); + prev_value = value; + } + + if (deltas.size() > 0) { + bool constant_delta = true; + double prev_delta = numeric_limits::quiet_NaN(); + + bool skip_first = true; + for (double delta : deltas) { + // First value is incorrect, it's the delta to 0 since no + // previous value existed yet + if (skip_first) { + skip_first = false; + continue; + } + if (std::isnan(prev_delta)) + prev_delta = delta; + + // 2*DBL_EPSILON doesn't work here, so use a workaround + if (abs(delta - prev_delta) > (delta/10)) + constant_delta = false; + + prev_delta = delta; + } + + if (constant_delta) + is_range_ = true; + } } QWidget* Enum::get_widget(QWidget *parent, bool auto_commit) { - if (selector_) - return selector_; - if (!getter_) return nullptr; @@ -53,53 +107,129 @@ QWidget* Enum::get_widget(QWidget *parent, bool auto_commit) if (!variant.gobj()) return nullptr; - selector_ = new QComboBox(parent); - for (unsigned int i = 0; i < values_.size(); i++) { - const pair &v = values_[i]; - selector_->addItem(v.second, qVariantFromValue(v.first)); - } + if (is_range_) { + // Use slider + if (slider_layout_widget_) + return slider_layout_widget_; + + slider_ = new QSlider(); + // Sliders can't handle float values, so we just use it to specify + // the number of steps that we're away from the range's beginning + slider_->setOrientation(Qt::Horizontal); + slider_->setMinimum(0); + slider_->setMaximum(values_.size() - 1); + slider_->setSingleStep(1); + + slider_label_ = new QLabel(); + + slider_layout_widget_ = new QWidget(parent); + QHBoxLayout *layout = new QHBoxLayout(slider_layout_widget_); + layout->addWidget(slider_); + layout->addWidget(slider_label_); + + update_widget(); + + if (auto_commit) + connect(slider_, SIGNAL(valueChanged(int)), + this, SLOT(on_value_changed(int))); + + return slider_layout_widget_; + + } else { + // Use combo box + if (selector_) + return selector_; + + selector_ = new QComboBox(parent); + for (unsigned int i = 0; i < values_.size(); i++) { + const pair &v = values_[i]; + selector_->addItem(v.second, qVariantFromValue(v.first)); + } - update_widget(); + update_widget(); - if (auto_commit) - connect(selector_, SIGNAL(currentIndexChanged(int)), - this, SLOT(on_current_item_changed(int))); + if (auto_commit) + connect(selector_, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_current_index_changed(int))); - return selector_; + return selector_; + } } void Enum::update_widget() { - if (!selector_) - return; - Glib::VariantBase variant = getter_(); assert(variant.gobj()); - for (unsigned int i = 0; i < values_.size(); i++) { - const pair &v = values_[i]; + if (is_range_) { - // g_variant_equal() doesn't handle floating point properly - if (v.first.is_of_type(Glib::VariantType("d"))) { - gdouble a, b; - g_variant_get(variant.gobj(), "d", &a); - g_variant_get((GVariant*)(v.first.gobj()), "d", &b); - if (abs(a - b) <= 2 * DBL_EPSILON) - selector_->setCurrentIndex(i); - } else { - // Check for "(dd)" type and handle it if it's found - if (v.first.is_of_type(Glib::VariantType("(dd)"))) { - gdouble a1, a2, b1, b2; - g_variant_get(variant.gobj(), "(dd)", &a1, &a2); - g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2); - if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \ - (abs(a2 - b2) <= 2 * DBL_EPSILON)) - selector_->setCurrentIndex(i); + // Use slider + if (!slider_layout_widget_) + return; + + for (unsigned int i = 0; i < values_.size(); i++) { + const pair &v = values_[i]; + + // g_variant_equal() doesn't handle floating point properly + if (v.first.is_of_type(Glib::VariantType("d"))) { + gdouble a, b; + g_variant_get(variant.gobj(), "d", &a); + g_variant_get((GVariant*)(v.first.gobj()), "d", &b); + + if (abs(a - b) <= 2 * DBL_EPSILON) { + slider_->setValue(i); + slider_label_->setText(v.second); + } + } else { + // Check for "(dd)" type and handle it if it's found + if (v.first.is_of_type(Glib::VariantType("(dd)"))) { + gdouble a1, a2, b1, b2; + g_variant_get(variant.gobj(), "(dd)", &a1, &a2); + g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2); + + if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \ + (abs(a2 - b2) <= 2 * DBL_EPSILON)) { + slider_->setValue(i); + slider_label_->setText(v.second); + } + + } else { + qWarning() << "Enum property" << name() << "encountered unsupported type"; + return; + } + } + } - } else - // Handle all other types - if (v.first.equal(variant)) + } else { + // Use combo box + if (!selector_) + return; + + for (unsigned int i = 0; i < values_.size(); i++) { + const pair &v = values_[i]; + + // g_variant_equal() doesn't handle floating point properly + if (v.first.is_of_type(Glib::VariantType("d"))) { + gdouble a, b; + g_variant_get(variant.gobj(), "d", &a); + g_variant_get((GVariant*)(v.first.gobj()), "d", &b); + if (abs(a - b) <= 2 * DBL_EPSILON) selector_->setCurrentIndex(i); + } else { + // Check for "(dd)" type and handle it if it's found + if (v.first.is_of_type(Glib::VariantType("(dd)"))) { + gdouble a1, a2, b1, b2; + g_variant_get(variant.gobj(), "(dd)", &a1, &a2); + g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2); + if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \ + (abs(a2 - b2) <= 2 * DBL_EPSILON)) + selector_->setCurrentIndex(i); + + } else + // Handle all other types + if (v.first.equal(variant)) + selector_->setCurrentIndex(i); + } } } } @@ -108,17 +238,36 @@ void Enum::commit() { assert(setter_); - if (!selector_) - return; + if (is_range_) { + // Use slider + if (!slider_layout_widget_) + return; + + setter_(values_.at(slider_->value()).first); + + update_widget(); + } else { + // Use combo box + if (!selector_) + return; + + const int index = selector_->currentIndex(); + if (index < 0) + return; - const int index = selector_->currentIndex(); - if (index < 0) - return; + setter_(selector_->itemData(index).value()); - setter_(selector_->itemData(index).value()); + // The combo box needs no update, it already shows the current value + // by definition: the user picked it + } +} + +void Enum::on_current_index_changed(int) +{ + commit(); } -void Enum::on_current_item_changed(int) +void Enum::on_value_changed(int) { commit(); } diff --git a/pv/prop/enum.hpp b/pv/prop/enum.hpp index 617ff071..1434e7e9 100644 --- a/pv/prop/enum.hpp +++ b/pv/prop/enum.hpp @@ -33,6 +33,8 @@ using std::vector; Q_DECLARE_METATYPE(Glib::VariantBase); class QComboBox; +class QLabel; +class QSlider; namespace pv { namespace prop { @@ -54,12 +56,18 @@ public: void commit(); private Q_SLOTS: - void on_current_item_changed(int); + void on_current_index_changed(int); + void on_value_changed(int); private: const vector< pair > values_; + bool is_range_; QComboBox *selector_; + + QWidget *slider_layout_widget_; + QSlider *slider_; + QLabel *slider_label_; }; } // namespace prop