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