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