Salome HOME
[bos #32216][CEA] GUI ergonomic with HDPI screens.
[modules/gui.git] / src / SalomeApp / SalomeApp_DoubleSpinBox.cxx
1 // Copyright (C) 2007-2024  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:      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     showValidationToolTip(res);
262   }
263
264   return res;
265 }
266
267 /*!
268   \brief This function is used to determine whether input is valid.
269   \return validating operation result
270 */
271 bool SalomeApp_DoubleSpinBox::isValid( QString& msg, bool toCorrect )
272 {
273   double value;
274   State aState = isValid( text(), value );
275
276   if( aState != Acceptable )
277   {
278     if( toCorrect )
279     {
280       if( aState == Incompatible )
281         msg += tr( "ERR_INCOMPATIBLE_TYPE" ).arg( text() ) + "\n";
282       else if( aState == NoVariable )
283         msg += tr( "ERR_NO_VARIABLE" ).arg( text() ) + "\n";
284       else if( aState == Invalid )
285         msg += tr( "ERR_INVALID_VALUE" ) + "\n";
286
287       setText( myCorrectValue );
288     }
289     return false;
290   }
291
292   return true;
293 }
294
295 /*!
296   \brief This function is used to set a default value for this spinbox.
297   \param value default value
298 */
299 void SalomeApp_DoubleSpinBox::setDefaultValue( const double value )
300 {
301   myDefaultValue = value;
302 }
303
304 /*!
305   \brief This function is used to set minimum and maximum values for this spinbox.
306   \param min minimum value
307   \param max maximum value
308 */
309 void SalomeApp_DoubleSpinBox::setRange( const double min, const double max )
310 {
311   QtxDoubleSpinBox::setRange( min, max );
312
313   myIsRangeSet = true;
314   myMinimum = min;
315   myMaximum = max;
316 }
317
318 /*!
319   \brief This function is used to set a current value for this spinbox.
320   \param value current value
321 */
322 void SalomeApp_DoubleSpinBox::setValue( const double value )
323 {
324   QtxDoubleSpinBox::setValue( value );
325
326   myCorrectValue = QtxDoubleSpinBox::textFromValue( value );
327   myTextValue = myCorrectValue;
328 }
329
330 /*!
331   \brief This function is used to set a text for this spinbox.
332   \param value current value
333 */
334 void SalomeApp_DoubleSpinBox::setText( const QString& value )
335 {
336   lineEdit()->setText(value);
337 }
338
339 /*!
340   \brief This function is used to determine whether input is valid.
341   \return validating operation result
342 */
343 SalomeApp_DoubleSpinBox::State SalomeApp_DoubleSpinBox::isValid( const QString& text, double& value ) const
344 {
345   SearchState aSearchState = findVariable( text, value );
346   if( aSearchState == NotFound )
347   {
348     bool ok = false;
349     value = locale().toDouble( text, &ok );
350     if ( !ok )
351       return NoVariable;
352   }
353   else if( aSearchState == IncorrectType )
354     return Incompatible;
355
356   if( !checkRange( value ) )
357     return Invalid;
358
359   return Acceptable;
360 }
361
362 /*!
363   \brief This function return a default acceptable value (commonly, 0.0).
364   \return default acceptable value
365 */
366 double SalomeApp_DoubleSpinBox::defaultValue() const
367 {
368   if( myMinimum > myDefaultValue || myMaximum < myDefaultValue )
369     return myMinimum;
370
371   return myDefaultValue;
372 }
373
374 /*!
375   \brief This function is used to check that string value lies within predefined range.
376   \return check status
377 */
378 bool SalomeApp_DoubleSpinBox::checkRange( const double value ) const
379 {
380   if( !myIsRangeSet )
381     return true;
382
383   return value >= myMinimum && value <= myMaximum;
384 }
385
386 /*!
387   \brief This function is used to determine whether input is a variable name and to get its value.
388   \return status of search operation
389 */
390 SalomeApp_DoubleSpinBox::SearchState SalomeApp_DoubleSpinBox::findVariable( const QString& name, double& value ) const
391 {
392   value = 0;
393   if( SalomeApp_Application* app = dynamic_cast<SalomeApp_Application*>( SUIT_Session::session()->activeApplication() ) )
394   {
395     _PTR(Study) studyDS = SalomeApp_Application::getStudy();
396
397     std::string aName = name.toStdString();
398     if( studyDS->IsVariable( aName ) )
399     {
400       if( studyDS->IsReal( aName ) || studyDS->IsInteger( aName ) || studyDS->IsString( aName ) )
401       {
402         if( studyDS->IsString( aName ) )
403         {
404 #ifndef DISABLE_PYCONSOLE
405           PyConsole_Interp* pyInterp = app->getPyInterp();
406           PyLockWrapper aLock; // Acquire GIL
407           std::string command;
408           command  = "import salome_notebook ; ";
409           command += "salome_notebook.notebook.setAsReal(\"";
410           command += aName;
411           command += "\")";
412           bool aResult;
413           aResult = pyInterp->run(command.c_str());
414           if(aResult)
415           {
416             return IncorrectType;
417           }
418 #endif
419         }
420         value = studyDS->GetReal( aName );
421         return Found;
422       }
423       return IncorrectType;
424     }
425   }
426   return NotFound;
427 }
428
429 void SalomeApp_DoubleSpinBox::showValidationToolTip(const QValidator::State state) const
430 {
431   // san: do we need to warn the user in Intermediate state???
432   if ( state == QValidator::Acceptable )
433   {
434     QToolTip::hideText();
435     return;
436   }
437
438   SalomeApp_DoubleSpinBox* that = const_cast<SalomeApp_DoubleSpinBox*>( this );
439   QPoint pos( size().width(), 0. );
440   QPoint globalPos = mapToGlobal( pos );
441   QString minVal = textFromValue( minimum() );
442   QString maxVal = textFromValue( maximum() );
443   
444   // Same stuff as in QtxDoubleSpinBox::textFromValue()
445   int digits = getPrecision();
446   
447   // For 'g' format, max. number of digits after the decimal point is getPrecision() - 1
448   // See also QtxDoubleSpinBox::validate()
449   if ( digits < 0 )
450     digits = qAbs( digits ) - 1;
451
452   QString templ( isAcceptNames() ? tr( "VALID_RANGE_VAR_MSG" ) : tr( "VALID_RANGE_NOVAR_MSG" ) );
453   QString msg( templ.arg( minVal ).arg( maxVal ).arg( digits ) );
454
455   // Add extra hints to the message (if any passed through dynamic properties)
456   QVariant propVal = property( "validity_tune_hint" );
457   if ( propVal.isValid() ){
458     QString extraInfo = propVal.toString();
459     if ( !extraInfo.isEmpty() ){
460       msg += "\n";
461       msg += extraInfo;
462     }
463   }
464
465   QToolTip::showText( globalPos, 
466                       msg, 
467                       that );
468 }
469
470 /*!
471   \brief This function is called when the spinbox recieves key press event.
472 */
473 void SalomeApp_DoubleSpinBox::keyPressEvent( QKeyEvent* e )
474 {
475   if ( e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter )
476     QWidget::keyPressEvent( e );
477   else
478     QtxDoubleSpinBox::keyPressEvent( e );
479 }
480
481 /*!
482   \brief This function is called when the spinbox recieves show event.
483 */
484 void SalomeApp_DoubleSpinBox::showEvent( QShowEvent* )
485 {
486   setText( myTextValue );
487 }
488
489 /*!
490   \brief Enables or disables variable names in the spin box.
491          By default, variable names are enabled.
492   \param flag If true, variable names are enabled.
493 */
494 void SalomeApp_DoubleSpinBox::setAcceptNames( const bool flag )
495 {
496   myAcceptNames = flag;
497 }
498
499 /*!
500   \brief Returns true if the spin box accepts variable names.
501 */
502 bool SalomeApp_DoubleSpinBox::isAcceptNames() const
503 {
504   return myAcceptNames;
505 }
506
507 /*!
508   \brief Enables or disables  tooltips in case of invalid or intermediate-state input.
509          Tooltips are enabled by default.
510   \param flag If true, tooltips are enabled.
511 */
512 void SalomeApp_DoubleSpinBox::setShowTipOnValidate( const bool flag )
513 {
514   myShowTip = flag;
515 }
516
517 /*!
518   \brief Returns true if tooltip should be shown in case of invalid or intermediate-state input.
519 */
520 bool SalomeApp_DoubleSpinBox::isShowTipOnValidate() const
521 {
522   return myShowTip;
523 }