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