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