Commit d036da6c authored by Victor Poughon's avatar Victor Poughon

Merge branch 'new_spinboxes' into 'develop'

New custom widgets for Float and Int parameters

See merge request orfeotoolbox/otb!123
parents 8338764f e3e4b307
......@@ -27,6 +27,8 @@
#include "otbWrapperQtWidgetParameterBase.h"
#endif //tag=QT4-boost-compatibility
#include "otbWrapperQtWidgetSpinBoxes.h"
namespace otb
{
namespace Wrapper
......@@ -44,19 +46,22 @@ public:
QtWidgetFloatParameter(FloatParameter*, QtWidgetModel*);
~QtWidgetFloatParameter() override;
protected slots:
void SetValue( double value );
private slots:
void OnCleared();
void OnValueChanged(double);
void OnEditingFinished();
private:
QtWidgetFloatParameter(const QtWidgetFloatParameter&); //purposely not implemented
void operator=(const QtWidgetFloatParameter&); //purposely not implemented
void DoCreateWidget() override;
void DoUpdateGUI() override;
bool eventFilter(QObject * o, QEvent * e) override;
QHBoxLayout * m_QHBoxLayout;
QDoubleSpinBox * m_QDoubleSpinBox;
QtWidgetDoubleSpinBox * m_QDoubleSpinBox;
FloatParameter::Pointer m_FloatParam;
};
......
......@@ -27,6 +27,8 @@
#include "otbWrapperQtWidgetParameterBase.h"
#endif //tag=QT4-boost-compatibility
#include "otbWrapperQtWidgetSpinBoxes.h"
namespace otb
{
......@@ -45,19 +47,22 @@ public:
QtWidgetIntParameter(IntParameter*, QtWidgetModel*);
~QtWidgetIntParameter() override;
protected slots:
void SetValue( int value );
private slots:
void OnCleared();
void OnValueChanged(int);
void OnEditingFinished();
private:
QtWidgetIntParameter(const QtWidgetIntParameter&); //purposely not implemented
void operator=(const QtWidgetIntParameter&); //purposely not implemented
void DoCreateWidget() override;
void DoUpdateGUI() override;
bool eventFilter( QObject * o, QEvent * e );
QHBoxLayout * m_QHBoxLayout;
QSpinBox * m_QSpinBox;
QtWidgetSpinBox* m_QSpinBox;
IntParameter::Pointer m_IntParam;
};
......
......@@ -158,12 +158,11 @@ protected slots:
*/
void ExecuteAndWriteOutputSlot();
public slots:
/**
* \brief Slots called every time one of the widget needs to be
* updated (e.g. by specialized parameter widgets).
*
* This slot is protected so it can only be called via Qt
* signal/slot mechanism and not directly by extern caller.
*/
void NotifyUpdate();
......
......@@ -66,7 +66,6 @@ public:
public slots:
void UpdateGUI();
virtual void SetActivationState( bool value );
void Reset();
protected slots:
void ParameterChanged(const QString& key);
......
/*
* Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
*
* This file is part of Orfeo Toolbox
*
* https://www.orfeo-toolbox.org/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef otbWrapperQtWidgetSpinBoxes_h
#define otbWrapperQtWidgetSpinBoxes_h
#include <QIcon>
#include <QLineEdit>
#include <QSpinBox>
#include "otbWrapperQtWidgetParameterBase.h"
namespace otb
{
namespace Wrapper
{
// A QLineEdit with a built-in reset button
class OTBQtWidget_EXPORT QtWidgetLineEdit : public QLineEdit
{
Q_OBJECT
public:
QtWidgetLineEdit( QWidget *parent = nullptr );
// Show, hide or get state of the clear button
void EnableClearButton();
void DisableClearButton();
static const int CLEAR_ICON_SIZE = 16;
signals:
// Triggered when the clear button is pressed
void Cleared();
private:
QIcon m_ClearIcon;
QAction *m_ClearAction = nullptr;
};
// A QSpinBox with QtWidgetLineEdit as a custom LineEdit
class OTBQtWidget_EXPORT QtWidgetSpinBox : public QSpinBox
{
Q_OBJECT
public:
explicit QtWidgetSpinBox( QWidget* parent = nullptr);
int valueFromText(const QString &text) const override;
QValidator::State validate( QString &input, int &pos ) const override;
// Show, hide or get state of the clear button
void EnableClearButton();
void DisableClearButton();
bool IsClearButtonEnabled() const;
void SetValueNoSignal(int);
signals:
// Triggered when the clear button is pressed
void Cleared();
private:
QtWidgetLineEdit* m_LineEdit;
};
// A QDoubleSpinBox with QtWidgetLineEdit as a custom LineEdit
// and custom handling of textFromValue / valueFromText for variable precision decimals
class OTBQtWidget_EXPORT QtWidgetDoubleSpinBox : public QDoubleSpinBox
{
Q_OBJECT
public:
explicit QtWidgetDoubleSpinBox( QWidget* parent = nullptr);
double valueFromText(const QString &text) const override;
QString textFromValue(double value) const override;
QValidator::State validate( QString &input, int &pos ) const override;
// Show, hide or get state of the clear button
void EnableClearButton();
void DisableClearButton();
bool IsClearButtonEnabled() const;
void SetValueNoSignal(double);
signals:
// Triggered when the clear button is pressed
void Cleared();
private:
QtWidgetLineEdit* m_LineEdit;
};
}
}
#endif
......@@ -58,6 +58,7 @@ set(OTBQtWidget_SRC
otbWrapperQtWidgetListEditItemModel.cxx
otbWrapperQtWidgetParameterList.cxx
otbWrapperQtWidgetBoolParameter.cxx
otbWrapperQtWidgetSpinBoxes.cxx
)
set(OTBQtWidget_MOC_HDR
......@@ -99,19 +100,26 @@ set(OTBQtWidget_MOC_HDR
../include/otbWrapperQtWidgetListEditItemModel.h
../include/otbWrapperQtWidgetParameterList.h
../include/otbWrapperQtWidgetBoolParameter.h
../include/otbWrapperQtWidgetSpinBoxes.h
)
set( OTBQtWidget_FORMS
otbWrapperQtWidgetListEditWidget.ui
)
set( OTBQtWidget_RESOURCES
otbWrapperQtWidgetIcons.qrc
)
qt5_wrap_cpp( OTBQtWidget_MOC_SRC ${OTBQtWidget_MOC_HDR} )
qt5_wrap_ui( OTBQtWidget_FORMS_HEADERS ${OTBQtWidget_FORMS} )
qt5_add_resources( OTBQtWidget_RESOURCES_RCC ${OTBQtWidget_RESOURCES} OPTIONS "-no-compress")
add_library( OTBQtWidget
${OTBQtWidget_SRC}
${OTBQtWidget_FORMS_HEADERS}
${OTBQtWidget_MOC_SRC}
${OTBQtWidget_RESOURCES_RCC}
)
target_link_libraries( OTBQtWidget
......
......@@ -20,6 +20,8 @@
#include "otbWrapperQtWidgetFloatParameter.h"
#include <limits>
namespace otb
{
namespace Wrapper
......@@ -38,17 +40,15 @@ QtWidgetFloatParameter::~QtWidgetFloatParameter()
void QtWidgetFloatParameter::DoUpdateGUI()
{
// Update the valid range if updated
m_QDoubleSpinBox->setRange(m_FloatParam->GetMinimumValue(),
m_FloatParam->GetMaximumValue());
bool signalsBlocked2 = m_QDoubleSpinBox->blockSignals( true );
m_QDoubleSpinBox->setRange(m_FloatParam->GetMinimumValue(), m_FloatParam->GetMaximumValue());
// Update the SpinBox value if parameter has been updated
if (m_FloatParam->HasValue())
{
m_QDoubleSpinBox->setValue(m_FloatParam->GetValue());
}
m_QDoubleSpinBox->blockSignals( signalsBlocked2 );
{
m_QDoubleSpinBox->SetValueNoSignal(m_FloatParam->GetValue());
}
// Set style depending on UserValue parameter flag
QFont f = m_QDoubleSpinBox->font();
if (m_FloatParam->HasUserValue())
{
......@@ -68,18 +68,34 @@ void QtWidgetFloatParameter::DoCreateWidget()
m_QHBoxLayout->setSpacing(0);
m_QHBoxLayout->setContentsMargins(0, 0, 0, 0);
m_QDoubleSpinBox = new QDoubleSpinBox;
m_QDoubleSpinBox->setDecimals(5);
m_QDoubleSpinBox = new QtWidgetDoubleSpinBox;
m_QDoubleSpinBox->setSingleStep(0.1);
m_QDoubleSpinBox->setDecimals(std::numeric_limits<float>::digits10); // max precision, this is 6 for IEEE float
m_QDoubleSpinBox->setRange(m_FloatParam->GetMinimumValue(), m_FloatParam->GetMaximumValue());
m_QDoubleSpinBox->setToolTip(
QString::fromStdString(
m_FloatParam->GetDescription()
)
);
m_QDoubleSpinBox->setToolTip( QString::fromStdString( m_FloatParam->GetDescription()));
// Block mouse wheel events to the QSpinBox
// this is to avoid grabbing focus when scrolling the parent QScrollArea
m_QDoubleSpinBox->setFocusPolicy(Qt::StrongFocus);
m_QDoubleSpinBox->installEventFilter(this);
// Set the SpinBox initial value to the parameter value
if (m_FloatParam->HasValue())
m_QDoubleSpinBox->SetValueNoSignal(m_FloatParam->GetValue());
// What happens when the Reset button is clicked
connect(m_QDoubleSpinBox, &QtWidgetDoubleSpinBox::Cleared,
this, &QtWidgetFloatParameter::OnCleared);
// What happens when the value changed because of user interaction (keyboard or arrows pressed)
// Note: to avoid calling this when the value changes automatically (reset button, DoParameterUpdate, XML load),
// calls to QSpinBox::setValue() are wrapped with signal blockers
connect(m_QDoubleSpinBox, static_cast<void (QtWidgetDoubleSpinBox::*)(double)>(&QtWidgetDoubleSpinBox::valueChanged),
this, &QtWidgetFloatParameter::OnValueChanged);
connect( m_QDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(SetValue(double)) );
connect( m_QDoubleSpinBox, SIGNAL(valueChanged(double)), GetModel(), SLOT(NotifyUpdate()) );
// What happens when the SpinBox looses focus, or enter is pressed
connect(m_QDoubleSpinBox, &QtWidgetDoubleSpinBox::editingFinished,
this, &QtWidgetFloatParameter::OnEditingFinished);
m_QHBoxLayout->addWidget(m_QDoubleSpinBox);
m_QHBoxLayout->addStretch();
......@@ -92,16 +108,45 @@ void QtWidgetFloatParameter::DoCreateWidget()
}
}
void QtWidgetFloatParameter::SetValue(double value)
void QtWidgetFloatParameter::OnCleared()
{
// Set the parameter to its default value
m_FloatParam->Reset();
// Reset the SpinBox value, but avoid triggering its valueChanged signal
m_QDoubleSpinBox->SetValueNoSignal(m_FloatParam->GetDefaultValue());
// Unset user value flag and hide the Reset button
m_FloatParam->SetUserValue(false);
m_QDoubleSpinBox->DisableClearButton();
// Call the application DoUpdateParameters, then all widgets' DoUpdateGUI (including this one)
this->GetModel()->NotifyUpdate();
}
void QtWidgetFloatParameter::OnValueChanged(double value)
{
// Set the parameter value, user value flag and show the Reset button
m_FloatParam->SetValue( static_cast<float>(value) );
/** moved to ParameterChanged slot in QtWidgetParameterBase:: **/
/** m_FloatParam->SetUserValue(true); **/
m_FloatParam->SetUserValue(true);
m_QDoubleSpinBox->EnableClearButton();
}
QString key( m_FloatParam->GetKey() );
emit ParameterChanged(key);
void QtWidgetFloatParameter::OnEditingFinished()
{
// Call the application DoUpdateParameters, then all widgets' DoUpdateGUI (including this one)
this->GetModel()->NotifyUpdate();
}
m_FloatParam->SetAutomaticValue(false);
// Filter mouse wheel events to avoid scrolling issues in parent QScrollArea
bool QtWidgetFloatParameter::eventFilter(QObject * o, QEvent * e)
{
if ( e->type() == QEvent::Wheel && qobject_cast<QAbstractSpinBox*>( o ) )
{
e->ignore();
return true;
}
return QWidget::eventFilter( o, e );
}
}
......
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>../../../../Utilities/Data/Icons/mIconClearText.png</file>
<file>../../../../Utilities/Data/Icons/mIconClearTextHover.png</file>
</qresource>
</RCC>
......@@ -35,6 +35,30 @@ QtWidgetIntParameter::~QtWidgetIntParameter()
{
}
void QtWidgetIntParameter::DoUpdateGUI()
{
// Update the valid range if updated
m_QSpinBox->setRange(m_IntParam->GetMinimumValue(), m_IntParam->GetMaximumValue());
// Update the SpinBox value if parameter has been updated
if (m_IntParam->HasValue())
{
m_QSpinBox->SetValueNoSignal(m_IntParam->GetValue());
}
// Set style depending on UserValue parameter flag
QFont f = m_QSpinBox->font();
if (m_IntParam->HasUserValue())
{
f.setBold(true);
}
else
{
f.setBold(false);
}
m_QSpinBox->setFont(f);
}
void QtWidgetIntParameter::DoCreateWidget()
{
// Set up input text edit
......@@ -42,14 +66,32 @@ void QtWidgetIntParameter::DoCreateWidget()
m_QHBoxLayout->setSpacing(0);
m_QHBoxLayout->setContentsMargins(0, 0, 0, 0);
m_QSpinBox = new QSpinBox;
m_QSpinBox = new QtWidgetSpinBox;
m_QSpinBox->setRange(m_IntParam->GetMinimumValue(), m_IntParam->GetMaximumValue());
m_QSpinBox->setToolTip(
QString::fromStdString( m_IntParam->GetDescription() )
);
m_QSpinBox->setToolTip(QString::fromStdString(m_IntParam->GetDescription()));
// Block mouse wheel events to the QSpinBox
// this is to avoid grabbing focus when scrolling the parent QScrollArea
m_QSpinBox->setFocusPolicy(Qt::StrongFocus);
m_QSpinBox->installEventFilter(this);
// Set the SpinBox initial value to the parameter value
if (m_IntParam->HasValue())
m_QSpinBox->SetValueNoSignal(m_IntParam->GetValue());
// What happens when the Reset button is clicked
connect(m_QSpinBox, &QtWidgetSpinBox::Cleared,
this, &QtWidgetIntParameter::OnCleared);
connect( m_QSpinBox, SIGNAL(valueChanged(int)), this, SLOT(SetValue(int)) );
connect( m_QSpinBox, SIGNAL(valueChanged(int)), GetModel(), SLOT(NotifyUpdate()) );
// What happens when the value changed because of user interaction (keyboard or arrows pressed)
// Note: to avoid calling this when the value changes automatically (reset button, DoParameterUpdate, XML load),
// calls to QSpinBox::setValue() are wrapped with signal blockers
connect(m_QSpinBox, static_cast<void (QtWidgetSpinBox::*)(int)>(&QtWidgetSpinBox::valueChanged),
this, &QtWidgetIntParameter::OnValueChanged);
// What happens when the SpinBox looses focus, or enter is pressed
connect(m_QSpinBox, &QtWidgetSpinBox::editingFinished,
this, &QtWidgetIntParameter::OnEditingFinished);
m_QHBoxLayout->addWidget(m_QSpinBox);
m_QHBoxLayout->addStretch();
......@@ -62,44 +104,47 @@ void QtWidgetIntParameter::DoCreateWidget()
}
}
void QtWidgetIntParameter::DoUpdateGUI()
void QtWidgetIntParameter::OnCleared()
{
// Update the valid range if updated
m_QSpinBox->setRange(m_IntParam->GetMinimumValue(),
m_IntParam->GetMaximumValue());
// Set the parameter to its default value
m_IntParam->Reset();
bool signalsBlocked2 = m_QSpinBox->blockSignals( true );
// Reset the SpinBox value, but avoid triggering its valueChanged signal
m_QSpinBox->SetValueNoSignal(m_IntParam->GetDefaultValue());
if (m_IntParam->HasValue())
{
m_QSpinBox->setValue(m_IntParam->GetValue());
}
m_QSpinBox->blockSignals( signalsBlocked2 );
// Unset user value flag and hide the Reset button
m_IntParam->SetUserValue(false);
m_QSpinBox->DisableClearButton();
QFont f = m_QSpinBox->font();
if (m_IntParam->HasUserValue())
{
f.setBold(true);
}
else
{
f.setBold(false);
}
m_QSpinBox->setFont(f);
// Call the application DoUpdateParameters, then all widgets' DoUpdateGUI (including this one)
this->GetModel()->NotifyUpdate();
}
void QtWidgetIntParameter::SetValue(int value)
void QtWidgetIntParameter::OnValueChanged(int value)
{
m_IntParam->SetValue(value);
/** moved to ParameterChanged slot in QtWidgetParameterBase:: **/
/**m_IntParam->SetUserValue(true); **/
// Set the parameter value, user value flag and show the Reset button
m_IntParam->SetValue( static_cast<float>(value) );
m_IntParam->SetUserValue(true);
m_QSpinBox->EnableClearButton();
}
QString key( m_IntParam->GetKey() );
emit ParameterChanged(key);
void QtWidgetIntParameter::OnEditingFinished()
{
// Call the application DoUpdateParameters, then all widgets' DoUpdateGUI (including this one)
this->GetModel()->NotifyUpdate();
}
m_IntParam->SetAutomaticValue(false);
// Filter mouse wheel events to avoid scrolling issues in parent QScrollArea
bool QtWidgetIntParameter::eventFilter( QObject * o, QEvent * e )
{
if ( e->type() == QEvent::Wheel && qobject_cast<QAbstractSpinBox*>( o ) )
{
e->ignore();
return true;
}
return QWidget::eventFilter( o, e );
}
}
}
......@@ -40,10 +40,10 @@ QtWidgetParameterBase::~QtWidgetParameterBase()
void QtWidgetParameterBase::CreateWidget()
{
this->DoCreateWidget();
// Connect the model update gui signal to this widget update gui slot
connect( GetModel(), &QtWidgetModel::UpdateGui, this, &QtWidgetParameterBase::UpdateGUI );
// connect the update signal to this widget
connect( GetModel(), SIGNAL(UpdateGui()), this, SLOT(UpdateGUI() ) );
this->DoCreateWidget();
}
void QtWidgetParameterBase::UpdateGUI()
......@@ -96,15 +96,6 @@ void QtWidgetParameterBase::SetActivationState( bool value )
}
// Slot connected to the signal emitted by the Reset Button
void QtWidgetParameterBase::Reset( )
{
m_Param->Reset();
m_Param->SetUserValue(false);
m_Param->SetAutomaticValue(false);
this->UpdateGUI();
}
const Parameter *
QtWidgetParameterBase
::GetParam() const
......
......@@ -40,11 +40,10 @@ QtWidgetParameterGroup::~QtWidgetParameterGroup()
void QtWidgetParameterGroup::DoUpdateGUI()
{
WidgetListIteratorType it = m_WidgetList.begin();
for (it = m_WidgetList.begin(); it != m_WidgetList.end(); ++it)
{
(*it)->UpdateGUI();
}
// Note that we do not need to call each child widget's UpdateGUI here,
// because they already each have a signal/slot connection that triggers it
// when the model updates.
// It is created in QtWidgetParameterBase::CreateWidget()
}
void QtWidgetParameterGroup::DoCreateWidget()
......@@ -103,26 +102,6 @@ void QtWidgetParameterGroup::DoCreateWidget()
}
gridLayout->addWidget(checkBox, i, 0);
// Reset Button
// Make sense only for NumericalParameter
if (dynamic_cast<IntParameter*>(param)
|| dynamic_cast<FloatParameter*>(param)
|| dynamic_cast<RadiusParameter*>(param)
/*|| dynamic_cast<RAMParameter*>(param)*/)
{
if( param->GetRole() != Role_Output )
{
QPushButton* resetButton = new QPushButton;
resetButton->setText("Reset");
resetButton->setToolTip("Reset the value of this parameter");
gridLayout->addWidget(resetButton, i, 3);
// Slots to connect to the reset button
connect( resetButton, SIGNAL(clicked()), specificWidget, SLOT(Reset()) );
connect( resetButton, SIGNAL(clicked()), GetModel(), SLOT(NotifyUpdate()) );
}
}
m_WidgetList.push_back(specificWidget);
}
else
......
/*
* Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
*
* This file is part of Orfeo Toolbox
*
* https://www.orfeo-toolbox.org/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "otbWrapperQtWidgetSpinBoxes.h"
#include <sstream>
#include <limits>
#include <locale>
#include <QAction>
namespace otb
{
namespace Wrapper
{
QtWidgetLineEdit::QtWidgetLineEdit( QWidget *parent )
: QLineEdit( parent )
{
// Setup the clear button icon
m_ClearIcon.addPixmap( QIcon(":/Utilities/Data/Icons/mIconClearText.png").pixmap( QSize( CLEAR_ICON_SIZE, CLEAR_ICON_SIZE)), QIcon::Normal, QIcon::On );
m_ClearIcon.addPixmap( QIcon(":/Utilities/Data/Icons/mIconClearTextHover.png").pixmap( QSize( CLEAR_ICON_SIZE, CLEAR_ICON_SIZE)) , QIcon::Selected, QIcon::On );
}
void QtWidgetLineEdit::EnableClearButton()
{
if ( m_ClearAction == nullptr )
{
m_ClearAction = new QAction(m_ClearIcon, QString("Reset"), this );
m_ClearAction->setCheckable( false );
this->addAction( m_ClearAction, QLineEdit::TrailingPosition );
// Forward the trigger signal
connect( m_ClearAction, &QAction::triggered, this, &QtWidgetLineEdit::Cleared );
}
}
void QtWidgetLineEdit::DisableClearButton()
{
if ( m_ClearAction != nullptr )
{
m_ClearAction->deleteLater();
m_ClearAction = nullptr;
}
}
QtWidgetSpinBox::QtWidgetSpinBox( QWidget* parent )
: QSpinBox( parent )
{
// Use a custom LineEdit a forward its Cleared signal
m_LineEdit = new QtWidgetLineEdit();
this->setLineEdit(m_LineEdit);
connect(m_LineEdit, &QtWidgetLineEdit::Cleared, this, &QtWidgetSpinBox::Cleared);
// Small Qt hack to prevent highlighting the text after it has changed (to improve UX a bit)
connect(this, static_cast<void (QtWidgetSpinBox::*)(int)>(&QtWidgetSpinBox::valueChanged), this, [&](int) {
m_LineEdit->deselect();
}, Qt::QueuedConnection);
// Add icon size and a 10px margin to minimum size hint
QSize msize = minimumSizeHint();
setMinimumSize( msize.width() + QtWidgetLineEdit::CLEAR_ICON_SIZE + 10,
std::max( msize.height(), QtWidgetLineEdit::CLEAR_ICON_SIZE + 10 ) );
}
void QtWidgetSpinBox::EnableClearButton()
{
m_LineEdit->EnableClearButton();
}
void QtWidgetSpinBox::DisableClearButton()
{
m_LineEdit->DisableClearButton();
}
// Set the SpinBox value without triggering its valueChanged signal
void QtWidgetSpinBox::SetValueNoSignal(int value)
{
this->blockSignals(true);
this->setValue(value);
this->blockSignals(false);
}
// We use a custom valueFromText to allow more flexible input than QSpinBox default behavior:
// - all inputs are allowed in our custom QtWidgetSpinBox::validate()
// - this method parses the text to int, if it fails keep the previous value
int QtWidgetSpinBox::valueFromText(const QString &text) const