]> sigrok.org Git - pulseview.git/blobdiff - pv/prop/enum.cpp
Fix #1035 by checking for exceptions when accessing config
[pulseview.git] / pv / prop / enum.cpp
index 894ba9f8e2754ded46f9e07b783484871d25eade..d62c901cf4c70d2c3848b3a3875d9d088596b794 100644 (file)
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <assert.h>
+#include <cassert>
+#include <cfloat>
+#include <cmath>
+#include <limits>
+#include <vector>
 
-#include <glib-2.0/glib.h>
 #include <QComboBox>
+#include <QDebug>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QSlider>
 
-#include "enum.h"
+#include <libsigrokcxx/libsigrokcxx.hpp>
 
-using namespace boost;
-using namespace std;
+#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;
 
 namespace pv {
 namespace prop {
 
-Enum::Enum(QString name,
-       vector<pair<const void*, QString> > values,
-       function<GVariant* ()> getter,
-       function<void (GVariant*)> setter) :
-       Property(name),
-       _values(values),
-       _getter(getter),
-       _setter(setter),
-       _selector(NULL)
+Enum::Enum(QString name, QString desc,
+       vector<pair<Glib::VariantBase, QString> > values,
+       Getter getter, Setter setter) :
+       Property(name, desc, getter, setter),
+       values_(values),
+       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<double> deltas;
+       double prev_value = 0;
+
+       for (const pair<Glib::VariantBase, QString> &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<double>::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)
+QWidget* Enum::get_widget(QWidget *parent, bool auto_commit)
 {
-       if (_selector)
-               return _selector;
-
-       const void *value = NULL;
-       if (_getter)
-               value = _getter();
-
-       _selector = new QComboBox(parent);
-       for (unsigned int i = 0; i < _values.size(); i++) {
-               const pair<const void*, QString> &v = _values[i];
-               _selector->addItem(v.second,
-                       qVariantFromValue((void*)v.first));
-               if (v.first == value)
-                       _selector->setCurrentIndex(i);
+       if (!getter_)
+               return nullptr;
+
+       Glib::VariantBase variant;
+
+       try {
+                variant = getter_();
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
+               return nullptr;
        }
 
-       return _selector;
+       if (!variant.gobj())
+               return nullptr;
+
+       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<Glib::VariantBase, QString> &v = values_[i];
+                       selector_->addItem(v.second, qVariantFromValue(v.first));
+               }
+
+               update_widget();
+
+               if (auto_commit)
+                       connect(selector_, SIGNAL(currentIndexChanged(int)),
+                               this, SLOT(on_current_index_changed(int)));
+
+               return selector_;
+       }
 }
 
-void Enum::commit()
+void Enum::update_widget()
 {
-       assert(_setter);
+       Glib::VariantBase variant;
 
-       if (!_selector)
+       try {
+               variant = getter_();
+       } catch (const sigrok::Error &e) {
+               qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
                return;
+       }
 
-       const int index = _selector->currentIndex();
-       if (index < 0)
-               return;
+       assert(variant.gobj());
 
-       _setter(_selector->itemData(index).value<GVariant*>());
+       if (is_range_) {
+
+               // Use slider
+               if (!slider_layout_widget_)
+                       return;
+
+               for (unsigned int i = 0; i < values_.size(); i++) {
+                       const pair<Glib::VariantBase, QString> &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 {
+               // Use combo box
+               if (!selector_)
+                       return;
+
+               for (unsigned int i = 0; i < values_.size(); i++) {
+                       const pair<Glib::VariantBase, QString> &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);
+                       }
+               }
+       }
+}
+
+void Enum::commit()
+{
+       assert(setter_);
+
+       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;
+
+               setter_(selector_->itemData(index).value<Glib::VariantBase>());
+
+               // 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_value_changed(int)
+{
+       commit();
 }
 
-} // prop
-} // pv
+}  // namespace prop
+}  // namespace pv