Salome HOME
Apply modifications in the editors by Enter/Tab event only.
[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
12 #include <limits>
13
14 const double PSEUDO_ZERO = 1.e-20;
15
16 /*!
17  \class ModuleBase_DoubleSpinBox
18  \brief Enhanced version of the Qt's double spin box.
19
20  The ModuleBase_DoubleSpinBox class represents the widget for entering the
21  floating point values. In addition to the functionality provided by
22  QDoubleSpinBox, this class supports "cleared" state - this is the
23  state corresponding to "None" (or empty) entered value.
24
25  To set "cleared" state use setCleared() method. To check if the spin
26  box stores "cleared" state, use isCleared() method.
27  For example:
28  \code
29  if (myDblSpinBox->isCleared()) {
30  ... // process "None" state
31  }
32  else {
33  double value = myDblSpinBox->value();
34  ... // process entered value
35  }
36  \endcode
37
38  Another useful feature is possibility to use scientific notation (e.g. 1.234e+18)
39  for the widget text. To enable this, negative precision should be specified either
40  through a constructor or using setPrecision() method.
41
42  Note that "decimals" property of QDoubleSpinBox is almost completely substituted
43  by "myPrecision" field of ModuleBase_DoubleSpinBox class. "decimals" is still used
44  for proper size hint calculation and for rounding minimum and maximum bounds of
45  the spin box range.
46  */
47
48 /*!
49  \brief Constructor.
50
51  Constructs a spin box with 0.0 as minimum value and 99.99 as maximum value,
52  a step value of 1.0 and a precision of 2 decimal places.
53  The value is initially set to 0.00.
54
55  \param theParent parent object
56  \param thePrecision precision of values input
57  */
58 ModuleBase_DoubleSpinBox::ModuleBase_DoubleSpinBox(QWidget* theParent, int thePrecision)
59     : QDoubleSpinBox(theParent),
60       myCleared(false),
61       myIsModified(false)
62 {
63   // VSR 01/07/2010: Disable thousands separator for spin box
64   // (to avoid inconsistency of double-2-string and string-2-double conversion)
65   QLocale loc;
66   loc.setNumberOptions(loc.numberOptions() | QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
67   setLocale(loc);
68
69   // MPV 15/09/2014: this must be set before setDecimals; otherwise in release mode setDecimals may crash
70   myPrecision = thePrecision;
71
72   // Use precision equal to default Qt decimals
73   // it's necessary to set decimals before the range setting,
74   // by default Qt rounds boundaries to 2 decimals at setRange
75   setDecimals(qAbs(myPrecision));
76
77   connect(lineEdit(), SIGNAL(textChanged( const QString& )), this,
78           SLOT(onTextChanged( const QString& )));
79
80   connect(this, SIGNAL(valueChanged(const QString&)), this, SLOT(onValueChanged(const QString&)));
81   connect(this, SIGNAL(editingFinished()), this, SLOT(onEditingFinished()));
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 #include <QKeyEvent>
204 void ModuleBase_DoubleSpinBox::keyPressEvent(QKeyEvent *theEvent)
205 {
206   myProcessedEvent = 0;
207
208   bool anIsModified = myIsModified;
209   QDoubleSpinBox::keyPressEvent(theEvent);
210
211   switch (theEvent->key()) {
212     case Qt::Key_Enter:
213     case Qt::Key_Return: {
214       if (anIsModified)
215         myProcessedEvent = theEvent;
216       /*qDebug("ModuleBase_DoubleSpinBox::keyPressEvent");
217       if (anIsModified) // we should not perform this event outside
218         theEvent->setAccepted(true);
219       else
220         theEvent->setAccepted(false);*/
221     }
222     break;
223     default:
224       break;
225   }
226 }
227
228 /*!
229  \brief Perform \a steps increment/decrement steps.
230
231  The \a steps value can be any integer number. If it is > 0,
232  the value incrementing is done, otherwise value is decremented
233  \a steps times.
234
235  \param steps number of increment/decrement steps
236  */
237 void ModuleBase_DoubleSpinBox::stepBy(int steps)
238 {
239   myCleared = false;
240
241   QDoubleSpinBox::stepBy(steps);
242   double tmpval = value();
243   if (qAbs(tmpval) < PSEUDO_ZERO)
244     tmpval = 0.;
245   if (tmpval < minimum())
246     tmpval = minimum();
247   else if (tmpval > maximum())
248     tmpval = maximum();
249   setValue(tmpval);
250 }
251
252 /*!
253  \brief This function is used to determine whether input is valid.
254  \param str currently entered value
255  \param pos cursor position in the string
256  \return validating operation result
257  */
258 QValidator::State ModuleBase_DoubleSpinBox::validate(QString& str, int& pos) const
259 {
260   QString pref = this->prefix();
261   QString suff = this->suffix();
262   uint overhead = pref.length() + suff.length();
263   QValidator::State state = QValidator::Invalid;
264
265   QDoubleValidator v(NULL);
266
267   // If 'g' format is used (myPrecision < 0), then
268   // myPrecision - 1 digits are allowed after the decimal point.
269   // Otherwise, expect myPrecision digits after the decimal point.
270   int decs = myPrecision < 0 ? qAbs(myPrecision) - 1 : myPrecision;
271
272   v.setLocale(this->locale());
273   v.setDecimals(decs);
274   v.setBottom(minimum());
275   v.setTop(maximum());
276   v.setNotation(
277       myPrecision >= 0 ? QDoubleValidator::StandardNotation : QDoubleValidator::ScientificNotation);
278
279   if (overhead == 0)
280     state = v.validate(str, pos);
281   else {
282     if ((uint)(str.length()) >= overhead && str.startsWith(pref) && str.right(suff.length()) == suff) {
283       QString core = str.mid(pref.length(), str.length() - overhead);
284       int corePos = pos - pref.length();
285       state = v.validate(core, corePos);
286       pos = corePos + pref.length();
287       str.replace(pref.length(), str.length() - overhead, core);
288     } else {
289       state = v.validate(str, pos);
290       if (state == QValidator::Invalid) {
291         QString special = this->specialValueText().trimmed();
292         QString candidate = str.trimmed();
293         if (special.startsWith(candidate)) {
294           if (candidate.length() == special.length())
295             state = QValidator::Acceptable;
296           else
297             state = QValidator::Intermediate;
298         }
299       }
300     }
301   }
302
303   // Treat values outside (min; max) range as Invalid
304   // This check is enabled by assigning "strict_validity_check" dynamic property
305   // with value "true" to the spin box instance.
306   if (state == QValidator::Intermediate) {
307     bool isOk;
308     double val = str.toDouble(&isOk);
309     if (isOk) {
310       QVariant propVal = property("strict_validity_check");
311       if (propVal.isValid() && propVal.canConvert(QVariant::Bool) && propVal.toBool()) {
312         if (val < minimum() || val > maximum())
313           state = QValidator::Invalid;
314       }
315     } else if (myPrecision < 0) {
316       // Consider too large negative exponent as Invalid
317       QChar e(locale().exponential());
318       int epos = str.indexOf(e, 0, Qt::CaseInsensitive);
319       if (epos != -1) {
320         epos++;  // Skip exponential symbol itself
321         QString exponent = str.right(str.length() - epos);
322         int expValue = exponent.toInt(&isOk);
323         if (isOk && expValue < std::numeric_limits<double>::min_exponent10)
324           state = QValidator::Invalid;
325       }
326     }
327   }
328
329   return state;
330 }
331
332 /*!
333  \brief Called when user enters the text in the spin box.
334  */
335 void ModuleBase_DoubleSpinBox::onTextChanged(const QString& )
336 {
337   myCleared = false;
338   myIsModified = true;
339 }
340
341 void ModuleBase_DoubleSpinBox::onValueChanged(const QString& theValue)
342 {
343   myIsModified = true;
344 }
345
346 void ModuleBase_DoubleSpinBox::onEditingFinished()
347 {
348   myIsModified = false;
349 }
350
351 bool ModuleBase_DoubleSpinBox::isEventProcessed(QKeyEvent* theEvent)
352 {
353   return myProcessedEvent && myProcessedEvent == theEvent;
354 }