Salome HOME
1966272677050b896165bf959e43da549355f32b
[modules/gui.git] / src / SalomeApp / SalomeApp_DoubleSpinBox.cxx
1 // Copyright (C) 2007-2022  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:      SalomeApp_DoubleSpinBox.cxx
21 // Author:    Oleg UVAROV
22 //
23
24 #ifndef DISABLE_PYCONSOLE
25   #include <PyConsole_Interp.h> // this include must be first (see PyInterp_base.h)!
26 #endif
27
28 #include "SalomeApp_DoubleSpinBox.h"
29 #include "SalomeApp_Application.h"
30 #include "SalomeApp_Study.h"
31
32 #include <SUIT_Session.h>
33
34 #include "SALOMEDSClient_ClientFactory.hxx" 
35 #include CORBA_SERVER_HEADER(SALOMEDS)
36
37 #include <QKeyEvent>
38 #include <QLineEdit>
39 #include <QToolTip>
40 #include <QRegExp>
41
42 #include <string>
43
44 /*!
45   \class SalomeApp_DoubleSpinBox
46 */
47
48 /*!
49   \brief Constructor.
50
51   Constructs a spin box with 0.0 as minimum value and 99.99 as maximum value,
52   a step value of 1.0 and a precision of 2 decimal places. 
53   The value is initially set to 0.00.
54
55   \param parent parent object
56 */
57 SalomeApp_DoubleSpinBox::SalomeApp_DoubleSpinBox( QWidget* parent )
58 : QtxDoubleSpinBox( parent ),
59   myDefaultValue( 0.0 ),
60   myIsRangeSet( false ),
61   myMinimum( 0.0 ),
62   myMaximum( 99.99 ),
63   myAcceptNames( true ),
64   myShowTip( true )
65 {
66   connectSignalsAndSlots();
67 }
68
69 /*!
70   \brief Constructor.
71
72   Constructs a spin box with specified minimum, maximum and step value.
73   The precision is set to 2 decimal places. 
74   The value is initially set to the minimum value.
75
76   \param min spin box minimum possible value
77   \param max spin box maximum possible value
78   \param step spin box increment/decrement value
79   \param parent parent object
80 */
81 SalomeApp_DoubleSpinBox::SalomeApp_DoubleSpinBox( double min, double max, double step, QWidget* parent )
82 : QtxDoubleSpinBox( min, max, step, parent ),
83   myDefaultValue( 0.0 ),
84   myIsRangeSet( false ),
85   myMinimum( min ),
86   myMaximum( max ),
87   myAcceptNames( true ),
88   myShowTip( true )
89 {
90   connectSignalsAndSlots();
91 }
92
93 /*!
94   \brief Constructor.
95
96   Constructs a spin box with specified minimum, maximum and step value.
97   The precision is set to 2 decimal places. 
98   The value is initially set to the minimum value.
99
100   \param min spin box minimum possible value
101   \param max spin box maximum possible value
102   \param step spin box increment/decrement value
103   \param parent parent object
104   \param acceptNames if true, enables variable names in the spin box
105   \param showTip if true, makes the widget show a tooltip when invalid text is entered by the user
106 */
107 SalomeApp_DoubleSpinBox::SalomeApp_DoubleSpinBox( double min, 
108                                                   double max, 
109                                                   double step, 
110                                                   int prec, 
111                                                   int dec, 
112                                                   QWidget* parent,
113                                                   bool acceptNames,
114                                                   bool showTip )
115 : QtxDoubleSpinBox( min, max, step, prec, dec, parent ),
116   myDefaultValue( 0.0 ),
117   myIsRangeSet( false ),
118   myMinimum( min ),
119   myMaximum( max ),
120   myAcceptNames( acceptNames ),
121   myShowTip( showTip )
122 {
123   connectSignalsAndSlots();
124 }
125
126 /*!
127   \brief Destructor.
128 */
129 SalomeApp_DoubleSpinBox::~SalomeApp_DoubleSpinBox()
130 {
131 }
132
133 /*!
134   \brief Perform \a steps increment/decrement steps.
135   
136   Re-implemented to handle cases when Notebook variable
137   name is specified by the user as the widget text.  
138   Otherwise, simply calls the base implementation.
139
140   \param steps number of increment/decrement steps
141 */
142 void SalomeApp_DoubleSpinBox::stepBy( int steps )
143 {
144   QString str  = text();
145   QString pref = prefix();
146   QString suff = suffix();
147   
148   if ( pref.length() && str.startsWith( pref ) )
149     str = str.right( str.length() - pref.length() );
150   if ( suff.length() && str.endsWith( suff ) )
151     str = str.left( str.length() - suff.length() );
152   
153   QRegExp varNameMask( "([a-z]|[A-Z]|_).*" );
154   if ( varNameMask.exactMatch( str ) )
155     return;
156
157   QtxDoubleSpinBox::stepBy( steps );
158 }
159
160 /*!
161   \brief Connect signals and slots.
162 */
163 void SalomeApp_DoubleSpinBox::connectSignalsAndSlots()
164 {
165   connect( this, SIGNAL( editingFinished() ),
166            this, SLOT( onEditingFinished() ) );
167
168   connect( this, SIGNAL( valueChanged( const QString& ) ),
169            this, SLOT( onTextChanged( const QString& ) ) );
170
171   connect( lineEdit(), SIGNAL( textChanged( const QString& ) ),
172            this, SLOT( onTextChanged( const QString& ) ) );
173
174   connect( lineEdit(), SIGNAL( textChanged( const QString& )),
175            this, SIGNAL( textChanged( const QString& ) ) );
176 }
177
178 /*!
179   \brief This function is called when editing is finished.
180 */
181 void SalomeApp_DoubleSpinBox::onEditingFinished()
182 {
183   if( myTextValue.isNull() )
184     myTextValue = text();
185
186   setText( myTextValue );
187 }
188
189 /*!
190   \brief This function is called when value is changed.
191 */
192 void SalomeApp_DoubleSpinBox::onTextChanged( const QString& text )
193 {
194   myTextValue = text;
195
196   double value = 0;
197   if( isValid( text, value ) == Acceptable )
198     myCorrectValue = text;
199 }
200
201 /*!
202   \brief Interpret text entered by the user as a value.
203   \param text text entered by the user
204   \return mapped value
205   \sa textFromValue()
206 */
207 double SalomeApp_DoubleSpinBox::valueFromText( const QString& text ) const
208 {
209   double value = 0;
210   if( isValid( text, value ) == Acceptable )
211     return value;
212
213   return defaultValue();
214 }
215
216 /*!
217   \brief This function is used by the spin box whenever it needs to display
218   the given value.
219
220   \param val spin box value
221   \return text representation of the value
222   \sa valueFromText()
223 */
224 QString SalomeApp_DoubleSpinBox::textFromValue( double val ) const
225 {
226   return QtxDoubleSpinBox::textFromValue( val );
227 }
228
229 /*!
230   \brief This function is used to determine whether input is valid.
231   \param str currently entered value
232   \param pos cursor position in the string
233   \return validating operation result
234 */
235 QValidator::State SalomeApp_DoubleSpinBox::validate( QString& str, int& pos ) const
236 {
237   QValidator::State res = QValidator::Invalid;
238
239   // Considering the input text as a variable name
240   // Applying Python identifier syntax:
241   // either a string starting with a letter, or a string starting with
242   // an underscore followed by at least one alphanumeric character
243   if ( isAcceptNames() ){
244     QRegExp varNameMask( "(([a-z]|[A-Z])([a-z]|[A-Z]|[0-9]|_)*)|(_([a-z]|[A-Z]|[0-9])+([a-z]|[A-Z]|[0-9]|_)*)" );
245     if ( varNameMask.exactMatch( str ) )
246       res = QValidator::Acceptable;
247   
248     if ( res == QValidator::Invalid ){
249       varNameMask.setPattern( "_" );
250       if ( varNameMask.exactMatch( str ) )  
251         res = QValidator::Intermediate;
252     }
253   }
254   
255   // Trying to interpret the current input text as a numeric value
256   if ( res == QValidator::Invalid )
257     res = QtxDoubleSpinBox::validate( str, pos );  
258   
259   // Show tooltip in case of invalid manual input
260   if ( isShowTipOnValidate() && lineEdit()->hasFocus() ){
261     if ( res != QValidator::Acceptable ){ // san: do we need to warn the user in Intermediate state???
262       SalomeApp_DoubleSpinBox* that = const_cast<SalomeApp_DoubleSpinBox*>( this );
263       QPoint pos( size().width(), 0. );
264       QPoint globalPos = mapToGlobal( pos );
265       QString minVal = textFromValue( minimum() );
266       QString maxVal = textFromValue( maximum() );
267       
268       // Same stuff as in QtxDoubleSpinBox::textFromValue()
269       int digits = getPrecision();
270       
271       // For 'g' format, max. number of digits after the decimal point is getPrecision() - 1
272       // See also QtxDoubleSpinBox::validate()
273       if ( digits < 0 )
274         digits = qAbs( digits ) - 1;
275       
276       QString templ( isAcceptNames() ? tr( "VALID_RANGE_VAR_MSG" ) : tr( "VALID_RANGE_NOVAR_MSG" ) );
277       QString msg( templ.arg( minVal ).arg( maxVal ).arg( digits ) );
278       
279       // Add extra hints to the message (if any passed through dynamic properties)
280       QVariant propVal = property( "validity_tune_hint" );
281       if ( propVal.isValid() ){
282         QString extraInfo = propVal.toString();
283         if ( !extraInfo.isEmpty() ){
284           msg += "\n";
285           msg += extraInfo;
286         }
287       }
288       
289       QToolTip::showText( globalPos, 
290                           msg, 
291                           that );
292     }
293     else
294       QToolTip::hideText();
295   }
296       
297   return res;
298 }
299
300 /*!
301   \brief This function is used to determine whether input is valid.
302   \return validating operation result
303 */
304 bool SalomeApp_DoubleSpinBox::isValid( QString& msg, bool toCorrect )
305 {
306   double value;
307   State aState = isValid( text(), value );
308
309   if( aState != Acceptable )
310   {
311     if( toCorrect )
312     {
313       if( aState == Incompatible )
314         msg += tr( "ERR_INCOMPATIBLE_TYPE" ).arg( text() ) + "\n";
315       else if( aState == NoVariable )
316         msg += tr( "ERR_NO_VARIABLE" ).arg( text() ) + "\n";
317       else if( aState == Invalid )
318         msg += tr( "ERR_INVALID_VALUE" ) + "\n";
319
320       setText( myCorrectValue );
321     }
322     return false;
323   }
324
325   return true;
326 }
327
328 /*!
329   \brief This function is used to set a default value for this spinbox.
330   \param value default value
331 */
332 void SalomeApp_DoubleSpinBox::setDefaultValue( const double value )
333 {
334   myDefaultValue = value;
335 }
336
337 /*!
338   \brief This function is used to set minimum and maximum values for this spinbox.
339   \param min minimum value
340   \param max maximum value
341 */
342 void SalomeApp_DoubleSpinBox::setRange( const double min, const double max )
343 {
344   QtxDoubleSpinBox::setRange( min, max );
345
346   myIsRangeSet = true;
347   myMinimum = min;
348   myMaximum = max;
349 }
350
351 /*!
352   \brief This function is used to set a current value for this spinbox.
353   \param value current value
354 */
355 void SalomeApp_DoubleSpinBox::setValue( const double value )
356 {
357   QtxDoubleSpinBox::setValue( value );
358
359   myCorrectValue = QtxDoubleSpinBox::textFromValue( value );
360   myTextValue = myCorrectValue;
361 }
362
363 /*!
364   \brief This function is used to set a text for this spinbox.
365   \param value current value
366 */
367 void SalomeApp_DoubleSpinBox::setText( const QString& value )
368 {
369   lineEdit()->setText(value);
370 }
371
372 /*!
373   \brief This function is used to determine whether input is valid.
374   \return validating operation result
375 */
376 SalomeApp_DoubleSpinBox::State SalomeApp_DoubleSpinBox::isValid( const QString& text, double& value ) const
377 {
378   SearchState aSearchState = findVariable( text, value );
379   if( aSearchState == NotFound )
380   {
381     bool ok = false;
382     value = locale().toDouble( text, &ok );
383     if ( !ok )
384       return NoVariable;
385   }
386   else if( aSearchState == IncorrectType )
387     return Incompatible;
388
389   if( !checkRange( value ) )
390     return Invalid;
391
392   return Acceptable;
393 }
394
395 /*!
396   \brief This function return a default acceptable value (commonly, 0.0).
397   \return default acceptable value
398 */
399 double SalomeApp_DoubleSpinBox::defaultValue() const
400 {
401   if( myMinimum > myDefaultValue || myMaximum < myDefaultValue )
402     return myMinimum;
403
404   return myDefaultValue;
405 }
406
407 /*!
408   \brief This function is used to check that string value lies within predefined range.
409   \return check status
410 */
411 bool SalomeApp_DoubleSpinBox::checkRange( const double value ) const
412 {
413   if( !myIsRangeSet )
414     return true;
415
416   return value >= myMinimum && value <= myMaximum;
417 }
418
419 /*!
420   \brief This function is used to determine whether input is a variable name and to get its value.
421   \return status of search operation
422 */
423 SalomeApp_DoubleSpinBox::SearchState SalomeApp_DoubleSpinBox::findVariable( const QString& name, double& value ) const
424 {
425   value = 0;
426   if( SalomeApp_Application* app = dynamic_cast<SalomeApp_Application*>( SUIT_Session::session()->activeApplication() ) )
427   {
428     _PTR(Study) studyDS = SalomeApp_Application::getStudy();
429
430     std::string aName = name.toStdString();
431     if( studyDS->IsVariable( aName ) )
432     {
433       if( studyDS->IsReal( aName ) || studyDS->IsInteger( aName ) || studyDS->IsString( aName ) )
434       {
435         if( studyDS->IsString( aName ) )
436         {
437 #ifndef DISABLE_PYCONSOLE
438           PyConsole_Interp* pyInterp = app->getPyInterp();
439           PyLockWrapper aLock; // Acquire GIL
440           std::string command;
441           command  = "import salome_notebook ; ";
442           command += "salome_notebook.notebook.setAsReal(\"";
443           command += aName;
444           command += "\")";
445           bool aResult;
446           aResult = pyInterp->run(command.c_str());
447           if(aResult)
448           {
449             return IncorrectType;
450           }
451 #endif
452         }
453         value = studyDS->GetReal( aName );
454         return Found;
455       }
456       return IncorrectType;
457     }
458   }
459   return NotFound;
460 }
461
462 /*!
463   \brief This function is called when the spinbox recieves key press event.
464 */
465 void SalomeApp_DoubleSpinBox::keyPressEvent( QKeyEvent* e )
466 {
467   if ( e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter )
468     QWidget::keyPressEvent( e );
469   else
470     QtxDoubleSpinBox::keyPressEvent( e );
471 }
472
473 /*!
474   \brief This function is called when the spinbox recieves show event.
475 */
476 void SalomeApp_DoubleSpinBox::showEvent( QShowEvent* )
477 {
478   setText( myTextValue );
479 }
480
481 /*!
482   \brief Enables or disables variable names in the spin box.
483          By default, variable names are enabled.
484   \param flag If true, variable names are enabled.
485 */
486 void SalomeApp_DoubleSpinBox::setAcceptNames( const bool flag )
487 {
488   myAcceptNames = flag;
489 }
490
491 /*!
492   \brief Returns true if the spin box accepts variable names.
493 */
494 bool SalomeApp_DoubleSpinBox::isAcceptNames() const
495 {
496   return myAcceptNames;
497 }
498
499 /*!
500   \brief Enables or disables  tooltips in case of invalid or intermediate-state input.
501          Tooltips are enabled by default.
502   \param flag If true, tooltips are enabled.
503 */
504 void SalomeApp_DoubleSpinBox::setShowTipOnValidate( const bool flag )
505 {
506   myShowTip = flag;
507 }
508
509 /*!
510   \brief Returns true if tooltip should be shown in case of invalid or intermediate-state input.
511 */
512 bool SalomeApp_DoubleSpinBox::isShowTipOnValidate() const
513 {
514   return myShowTip;
515 }