Salome HOME
Merge branch 'master' into cgt/devCEA
[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 #include "ModuleBase_Tools.h"
8
9 #include <QLineEdit>
10 #include <QDoubleValidator>
11 #include <QVariant>
12 #include <QKeyEvent>
13
14 #include <limits>
15
16 const double PSEUDO_ZERO = 1.e-20;
17
18 /*!
19  \class ModuleBase_DoubleSpinBox
20  \brief Enhanced version of the Qt's double spin box.
21
22  The ModuleBase_DoubleSpinBox class represents the widget for entering the
23  floating point values. In addition to the functionality provided by
24  QDoubleSpinBox, this class supports "cleared" state - this is the
25  state corresponding to "None" (or empty) entered value.
26
27  To set "cleared" state use setCleared() method. To check if the spin
28  box stores "cleared" state, use isCleared() method.
29  For example:
30  \code
31  if (myDblSpinBox->isCleared()) {
32  ... // process "None" state
33  }
34  else {
35  double value = myDblSpinBox->value();
36  ... // process entered value
37  }
38  \endcode
39
40  Another useful feature is possibility to use scientific notation (e.g. 1.234e+18)
41  for the widget text. To enable this, negative precision should be specified either
42  through a constructor or using setPrecision() method.
43
44  Note that "decimals" property of QDoubleSpinBox is almost completely substituted
45  by "myPrecision" field of ModuleBase_DoubleSpinBox class. "decimals" is still used
46  for proper size hint calculation and for rounding minimum and maximum bounds of
47  the spin box range.
48  */
49
50 /*!
51  \brief Constructor.
52
53  Constructs a spin box with 0.0 as minimum value and 99.99 as maximum value,
54  a step value of 1.0 and a precision of 2 decimal places.
55  The value is initially set to 0.00.
56
57  \param theParent parent object
58  \param thePrecision precision of values input
59  */
60 ModuleBase_DoubleSpinBox::ModuleBase_DoubleSpinBox(QWidget* theParent, int thePrecision)
61     : QDoubleSpinBox(theParent),
62       myCleared(false),
63       myIsEmitKeyPressEvent(false)
64 {
65   setLocale(ModuleBase_Tools::doubleLocale());
66
67   // MPV 15/09/2014: this must be set before setDecimals;
68   // 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(qAbs(myPrecision));
75
76   connect(lineEdit(), SIGNAL(textChanged( const QString& )), this,
77           SLOT(onTextChanged( const QString& )));
78
79   myEnabledBaseColor = palette().color(QPalette::Active, QPalette::Base);
80 }
81
82 /*!
83  \brief Destructor.
84  */
85 ModuleBase_DoubleSpinBox::~ModuleBase_DoubleSpinBox()
86 {
87 }
88
89 /*!
90  \brief Check if spin box is in the "cleared" state.
91  \return \c true if spin box is cleared
92  \sa setCleared()
93  */
94 bool ModuleBase_DoubleSpinBox::isCleared() const
95 {
96   return myCleared;
97 }
98
99 /*!
100  \brief Change "cleared" status of the spin box.
101  \param on new "cleared" status
102  \sa isCleared()
103  */
104 void ModuleBase_DoubleSpinBox::setCleared(const bool on)
105 {
106   if (myCleared == on)
107     return;
108
109   myCleared = on;
110   setSpecialValueText(specialValueText());
111 }
112
113 /*!
114  \brief Set precision of the spin box
115
116  If precision value is less than 0, the 'g' format is used for value output,
117  otherwise 'f' format is used.
118
119  \param prec new precision value.
120  \sa precision()
121  */
122 void ModuleBase_DoubleSpinBox::setPrecision(const int prec)
123 {
124   int newPrec = qAbs(prec);
125   int oldPrec = qAbs(myPrecision);
126   myPrecision = prec;
127   if (newPrec != oldPrec)
128     update();
129 }
130
131 /*!
132  \brief Get precision value of the spin box
133  \return current precision value
134  \sa setPrecision()
135  */
136 int ModuleBase_DoubleSpinBox::getPrecision() const
137 {
138   return myPrecision;
139 }
140
141 /*!
142  \brief Interpret text entered by the user as a value.
143  \param text text entered by the user
144  \return mapped value
145  \sa textFromValue()
146  */
147 double ModuleBase_DoubleSpinBox::valueFromText(const QString& text) const
148 {
149   if (myPrecision < 0)
150     return text.toDouble();
151
152   return QDoubleSpinBox::valueFromText(text);
153 }
154
155 /*!
156  \brief This function is used by the spin box whenever it needs to display
157  the given value.
158
159  \param val spin box value
160  \return text representation of the value
161  \sa valueFromText()
162  */
163 QString ModuleBase_DoubleSpinBox::textFromValue(double val) const
164 {
165   QString s = locale().toString(val, myPrecision >= 0 ? 'f' : 'g', qAbs(myPrecision));
166   return removeTrailingZeroes(s);
167 }
168
169 /*!
170  \brief Return source string with removed leading and trailing zeros.
171  \param src source string
172  \return resulting string
173  */
174 QString ModuleBase_DoubleSpinBox::removeTrailingZeroes(const QString& src) const
175 {
176   QString delim(locale().decimalPoint());
177
178   int idx = src.lastIndexOf(delim);
179   if (idx == -1)
180     return src;
181
182   QString iPart = src.left(idx);
183   QString fPart = src.mid(idx + 1);
184   QString ePart = "";
185   int idx1 = fPart.lastIndexOf(QRegExp("e[+|-]?[0-9]+"));
186   if (idx1 >= 0) {
187     ePart = fPart.mid(idx1);
188     fPart = fPart.left(idx1);
189   }
190
191   fPart.remove(QRegExp("0+$"));
192
193   QString res = iPart;
194   if (!fPart.isEmpty())
195     res += delim + fPart;
196   res += ePart;
197
198   return res;
199 }
200
201 void ModuleBase_DoubleSpinBox::keyPressEvent(QKeyEvent* theEvent)
202 {
203   switch (theEvent->key()) {
204     case Qt::Key_Enter:
205     case Qt::Key_Return: {
206       // do not react to the Enter key, the property panel processes it
207       if (!myIsEmitKeyPressEvent)
208         return;
209     }
210     break;
211     default:
212       break;
213   }
214   QDoubleSpinBox::keyPressEvent(theEvent);
215 }
216
217 void ModuleBase_DoubleSpinBox::keyReleaseEvent(QKeyEvent* theEvent)
218 {
219   switch (theEvent->key()) {
220     case Qt::Key_Enter:
221     case Qt::Key_Return: {
222       // the enter has already been processed when key is pressed,
223       // key release should not be processed in operation manager
224       if (myIsEmitKeyPressEvent) {
225         theEvent->accept();
226         emit enterReleased();
227         return;
228       }
229     }
230     break;
231     default:
232       break;
233   }
234   QDoubleSpinBox::keyReleaseEvent(theEvent);
235 }
236
237 /*!
238  \brief Perform \a steps increment/decrement steps.
239
240  The \a steps value can be any integer number. If it is > 0,
241  the value incrementing is done, otherwise value is decremented
242  \a steps times.
243
244  \param steps number of increment/decrement steps
245  */
246 void ModuleBase_DoubleSpinBox::stepBy(int steps)
247 {
248   myCleared = false;
249
250   QDoubleSpinBox::stepBy(steps);
251   double tmpval = value();
252   if (qAbs(tmpval) < PSEUDO_ZERO)
253     tmpval = 0.;
254   if (tmpval < minimum())
255     tmpval = minimum();
256   else if (tmpval > maximum())
257     tmpval = maximum();
258   setValue(tmpval);
259 }
260
261 /*!
262  \brief This function is used to determine whether input is valid.
263  \param str currently entered value
264  \param pos cursor position in the string
265  \return validating operation result
266  */
267 QValidator::State ModuleBase_DoubleSpinBox::validate(QString& str, int& pos) const
268 {
269   QString pref = this->prefix();
270   QString suff = this->suffix();
271   uint overhead = pref.length() + suff.length();
272   QValidator::State state = QValidator::Invalid;
273
274   QDoubleValidator v(NULL);
275
276   // If 'g' format is used (myPrecision < 0), then
277   // myPrecision - 1 digits are allowed after the decimal point.
278   // Otherwise, expect myPrecision digits after the decimal point.
279   int decs = myPrecision < 0 ? qAbs(myPrecision) - 1 : myPrecision;
280
281   v.setLocale(this->locale());
282   v.setDecimals(decs);
283   v.setBottom(minimum());
284   v.setTop(maximum());
285   v.setNotation(
286       myPrecision >= 0 ? QDoubleValidator::StandardNotation : QDoubleValidator::ScientificNotation);
287
288   if (overhead == 0)
289     state = v.validate(str, pos);
290   else {
291     if ((uint)(str.length()) >= overhead &&
292          str.startsWith(pref) &&
293          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 }