Salome HOME
Merge branch 'Dev_0.7.1' of newgeom:newgeom.git into Dev_0.7.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 theParent parent object
56  \param thePrecision precision of values input
57  */
58 ModuleBase_DoubleSpinBox::ModuleBase_DoubleSpinBox(QWidget* theParent, int thePrecision)
59     : QDoubleSpinBox(theParent),
60       myCleared(false)
61 {
62   // VSR 01/07/2010: Disable thousands separator for spin box
63   // (to avoid incosistency of double-2-string and string-2-double conversion)
64   QLocale loc;
65   loc.setNumberOptions(loc.numberOptions() | QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
66   setLocale(loc);
67
68   // MPV 15/09/2014: this must be set before setDecimals; otherwise in release mode setDecimals may crash
69   myPrecision = thePrecision;
70
71   // Use precision equal to default Qt decimals
72   // it's necessary to set decimals before the range setting,
73   // by default Qt rounds boundaries to 2 decimals at setRange
74   setDecimals(thePrecision);
75
76   connect(lineEdit(), SIGNAL(textChanged( const QString& )), this,
77           SLOT(onTextChanged( const QString& )));
78 }
79
80 /*!
81  \brief Destructor.
82  */
83 ModuleBase_DoubleSpinBox::~ModuleBase_DoubleSpinBox()
84 {
85 }
86
87 /*!
88  \brief Check if spin box is in the "cleared" state.
89  \return \c true if spin box is cleared
90  \sa setCleared()
91  */
92 bool ModuleBase_DoubleSpinBox::isCleared() const
93 {
94   return myCleared;
95 }
96
97 /*!
98  \brief Change "cleared" status of the spin box.
99  \param on new "cleared" status
100  \sa isCleared()
101  */
102 void ModuleBase_DoubleSpinBox::setCleared(const bool on)
103 {
104   if (myCleared == on)
105     return;
106
107   myCleared = on;
108   setSpecialValueText(specialValueText());
109 }
110
111 /*!
112  \brief Set precision of the spin box
113
114  If precision value is less than 0, the 'g' format is used for value output,
115  otherwise 'f' format is used.
116
117  \param prec new precision value.
118  \sa precision()
119  */
120 void ModuleBase_DoubleSpinBox::setPrecision(const int prec)
121 {
122   int newPrec = qMax(prec, 0);
123   int oldPrec = qMax(myPrecision, 0);
124   myPrecision = prec;
125   if (newPrec != oldPrec)
126     update();
127 }
128
129 /*!
130  \brief Get precision value of the spin box
131  \return current prevision value
132  \sa setPrecision()
133  */
134 int ModuleBase_DoubleSpinBox::getPrecision() const
135 {
136   return myPrecision;
137 }
138
139 /*!
140  \brief Interpret text entered by the user as a value.
141  \param text text entered by the user
142  \return mapped value
143  \sa textFromValue()
144  */
145 double ModuleBase_DoubleSpinBox::valueFromText(const QString& text) const
146 {
147   if (myPrecision < 0)
148     return text.toDouble();
149
150   return QDoubleSpinBox::valueFromText(text);
151 }
152
153 /*!
154  \brief This function is used by the spin box whenever it needs to display
155  the given value.
156
157  \param val spin box value
158  \return text representation of the value
159  \sa valueFromText()
160  */
161 QString ModuleBase_DoubleSpinBox::textFromValue(double val) const
162 {
163   QString s = locale().toString(val, myPrecision >= 0 ? 'f' : 'g', qAbs(myPrecision));
164   return removeTrailingZeroes(s);
165 }
166
167 /*!
168  \brief Return source string with removed leading and trailing zeros.
169  \param src source string
170  \return resulting string
171  */
172 QString ModuleBase_DoubleSpinBox::removeTrailingZeroes(const QString& src) const
173 {
174   QString delim(locale().decimalPoint());
175
176   int idx = src.lastIndexOf(delim);
177   if (idx == -1)
178     return src;
179
180   QString iPart = src.left(idx);
181   QString fPart = src.mid(idx + 1);
182   QString ePart = "";
183   int idx1 = fPart.lastIndexOf(QRegExp("e[+|-]?[0-9]+"));
184   if (idx1 >= 0) {
185     ePart = fPart.mid(idx1);
186     fPart = fPart.left(idx1);
187   }
188
189   fPart.remove(QRegExp("0+$"));
190
191   QString res = iPart;
192   if (!fPart.isEmpty())
193     res += delim + fPart;
194   res += ePart;
195
196   return res;
197 }
198
199 /*!
200  \brief Perform \a steps increment/decrement steps.
201
202  The \a steps value can be any integer number. If it is > 0,
203  the value incrementing is done, otherwise value is decremented
204  \a steps times.
205
206  \param steps number of increment/decrement steps
207  */
208 void ModuleBase_DoubleSpinBox::stepBy(int steps)
209 {
210   myCleared = false;
211
212   QDoubleSpinBox::stepBy(steps);
213   double tmpval = value();
214   if (qAbs(tmpval) < PSEUDO_ZERO)
215     tmpval = 0.;
216   if (tmpval < minimum())
217     tmpval = minimum();
218   else if (tmpval > maximum())
219     tmpval = maximum();
220   setValue(tmpval);
221 }
222
223 /*!
224  \brief This function is used to determine whether input is valid.
225  \param str currently entered value
226  \param pos cursor position in the string
227  \return validating operation result
228  */
229 QValidator::State ModuleBase_DoubleSpinBox::validate(QString& str, int& pos) const
230 {
231   QString pref = this->prefix();
232   QString suff = this->suffix();
233   uint overhead = pref.length() + suff.length();
234   QValidator::State state = QValidator::Invalid;
235
236   QDoubleValidator v(NULL);
237
238   // If 'g' format is used (myPrecision < 0), then
239   // myPrecision - 1 digits are allowed after the decimal point.
240   // Otherwise, expect myPrecision digits after the decimal point.
241   int decs = myPrecision < 0 ? qAbs(myPrecision) - 1 : myPrecision;
242
243   v.setDecimals(decs);
244   v.setBottom(minimum());
245   v.setTop(maximum());
246   v.setNotation(
247       myPrecision >= 0 ? QDoubleValidator::StandardNotation : QDoubleValidator::ScientificNotation);
248
249   if (overhead == 0)
250     state = v.validate(str, pos);
251   else {
252     if ((uint)(str.length()) >= overhead && str.startsWith(pref) && str.right(suff.length()) == suff) {
253       QString core = str.mid(pref.length(), str.length() - overhead);
254       int corePos = pos - pref.length();
255       state = v.validate(core, corePos);
256       pos = corePos + pref.length();
257       str.replace(pref.length(), str.length() - overhead, core);
258     } else {
259       state = v.validate(str, pos);
260       if (state == QValidator::Invalid) {
261         QString special = this->specialValueText().trimmed();
262         QString candidate = str.trimmed();
263         if (special.startsWith(candidate)) {
264           if (candidate.length() == special.length())
265             state = QValidator::Acceptable;
266           else
267             state = QValidator::Intermediate;
268         }
269       }
270     }
271   }
272
273   // Treat values ouside (min; max) range as Invalid
274   // This check is enabled by assigning "strict_validity_check" dynamic property
275   // with value "true" to the spin box instance.
276   if (state == QValidator::Intermediate) {
277     bool isOk;
278     double val = str.toDouble(&isOk);
279     if (isOk) {
280       QVariant propVal = property("strict_validity_check");
281       if (propVal.isValid() && propVal.canConvert(QVariant::Bool) && propVal.toBool()) {
282         if (val < minimum() || val > maximum())
283           state = QValidator::Invalid;
284       }
285     } else if (myPrecision < 0) {
286       // Consider too large negative exponent as Invalid
287       QChar e(locale().exponential());
288       int epos = str.indexOf(e, 0, Qt::CaseInsensitive);
289       if (epos != -1) {
290         epos++;  // Skip exponential symbol itself
291         QString exponent = str.right(str.length() - epos);
292         int expValue = exponent.toInt(&isOk);
293         if (isOk && expValue < std::numeric_limits<double>::min_exponent10)
294           state = QValidator::Invalid;
295       }
296     }
297   }
298
299   return state;
300 }
301
302 /*!
303  \brief Called when user enters the text in the spin box.
304  */
305 void ModuleBase_DoubleSpinBox::onTextChanged(const QString& )
306 {
307   myCleared = false;
308 }