From 6b7208040225894069ea47cce23db1c22ca6d6ad Mon Sep 17 00:00:00 2001 From: san Date: Mon, 5 Apr 2010 11:41:07 +0000 Subject: [PATCH] Issue 0020580: improved validation in integer and double spin boxes --- src/Qtx/QtxDoubleSpinBox.cxx | 57 ++++++-- src/SalomeApp/SalomeApp_DoubleSpinBox.cxx | 150 +++++++++++++++++++- src/SalomeApp/SalomeApp_DoubleSpinBox.h | 13 +- src/SalomeApp/SalomeApp_IntSpinBox.cxx | 138 +++++++++++++++++- src/SalomeApp/SalomeApp_IntSpinBox.h | 13 +- src/SalomeApp/resources/SalomeApp_msg_en.ts | 26 ++++ 6 files changed, 378 insertions(+), 19 deletions(-) diff --git a/src/Qtx/QtxDoubleSpinBox.cxx b/src/Qtx/QtxDoubleSpinBox.cxx index dad449f6c..579509e92 100644 --- a/src/Qtx/QtxDoubleSpinBox.cxx +++ b/src/Qtx/QtxDoubleSpinBox.cxx @@ -27,6 +27,8 @@ #include #include +#include + /*! \class QtxDoubleSpinBox \brief Enhanced version of the Qt's double spin box. @@ -48,6 +50,15 @@ ... // 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 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::min_exponent10 ) + state = QValidator::Invalid; + } + } + } + return state; } diff --git a/src/SalomeApp/SalomeApp_DoubleSpinBox.cxx b/src/SalomeApp/SalomeApp_DoubleSpinBox.cxx index f83897839..896c8528b 100644 --- a/src/SalomeApp/SalomeApp_DoubleSpinBox.cxx +++ b/src/SalomeApp/SalomeApp_DoubleSpinBox.cxx @@ -33,6 +33,8 @@ #include #include +#include +#include #include @@ -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( 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; +} diff --git a/src/SalomeApp/SalomeApp_DoubleSpinBox.h b/src/SalomeApp/SalomeApp_DoubleSpinBox.h index c5b6539ca..215083bed 100644 --- a/src/SalomeApp/SalomeApp_DoubleSpinBox.h +++ b/src/SalomeApp/SalomeApp_DoubleSpinBox.h @@ -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 diff --git a/src/SalomeApp/SalomeApp_IntSpinBox.cxx b/src/SalomeApp/SalomeApp_IntSpinBox.cxx index 76625adc9..7ab394320 100644 --- a/src/SalomeApp/SalomeApp_IntSpinBox.cxx +++ b/src/SalomeApp/SalomeApp_IntSpinBox.cxx @@ -33,6 +33,8 @@ #include #include +#include +#include #include @@ -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( 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; +} diff --git a/src/SalomeApp/SalomeApp_IntSpinBox.h b/src/SalomeApp/SalomeApp_IntSpinBox.h index d15fe99c1..3681cd175 100644 --- a/src/SalomeApp/SalomeApp_IntSpinBox.h +++ b/src/SalomeApp/SalomeApp_IntSpinBox.h @@ -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 diff --git a/src/SalomeApp/resources/SalomeApp_msg_en.ts b/src/SalomeApp/resources/SalomeApp_msg_en.ts index 9654d27fa..1caff4a7a 100644 --- a/src/SalomeApp/resources/SalomeApp_msg_en.ts +++ b/src/SalomeApp/resources/SalomeApp_msg_en.ts @@ -453,4 +453,30 @@ Please edit its parameters or remove it from table. Failed to update study! + + SalomeApp_DoubleSpinBox + + VALID_RANGE_VAR_MSG + Specify either a variable name or +a floating-point value in range ( %1; %2 ) +with %3-digit precision + + + VALID_RANGE_NOVAR_MSG + Specify a floating-point value in range ( %1; %2 ) +with %3-digit precision + + + + SalomeApp_IntSpinBox + + VALID_RANGE_VAR_MSG + Specify either a variable name or +an integer value in range ( %1; %2 ) + + + VALID_RANGE_NOVAR_MSG + Specify an integer value in range ( %1; %2 ) + + -- 2.39.2