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