Move number formatting setting to CAF to enable use in UI editors

* Move RiaNumberFormat to cafPdmUiNumberFormat in caf framework
* Add unit test
This commit is contained in:
Magne Sjaastad authored and GitHub committed 2026-02-16 11:32:04 +01:00
1 parent 6cdcc4493a
commit 9aa5a3d222
26 files changed
+481 -197

No files matched your search

@@ -67,6 +67,8 @@ set(PROJECT_FILES
cafPdmUiLabelEditor.cpp
cafPdmUiCheckBoxAndTextEditor.h
cafPdmUiCheckBoxAndTextEditor.cpp
cafPdmUiNumberFormat.h
cafPdmUiNumberFormat.cpp
cafPdmUiValueRangeEditor.h
cafPdmUiValueRangeEditor.cpp
# object editors
@@ -41,6 +41,7 @@
#include "cafPdmUiDefaultObjectEditor.h"
#include "cafPdmUiFieldEditorHandle.h"
#include "cafPdmUiLineEditor.h"
#include "cafPdmUiNumberFormat.h"
#include <QHBoxLayout>
#include <QLabel>
@@ -62,6 +63,24 @@ void PdmUiCheckBoxAndTextEditor::configureAndUpdateUi( const QString& uiConfigNa
m_lineEdit->setToolTip( uiField()->uiToolTip( uiConfigName ) );
// Read display format from map-based attributes
int decimals = -1;
int numberFormat = static_cast<int>( caf::NumberFormatType::AUTO );
if ( auto uiItem = uiField() )
{
if ( auto val = uiItem->attribute<int>( Keys::DECIMALS, uiConfigName ) )
{
decimals = val.value();
}
if ( auto val = uiItem->attribute<int>( Keys::NUMBER_FORMAT, uiConfigName ) )
{
numberFormat = val.value();
}
uiItem->validateAttributes( "PdmUiCheckBoxAndTextEditor", SUPPORTED_ATTRIBUTES, uiConfigName );
}
bool isChecked = false;
QString textString;
@@ -72,8 +91,19 @@ void PdmUiCheckBoxAndTextEditor::configureAndUpdateUi( const QString& uiConfigNa
QList<QVariant> lst = variantValue.toList();
if ( lst.size() == 2 )
{
isChecked = lst[0].toBool();
textString = lst[1].toString();
isChecked = lst[0].toBool();
bool valueOk = false;
double value = lst[1].toDouble( &valueOk );
if ( valueOk && decimals >= 0 )
{
auto fmt = static_cast<NumberFormatType>( numberFormat );
textString = PdmUiNumberFormat::valueToText( value, fmt, decimals );
}
else
{
textString = lst[1].toString();
}
}
}
@@ -45,6 +45,8 @@
#include <QString>
#include <QWidget>
#include <set>
namespace caf
{
//==================================================================================================
@@ -59,6 +61,16 @@ public:
PdmUiCheckBoxAndTextEditor() {}
~PdmUiCheckBoxAndTextEditor() override {}
// Attribute key constants for compile-time safety and discoverability
struct Keys
{
static inline const QString DECIMALS = QStringLiteral( "decimals" );
static inline const QString NUMBER_FORMAT = QStringLiteral( "numberFormat" );
};
// Set of all supported attributes for validation
inline static const std::set<QString> SUPPORTED_ATTRIBUTES = { Keys::DECIMALS, Keys::NUMBER_FORMAT };
protected:
QWidget* createEditorWidget( QWidget* parent ) override;
void configureAndUpdateUi( const QString& uiConfigName ) override;
@@ -36,6 +36,8 @@
#include "cafPdmUiDoubleValueEditor.h"
#include "cafPdmUiNumberFormat.h"
#include "cafFactory.h"
#include "cafPdmField.h"
#include "cafPdmLogging.h"
@@ -97,7 +99,7 @@ void PdmUiDoubleValueEditor::configureAndUpdateUi( const QString& uiConfigName )
if ( auto val = uiItem->attribute<int>( Keys::NUMBER_FORMAT, uiConfigName ) )
{
m_attributes.m_numberFormat = static_cast<PdmUiDoubleValueEditorAttribute::NumberFormat>( val.value() );
m_attributes.m_numberFormat = static_cast<NumberFormatType>( val.value() );
}
// Validate: warn about unsupported attributes
@@ -109,12 +111,7 @@ void PdmUiDoubleValueEditor::configureAndUpdateUi( const QString& uiConfigName )
QString textValue;
if ( valueOk )
{
if ( m_attributes.m_numberFormat == PdmUiDoubleValueEditorAttribute::NumberFormat::FIXED )
textValue = QString::number( value, 'f', m_attributes.m_decimals );
else if ( m_attributes.m_numberFormat == PdmUiDoubleValueEditorAttribute::NumberFormat::SCIENTIFIC )
textValue = QString::number( value, 'e', m_attributes.m_decimals );
else
textValue = QString::number( value, 'g', m_attributes.m_decimals );
textValue = PdmUiNumberFormat::valueToText( value, m_attributes.m_numberFormat, m_attributes.m_decimals );
}
else
{
@@ -37,6 +37,7 @@
#pragma once
#include "cafPdmUiFieldLabelEditorHandle.h"
#include "cafPdmUiNumberFormat.h"
#include <QDoubleValidator>
#include <QGroupBox>
@@ -54,22 +55,16 @@ namespace caf
class PdmUiDoubleValueEditorAttribute : public PdmUiEditorAttribute
{
public:
enum class NumberFormat
{
FIXED,
SCIENTIFIC,
AUTOMATIC
};
PdmUiDoubleValueEditorAttribute()
{
m_decimals = 6;
m_numberFormat = NumberFormat::AUTOMATIC;
m_numberFormat = NumberFormatType::AUTO;
}
void setFixedWithTwoDecimals()
{
m_decimals = 2;
m_numberFormat = NumberFormat::FIXED;
m_numberFormat = NumberFormatType::FIXED;
}
// Convenience function to set the number format to fixed with two decimals
@@ -83,7 +78,7 @@ public:
public:
int m_decimals;
NumberFormat m_numberFormat;
NumberFormatType m_numberFormat;
QPointer<QDoubleValidator> m_validator;
};
@@ -0,0 +1,47 @@
#include "cafPdmUiNumberFormat.h"
#include "cafAppEnum.h"
template <>
void caf::AppEnum<caf::NumberFormatType>::setUp()
{
addItem( caf::NumberFormatType::AUTO, "AUTO", "Automatic" );
addItem( caf::NumberFormatType::FIXED, "FIXED", "Fixed, decimal" );
addItem( caf::NumberFormatType::SCIENTIFIC, "SCIENTIFIC", "Scientific notation" );
setDefault( caf::NumberFormatType::FIXED );
};
namespace caf
{
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString PdmUiNumberFormat::valueToText( double value, NumberFormatType numberFormat, int precision )
{
switch ( numberFormat )
{
case NumberFormatType::FIXED:
return QString::number( value, 'f', precision );
case NumberFormatType::SCIENTIFIC:
return QString::number( value, 'e', precision );
default:
return QString::number( value );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString PdmUiNumberFormat::sprintfFormat( NumberFormatType numberFormat, int precision )
{
switch ( numberFormat )
{
case NumberFormatType::FIXED:
return QString( "%.%1f" ).arg( precision );
case NumberFormatType::SCIENTIFIC:
return QString( "%.%1e" ).arg( precision );
default:
return QString( "%.%1g" ).arg( precision );
}
}
} // namespace caf
@@ -0,0 +1,20 @@
#pragma once
#include <QString>
namespace caf
{
enum class NumberFormatType
{
AUTO,
SCIENTIFIC,
FIXED
};
class PdmUiNumberFormat
{
public:
static QString valueToText( double value, NumberFormatType numberFormat, int precision );
static QString sprintfFormat( NumberFormatType numberFormat, int precision );
};
} // namespace caf
@@ -13,8 +13,10 @@ qt_standard_project_setup()
include_directories(${CMAKE_CURRENT_SOURCE_DIR} # required for gtest-all.cpp
)
set(PROJECT_FILES cafUserInterface_UnitTests.cpp cafPdmUiTreeViewModelTest.cpp
cafPdmUiTreeSelectionQModelTest.cpp gtest/gtest-all.cpp
set(PROJECT_FILES
cafUserInterface_UnitTests.cpp cafPdmUiTreeViewModelTest.cpp
cafPdmUiTreeSelectionQModelTest.cpp cafPdmUiNumberFormatTest.cpp
cafPdmUiCheckBoxAndTextEditorTest.cpp gtest/gtest-all.cpp
)
qt_add_executable(${PROJECT_NAME} ${PROJECT_FILES})
@@ -0,0 +1,243 @@
#include "gtest/gtest.h"
#include "cafInternalPdmValueFieldSpecializations.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
#include "cafPdmUiCheckBoxAndTextEditor.h"
#include "cafPdmUiNumberFormat.h"
#include <QCheckBox>
#include <QLineEdit>
#include <utility>
using namespace caf;
class CheckBoxAndTextTestObj : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
CheckBoxAndTextTestObj()
{
CAF_PDM_InitObject( "CheckBoxAndTextTestObj" );
auto defaultPair = std::make_pair( false, QString( "" ) );
CAF_PDM_InitField( &m_checkableText, "CheckableText", defaultPair, "Checkable Text" );
auto doublePair = std::make_pair( true, 3.14 );
CAF_PDM_InitField( &m_checkableDouble, "CheckableDouble", doublePair, "Checkable Double" );
}
caf::PdmField<std::pair<bool, QString>> m_checkableText;
caf::PdmField<std::pair<bool, double>> m_checkableDouble;
};
CAF_PDM_SOURCE_INIT( CheckBoxAndTextTestObj, "CheckBoxAndTextTestObj" );
//--------------------------------------------------------------------------------------------------
/// Test default field values for pair<bool, QString>
//--------------------------------------------------------------------------------------------------
TEST( PdmUiCheckBoxAndTextEditorTest, DefaultValues )
{
CheckBoxAndTextTestObj obj;
EXPECT_EQ( obj.m_checkableText().first, false );
EXPECT_EQ( obj.m_checkableText().second.toStdString(), "" );
EXPECT_EQ( obj.m_checkableDouble().first, true );
EXPECT_DOUBLE_EQ( obj.m_checkableDouble().second, 3.14 );
}
//--------------------------------------------------------------------------------------------------
/// Test setting and getting pair<bool, QString> field values
//--------------------------------------------------------------------------------------------------
TEST( PdmUiCheckBoxAndTextEditorTest, SetAndGetPairValues )
{
CheckBoxAndTextTestObj obj;
obj.m_checkableText = std::make_pair( true, QString( "hello" ) );
EXPECT_EQ( obj.m_checkableText().first, true );
EXPECT_EQ( obj.m_checkableText().second.toStdString(), "hello" );
obj.m_checkableText = std::make_pair( false, QString( "world" ) );
EXPECT_EQ( obj.m_checkableText().first, false );
EXPECT_EQ( obj.m_checkableText().second.toStdString(), "world" );
}
//--------------------------------------------------------------------------------------------------
/// Test QVariant conversion round-trip for pair<bool, QString>
//--------------------------------------------------------------------------------------------------
TEST( PdmUiCheckBoxAndTextEditorTest, QVariantRoundTrip )
{
auto original = std::make_pair( true, QString( "test value" ) );
QVariant variant = PdmValueFieldSpecialization<std::pair<bool, QString>>::convert( original );
EXPECT_TRUE( variant.canConvert<QList<QVariant>>() );
QList<QVariant> lst = variant.toList();
EXPECT_EQ( lst.size(), 2 );
EXPECT_EQ( lst[0].toBool(), true );
EXPECT_EQ( lst[1].toString().toStdString(), "test value" );
std::pair<bool, QString> restored;
PdmValueFieldSpecialization<std::pair<bool, QString>>::setFromVariant( variant, restored );
EXPECT_EQ( restored.first, original.first );
EXPECT_EQ( restored.second, original.second );
}
//--------------------------------------------------------------------------------------------------
/// Test QVariant conversion for unchecked state
//--------------------------------------------------------------------------------------------------
TEST( PdmUiCheckBoxAndTextEditorTest, QVariantUncheckedState )
{
auto original = std::make_pair( false, QString( "disabled text" ) );
QVariant variant = PdmValueFieldSpecialization<std::pair<bool, QString>>::convert( original );
QList<QVariant> lst = variant.toList();
EXPECT_EQ( lst[0].toBool(), false );
EXPECT_EQ( lst[1].toString().toStdString(), "disabled text" );
}
//--------------------------------------------------------------------------------------------------
/// Test number formatting used by the editor for double values
//--------------------------------------------------------------------------------------------------
TEST( PdmUiCheckBoxAndTextEditorTest, NumberFormattingForDoubleValues )
{
auto pair = std::make_pair( true, 1234.5678 );
QVariant variant = PdmValueFieldSpecialization<std::pair<bool, double>>::convert( pair );
QList<QVariant> lst = variant.toList();
EXPECT_EQ( lst.size(), 2 );
bool valueOk = false;
double value = lst[1].toDouble( &valueOk );
EXPECT_TRUE( valueOk );
// Test the formatting path used in configureAndUpdateUi
QString fixedText = PdmUiNumberFormat::valueToText( value, NumberFormatType::FIXED, 2 );
EXPECT_EQ( fixedText.toStdString(), "1234.57" );
QString scientificText = PdmUiNumberFormat::valueToText( value, NumberFormatType::SCIENTIFIC, 3 );
EXPECT_EQ( scientificText.toStdString(), "1.235e+03" );
}
//--------------------------------------------------------------------------------------------------
/// Test QVariant round-trip for pair<bool, double>
//--------------------------------------------------------------------------------------------------
TEST( PdmUiCheckBoxAndTextEditorTest, QVariantRoundTripDouble )
{
auto original = std::make_pair( true, 42.5 );
QVariant variant = PdmValueFieldSpecialization<std::pair<bool, double>>::convert( original );
std::pair<bool, double> restored;
PdmValueFieldSpecialization<std::pair<bool, double>>::setFromVariant( variant, restored );
EXPECT_EQ( restored.first, original.first );
EXPECT_DOUBLE_EQ( restored.second, original.second );
}
//--------------------------------------------------------------------------------------------------
/// Test field uiValue returns correct QVariant list
//--------------------------------------------------------------------------------------------------
TEST( PdmUiCheckBoxAndTextEditorTest, FieldUiValue )
{
CheckBoxAndTextTestObj obj;
obj.m_checkableText = std::make_pair( true, QString( "ui test" ) );
QVariant uiValue = obj.m_checkableText.uiCapability()->uiValue();
EXPECT_TRUE( uiValue.canConvert<QList<QVariant>>() );
QList<QVariant> lst = uiValue.toList();
EXPECT_EQ( lst.size(), 2 );
EXPECT_EQ( lst[0].toBool(), true );
EXPECT_EQ( lst[1].toString().toStdString(), "ui test" );
}
//--------------------------------------------------------------------------------------------------
/// Test configureAndUpdateUi with checked string field
//--------------------------------------------------------------------------------------------------
TEST( PdmUiCheckBoxAndTextEditorTest, ConfigureAndUpdateUi_CheckedStringField )
{
CheckBoxAndTextTestObj obj;
obj.m_checkableText = std::make_pair( true, QString( "my text" ) );
QWidget parentWidget;
PdmUiCheckBoxAndTextEditor editor;
editor.setUiField( obj.m_checkableText.uiCapability() );
editor.createWidgets( &parentWidget );
editor.updateUi();
auto* editorWidget = editor.editorWidget();
auto* checkBox = editorWidget->findChild<QCheckBox*>();
auto* lineEdit = editorWidget->findChild<QLineEdit*>();
ASSERT_NE( checkBox, nullptr );
ASSERT_NE( lineEdit, nullptr );
EXPECT_TRUE( checkBox->isChecked() );
EXPECT_EQ( lineEdit->text().toStdString(), "my text" );
EXPECT_TRUE( lineEdit->isEnabled() );
}
//--------------------------------------------------------------------------------------------------
/// Test configureAndUpdateUi with unchecked field disables line edit
//--------------------------------------------------------------------------------------------------
TEST( PdmUiCheckBoxAndTextEditorTest, ConfigureAndUpdateUi_UncheckedDisablesLineEdit )
{
CheckBoxAndTextTestObj obj;
obj.m_checkableText = std::make_pair( false, QString( "disabled text" ) );
QWidget parentWidget;
PdmUiCheckBoxAndTextEditor editor;
editor.setUiField( obj.m_checkableText.uiCapability() );
editor.createWidgets( &parentWidget );
editor.updateUi();
auto* editorWidget = editor.editorWidget();
auto* checkBox = editorWidget->findChild<QCheckBox*>();
auto* lineEdit = editorWidget->findChild<QLineEdit*>();
ASSERT_NE( checkBox, nullptr );
ASSERT_NE( lineEdit, nullptr );
EXPECT_FALSE( checkBox->isChecked() );
EXPECT_EQ( lineEdit->text().toStdString(), "disabled text" );
EXPECT_FALSE( lineEdit->isEnabled() );
}
//--------------------------------------------------------------------------------------------------
/// Test configureAndUpdateUi with double field and number formatting attributes
//--------------------------------------------------------------------------------------------------
TEST( PdmUiCheckBoxAndTextEditorTest, ConfigureAndUpdateUi_DoubleWithNumberFormat )
{
CheckBoxAndTextTestObj obj;
obj.m_checkableDouble = std::make_pair( true, 1234.5678 );
obj.m_checkableDouble.uiCapability()->setAttribute( PdmUiCheckBoxAndTextEditor::Keys::DECIMALS, 2 );
obj.m_checkableDouble.uiCapability()->setAttribute( PdmUiCheckBoxAndTextEditor::Keys::NUMBER_FORMAT,
static_cast<int>( NumberFormatType::FIXED ) );
QWidget parentWidget;
PdmUiCheckBoxAndTextEditor editor;
editor.setUiField( obj.m_checkableDouble.uiCapability() );
editor.createWidgets( &parentWidget );
editor.updateUi();
auto* editorWidget = editor.editorWidget();
auto* checkBox = editorWidget->findChild<QCheckBox*>();
auto* lineEdit = editorWidget->findChild<QLineEdit*>();
ASSERT_NE( checkBox, nullptr );
ASSERT_NE( lineEdit, nullptr );
EXPECT_TRUE( checkBox->isChecked() );
EXPECT_EQ( lineEdit->text().toStdString(), "1234.57" );
EXPECT_TRUE( lineEdit->isEnabled() );
}
@@ -0,0 +1,51 @@
#include "gtest/gtest.h"
#include "cafPdmUiNumberFormat.h"
using namespace caf;
TEST( PdmUiNumberFormatTest, ValueToTextFixed )
{
QString result = PdmUiNumberFormat::valueToText( 3.14159, NumberFormatType::FIXED, 2 );
EXPECT_EQ( result.toStdString(), "3.14" );
result = PdmUiNumberFormat::valueToText( 0.0, NumberFormatType::FIXED, 3 );
EXPECT_EQ( result.toStdString(), "0.000" );
result = PdmUiNumberFormat::valueToText( -1.5, NumberFormatType::FIXED, 1 );
EXPECT_EQ( result.toStdString(), "-1.5" );
}
TEST( PdmUiNumberFormatTest, ValueToTextScientific )
{
QString result = PdmUiNumberFormat::valueToText( 1000.0, NumberFormatType::SCIENTIFIC, 2 );
EXPECT_EQ( result.toStdString(), "1.00e+03" );
result = PdmUiNumberFormat::valueToText( 0.001, NumberFormatType::SCIENTIFIC, 1 );
EXPECT_EQ( result.toStdString(), "1.0e-03" );
}
TEST( PdmUiNumberFormatTest, ValueToTextAuto )
{
QString result = PdmUiNumberFormat::valueToText( 42.0, NumberFormatType::AUTO, 6 );
EXPECT_EQ( result.toStdString(), "42" );
}
TEST( PdmUiNumberFormatTest, SprintfFormatFixed )
{
QString result = PdmUiNumberFormat::sprintfFormat( NumberFormatType::FIXED, 3 );
EXPECT_EQ( result.toStdString(), "%.3f" );
}
TEST( PdmUiNumberFormatTest, SprintfFormatScientific )
{
QString result = PdmUiNumberFormat::sprintfFormat( NumberFormatType::SCIENTIFIC, 2 );
EXPECT_EQ( result.toStdString(), "%.2e" );
}
TEST( PdmUiNumberFormatTest, SprintfFormatAuto )
{
QString result = PdmUiNumberFormat::sprintfFormat( NumberFormatType::AUTO, 4 );
EXPECT_EQ( result.toStdString(), "%.4g" );
}