]> sigrok.org Git - pulseview.git/blob - pv/prop/enum.cpp
Fix #1035 by checking for exceptions when accessing config
[pulseview.git] / pv / prop / enum.cpp
1 /*
2  * This file is part of the PulseView project.
3  *
4  * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
5  *
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.
10  *
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.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <cassert>
21 #include <cfloat>
22 #include <cmath>
23 #include <limits>
24 #include <vector>
25
26 #include <QComboBox>
27 #include <QDebug>
28 #include <QHBoxLayout>
29 #include <QLabel>
30 #include <QSlider>
31
32 #include <libsigrokcxx/libsigrokcxx.hpp>
33
34 #include "enum.hpp"
35
36 using std::abs;
37 // Note that "using std::isnan;" is _not_ put here since that would break
38 // compilation on some platforms. Use "std::isnan()" instead in checks below.
39 using std::numeric_limits;
40 using std::pair;
41 using std::vector;
42
43 namespace pv {
44 namespace prop {
45
46 Enum::Enum(QString name, QString desc,
47         vector<pair<Glib::VariantBase, QString> > values,
48         Getter getter, Setter setter) :
49         Property(name, desc, getter, setter),
50         values_(values),
51         is_range_(false),
52         selector_(nullptr),
53         slider_layout_widget_(nullptr),
54         slider_(nullptr),
55         slider_label_(nullptr)
56 {
57         // Try to determine whether the values make up a range, created by e.g.
58         // std_gvar_min_max_step_thresholds()
59
60         vector<double> deltas;
61         double prev_value = 0;
62
63         for (const pair<Glib::VariantBase, QString> &v : values_) {
64                 gdouble value;
65                 if (v.first.is_of_type(Glib::VariantType("d"))) {
66                         g_variant_get((GVariant*)(v.first.gobj()), "d", &value);
67                 } else if (v.first.is_of_type(Glib::VariantType("(dd)"))) {
68                         gdouble dummy;
69                         g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &value, &dummy);
70                 } else
71                         break; // Type not d or (dd), so not a range that we can handle
72                 deltas.push_back(value - prev_value);
73                 prev_value = value;
74         }
75
76         if (deltas.size() > 0) {
77                 bool constant_delta = true;
78                 double prev_delta = numeric_limits<double>::quiet_NaN();
79
80                 bool skip_first = true;
81                 for (double delta : deltas) {
82                         // First value is incorrect, it's the delta to 0 since no
83                         // previous value existed yet
84                         if (skip_first) {
85                                 skip_first = false;
86                                 continue;
87                         }
88                         if (std::isnan(prev_delta))
89                                 prev_delta = delta;
90
91                         // 2*DBL_EPSILON doesn't work here, so use a workaround
92                         if (abs(delta - prev_delta) > (delta/10))
93                                 constant_delta = false;
94
95                         prev_delta = delta;
96                 }
97
98                 if (constant_delta)
99                         is_range_ = true;
100         }
101 }
102
103 QWidget* Enum::get_widget(QWidget *parent, bool auto_commit)
104 {
105         if (!getter_)
106                 return nullptr;
107
108         Glib::VariantBase variant;
109
110         try {
111                  variant = getter_();
112         } catch (const sigrok::Error &e) {
113                 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
114                 return nullptr;
115         }
116
117         if (!variant.gobj())
118                 return nullptr;
119
120         if (is_range_) {
121                 // Use slider
122                 if (slider_layout_widget_)
123                         return slider_layout_widget_;
124
125                 slider_ = new QSlider();
126                 // Sliders can't handle float values, so we just use it to specify
127                 // the number of steps that we're away from the range's beginning
128                 slider_->setOrientation(Qt::Horizontal);
129                 slider_->setMinimum(0);
130                 slider_->setMaximum(values_.size() - 1);
131                 slider_->setSingleStep(1);
132
133                 slider_label_ = new QLabel();
134
135                 slider_layout_widget_ = new QWidget(parent);
136                 QHBoxLayout *layout = new QHBoxLayout(slider_layout_widget_);
137                 layout->addWidget(slider_);
138                 layout->addWidget(slider_label_);
139
140                 update_widget();
141
142                 if (auto_commit)
143                         connect(slider_, SIGNAL(valueChanged(int)),
144                                 this, SLOT(on_value_changed(int)));
145
146                 return slider_layout_widget_;
147
148         } else {
149                 // Use combo box
150                 if (selector_)
151                         return selector_;
152
153                 selector_ = new QComboBox(parent);
154                 for (unsigned int i = 0; i < values_.size(); i++) {
155                         const pair<Glib::VariantBase, QString> &v = values_[i];
156                         selector_->addItem(v.second, qVariantFromValue(v.first));
157                 }
158
159                 update_widget();
160
161                 if (auto_commit)
162                         connect(selector_, SIGNAL(currentIndexChanged(int)),
163                                 this, SLOT(on_current_index_changed(int)));
164
165                 return selector_;
166         }
167 }
168
169 void Enum::update_widget()
170 {
171         Glib::VariantBase variant;
172
173         try {
174                 variant = getter_();
175         } catch (const sigrok::Error &e) {
176                 qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what());
177                 return;
178         }
179
180         assert(variant.gobj());
181
182         if (is_range_) {
183
184                 // Use slider
185                 if (!slider_layout_widget_)
186                         return;
187
188                 for (unsigned int i = 0; i < values_.size(); i++) {
189                         const pair<Glib::VariantBase, QString> &v = values_[i];
190
191                         // g_variant_equal() doesn't handle floating point properly
192                         if (v.first.is_of_type(Glib::VariantType("d"))) {
193                                 gdouble a, b;
194                                 g_variant_get(variant.gobj(), "d", &a);
195                                 g_variant_get((GVariant*)(v.first.gobj()), "d", &b);
196
197                                 if (abs(a - b) <= 2 * DBL_EPSILON) {
198                                         slider_->setValue(i);
199                                         slider_label_->setText(v.second);
200                                 }
201                         } else {
202                                 // Check for "(dd)" type and handle it if it's found
203                                 if (v.first.is_of_type(Glib::VariantType("(dd)"))) {
204                                         gdouble a1, a2, b1, b2;
205                                         g_variant_get(variant.gobj(), "(dd)", &a1, &a2);
206                                         g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2);
207
208                                         if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \
209                                                 (abs(a2 - b2) <= 2 * DBL_EPSILON)) {
210                                                 slider_->setValue(i);
211                                                 slider_label_->setText(v.second);
212                                         }
213
214                                 } else {
215                                         qWarning() << "Enum property" << name() << "encountered unsupported type";
216                                         return;
217                                 }
218                         }
219                 }
220
221         } else {
222                 // Use combo box
223                 if (!selector_)
224                         return;
225
226                 for (unsigned int i = 0; i < values_.size(); i++) {
227                         const pair<Glib::VariantBase, QString> &v = values_[i];
228
229                         // g_variant_equal() doesn't handle floating point properly
230                         if (v.first.is_of_type(Glib::VariantType("d"))) {
231                                 gdouble a, b;
232                                 g_variant_get(variant.gobj(), "d", &a);
233                                 g_variant_get((GVariant*)(v.first.gobj()), "d", &b);
234                                 if (abs(a - b) <= 2 * DBL_EPSILON)
235                                         selector_->setCurrentIndex(i);
236                         } else {
237                                 // Check for "(dd)" type and handle it if it's found
238                                 if (v.first.is_of_type(Glib::VariantType("(dd)"))) {
239                                         gdouble a1, a2, b1, b2;
240                                         g_variant_get(variant.gobj(), "(dd)", &a1, &a2);
241                                         g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2);
242                                         if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \
243                                                 (abs(a2 - b2) <= 2 * DBL_EPSILON))
244                                                 selector_->setCurrentIndex(i);
245
246                                 } else
247                                         // Handle all other types
248                                         if (v.first.equal(variant))
249                                                 selector_->setCurrentIndex(i);
250                         }
251                 }
252         }
253 }
254
255 void Enum::commit()
256 {
257         assert(setter_);
258
259         if (is_range_) {
260                 // Use slider
261                 if (!slider_layout_widget_)
262                         return;
263
264                 setter_(values_.at(slider_->value()).first);
265
266                 update_widget();
267         } else {
268                 // Use combo box
269                 if (!selector_)
270                         return;
271
272                 const int index = selector_->currentIndex();
273                 if (index < 0)
274                         return;
275
276                 setter_(selector_->itemData(index).value<Glib::VariantBase>());
277
278                 // The combo box needs no update, it already shows the current value
279                 // by definition: the user picked it
280         }
281 }
282
283 void Enum::on_current_index_changed(int)
284 {
285         commit();
286 }
287
288 void Enum::on_value_changed(int)
289 {
290         commit();
291 }
292
293 }  // namespace prop
294 }  // namespace pv