Salome HOME
Issue #1659 New widget for supporting optional inputs : rename CheckGroupBox to Optio...
[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       myIsEmitKeyPressEvent(false)
63 {
64   // VSR 01/07/2010: Disable thousands separator for spin box
65   // (to avoid inconsistency of double-2-string and string-2-double conversion)
66   QLocale loc;
67   loc.setNumberOptions(loc.numberOptions() | QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
68   setLocale(loc);
69
70   // MPV 15/09/2014: this must be set before setDecimals; otherwise in release mode setDecimals may crash
71   myPrecision = thePrecision;
72
73   // Use precision equal to default Qt decimals
74   // it's necessary to set decimals before the range setting,
75   // by default Qt rounds boundaries to 2 decimals at setRange
76   setDecimals(qAbs(myPrecision));
77
78   connect(lineEdit(), SIGNAL(textChanged( const QString& )), this,
79           SLOT(onTextChanged( const QString& )));
80
81   myEnabledBaseColor = palette().color(QPalette::Active, QPalette::Base);
82 }
83
84 /*!
85  \brief Destructor.
86  */
87 ModuleBase_DoubleSpinBox::~ModuleBase_DoubleSpinBox()
88 {
89 }
90
91 /*!
92  \brief Check if spin box is in the "cleared" state.
93  \return \c true if spin box is cleared
94  \sa setCleared()
95  */
96 bool ModuleBase_DoubleSpinBox::isCleared() const
97 {
98   return myCleared;
99 }
100
101 /*!
102  \brief Change "cleared" status of the spin box.
103  \param on new "cleared" status
104  \sa isCleared()
105  */
106 void ModuleBase_DoubleSpinBox::setCleared(const bool on)
107 {
108   if (myCleared == on)
109     return;
110
111   myCleared = on;
112   setSpecialValueText(specialValueText());
113 }
114
115 /*!
116  \brief Set precision of the spin box
117
118  If precision value is less than 0, the 'g' format is used for value output,
119  otherwise 'f' format is used.
120
121  \param prec new precision value.
122  \sa precision()
123  */
124 void ModuleBase_DoubleSpinBox::setPrecision(const int prec)
125 {
126   int newPrec = qAbs(prec);
127   int oldPrec = qAbs(myPrecision);
128   myPrecision = prec;
129   if (newPrec != oldPrec)
130     update();
131 }
132
133 /*!
134  \brief Get precision value of the spin box
135  \return current precision value
136  \sa setPrecision()
137  */
138 int ModuleBase_DoubleSpinBox::getPrecision() const
139 {
140   return myPrecision;
141 }
142
143 /*!
144  \brief Interpret text entered by the user as a value.
145  \param text text entered by the user
146  \return mapped value
147  \sa textFromValue()
148  */
149 double ModuleBase_DoubleSpinBox::valueFromText(const QString& text) const
150 {
151   if (myPrecision < 0)
152     return text.toDouble();
153
154   return QDoubleSpinBox::valueFromText(text);
155 }
156
157 /*!
158  \brief This function is used by the spin box whenever it needs to display
159  the given value.
160
161  \param val spin box value
162  \return text representation of the value
163  \sa valueFromText()
164  */
165 QString ModuleBase_DoubleSpinBox::textFromValue(double val) const
166 {
167   QString s = locale().toString(val, myPrecision >= 0 ? 'f' : 'g', qAbs(myPrecision));
168   return removeTrailingZeroes(s);
169 }
170
171 /*!
172  \brief Return source string with removed leading and trailing zeros.
173  \param src source string
174  \return resulting string
175  */
176 QString ModuleBase_DoubleSpinBox::removeTrailingZeroes(const QString& src) const
177 {
178   QString delim(locale().decimalPoint());
179
180   int idx = src.lastIndexOf(delim);
181   if (idx == -1)
182     return src;
183
184   QString iPart = src.left(idx);
185   QString fPart = src.mid(idx + 1);
186   QString ePart = "";
187   int idx1 = fPart.lastIndexOf(QRegExp("e[+|-]?[0-9]+"));
188   if (idx1 >= 0) {
189     ePart = fPart.mid(idx1);
190     fPart = fPart.left(idx1);
191   }
192
193   fPart.remove(QRegExp("0+$"));
194
195   QString res = iPart;
196   if (!fPart.isEmpty())
197     res += delim + fPart;
198   res += ePart;
199
200   return res;
201 }
202
203 void ModuleBase_DoubleSpinBox::keyPressEvent(QKeyEvent* theEvent)
204 {
205   switch (theEvent->key()) {
206     case Qt::Key_Enter:
207     case Qt::Key_Return: {
208       // do not react to the Enter key, the property panel processes it
209       if (!myIsEmitKeyPressEvent)
210         return;
211     }
212     break;
213     default:
214       break;
215   }
216   QDoubleSpinBox::keyPressEvent(theEvent);
217 }
218
219 void ModuleBase_DoubleSpinBox::keyReleaseEvent(QKeyEvent* theEvent)
220 {
221   switch (theEvent->key()) {
222     case Qt::Key_Enter:
223     case Qt::Key_Return: {
224       // the enter has already been processed when key is pressed,
225       // key release should not be processed in operation manager
226       if (myIsEmitKeyPressEvent) {
227         theEvent->accept();
228         emit enterReleased();
229         return;
230       }
231     }
232     break;
233     default:
234       break;
235   }
236   QDoubleSpinBox::keyReleaseEvent(theEvent);
237 }
238
239 /*!
240  \brief Perform \a steps increment/decrement steps.
241
242  The \a steps value can be any integer number. If it is > 0,
243  the value incrementing is done, otherwise value is decremented
244  \a steps times.
245
246  \param steps number of increment/decrement steps
247  */
248 void ModuleBase_DoubleSpinBox::stepBy(int steps)
249 {
250   myCleared = false;
251
252   QDoubleSpinBox::stepBy(steps);
253   double tmpval = value();
254   if (qAbs(tmpval) < PSEUDO_ZERO)
255     tmpval = 0.;
256   if (tmpval < minimum())
257     tmpval = minimum();
258   else if (tmpval > maximum())
259     tmpval = maximum();
260   setValue(tmpval);
261 }
262
263 /*!
264  \brief This function is used to determine whether input is valid.
265  \param str currently entered value
266  \param pos cursor position in the string
267  \return validating operation result
268  */
269 QValidator::State ModuleBase_DoubleSpinBox::validate(QString& str, int& pos) const
270 {
271   QString pref = this->prefix();
272   QString suff = this->suffix();
273   uint overhead = pref.length() + suff.length();
274   QValidator::State state = QValidator::Invalid;
275
276   QDoubleValidator v(NULL);
277
278   // If 'g' format is used (myPrecision < 0), then
279   // myPrecision - 1 digits are allowed after the decimal point.
280   // Otherwise, expect myPrecision digits after the decimal point.
281   int decs = myPrecision < 0 ? qAbs(myPrecision) - 1 : myPrecision;
282
283   v.setLocale(this->locale());
284   v.setDecimals(decs);
285   v.setBottom(minimum());
286   v.setTop(maximum());
287   v.setNotation(
288       myPrecision >= 0 ? QDoubleValidator::StandardNotation : QDoubleValidator::ScientificNotation);
289
290   if (overhead == 0)
291     state = v.validate(str, pos);
292   else {
293     if ((uint)(str.length()) >= overhead && str.startsWith(pref) && str.right(suff.length()) == suff) {
294       QString core = str.mid(pref.length(), str.length() - overhead);
295       int corePos = pos - pref.length();
296       state = v.validate(core, corePos);
297       pos = corePos + pref.length();
298       str.replace(pref.length(), str.length() - overhead, core);
299     } else {
300       state = v.validate(str, pos);
301       if (state == QValidator::Invalid) {
302         QString special = this->specialValueText().trimmed();
303         QString candidate = str.trimmed();
304         if (special.startsWith(candidate)) {
305           if (candidate.length() == special.length())
306             state = QValidator::Acceptable;
307           else
308             state = QValidator::Intermediate;
309         }
310       }
311     }
312   }
313
314   // Treat values outside (min; max) range as Invalid
315   // This check is enabled by assigning "strict_validity_check" dynamic property
316   // with value "true" to the spin box instance.
317   if (state == QValidator::Intermediate) {
318     bool isOk;
319     double val = str.toDouble(&isOk);
320     if (isOk) {
321       QVariant propVal = property("strict_validity_check");
322       if (propVal.isValid() && propVal.canConvert(QVariant::Bool) && propVal.toBool()) {
323         if (val < minimum() || val > maximum())
324           state = QValidator::Invalid;
325       }
326     } else if (myPrecision < 0) {
327       // Consider too large negative exponent as Invalid
328       QChar e(locale().exponential());
329       int epos = str.indexOf(e, 0, Qt::CaseInsensitive);
330       if (epos != -1) {
331         epos++;  // Skip exponential symbol itself
332         QString exponent = str.right(str.length() - epos);
333         int expValue = exponent.toInt(&isOk);
334         if (isOk && expValue < std::numeric_limits<double>::min_exponent10)
335           state = QValidator::Invalid;
336       }
337     }
338   }
339
340   return state;
341 }
342
343 /*!
344  \brief Called when user enters the text in the spin box.
345  */
346 void ModuleBase_DoubleSpinBox::onTextChanged(const QString& )
347 {
348   myCleared = false;
349 }
350
351 bool ModuleBase_DoubleSpinBox::enableKeyPressEvent(const bool& theEnable)
352 {
353   bool aPreviousValue = myIsEmitKeyPressEvent;
354   myIsEmitKeyPressEvent = theEnable;
355
356   return aPreviousValue;
357 }
358
359 void ModuleBase_DoubleSpinBox::setValueEnabled(const bool& theEnable)
360 {
361   setReadOnly(!theEnable);
362   
363   QPalette aPal = palette();
364   aPal.setColor(QPalette::All, QPalette::Base,
365                 theEnable? myEnabledBaseColor : aPal.color(QPalette::Disabled, QPalette::Base));
366   setPalette(aPal);
367 }