Salome HOME
Merge branch 'BR_PYTHON_PLUGIN' of newgeom:newgeom.git into Dev_0.6.1
[modules/shaper.git] / src / ModuleBase / ModuleBase_DoubleSpinBox.cpp
1 // Copyright (C) 2014-20xx CEA/DEN, EDF R&D
2
3 // File:      ModuleBase_DoubleSpinBox.cxx
4 // Author:    Sergey TELKOV
5 //
6 #include "ModuleBase_DoubleSpinBox.h"
7
8 #include <QLineEdit>
9 #include <QDoubleValidator>
10 #include <QVariant>
11
12 #include <limits>
13
14 const double PSEUDO_ZERO = 1.e-20;
15
16 /*!
17  \class ModuleBase_DoubleSpinBox
18  \brief Enhanced version of the Qt's double spin box.
19
20  The ModuleBase_DoubleSpinBox class represents the widget for entering the
21  floating point values. In addition to the functionality provided by
22  QDoubleSpinBox, this class supports "cleared" state - this is the
23  state corresponding to "None" (or empty) entered value.
24
25  To set "cleared" state use setCleared() method. To check if the spin
26  box stores "cleared" state, use isCleared() method.
27  For example:
28  \code
29  if (myDblSpinBox->isCleared()) {
30  ... // process "None" state
31  }
32  else {
33  double value = myDblSpinBox->value();
34  ... // process entered value
35  }
36  \endcode
37
38  Another useful feature is possibility to use scientific notation (e.g. 1.234e+18)
39  for the widegt text. To enable this, negative precision should be specified either
40  through a constructor or using setPrecision() method.
41
42  Note that "decimals" property of QDoubleSpinBox is almost completely substituted
43  by "myPrecision" field of ModuleBase_DoubleSpinBox class. "decimals" is still used
44  for proper size hint calculation and for rounding minimum and maximum bounds of
45  the spin box range.
46  */
47
48 /*!
49  \brief Constructor.
50
51  Constructs a spin box with 0.0 as minimum value and 99.99 as maximum value,
52  a step value of 1.0 and a precision of 2 decimal places.
53  The value is initially set to 0.00.
54
55  \param parent parent object
56  */
57 ModuleBase_DoubleSpinBox::ModuleBase_DoubleSpinBox(QWidget* parent, int thePrecision)
58     : QDoubleSpinBox(parent),
59       myCleared(false)
60 {
61   // VSR 01/07/2010: Disable thousands separator for spin box
62   // (to avoid incosistency of double-2-string and string-2-double conversion)
63   QLocale loc;
64   loc.setNumberOptions(loc.numberOptions() | QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
65   setLocale(loc);
66
67   // MPV 15/09/2014: this must be set before setDecimals; otherwise in release mode setDecimals may crash
68   myPrecision = thePrecision;
69
70   // Use precision equal to default Qt decimals
71   // it's necessary to set decimals before the range setting,
72   // by default Qt rounds boundaries to 2 decimals at setRange
73   setDecimals(thePrecision);
74
75   connect(lineEdit(), SIGNAL(textChanged( const QString& )), this,
76           SLOT(onTextChanged( const QString& )));
77 }
78
79 /*!
80  \brief Destructor.
81  */
82 ModuleBase_DoubleSpinBox::~ModuleBase_DoubleSpinBox()
83 {
84 }
85
86 /*!
87  \brief Check if spin box is in the "cleared" state.
88  \return \c true if spin box is cleared
89  \sa setCleared()
90  */
91 bool ModuleBase_DoubleSpinBox::isCleared() const
92 {
93   return myCleared;
94 }
95
96 /*!
97  \brief Change "cleared" status of the spin box.
98  \param on new "cleared" status
99  \sa isCleared()
100  */
101 void ModuleBase_DoubleSpinBox::setCleared(const bool on)
102 {
103   if (myCleared == on)
104     return;
105
106   myCleared = on;
107   setSpecialValueText(specialValueText());
108 }
109
110 /*!
111  \brief Set precision of the spin box
112
113  If precision value is less than 0, the 'g' format is used for value output,
114  otherwise 'f' format is used.
115
116  \param prec new precision value.
117  \sa precision()
118  */
119 void ModuleBase_DoubleSpinBox::setPrecision(const int prec)
120 {
121   int newPrec = qMax(prec, 0);
122   int oldPrec = qMax(myPrecision, 0);
123   myPrecision = prec;
124   if (newPrec != oldPrec)
125     update();
126 }
127
128 /*!
129  \brief Get precision value of the spin box
130  \return current prevision value
131  \sa setPrecision()
132  */
133 int ModuleBase_DoubleSpinBox::getPrecision() const
134 {
135   return myPrecision;
136 }
137
138 /*!
139  \brief Interpret text entered by the user as a value.
140  \param text text entered by the user
141  \return mapped value
142  \sa textFromValue()
143  */
144 double ModuleBase_DoubleSpinBox::valueFromText(const QString& text) const
145 {
146   if (myPrecision < 0)
147     return text.toDouble();
148
149   return QDoubleSpinBox::valueFromText(text);
150 }
151
152 /*!
153  \brief This function is used by the spin box whenever it needs to display
154  the given value.
155
156  \param val spin box value
157  \return text representation of the value
158  \sa valueFromText()
159  */
160 QString ModuleBase_DoubleSpinBox::textFromValue(double val) const
161 {
162   QString s = locale().toString(val, myPrecision >= 0 ? 'f' : 'g', qAbs(myPrecision));
163   return removeTrailingZeroes(s);
164 }
165
166 /*!
167  \brief Return source string with removed leading and trailing zeros.
168  \param str source string
169  \return resulting string
170  */
171 QString ModuleBase_DoubleSpinBox::removeTrailingZeroes(const QString& src) const
172 {
173   QString delim(locale().decimalPoint());
174
175   int idx = src.lastIndexOf(delim);
176   if (idx == -1)
177     return src;
178
179   QString iPart = src.left(idx);
180   QString fPart = src.mid(idx + 1);
181   QString ePart = "";
182   int idx1 = fPart.lastIndexOf(QRegExp("e[+|-]?[0-9]+"));
183   if (idx1 >= 0) {
184     ePart = fPart.mid(idx1);
185     fPart = fPart.left(idx1);
186   }
187
188   fPart.remove(QRegExp("0+$"));
189
190   QString res = iPart;
191   if (!fPart.isEmpty())
192     res += delim + fPart;
193   res += ePart;
194
195   return res;
196 }
197
198 /*!
199  \brief Perform \a steps increment/decrement steps.
200
201  The \a steps value can be any integer number. If it is > 0,
202  the value incrementing is done, otherwise value is decremented
203  \a steps times.
204
205  \param steps number of increment/decrement steps
206  */
207 void ModuleBase_DoubleSpinBox::stepBy(int steps)
208 {
209   myCleared = false;
210
211   QDoubleSpinBox::stepBy(steps);
212   double tmpval = value();
213   if (qAbs(tmpval) < PSEUDO_ZERO)
214     tmpval = 0.;
215   if (tmpval < minimum())
216     tmpval = minimum();
217   else if (tmpval > maximum())
218     tmpval = maximum();
219   setValue(tmpval);
220 }
221
222 /*!
223  \brief This function is used to determine whether input is valid.
224  \param str currently entered value
225  \param pos cursor position in the string
226  \return validating operation result
227  */
228 QValidator::State ModuleBase_DoubleSpinBox::validate(QString& str, int& pos) const
229 {
230   QString pref = this->prefix();
231   QString suff = this->suffix();
232   uint overhead = pref.length() + suff.length();
233   QValidator::State state = QValidator::Invalid;
234
235   QDoubleValidator v(NULL);
236
237   // If 'g' format is used (myPrecision < 0), then
238   // myPrecision - 1 digits are allowed after the decimal point.
239   // Otherwise, expect myPrecision digits after the decimal point.
240   int decs = myPrecision < 0 ? qAbs(myPrecision) - 1 : myPrecision;
241
242   v.setDecimals(decs);
243   v.setBottom(minimum());
244   v.setTop(maximum());
245   v.setNotation(
246       myPrecision >= 0 ? QDoubleValidator::StandardNotation : QDoubleValidator::ScientificNotation);
247
248   if (overhead == 0)
249     state = v.validate(str, pos);
250   else {
251     if ((uint)(str.length()) >= overhead && str.startsWith(pref) && str.right(suff.length()) == suff) {
252       QString core = str.mid(pref.length(), str.length() - overhead);
253       int corePos = pos - pref.length();
254       state = v.validate(core, corePos);
255       pos = corePos + pref.length();
256       str.replace(pref.length(), str.length() - overhead, core);
257     } else {
258       state = v.validate(str, pos);
259       if (state == QValidator::Invalid) {
260         QString special = this->specialValueText().trimmed();
261         QString candidate = str.trimmed();
262         if (special.startsWith(candidate)) {
263           if (candidate.length() == special.length())
264             state = QValidator::Acceptable;
265           else
266             state = QValidator::Intermediate;
267         }
268       }
269     }
270   }
271
272   // Treat values ouside (min; max) range as Invalid
273   // This check is enabled by assigning "strict_validity_check" dynamic property
274   // with value "true" to the spin box instance.
275   if (state == QValidator::Intermediate) {
276     bool isOk;
277     double val = str.toDouble(&isOk);
278     if (isOk) {
279       QVariant propVal = property("strict_validity_check");
280       if (propVal.isValid() && propVal.canConvert(QVariant::Bool) && propVal.toBool()) {
281         if (val < minimum() || val > maximum())
282           state = QValidator::Invalid;
283       }
284     } else if (myPrecision < 0) {
285       // Consider too large negative exponent as Invalid
286       QChar e(locale().exponential());
287       int epos = str.indexOf(e, 0, Qt::CaseInsensitive);
288       if (epos != -1) {
289         epos++;  // Skip exponential symbol itself
290         QString exponent = str.right(str.length() - epos);
291         int expValue = exponent.toInt(&isOk);
292         if (isOk && expValue < std::numeric_limits<double>::min_exponent10)
293           state = QValidator::Invalid;
294       }
295     }
296   }
297
298   return state;
299 }
300
301 /*!
302  \brief Called when user enters the text in the spin box.
303  \param txt current spin box text (not used)
304  */
305 void ModuleBase_DoubleSpinBox::onTextChanged(const QString& /*txt*/)
306 {
307   myCleared = false;
308 }