]> SALOME platform Git repositories - modules/gui.git/commitdiff
Salome HOME
Issue 0020580: improved validation in integer and double spin boxes
authorsan <san@opencascade.com>
Mon, 5 Apr 2010 11:41:07 +0000 (11:41 +0000)
committersan <san@opencascade.com>
Mon, 5 Apr 2010 11:41:07 +0000 (11:41 +0000)
src/Qtx/QtxDoubleSpinBox.cxx
src/SalomeApp/SalomeApp_DoubleSpinBox.cxx
src/SalomeApp/SalomeApp_DoubleSpinBox.h
src/SalomeApp/SalomeApp_IntSpinBox.cxx
src/SalomeApp/SalomeApp_IntSpinBox.h
src/SalomeApp/resources/SalomeApp_msg_en.ts

index dad449f6caae8e58439e932c56d5d7cdeadc3df2..579509e92addc6ae979636ef8618d7e502d465a2 100644 (file)
@@ -27,6 +27,8 @@
 #include <QLineEdit>
 #include <QDoubleValidator>
 
+#include <limits>
+
 /*!
   \class QtxDoubleSpinBox
   \brief Enhanced version of the Qt's double spin box.
     ... // process entered value
   }
   \endcode
+
+  Another useful feature is possibility to use scientific notation (e.g. 1.234e+18) 
+  for the widegt text. To enable this, negative precision should be specified either
+  through a constructor or using setPrecision() method.
+
+  Note that "decimals" property of QDoubleSpinBox is almost completely substituted
+  by "myPrecision" field of QtxDoubleSpinBox class. "decimals" is still used 
+  for proper size hint calculation and for rounding minimum and maximum bounds of
+  the spin box range.
 */
 
 /*!
@@ -97,12 +108,15 @@ QtxDoubleSpinBox::QtxDoubleSpinBox( double min, double max, double step, QWidget
   \brief Constructor.
 
   Constructs a spin box with specified minimum, maximum and step value.
-  The precision is set to 2 decimal places. 
+  The precision is set to <prec> decimal places. 
   The value is initially set to the minimum value.
 
   \param min spin box minimum possible value
   \param max spin box maximum possible value
   \param step spin box increment/decrement value
+  \param prec non-negative values means the number of digits after the decimal point, 
+              negative value means the maximum number of significant digits for the scientific notation
+  \param dec number of digits after the decimal point passed to base Qt class (used for correct control sizing only!)
   \param parent parent object
 */
 QtxDoubleSpinBox::QtxDoubleSpinBox( double min, double max, double step, int prec, int dec, QWidget* parent )
@@ -202,7 +216,7 @@ double QtxDoubleSpinBox::valueFromText( const QString& text ) const
 */
 QString QtxDoubleSpinBox::textFromValue( double val ) const
 {
-  QString s = QLocale().toString( val, myPrecision >= 0 ? 'f' : 'g', myPrecision == 0 ? 6 : qAbs( myPrecision ) );
+  QString s = QLocale().toString( val, myPrecision >= 0 ? 'f' : 'g', qAbs( myPrecision ) );
   return removeTrailingZeroes( s );
 }
 
@@ -256,19 +270,23 @@ void QtxDoubleSpinBox::stepBy( int steps )
 */
 QValidator::State QtxDoubleSpinBox::validate( QString& str, int& pos ) const
 {
-  if (myPrecision >= 0)
-    return QDoubleSpinBox::validate(str, pos);
-
   QString pref = this->prefix();
   QString suff = this->suffix();
   uint overhead = pref.length() + suff.length();
   QValidator::State state = QValidator::Invalid;
-
+  
   QDoubleValidator v (NULL);
-  v.setDecimals( decimals() );
+  
+  // If 'g' format is used (myPrecision < 0), then
+  // myPrecision - 1 digits are allowed after the decimal point.
+  // Otherwise, expect myPrecision digits after the decimal point.
+  int decs = myPrecision < 0 ? qAbs( myPrecision ) - 1 : myPrecision;
+  v.setDecimals( decs );
   v.setBottom( minimum() );
   v.setTop( maximum() );
-  v.setNotation( QDoubleValidator::ScientificNotation );
+  v.setNotation( myPrecision >= 0 ? QDoubleValidator::StandardNotation : 
+                                    QDoubleValidator::ScientificNotation );
 
   if ( overhead == 0 )
     state = v.validate( str, pos );
@@ -300,6 +318,29 @@ QValidator::State QtxDoubleSpinBox::validate( QString& str, int& pos ) const
             }
         }
     }
+
+  // Treat values ouside (min; max) range as Invalid
+  if ( state == QValidator::Intermediate ){
+    bool isOk;
+    double val = str.toDouble( &isOk );
+    if ( isOk ){
+      if ( val < minimum() || val > maximum() )
+        state = QValidator::Invalid;
+    }
+    else if ( myPrecision < 0 ){
+      // Consider too large negative exponent as Invalid
+      QChar e( QLocale().exponential() );
+      int epos = str.indexOf( e, 0, Qt::CaseInsensitive );
+      if ( epos != -1 ){
+        epos++; // Skip exponential symbol itself
+        QString exponent = str.right( str.length() - epos );
+        int expValue = exponent.toInt( &isOk );
+        if ( isOk && expValue < std::numeric_limits<double>::min_exponent10 )
+          state = QValidator::Invalid;
+      }
+    }
+  }
+
   return state;
 }
 
index f83897839832d876c939867e6b6f3c6b6c8e8ce6..896c8528b41bee79300b33789e0b605ff5533583 100644 (file)
@@ -33,6 +33,8 @@
 
 #include <QKeyEvent>
 #include <QLineEdit>
+#include <QToolTip>
+#include <QRegExp>
 
 #include <string>
 
@@ -54,7 +56,9 @@ SalomeApp_DoubleSpinBox::SalomeApp_DoubleSpinBox( QWidget* parent )
   myDefaultValue( 0.0 ),
   myIsRangeSet( false ),
   myMinimum( 0.0 ),
-  myMaximum( 99.99 )
+  myMaximum( 99.99 ),
+  myAcceptNames( true ),
+  myShowTip( true )
 {
   connectSignalsAndSlots();
 }
@@ -76,7 +80,9 @@ SalomeApp_DoubleSpinBox::SalomeApp_DoubleSpinBox( double min, double max, double
   myDefaultValue( 0.0 ),
   myIsRangeSet( false ),
   myMinimum( min ),
-  myMaximum( max )
+  myMaximum( max ),
+  myAcceptNames( true ),
+  myShowTip( true )
 {
   connectSignalsAndSlots();
 }
@@ -92,13 +98,24 @@ SalomeApp_DoubleSpinBox::SalomeApp_DoubleSpinBox( double min, double max, double
   \param max spin box maximum possible value
   \param step spin box increment/decrement value
   \param parent parent object
+  \param acceptNames if true, enables variable names in the spin box
+  \param showTip if true, makes the widget show a tooltip when invalid text is entered by the user
 */
-SalomeApp_DoubleSpinBox::SalomeApp_DoubleSpinBox( double min, double max, double step, int prec, int dec, QWidget* parent )
+SalomeApp_DoubleSpinBox::SalomeApp_DoubleSpinBox( double min, 
+                                                  double max, 
+                                                  double step, 
+                                                  int prec, 
+                                                  int dec, 
+                                                  QWidget* parent,
+                                                  bool acceptNames,
+                                                  bool showTip )
 : QtxDoubleSpinBox( min, max, step, prec, dec, parent ),
   myDefaultValue( 0.0 ),
   myIsRangeSet( false ),
   myMinimum( min ),
-  myMaximum( max )
+  myMaximum( max ),
+  myAcceptNames( acceptNames ),
+  myShowTip( showTip )
 {
   connectSignalsAndSlots();
 }
@@ -110,6 +127,33 @@ SalomeApp_DoubleSpinBox::~SalomeApp_DoubleSpinBox()
 {
 }
 
+/*!
+  \brief Perform \a steps increment/decrement steps.
+  
+  Re-implemented to handle cases when Notebook variable
+  name is specified by the user as the widget text.  
+  Otherwise, simply calls the base implementation.
+
+  \param steps number of increment/decrement steps
+*/
+void SalomeApp_DoubleSpinBox::stepBy( int steps )
+{
+  QString str  = text();
+  QString pref = prefix();
+  QString suff = suffix();
+  
+  if ( pref.length() && str.startsWith( pref ) )
+    str = str.right( str.length() - pref.length() );
+  if ( suff.length() && str.endsWith( suff ) )
+    str = str.left( str.length() - suff.length() );
+  
+  QRegExp varNameMask( "([a-z]|[A-Z]|_).*" );
+  if ( varNameMask.exactMatch( str ) )
+    return;
+
+  QtxDoubleSpinBox::stepBy( steps );
+}
+
 /*!
   \brief Connect signals and slots.
 */
@@ -187,7 +231,67 @@ QString SalomeApp_DoubleSpinBox::textFromValue( double val ) const
 */
 QValidator::State SalomeApp_DoubleSpinBox::validate( QString& str, int& pos ) const
 {
-  return QValidator::Acceptable;
+  QValidator::State res = QValidator::Invalid;
+
+  // Considering the input text as a variable name
+  // Applying Python identifier syntax:
+  // either a string starting with a letter, or a string starting with
+  // an underscore followed by at least one alphanumeric character
+  if ( isAcceptNames() ){
+    QRegExp varNameMask( "(([a-z]|[A-Z])([a-z]|[A-Z]|[0-9]|_)*)|(_([a-z]|[A-Z]|[0-9])+([a-z]|[A-Z]|[0-9]|_)*)" );
+    if ( varNameMask.exactMatch( str ) )
+      res = QValidator::Acceptable;
+  
+    if ( res == QValidator::Invalid ){
+      varNameMask.setPattern( "_" );
+      if ( varNameMask.exactMatch( str ) )  
+        res = QValidator::Intermediate;
+    }
+  }
+  
+  // Trying to interpret the current input text as a numeric value
+  if ( res == QValidator::Invalid )
+    res = QtxDoubleSpinBox::validate( str, pos );  
+  
+  // Show tooltip in case of invalid manual input
+  if ( isShowTipOnValidate() && lineEdit()->hasFocus() ){
+    if ( res != QValidator::Acceptable ){ // san: do we need to warn the user in Intermediate state???
+      SalomeApp_DoubleSpinBox* that = const_cast<SalomeApp_DoubleSpinBox*>( this );
+      QPoint pos( size().width(), 0. );
+      QPoint globalPos = mapToGlobal( pos );
+      QString minVal = textFromValue( minimum() );
+      QString maxVal = textFromValue( maximum() );
+      
+      // Same stuff as in QtxDoubleSpinBox::textFromValue()
+      int digits = getPrecision();
+      
+      // For 'g' format, max. number of digits after the decimal point is getPrecision() - 1
+      // See also QtxDoubleSpinBox::validate()
+      if ( digits < 0 )
+        digits = qAbs( digits ) - 1;
+      
+      QString templ( isAcceptNames() ? tr( "VALID_RANGE_VAR_MSG" ) : tr( "VALID_RANGE_NOVAR_MSG" ) );
+      QString msg( templ.arg( minVal ).arg( maxVal ).arg( digits ) );
+      
+      // Add extra hints to the message (if any passed through dynamic properties)
+      QVariant propVal = property( "validity_tune_hint" );
+      if ( propVal.isValid() ){
+        QString extraInfo = propVal.toString();
+        if ( !extraInfo.isEmpty() ){
+          msg += "\n";
+          msg += extraInfo;
+        }
+      }
+      
+      QToolTip::showText( globalPos, 
+                          msg, 
+                          that );
+    }
+    else
+      QToolTip::hideText();
+  }
+      
+  return res;
 }
 
 /*!
@@ -372,3 +476,39 @@ void SalomeApp_DoubleSpinBox::showEvent( QShowEvent* )
 {
   setText( myTextValue );
 }
+
+/*!
+  \brief Enables or disables variable names in the spin box.
+         By default, variable names are enabled.
+  \param flag If true, variable names are enabled.
+*/
+void SalomeApp_DoubleSpinBox::setAcceptNames( const bool flag )
+{
+  myAcceptNames = flag;
+}
+
+/*!
+  \brief Returns true if the spin box accepts variable names.
+*/
+bool SalomeApp_DoubleSpinBox::isAcceptNames() const
+{
+  return myAcceptNames;
+}
+
+/*!
+  \brief Enables or disables  tooltips in case of invalid or intermediate-state input.
+         Tooltips are enabled by default.
+  \param flag If true, tooltips are enabled.
+*/
+void SalomeApp_DoubleSpinBox::setShowTipOnValidate( const bool flag )
+{
+  myShowTip = myShowTip;
+}
+
+/*!
+  \brief Returns true if tooltip should be shown in case of invalid or intermediate-state input.
+*/
+bool SalomeApp_DoubleSpinBox::isShowTipOnValidate() const
+{
+  return myShowTip;
+}
index c5b6539ca6f12e686f1b9c52ba8f42faa4d8d115..215083bed0fed04f57b7688db0c51eea41783970 100644 (file)
@@ -38,9 +38,11 @@ class SALOMEAPP_EXPORT SalomeApp_DoubleSpinBox : public QtxDoubleSpinBox
 public:
   SalomeApp_DoubleSpinBox( QWidget* = 0 );
   SalomeApp_DoubleSpinBox( double, double, double = 1, QWidget* = 0 );
-  SalomeApp_DoubleSpinBox( double, double, double, int, int, QWidget* = 0 );
+  SalomeApp_DoubleSpinBox( double, double, double, int, int, QWidget* = 0, bool = true, bool = true );
   virtual ~SalomeApp_DoubleSpinBox();
 
+  virtual void              stepBy( int );
+
   virtual double            valueFromText( const QString& ) const;
   virtual QString           textFromValue( double ) const;
 
@@ -55,6 +57,12 @@ public:
 
   virtual void              setText(const QString& );
 
+  void                      setAcceptNames( const bool );
+  bool                      isAcceptNames() const;
+
+  void                      setShowTipOnValidate( const bool );
+  bool                      isShowTipOnValidate() const;
+
 signals:
   void                      textChanged( const QString& );
 
@@ -86,6 +94,9 @@ private:
 
   QString                   myCorrectValue;
   QString                   myTextValue;
+
+  bool                      myAcceptNames;
+  bool                      myShowTip;
 };
 
 #endif
index 76625adc9c7c12da3e7279678a76f6fc196dd070..7ab3943202d9a2b0991c66c7fb9071513d0b2363 100644 (file)
@@ -33,6 +33,8 @@
 
 #include <QKeyEvent>
 #include <QLineEdit>
+#include <QToolTip>
+#include <QRegExp>
 
 #include <string>
 
@@ -50,7 +52,9 @@
 */
 SalomeApp_IntSpinBox::SalomeApp_IntSpinBox( QWidget* parent )
 : QtxIntSpinBox( parent ),
-  myDefaultValue( 0 )
+  myDefaultValue( 0 ),
+  myAcceptNames( true ),
+  myShowTip( true )
 {
   connectSignalsAndSlots();
 }
@@ -65,10 +69,19 @@ SalomeApp_IntSpinBox::SalomeApp_IntSpinBox( QWidget* parent )
   \param max spin box maximum possible value
   \param step spin box increment/decrement value
   \param parent parent object
+  \param acceptNames if true, enables variable names in the spin box
+  \param showTip if true, makes the widget show a tooltip when invalid text is entered by the user
 */
-SalomeApp_IntSpinBox::SalomeApp_IntSpinBox( int min, int max, int step, QWidget* parent )
+SalomeApp_IntSpinBox::SalomeApp_IntSpinBox( int min, 
+                                            int max, 
+                                            int step, 
+                                            QWidget* parent,
+                                            bool acceptNames,
+                                            bool showTip )
 : QtxIntSpinBox( min, max, step, parent ),
-  myDefaultValue( 0 )
+  myDefaultValue( 0 ),
+  myAcceptNames( acceptNames ),
+  myShowTip( showTip )
 {
   connectSignalsAndSlots();
 }
@@ -80,6 +93,34 @@ SalomeApp_IntSpinBox::~SalomeApp_IntSpinBox()
 {
 }
 
+
+/*!
+  \brief Perform \a steps increment/decrement steps.
+  
+  Re-implemented to handle cases when Notebook variable
+  name is specified by the user as the widget text.  
+  Otherwise, simply calls the base implementation.
+
+  \param steps number of increment/decrement steps
+*/
+void SalomeApp_IntSpinBox::stepBy( int steps )
+{
+  QString str  = text();
+  QString pref = prefix();
+  QString suff = suffix();
+  
+  if ( pref.length() && str.startsWith( pref ) )
+    str = str.right( str.length() - pref.length() );
+  if ( suff.length() && str.endsWith( suff ) )
+    str = str.left( str.length() - suff.length() );
+  
+  QRegExp varNameMask( "([a-z]|[A-Z]|_).*" );
+  if ( varNameMask.exactMatch( str ) )
+    return;
+
+  QtxIntSpinBox::stepBy( steps );
+}
+
 /*!
   \brief Connect signals and slots.
 */
@@ -157,7 +198,60 @@ QString SalomeApp_IntSpinBox::textFromValue( int val ) const
 */
 QValidator::State SalomeApp_IntSpinBox::validate( QString& str, int& pos ) const
 {
-  return QValidator::Acceptable;
+  //return QValidator::Acceptable;
+  QValidator::State res = QValidator::Invalid;
+
+  // Considering the input text as a variable name
+  // Applying Python identifier syntax:
+  // either a string starting with a letter, or a string starting with
+  // an underscore followed by at least one alphanumeric character
+  if ( isAcceptNames() ){
+    QRegExp varNameMask( "(([a-z]|[A-Z])([a-z]|[A-Z]|[0-9]|_)*)|(_([a-z]|[A-Z]|[0-9])+([a-z]|[A-Z]|[0-9]|_)*)" );
+    if ( varNameMask.exactMatch( str ) )
+      res = QValidator::Acceptable;
+  
+    if ( res == QValidator::Invalid ){
+      varNameMask.setPattern( "_" );
+      if ( varNameMask.exactMatch( str ) )  
+        res = QValidator::Intermediate;
+    }
+  }
+  
+  // Trying to interpret the current input text as a numeric value
+  if ( res == QValidator::Invalid )
+    res = QtxIntSpinBox::validate( str, pos );  
+  
+  // Show tooltip in case of invalid manual input
+  if ( isShowTipOnValidate() && lineEdit()->hasFocus() ){
+    if ( res != QValidator::Acceptable ){ // san: do we need to warn the user in Intermediate state???
+      SalomeApp_IntSpinBox* that = const_cast<SalomeApp_IntSpinBox*>( this );
+      QPoint pos( size().width(), 0. );
+      QPoint globalPos = mapToGlobal( pos );
+      QString minVal = textFromValue( minimum() );
+      QString maxVal = textFromValue( maximum() );
+      
+      QString templ( isAcceptNames() ? tr( "VALID_RANGE_VAR_MSG" ) : tr( "VALID_RANGE_NOVAR_MSG" ) );      
+      QString msg( templ.arg( minVal ).arg( maxVal ) );
+      
+      // Add extra hints to the message (if any passed through dynamic properties)
+      QVariant propVal = property( "validity_tune_hint" );
+      if ( propVal.isValid() ){
+        QString extraInfo = propVal.toString();
+        if ( !extraInfo.isEmpty() ){
+          msg += "\n";
+          msg += extraInfo;
+        }
+      }
+  
+      QToolTip::showText( globalPos, 
+                          msg, 
+                          that );
+    }
+    else
+      QToolTip::hideText();
+  }
+      
+  return res;
 }
 
 /*!
@@ -330,3 +424,39 @@ void SalomeApp_IntSpinBox::showEvent( QShowEvent* )
 {
   setText( myTextValue );
 }
+
+/*!
+  \brief Enables or disables variable names in the spin box.
+         By default, variable names are enabled.
+  \param flag If true, variable names are enabled.
+*/
+void SalomeApp_IntSpinBox::setAcceptNames( const bool flag )
+{
+  myAcceptNames = flag;
+}
+
+/*!
+  \brief Returns true if the spin box accepts variable names.
+*/
+bool SalomeApp_IntSpinBox::isAcceptNames() const
+{
+  return myAcceptNames;
+}
+
+/*!
+  \brief Enables or disables  tooltips in case of invalid or intermediate-state input.
+         Tooltips are enabled by default.
+  \param flag If true, tooltips are enabled.
+*/
+void SalomeApp_IntSpinBox::setShowTipOnValidate( const bool flag )
+{
+  myShowTip = myShowTip;
+}
+
+/*!
+  \brief Returns true if tooltip should be shown in case of invalid or intermediate-state input.
+*/
+bool SalomeApp_IntSpinBox::isShowTipOnValidate() const
+{
+  return myShowTip;
+}
index d15fe99c1d811f777e93a1e645366729c124abb1..3681cd175d18b5be957a6613d721a2b6d6e90ec7 100644 (file)
@@ -37,9 +37,11 @@ class SALOMEAPP_EXPORT SalomeApp_IntSpinBox : public QtxIntSpinBox
 
 public:
   SalomeApp_IntSpinBox( QWidget* = 0 );
-  SalomeApp_IntSpinBox( int, int, int = 1, QWidget* = 0 );
+  SalomeApp_IntSpinBox( int, int, int = 1, QWidget* = 0, bool = true, bool = true );
   virtual ~SalomeApp_IntSpinBox();
 
+  virtual void              stepBy( int );
+
   virtual int               valueFromText( const QString& ) const;
   virtual QString           textFromValue( int ) const;
 
@@ -53,6 +55,12 @@ public:
 
   virtual void              setText(const QString& );
 
+  void                      setAcceptNames( const bool );
+  bool                      isAcceptNames() const;
+
+  void                      setShowTipOnValidate( const bool );
+  bool                      isShowTipOnValidate() const;
+
 signals:
   void                      textChanged( const QString& );
 
@@ -80,6 +88,9 @@ private:
 
   QString                   myCorrectValue;
   QString                   myTextValue;
+
+  bool                      myAcceptNames;
+  bool                      myShowTip;
 };
 
 #endif
index 9654d27fa35a255af1b6ba66c646237d7920bbbf..1caff4a7a5f44e49f66adfd9dfdc04ee355dd302 100644 (file)
@@ -453,4 +453,30 @@ Please edit its parameters or remove it from table.</translation>
            <translation>Failed to update study!</translation>
        </message>
 </context>
+<context>
+    <name>SalomeApp_DoubleSpinBox</name>  
+        <message>
+            <source>VALID_RANGE_VAR_MSG</source>
+            <translation>Specify either a variable name or
+a floating-point value in range ( %1; %2 )
+with %3-digit precision</translation>
+        </message>   
+        <message>
+            <source>VALID_RANGE_NOVAR_MSG</source>
+            <translation>Specify a floating-point value in range ( %1; %2 )
+with %3-digit precision</translation>
+        </message>
+</context>
+<context>
+    <name>SalomeApp_IntSpinBox</name>  
+        <message>
+            <source>VALID_RANGE_VAR_MSG</source>
+            <translation>Specify either a variable name or
+an integer value in range ( %1; %2 )</translation>
+        </message>   
+        <message>
+            <source>VALID_RANGE_NOVAR_MSG</source>
+            <translation>Specify an integer value in range ( %1; %2 )</translation>
+        </message>
+</context>
 </TS>