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