]> SALOME platform Git repositories - modules/shaper.git/blob - src/ModuleBase/ModuleBase_DoubleSpinBox.cpp
Salome HOME
Issues #2173, #2169: processing focus in widget by SHAPER loop (instead of Qt loop...
[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 {
64   setLocale(ModuleBase_Tools::doubleLocale());
65
66   // MPV 15/09/2014: this must be set before setDecimals;
67   // otherwise in release mode setDecimals may crash
68   myPrecision = thePrecision;
69
70   // Use precision equal to default Qt decimals
71   // it's necessary to set decimals before the range setting,
72   // by default Qt rounds boundaries to 2 decimals at setRange
73   setDecimals(qAbs(myPrecision));
74
75   connect(lineEdit(), SIGNAL(textChanged( const QString& )), this,
76           SLOT(onTextChanged( const QString& )));
77
78   myEnabledBaseColor = palette().color(QPalette::Active, QPalette::Base);
79 }
80
81 /*!
82  \brief Destructor.
83  */
84 ModuleBase_DoubleSpinBox::~ModuleBase_DoubleSpinBox()
85 {
86 }
87
88 /*!
89  \brief Check if spin box is in the "cleared" state.
90  \return \c true if spin box is cleared
91  \sa setCleared()
92  */
93 bool ModuleBase_DoubleSpinBox::isCleared() const
94 {
95   return myCleared;
96 }
97
98 /*!
99  \brief Change "cleared" status of the spin box.
100  \param on new "cleared" status
101  \sa isCleared()
102  */
103 void ModuleBase_DoubleSpinBox::setCleared(const bool on)
104 {
105   if (myCleared == on)
106     return;
107
108   myCleared = on;
109   setSpecialValueText(specialValueText());
110 }
111
112 /*!
113  \brief Set precision of the spin box
114
115  If precision value is less than 0, the 'g' format is used for value output,
116  otherwise 'f' format is used.
117
118  \param prec new precision value.
119  \sa precision()
120  */
121 void ModuleBase_DoubleSpinBox::setPrecision(const int prec)
122 {
123   int newPrec = qAbs(prec);
124   int oldPrec = qAbs(myPrecision);
125   myPrecision = prec;
126   if (newPrec != oldPrec)
127     update();
128 }
129
130 /*!
131  \brief Get precision value of the spin box
132  \return current precision value
133  \sa setPrecision()
134  */
135 int ModuleBase_DoubleSpinBox::getPrecision() const
136 {
137   return myPrecision;
138 }
139
140 /*!
141  \brief Interpret text entered by the user as a value.
142  \param text text entered by the user
143  \return mapped value
144  \sa textFromValue()
145  */
146 double ModuleBase_DoubleSpinBox::valueFromText(const QString& text) const
147 {
148   if (myPrecision < 0)
149     return text.toDouble();
150
151   return QDoubleSpinBox::valueFromText(text);
152 }
153
154 /*!
155  \brief This function is used by the spin box whenever it needs to display
156  the given value.
157
158  \param val spin box value
159  \return text representation of the value
160  \sa valueFromText()
161  */
162 QString ModuleBase_DoubleSpinBox::textFromValue(double val) const
163 {
164   QString s = locale().toString(val, myPrecision >= 0 ? 'f' : 'g', qAbs(myPrecision));
165   return removeTrailingZeroes(s);
166 }
167
168 /*!
169  \brief Return source string with removed leading and trailing zeros.
170  \param src source string
171  \return resulting string
172  */
173 QString ModuleBase_DoubleSpinBox::removeTrailingZeroes(const QString& src) const
174 {
175   QString delim(locale().decimalPoint());
176
177   int idx = src.lastIndexOf(delim);
178   if (idx == -1)
179     return src;
180
181   QString iPart = src.left(idx);
182   QString fPart = src.mid(idx + 1);
183   QString ePart = "";
184   int idx1 = fPart.lastIndexOf(QRegExp("e[+|-]?[0-9]+"));
185   if (idx1 >= 0) {
186     ePart = fPart.mid(idx1);
187     fPart = fPart.left(idx1);
188   }
189
190   fPart.remove(QRegExp("0+$"));
191
192   QString res = iPart;
193   if (!fPart.isEmpty())
194     res += delim + fPart;
195   res += ePart;
196
197   return res;
198 }
199
200 /*!
201  \brief Perform \a steps increment/decrement steps.
202
203  The \a steps value can be any integer number. If it is > 0,
204  the value incrementing is done, otherwise value is decremented
205  \a steps times.
206
207  \param steps number of increment/decrement steps
208  */
209 void ModuleBase_DoubleSpinBox::stepBy(int steps)
210 {
211   myCleared = false;
212
213   QDoubleSpinBox::stepBy(steps);
214   double tmpval = value();
215   if (qAbs(tmpval) < PSEUDO_ZERO)
216     tmpval = 0.;
217   if (tmpval < minimum())
218     tmpval = minimum();
219   else if (tmpval > maximum())
220     tmpval = maximum();
221   setValue(tmpval);
222 }
223
224 /*!
225  \brief This function is used to determine whether input is valid.
226  \param str currently entered value
227  \param pos cursor position in the string
228  \return validating operation result
229  */
230 QValidator::State ModuleBase_DoubleSpinBox::validate(QString& str, int& pos) const
231 {
232   QString pref = this->prefix();
233   QString suff = this->suffix();
234   uint overhead = pref.length() + suff.length();
235   QValidator::State state = QValidator::Invalid;
236
237   QDoubleValidator v(NULL);
238
239   // If 'g' format is used (myPrecision < 0), then
240   // myPrecision - 1 digits are allowed after the decimal point.
241   // Otherwise, expect myPrecision digits after the decimal point.
242   int decs = myPrecision < 0 ? qAbs(myPrecision) - 1 : myPrecision;
243
244   v.setLocale(this->locale());
245   v.setDecimals(decs);
246   v.setBottom(minimum());
247   v.setTop(maximum());
248   v.setNotation(
249       myPrecision >= 0 ? QDoubleValidator::StandardNotation : QDoubleValidator::ScientificNotation);
250
251   if (overhead == 0)
252     state = v.validate(str, pos);
253   else {
254     if ((uint)(str.length()) >= overhead &&
255          str.startsWith(pref) &&
256          str.right(suff.length()) == suff) {
257       QString core = str.mid(pref.length(), str.length() - overhead);
258       int corePos = pos - pref.length();
259       state = v.validate(core, corePos);
260       pos = corePos + pref.length();
261       str.replace(pref.length(), str.length() - overhead, core);
262     } else {
263       state = v.validate(str, pos);
264       if (state == QValidator::Invalid) {
265         QString special = this->specialValueText().trimmed();
266         QString candidate = str.trimmed();
267         if (special.startsWith(candidate)) {
268           if (candidate.length() == special.length())
269             state = QValidator::Acceptable;
270           else
271             state = QValidator::Intermediate;
272         }
273       }
274     }
275   }
276
277   // Treat values outside (min; max) range as Invalid
278   // This check is enabled by assigning "strict_validity_check" dynamic property
279   // with value "true" to the spin box instance.
280   if (state == QValidator::Intermediate) {
281     bool isOk;
282     double val = str.toDouble(&isOk);
283     if (isOk) {
284       QVariant propVal = property("strict_validity_check");
285       if (propVal.isValid() && propVal.canConvert(QVariant::Bool) && propVal.toBool()) {
286         if (val < minimum() || val > maximum())
287           state = QValidator::Invalid;
288       }
289     } else if (myPrecision < 0) {
290       // Consider too large negative exponent as Invalid
291       QChar e(locale().exponential());
292       int epos = str.indexOf(e, 0, Qt::CaseInsensitive);
293       if (epos != -1) {
294         epos++;  // Skip exponential symbol itself
295         QString exponent = str.right(str.length() - epos);
296         int expValue = exponent.toInt(&isOk);
297         if (isOk && expValue < std::numeric_limits<double>::min_exponent10)
298           state = QValidator::Invalid;
299       }
300     }
301   }
302
303   return state;
304 }
305
306 /*!
307  \brief Called when user enters the text in the spin box.
308  */
309 void ModuleBase_DoubleSpinBox::onTextChanged(const QString& )
310 {
311   myCleared = false;
312 }
313
314 void ModuleBase_DoubleSpinBox::setValueEnabled(const bool& theEnable)
315 {
316   setReadOnly(!theEnable);
317
318   QPalette aPal = palette();
319   aPal.setColor(QPalette::All, QPalette::Base,
320                 theEnable? myEnabledBaseColor : aPal.color(QPalette::Disabled, QPalette::Base));
321   setPalette(aPal);
322 }