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