Salome HOME
Add copyright header according to request of CEA from 06.06.2017
[modules/shaper.git] / src / ModuleBase / ModuleBase_DoubleSpinBox.cpp
1 // Copyright (C) 2014-2017  CEA/DEN, EDF R&D
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or
18 // email : webmaster.salome@opencascade.com<mailto:webmaster.salome@opencascade.com>
19 //
20
21 #include "ModuleBase_DoubleSpinBox.h"
22 #include "ModuleBase_Tools.h"
23
24 #include <QLineEdit>
25 #include <QDoubleValidator>
26 #include <QVariant>
27 #include <QKeyEvent>
28
29 #include <limits>
30
31 const double PSEUDO_ZERO = 1.e-20;
32
33 /*!
34  \class ModuleBase_DoubleSpinBox
35  \brief Enhanced version of the Qt's double spin box.
36
37  The ModuleBase_DoubleSpinBox class represents the widget for entering the
38  floating point values. In addition to the functionality provided by
39  QDoubleSpinBox, this class supports "cleared" state - this is the
40  state corresponding to "None" (or empty) entered value.
41
42  To set "cleared" state use setCleared() method. To check if the spin
43  box stores "cleared" state, use isCleared() method.
44  For example:
45  \code
46  if (myDblSpinBox->isCleared()) {
47  ... // process "None" state
48  }
49  else {
50  double value = myDblSpinBox->value();
51  ... // process entered value
52  }
53  \endcode
54
55  Another useful feature is possibility to use scientific notation (e.g. 1.234e+18)
56  for the widget text. To enable this, negative precision should be specified either
57  through a constructor or using setPrecision() method.
58
59  Note that "decimals" property of QDoubleSpinBox is almost completely substituted
60  by "myPrecision" field of ModuleBase_DoubleSpinBox class. "decimals" is still used
61  for proper size hint calculation and for rounding minimum and maximum bounds of
62  the spin box range.
63  */
64
65 /*!
66  \brief Constructor.
67
68  Constructs a spin box with 0.0 as minimum value and 99.99 as maximum value,
69  a step value of 1.0 and a precision of 2 decimal places.
70  The value is initially set to 0.00.
71
72  \param theParent parent object
73  \param thePrecision precision of values input
74  */
75 ModuleBase_DoubleSpinBox::ModuleBase_DoubleSpinBox(QWidget* theParent, int thePrecision)
76     : QDoubleSpinBox(theParent),
77       myCleared(false)
78 {
79   setLocale(ModuleBase_Tools::doubleLocale());
80
81   // MPV 15/09/2014: this must be set before setDecimals;
82   // otherwise in release mode setDecimals may crash
83   myPrecision = thePrecision;
84
85   // Use precision equal to default Qt decimals
86   // it's necessary to set decimals before the range setting,
87   // by default Qt rounds boundaries to 2 decimals at setRange
88   setDecimals(qAbs(myPrecision));
89
90   connect(lineEdit(), SIGNAL(textChanged( const QString& )), this,
91           SLOT(onTextChanged( const QString& )));
92
93   myEnabledBaseColor = palette().color(QPalette::Active, QPalette::Base);
94 }
95
96 /*!
97  \brief Destructor.
98  */
99 ModuleBase_DoubleSpinBox::~ModuleBase_DoubleSpinBox()
100 {
101 }
102
103 /*!
104  \brief Check if spin box is in the "cleared" state.
105  \return \c true if spin box is cleared
106  \sa setCleared()
107  */
108 bool ModuleBase_DoubleSpinBox::isCleared() const
109 {
110   return myCleared;
111 }
112
113 /*!
114  \brief Change "cleared" status of the spin box.
115  \param on new "cleared" status
116  \sa isCleared()
117  */
118 void ModuleBase_DoubleSpinBox::setCleared(const bool on)
119 {
120   if (myCleared == on)
121     return;
122
123   myCleared = on;
124   setSpecialValueText(specialValueText());
125 }
126
127 /*!
128  \brief Set precision of the spin box
129
130  If precision value is less than 0, the 'g' format is used for value output,
131  otherwise 'f' format is used.
132
133  \param prec new precision value.
134  \sa precision()
135  */
136 void ModuleBase_DoubleSpinBox::setPrecision(const int prec)
137 {
138   int newPrec = qAbs(prec);
139   int oldPrec = qAbs(myPrecision);
140   myPrecision = prec;
141   if (newPrec != oldPrec)
142     update();
143 }
144
145 /*!
146  \brief Get precision value of the spin box
147  \return current precision value
148  \sa setPrecision()
149  */
150 int ModuleBase_DoubleSpinBox::getPrecision() const
151 {
152   return myPrecision;
153 }
154
155 /*!
156  \brief Interpret text entered by the user as a value.
157  \param text text entered by the user
158  \return mapped value
159  \sa textFromValue()
160  */
161 double ModuleBase_DoubleSpinBox::valueFromText(const QString& text) const
162 {
163   if (myPrecision < 0)
164     return text.toDouble();
165
166   return QDoubleSpinBox::valueFromText(text);
167 }
168
169 /*!
170  \brief This function is used by the spin box whenever it needs to display
171  the given value.
172
173  \param val spin box value
174  \return text representation of the value
175  \sa valueFromText()
176  */
177 QString ModuleBase_DoubleSpinBox::textFromValue(double val) const
178 {
179   QString s = locale().toString(val, myPrecision >= 0 ? 'f' : 'g', qAbs(myPrecision));
180   return removeTrailingZeroes(s);
181 }
182
183 /*!
184  \brief Return source string with removed leading and trailing zeros.
185  \param src source string
186  \return resulting string
187  */
188 QString ModuleBase_DoubleSpinBox::removeTrailingZeroes(const QString& src) const
189 {
190   QString delim(locale().decimalPoint());
191
192   int idx = src.lastIndexOf(delim);
193   if (idx == -1)
194     return src;
195
196   QString iPart = src.left(idx);
197   QString fPart = src.mid(idx + 1);
198   QString ePart = "";
199   int idx1 = fPart.lastIndexOf(QRegExp("e[+|-]?[0-9]+"));
200   if (idx1 >= 0) {
201     ePart = fPart.mid(idx1);
202     fPart = fPart.left(idx1);
203   }
204
205   fPart.remove(QRegExp("0+$"));
206
207   QString res = iPart;
208   if (!fPart.isEmpty())
209     res += delim + fPart;
210   res += ePart;
211
212   return res;
213 }
214
215 /*!
216  \brief Perform \a steps increment/decrement steps.
217
218  The \a steps value can be any integer number. If it is > 0,
219  the value incrementing is done, otherwise value is decremented
220  \a steps times.
221
222  \param steps number of increment/decrement steps
223  */
224 void ModuleBase_DoubleSpinBox::stepBy(int steps)
225 {
226   myCleared = false;
227
228   QDoubleSpinBox::stepBy(steps);
229   double tmpval = value();
230   if (qAbs(tmpval) < PSEUDO_ZERO)
231     tmpval = 0.;
232   if (tmpval < minimum())
233     tmpval = minimum();
234   else if (tmpval > maximum())
235     tmpval = maximum();
236   setValue(tmpval);
237 }
238
239 /*!
240  \brief This function is used to determine whether input is valid.
241  \param str currently entered value
242  \param pos cursor position in the string
243  \return validating operation result
244  */
245 QValidator::State ModuleBase_DoubleSpinBox::validate(QString& str, int& pos) const
246 {
247   QString pref = this->prefix();
248   QString suff = this->suffix();
249   uint overhead = pref.length() + suff.length();
250   QValidator::State state = QValidator::Invalid;
251
252   QDoubleValidator v(NULL);
253
254   // If 'g' format is used (myPrecision < 0), then
255   // myPrecision - 1 digits are allowed after the decimal point.
256   // Otherwise, expect myPrecision digits after the decimal point.
257   int decs = myPrecision < 0 ? qAbs(myPrecision) - 1 : myPrecision;
258
259   v.setLocale(this->locale());
260   v.setDecimals(decs);
261   v.setBottom(minimum());
262   v.setTop(maximum());
263   v.setNotation(
264       myPrecision >= 0 ? QDoubleValidator::StandardNotation : QDoubleValidator::ScientificNotation);
265
266   if (overhead == 0)
267     state = v.validate(str, pos);
268   else {
269     if ((uint)(str.length()) >= overhead &&
270          str.startsWith(pref) &&
271          str.right(suff.length()) == suff) {
272       QString core = str.mid(pref.length(), str.length() - overhead);
273       int corePos = pos - pref.length();
274       state = v.validate(core, corePos);
275       pos = corePos + pref.length();
276       str.replace(pref.length(), str.length() - overhead, core);
277     } else {
278       state = v.validate(str, pos);
279       if (state == QValidator::Invalid) {
280         QString special = this->specialValueText().trimmed();
281         QString candidate = str.trimmed();
282         if (special.startsWith(candidate)) {
283           if (candidate.length() == special.length())
284             state = QValidator::Acceptable;
285           else
286             state = QValidator::Intermediate;
287         }
288       }
289     }
290   }
291
292   // Treat values outside (min; max) range as Invalid
293   // This check is enabled by assigning "strict_validity_check" dynamic property
294   // with value "true" to the spin box instance.
295   if (state == QValidator::Intermediate) {
296     bool isOk;
297     double val = str.toDouble(&isOk);
298     if (isOk) {
299       QVariant propVal = property("strict_validity_check");
300       if (propVal.isValid() && propVal.canConvert(QVariant::Bool) && propVal.toBool()) {
301         if (val < minimum() || val > maximum())
302           state = QValidator::Invalid;
303       }
304     } else if (myPrecision < 0) {
305       // Consider too large negative exponent as Invalid
306       QChar e(locale().exponential());
307       int epos = str.indexOf(e, 0, Qt::CaseInsensitive);
308       if (epos != -1) {
309         epos++;  // Skip exponential symbol itself
310         QString exponent = str.right(str.length() - epos);
311         int expValue = exponent.toInt(&isOk);
312         if (isOk && expValue < std::numeric_limits<double>::min_exponent10)
313           state = QValidator::Invalid;
314       }
315     }
316   }
317
318   return state;
319 }
320
321 /*!
322  \brief Called when user enters the text in the spin box.
323  */
324 void ModuleBase_DoubleSpinBox::onTextChanged(const QString& )
325 {
326   myCleared = false;
327 }
328
329 void ModuleBase_DoubleSpinBox::setValueEnabled(const bool& theEnable)
330 {
331   setReadOnly(!theEnable);
332
333   QPalette aPal = palette();
334   aPal.setColor(QPalette::All, QPalette::Base,
335                 theEnable? myEnabledBaseColor : aPal.color(QPalette::Disabled, QPalette::Base));
336   setPalette(aPal);
337 }