Salome HOME
#18963 Minimize compiler warnings
[modules/gui.git] / src / Qtx / QtxDoubleSpinBox.cxx
1 // Copyright (C) 2007-2020  CEA/DEN, EDF R&D, OPEN CASCADE
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
18 //
19
20 // File:      QtxDoubleSpinBox.cxx
21 // Author:    Sergey TELKOV
22 //
23 #include "QtxDoubleSpinBox.h"
24
25 #include <QLineEdit>
26 #include <QDoubleValidator>
27 #include <QVariant>
28
29 #include <limits>
30
31 const double PSEUDO_ZERO = 1.e-20;
32
33 /*!
34   \class QtxDoubleSpinBox
35   \brief Enhanced version of the Qt's double spin box.
36
37   The QtxDoubleSpinBox 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 widegt 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 QtxDoubleSpinBox 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 parent parent object
73 */
74 QtxDoubleSpinBox::QtxDoubleSpinBox( QWidget* parent )
75 : QDoubleSpinBox( parent ),
76   myCleared( false )
77 {
78   // VSR 01/07/2010: Disable thousands separator for spin box
79   // (to avoid incosistency of double-2-string and string-2-double conversion)
80   QLocale loc;
81   loc.setNumberOptions(loc.numberOptions() | QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
82   setLocale(loc);
83
84   // Use precision equal to default Qt decimals
85   myPrecision = decimals();
86   
87   connect( lineEdit(), SIGNAL( textChanged( const QString& ) ), 
88            this, SLOT( onTextChanged( const QString& ) ) );
89 }
90
91 /*!
92   \brief Constructor.
93
94   Constructs a spin box with specified minimum, maximum and step value.
95   The precision is set to 2 decimal places. 
96   The value is initially set to the minimum value.
97
98   \param min spin box minimum possible value
99   \param max spin box maximum possible value
100   \param step spin box increment/decrement value
101   \param parent parent object
102 */
103 QtxDoubleSpinBox::QtxDoubleSpinBox( double min, double max, double step, QWidget* parent )
104 : QDoubleSpinBox( parent ),
105   myCleared( false )
106 {
107   // VSR 01/07/2010: Disable thousands separator for spin box
108   // (to avoid incosistency of double-2-string and string-2-double conversion)
109   QLocale loc;
110   loc.setNumberOptions(loc.numberOptions() | QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
111   setLocale(loc);
112
113   // Use precision equal to default Qt decimals
114   myPrecision = decimals();
115   
116   setMinimum( min );
117   setMaximum( max );
118   setSingleStep( step );
119
120   connect( lineEdit(), SIGNAL( textChanged( const QString& ) ), 
121            this, SLOT( onTextChanged( const QString& ) ) );
122 }
123
124 /*!
125   \brief Constructor.
126
127   Constructs a spin box with specified minimum, maximum and step value.
128   The precision is set to <prec> decimal places. 
129   The value is initially set to the minimum value.
130
131   \param min spin box minimum possible value
132   \param max spin box maximum possible value
133   \param step spin box increment/decrement value
134   \param prec non-negative values means the number of digits after the decimal point, 
135               negative value means the maximum number of significant digits for the scientific notation
136   \param dec number of digits after the decimal point passed to base Qt class (used for correct control sizing only!)
137   \param parent parent object
138 */
139 QtxDoubleSpinBox::QtxDoubleSpinBox( double min, double max, double step, int prec, int dec, QWidget* parent )
140 : QDoubleSpinBox( parent ),
141   myCleared( false ),
142   myPrecision( prec )
143 {
144   // VSR 01/07/2010: Disable thousands separator for spin box
145   // (to avoid incosistency of double-2-string and string-2-double conversion)
146   QLocale loc;
147   loc.setNumberOptions(loc.numberOptions() | QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
148   setLocale(loc);
149
150   setDecimals( dec );
151   setMinimum( min );
152   setMaximum( max );
153   setSingleStep( step );
154
155   connect( lineEdit(), SIGNAL( textChanged( const QString& ) ), 
156            this, SLOT( onTextChanged( const QString& ) ) );
157 }
158
159 /*!
160   \brief Destructor.
161 */
162 QtxDoubleSpinBox::~QtxDoubleSpinBox()
163 {
164 }
165
166 /*!
167   \brief Check if spin box is in the "cleared" state.
168   \return \c true if spin box is cleared
169   \sa setCleared()
170 */
171 bool QtxDoubleSpinBox::isCleared() const
172 {
173   return myCleared;
174 }
175
176 /*!
177   \brief Change "cleared" status of the spin box.
178   \param on new "cleared" status
179   \sa isCleared()
180 */
181 void QtxDoubleSpinBox::setCleared( const bool on )
182 {
183   if ( myCleared == on )
184     return;
185   
186   myCleared = on;
187   setSpecialValueText( specialValueText() );
188 }
189
190 /*!
191   \brief Set precision of the spin box
192   
193   If precision value is less than 0, the 'g' format is used for value output,
194   otherwise 'f' format is used.
195
196   \param prec new precision value.
197   \sa precision()
198 */
199 void QtxDoubleSpinBox::setPrecision( const int prec )
200 {
201   int newPrec = qMax( prec, 0 );
202   int oldPrec = qMax( myPrecision, 0 );
203   myPrecision = prec;
204   if ( newPrec != oldPrec )
205     update();
206 }
207
208 /*!
209   \brief Get precision value of the spin box
210   \return current prevision value
211   \sa setPrecision()
212 */
213 int QtxDoubleSpinBox::getPrecision() const
214 {
215   return myPrecision;
216 }
217
218 /*!
219   \brief Interpret text entered by the user as a value.
220   \param text text entered by the user
221   \return mapped value
222   \sa textFromValue()
223 */
224 double QtxDoubleSpinBox::valueFromText( const QString& text ) const
225 {
226   if (myPrecision < 0)
227     return text.toDouble();
228
229   return QDoubleSpinBox::valueFromText(text);
230 }
231
232 /*!
233   \brief This function is used by the spin box whenever it needs to display
234   the given value.
235
236   \param val spin box value
237   \return text representation of the value
238   \sa valueFromText()
239 */
240 QString QtxDoubleSpinBox::textFromValue( double val ) const
241 {
242   QString s = locale().toString( val, myPrecision >= 0 ? 'f' : 'g', qAbs( myPrecision ) );
243   return removeTrailingZeroes( s );
244 }
245
246 /*!
247   \brief Return source string with removed leading and trailing zeros.
248   \param str source string
249   \return resulting string
250 */
251 QString QtxDoubleSpinBox::removeTrailingZeroes( const QString& src ) const
252 {
253   QString delim( locale().decimalPoint() );
254
255   int idx = src.lastIndexOf( delim );
256   if ( idx == -1 )
257     return src;
258
259   QString iPart = src.left( idx );
260   QString fPart = src.mid( idx + 1 );
261   QString ePart = "";
262   int idx1 = fPart.lastIndexOf( QRegExp( "e[+|-]?[0-9]+" ) );
263   if ( idx1 >= 0 ) {
264     ePart = fPart.mid( idx1 );
265     fPart = fPart.left( idx1 );
266   }
267
268   fPart.remove( QRegExp( "0+$" ) );
269
270   QString res = iPart;
271   if ( !fPart.isEmpty() )
272     res += delim + fPart;
273   res += ePart;
274
275   return res;
276 }
277
278 /*!
279   \brief Perform \a steps increment/decrement steps.
280   
281   The \a steps value can be any integer number. If it is > 0,
282   the value incrementing is done, otherwise value is decremented
283   \a steps times.  
284
285   \param steps number of increment/decrement steps
286 */
287 void QtxDoubleSpinBox::stepBy( int steps )
288 {
289   myCleared = false;
290
291   QDoubleSpinBox::stepBy( steps );
292   double tmpval = value();
293   if ( qAbs( tmpval ) < PSEUDO_ZERO ) tmpval = 0.;
294   if ( tmpval < minimum() ) tmpval = minimum();
295   else if ( tmpval > maximum() ) tmpval = maximum();
296   setValue( tmpval );
297 }
298
299 /*!
300   \brief This function is used to determine whether input is valid.
301   \param str currently entered value
302   \param pos cursor position in the string
303   \return validating operation result
304 */
305 QValidator::State QtxDoubleSpinBox::validate( QString& str, int& pos ) const
306 {
307   QString pref = this->prefix();
308   QString suff = this->suffix();
309   int overhead = pref.length() + suff.length();
310   QValidator::State state = QValidator::Invalid;
311   
312   QDoubleValidator v (NULL);
313   
314   // If 'g' format is used (myPrecision < 0), then
315   // myPrecision - 1 digits are allowed after the decimal point.
316   // Otherwise, expect myPrecision digits after the decimal point.
317   int decs = myPrecision < 0 ? qAbs( myPrecision ) - 1 : myPrecision;
318  
319   v.setDecimals( decs );
320   v.setBottom( minimum() );
321   v.setTop( maximum() );
322   v.setNotation( myPrecision >= 0 ? QDoubleValidator::StandardNotation : 
323                                     QDoubleValidator::ScientificNotation );
324
325   if ( overhead == 0 )
326     state = v.validate( str, pos );
327   else
328     {
329       if ( str.length() >= overhead && str.startsWith( pref ) &&
330            str.right( suff.length() ) == suff )
331         {
332           QString core = str.mid( pref.length(), str.length() - overhead );
333           int corePos = pos - pref.length();
334           state = v.validate( core, corePos );
335           pos = corePos + pref.length();
336           str.replace( pref.length(), str.length() - overhead, core );
337         }
338       else
339         {
340           state = v.validate( str, pos );
341           if ( state == QValidator::Invalid )
342             {
343               QString special = this->specialValueText().trimmed();
344               QString candidate = str.trimmed();
345               if ( special.startsWith( candidate ) )
346                 {
347                   if ( candidate.length() == special.length() )
348                     state = QValidator::Acceptable;
349                   else
350                     state = QValidator::Intermediate;
351                 }
352             }
353         }
354     }
355
356   // Treat values ouside (min; max) range as Invalid
357   // This check is enabled by assigning "strict_validity_check" dynamic property
358   // with value "true" to the spin box instance.
359   if ( state == QValidator::Intermediate ){
360     bool isOk;
361     double val = str.toDouble( &isOk );
362     if ( isOk ){
363       QVariant propVal = property( "strict_validity_check" );
364       if ( propVal.isValid() && propVal.canConvert( QVariant::Bool ) && propVal.toBool() ){
365         if ( val < minimum() || val > maximum() )
366           state = QValidator::Invalid;
367       }
368     }
369     else if ( myPrecision < 0 ){
370       // Consider too large negative exponent as Invalid
371       QChar e( locale().exponential() );
372       int epos = str.indexOf( e, 0, Qt::CaseInsensitive );
373       if ( epos != -1 ){
374         epos++; // Skip exponential symbol itself
375         QString exponent = str.right( str.length() - epos );
376         int expValue = exponent.toInt( &isOk );
377         if ( isOk && expValue < std::numeric_limits<double>::min_exponent10 )
378           state = QValidator::Invalid;
379       }
380     }
381   }
382
383   return state;
384 }
385
386 /*!
387   \brief Called when user enters the text in the spin box.
388   \param txt current spin box text (not used)
389 */
390 void QtxDoubleSpinBox::onTextChanged( const QString& /*txt*/ )
391 {
392   myCleared = false;
393 }