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