Skip to content

Modernize field concept#835

Closed
magnesj wants to merge 26 commits intodevfrom
investigate-field-concept
Closed

Modernize field concept#835
magnesj wants to merge 26 commits intodevfrom
investigate-field-concept

Conversation

@magnesj
Copy link
Copy Markdown
Owner

@magnesj magnesj commented Apr 2, 2026

Closes #834

magnesj added 5 commits April 2, 2026 21:28
…nt/pdmVariantEqual

Introduces free function customization points to replace PdmValueFieldSpecialization.
Default implementations handle Qt-metatype-registered types. Overloads provided for
std::vector, std::pair, PdmPointer, AppEnum, FilePath, float, and double.
…riant free functions

Replace PdmValueFieldSpecialization class templates in Vec3d, Mat4d, Color3f, Mat3d headers
with free function overloads pdmToVariant/pdmFromVariant and full specializations of
pdmVariantEqual. Update all call sites in unit tests and application code accordingly.
…free functions

Replace PdmValueFieldSpecialization<T>::convert/setFromVariant calls with
pdmToVariant/pdmFromVariant free functions in PdmDataValueField, PdmProxyValueField,
PdmUiCheckBoxAndTextEditor, and PdmUiValueRangeEditor.
…tEqual free functions

Replace all PdmValueFieldSpecialization<T>::convert, setFromVariant, and isEqual calls
in PdmUiFieldSpecializationForValueSpec, std::vector/pair/optional specializations, and
PdmFieldUiCap::isQVariantDataEqual.
…ining call sites

Delete the old policy class header. Update unit tests, comment references, and CMakeLists.
All consumers now use pdmToVariant/pdmFromVariant/pdmVariantEqual from cafPdmFieldTraits.h.
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Modernize field concept: Replace PdmValueFieldSpecialization with free function traits

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Replace PdmValueFieldSpecialization class templates with ADL-based free functions
• Introduce pdmToVariant, pdmFromVariant, pdmVariantEqual customization points
• Migrate all call sites in core PDM, UI framework, and application code
• Remove deprecated cafInternalPdmValueFieldSpecializations.h header
Diagram
flowchart LR
  Old["PdmValueFieldSpecialization<br/>class templates"]
  New["pdmToVariant/<br/>pdmFromVariant/<br/>pdmVariantEqual<br/>free functions"]
  CallSites["PDM core, UI framework,<br/>application code"]
  Old -->|"migrate to"| New
  New -->|"used by"| CallSites
Loading

Grey Divider

File Changes

1. Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldTraits.h ✨ Enhancement +255/-0

New ADL-based field trait customization points

Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldTraits.h


2. Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafInternalPdmValueFieldSpecializations.h Miscellaneous +0/-239

Removed deprecated policy class header

Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafInternalPdmValueFieldSpecializations.h


3. Fwk/AppFwk/cafPdmCvf/cafPdmCoreVec3d.h ✨ Enhancement +19/-28

Replace class template with free function overloads

Fwk/AppFwk/cafPdmCvf/cafPdmCoreVec3d.h


View more (20)
4. Fwk/AppFwk/cafPdmCvf/cafPdmCoreMat4d.h ✨ Enhancement +19/-28

Replace class template with free function overloads

Fwk/AppFwk/cafPdmCvf/cafPdmCoreMat4d.h


5. Fwk/AppFwk/cafPdmCvf/cafPdmCoreColor3f.h ✨ Enhancement +16/-25

Replace class template with free function overloads

Fwk/AppFwk/cafPdmCvf/cafPdmCoreColor3f.h


6. Fwk/AppFwk/cafPdmCvf/cafPdmMat3d/cafPdmCoreMat3d.h ✨ Enhancement +21/-28

Replace class template with free function overloads

Fwk/AppFwk/cafPdmCvf/cafPdmMat3d/cafPdmCoreMat3d.h


7. Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmDataValueField.h ✨ Enhancement +5/-3

Update to use pdmToVariant and pdmFromVariant

Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmDataValueField.h


8. Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmProxyValueField.h ✨ Enhancement +5/-3

Update to use pdmToVariant and pdmFromVariant

Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmProxyValueField.h


9. Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafInternalPdmFieldTypeSpecializations.h ✨ Enhancement +26/-15

Update UI specializations to use free functions

Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafInternalPdmFieldTypeSpecializations.h


10. Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiFieldSpecialization.h 📝 Documentation +2/-2

Update documentation to reference free functions

Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiFieldSpecialization.h


11. Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafInternalPdmUiFieldCapability.inl ✨ Enhancement +1/-1

Replace isEqual call with pdmVariantEqual

Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafInternalPdmUiFieldCapability.inl


12. Fwk/AppFwk/cafUserInterface/cafPdmUiCheckBoxAndTextEditor.cpp ✨ Enhancement +2/-2

Update to use pdmToVariant free function

Fwk/AppFwk/cafUserInterface/cafPdmUiCheckBoxAndTextEditor.cpp


13. Fwk/AppFwk/cafUserInterface/cafPdmUiValueRangeEditor.cpp ✨ Enhancement +2/-2

Update to use pdmToVariant free function

Fwk/AppFwk/cafUserInterface/cafPdmUiValueRangeEditor.cpp


14. Fwk/AppFwk/cafUserInterface/cafUserInterface_UnitTests/cafPdmUiCheckBoxAndTextEditorTest.cpp 🧪 Tests +7/-7

Update unit tests to use free functions

Fwk/AppFwk/cafUserInterface/cafUserInterface_UnitTests/cafPdmUiCheckBoxAndTextEditorTest.cpp


15. Fwk/AppFwk/cafPdmCvf/cafPdmCvf_UnitTests/cafPdmCoreVec3dTest.cpp 🧪 Tests +2/-2

Update unit tests to use free functions

Fwk/AppFwk/cafPdmCvf/cafPdmCvf_UnitTests/cafPdmCoreVec3dTest.cpp


16. Fwk/AppFwk/cafPdmCvf/cafPdmCvf_UnitTests/cafPdmCoreMat4dTest.cpp 🧪 Tests +2/-2

Update unit tests to use free functions

Fwk/AppFwk/cafPdmCvf/cafPdmCvf_UnitTests/cafPdmCoreMat4dTest.cpp


17. Fwk/AppFwk/cafPdmCvf/cafPdmCvf_UnitTests/cafPdmCoreColor3fTest.cpp 🧪 Tests +2/-2

Update unit tests to use free functions

Fwk/AppFwk/cafPdmCvf/cafPdmCvf_UnitTests/cafPdmCoreColor3fTest.cpp


18. ApplicationLibCode/Commands/3dView/RicStoreUserDefinedCameraFeature.cpp ✨ Enhancement +3/-3

Update to use pdmToVariant free function

ApplicationLibCode/Commands/3dView/RicStoreUserDefinedCameraFeature.cpp


19. ApplicationLibCode/Commands/3dView/RicApplyUserDefinedCameraFeature.cpp ✨ Enhancement +3/-3

Update to use pdmFromVariant free function

ApplicationLibCode/Commands/3dView/RicApplyUserDefinedCameraFeature.cpp


20. ApplicationLibCode/Commands/AnnotationCommands/RicTextAnnotation3dEditor.cpp ✨ Enhancement +1/-1

Update to use pdmToVariant free function

ApplicationLibCode/Commands/AnnotationCommands/RicTextAnnotation3dEditor.cpp


21. ApplicationLibCode/Commands/WellPathCommands/PointTangentManipulator/RicPolylineTarget3dEditor.cpp ✨ Enhancement +1/-1

Update to use pdmToVariant free function

ApplicationLibCode/Commands/WellPathCommands/PointTangentManipulator/RicPolylineTarget3dEditor.cpp


22. ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathGeometryDef.cpp ✨ Enhancement +1/-1

Update to use pdmFromVariant free function

ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathGeometryDef.cpp


23. Fwk/AppFwk/cafProjectDataModel/cafPdmCore/CMakeLists.txt ⚙️ Configuration changes +1/-1

Update CMakeLists to reference new header

Fwk/AppFwk/cafProjectDataModel/cafPdmCore/CMakeLists.txt


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 2, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. Optional variant roundtrip broken🐞 Bug ≡ Correctness
Description
PdmDataValueField::toQVariant/setFromQVariant now delegates to pdmToVariant/pdmFromVariant, but
cafPdmFieldTraits.h has no std::optional overloads, so optional fields can be
serialized/deserialized via the generic QVariant::fromValue / QVariant::value paths instead of the
established “underlying T or empty QVariant” representation.
Code

Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmDataValueField.h[R123-134]

QVariant toQVariant() const override
{
CAF_ASSERT( isInitializedByInitFieldMacro() );
-        return PdmValueFieldSpecialization<DataType>::convert( m_fieldValue );
+        using caf::pdmToVariant;
+        return pdmToVariant( m_fieldValue );
}
void setFromQVariant( const QVariant& variant ) override
{
CAF_ASSERT( isInitializedByInitFieldMacro() );
-        PdmValueFieldSpecialization<DataType>::setFromVariant( variant, m_fieldValue );
+        using caf::pdmFromVariant;
+        pdmFromVariant( variant, m_fieldValue );
}
Evidence
Optional fields are represented in the UI layer as either pdmToVariant(T) or an empty QVariant (not
as std::optional<T> inside a QVariant). With the PR, core persistence and generic
setFromQVariant/toQVariant now rely on cafPdmFieldTraits’ default templates (QVariant::fromValue /
QVariant::value) which do not define how std::optional should be stored/read, risking loss of
optional values when writing/reading settings via QSettings.

Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmDataValueField.h[123-134]
Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldTraits.h[65-75]
Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafInternalPdmFieldTypeSpecializations.h[211-244]
Fwk/AppFwk/cafProjectDataModel/cafPdmXml/cafPdmSettings.cpp[60-90]
Fwk/AppFwk/cafProjectDataModel/cafPdmXml/cafPdmSettings.cpp[132-137]
ApplicationLibCode/ProjectDataModel/Completions/RimWellPathCompletionSettings.h[118-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`caf::PdmDataValueField<T>::toQVariant/setFromQVariant` now uses `pdmToVariant/pdmFromVariant`. `cafPdmFieldTraits.h` defines these for generic types, `std::vector`, `std::pair`, etc., but **does not define how `std::optional<T>` is represented in QVariant**.
The UI layer already represents `std::optional<T>` as either:
- `pdmToVariant(T)` when it has a value
- `QVariant()` when empty
The core layer should use the same representation so that optional fields round-trip through `QSettings` and other QVariant-based paths.
## Issue Context
`PdmSettings::writeFieldsToApplicationStore/readFieldsFromApplicationStore` persists fields through `PdmValueField::toQVariant/setFromQVariant`. Optional fields exist in app code.
## Fix Focus Areas
- Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldTraits.h[65-106]
- Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldTraits.h[193-231]
- Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmDataValueField.h[123-134]
## Suggested change
1. Include `<optional>` in `cafPdmFieldTraits.h`.
2. Add overloads:
- `QVariant pdmToVariant(const std::optional<T>&)` -> empty QVariant for nullopt; otherwise delegate to `pdmToVariant(*value)`.
- `void pdmFromVariant(const QVariant&, std::optional<T>&)` -> reset on invalid/empty-string; otherwise parse underlying `T` via `pdmFromVariant` and assign.
3. (Optional but recommended) Add unit tests for optional round-trip through QVariant and through `PdmSettings` style QSettings path if tests exist.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Optional equality check missing🐞 Bug ☼ Reliability
Description
PdmFieldUiCap::isQVariantDataEqual now calls pdmVariantEqual<std::optional<T>> on UI-based variants,
but pdmVariantEqual has no optional-aware branch, so optional field changes can be misclassified as
“equal” and suppress notifyFieldChanged/fieldChangedByUi propagation.
Code

Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafInternalPdmUiFieldCapability.inl[286]

+    return caf::pdmVariantEqual<typename FieldType::FieldDataType>( oldUiBasedQVariant, newUiBasedQVariant );
Evidence
UI conversion for optional fields returns either pdmToVariant(T) or an empty QVariant(). The new
equality path compares using pdmVariantEqual; since pdmVariantEqual falls back to `a.value() ==
b.value()` for types it doesn’t specially handle, there is no defined comparison for the
‘underlying-or-empty’ optional representation, and false-equality will skip
PdmUiFieldHandle::notifyFieldChanged processing.

Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafInternalPdmUiFieldCapability.inl[282-287]
Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafInternalPdmFieldTypeSpecializations.h[211-244]
Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldTraits.h[193-231]
Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafPdmUiFieldHandle.cpp[57-76]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PdmFieldUiCap::isQVariantDataEqual` compares UI-based variants using `caf::pdmVariantEqual<FieldDataType>`. For `FieldDataType = std::optional<T>`, the UI-based variant is **not** a `QVariant` carrying an `std::optional<T>`; it is either `QVariant()` or a QVariant carrying the underlying `T`.
Without an optional-aware branch in `pdmVariantEqual`, optional field updates may not trigger `notifyFieldChanged`, breaking downstream UI refresh and `fieldChangedByUi` callbacks.
## Issue Context
UI optional conversion:
- has value -> `pdmToVariant(value.value())`
- no value -> `QVariant()`
Equality must match that representation.
## Fix Focus Areas
- Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldTraits.h[193-231]
- Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafInternalPdmUiFieldCapability.inl[282-287]
- Fwk/AppFwk/cafProjectDataModel/cafPdmUiCore/cafInternalPdmFieldTypeSpecializations.h[211-244]
## Suggested change
Implement optional-aware equality in `cafPdmFieldTraits.h`, e.g.:
- Add an `is_std_optional<T>` trait and an `if constexpr` branch in `pdmVariantEqual<T>`.
- Semantics:
- If both `a` and `b` are invalid/empty -> equal
- If exactly one is invalid/empty -> not equal
- Otherwise compare underlying values via `pdmVariantEqual<UnderlyingT>(a,b)` (or `pdmFromVariant` into `UnderlyingT` and compare).
Add unit coverage for transitions:
- nullopt -> value
- value -> nullopt
- value1 -> value2

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmDataValueField.h
magnesj and others added 21 commits April 3, 2026 09:24
- PdmUiFieldSpecialization<PdmPointer<T>> and <AppEnum<T>> now delegate convert/setFromVariant
  to pdmToVariant/pdmFromVariant instead of duplicating the same logic
- Add list.reserve() in pdmToVariant(std::vector<T>) to avoid repeated reallocations
…n build

- Add cafPdmFieldTraitsTest.cpp with 33 tests covering round-trips and
  pdmVariantEqual for int, double, float, bool, QString, FilePath,
  AppEnum, vector, pair, and PdmPointer types
- Add VariantEqualTest to cafPdmCoreVec3dTest.cpp and cafPdmCoreColor3fTest.cpp
- Include cafPdmCvf_UnitTests in RESINSIGHT_INCLUDE_APPFWK_TESTS build
Add std::optional overloads to cafPdmFieldTraits.h so optional fields
round-trip correctly and pdmVariantEqual<std::optional<T>> uses the
has-value/empty-QVariant convention rather than falling back to
a.value<std::optional<T>>() which always returns nullopt.

Simplify PdmUiFieldSpecialization<std::optional<T>> to delegate to the
free functions. Add 7 tests covering round-trip and equality.
Move type-specific free function overloads into the headers that define
those types (cafPdmPointer.h, cafAppEnum.h, cafFilePath.h), so a simple
PdmField<int> no longer forces every TU to parse those heavy headers.

Replace if constexpr dispatch in pdmVariantEqual with PdmVariantEqualImpl<T>
class template partial specializations, which also fixes the extensibility
for nested containers (e.g. std::vector<cvf::Vec3d>) since class template
partial specialization is allowed where function template partial
specialization is not.

cafPdmFieldTraits.h now only requires <QVariant> and standard library
headers. Each type's equality and conversion logic lives next to its type.
- Remove redundant forward declarations for PdmPointer/AppEnum in
  cafInternalPdmFieldTypeSpecializations.h (full headers now included)
- Fix std::list<T> specialization to use pdmToVariant/pdmFromVariant
  instead of raw QVariant(*it)/lst[i].value<T>() — consistent with vector
- Remove redundant #include <QVariant> from cafAppEnum.h and cafFilePath.h
  (already provided by cafPdmFieldTraits.h)
- Simplify PdmVariantEqualImpl<optional<T>>::equal to use !a.isValid()
  instead of allocating toString().remove('"').isEmpty() — pdmToVariant
  for nullopt always produces QVariant() (invalid)
…ter and std::optional

PdmUiFieldSpecialization<PdmPointer<T>> and PdmUiFieldSpecialization<std::optional<T>>
were pure pass-throughs to pdmToVariant/pdmFromVariant/pdmVariantEqual, identical in
behavior to the primary template. Removing them lets the primary template handle these
types via ADL. Also removes the cafPdmObjectHandle.h include which was only needed for
the PdmPointer specialization, reducing include weight for all TUs using cafPdmUiFieldSpecialization.h.
Adds cafPdm_CompileBench, an OBJECT library with five translation units
that each define 20 PdmObject subclasses using different field types:
basic (int/double/bool/QString), AppEnum, containers (vector/optional/pair),
FilePath, and a mixed file covering all types.

Unity build is explicitly disabled on the target so each file compiles
independently, giving per-file timing via ninja -j1.
Move <QIcon> and <QPixmap> out of cafIconProvider.h into cafIconProvider.cpp
by declaring the destructor out-of-line. This prevents these heavy Qt GUI
headers from being pulled into every translation unit that includes
cafPdmObject.h.

Remove cafPdmOptionItemInfo.h from cafPdmUiItem.h — PdmUiItem does not use
PdmOptionItemInfo directly. Move the include to cafPdmUiFieldHandle.h and
cafPdmUiObjectHandle.h which actually require it. Fix downstream headers
that relied on the transitive include (cafPdmUiItemInfo.h, cafPdmUiFieldEditorHandle.h).

Measured improvement over dev branch: 3–7% reduction in bench file compile
times, stacking on top of the cafPdmFieldTraits.h include-weight reduction.
…r.h from cafInternalPdmFieldTypeSpecializations.h

cafPdmUiItem.h included <string> but std::string is not used anywhere in the header.

cafPdmPointer.h was left in cafInternalPdmFieldTypeSpecializations.h after commit
bfd12be removed the PdmUiFieldSpecialization<PdmPointer<T>> specialization that
required it. Nothing in the remaining code uses PdmPointer, so the include is a
leftover.

Also remove the unused appEnum parameter name from valueOptions in the AppEnum
specialization — it was never referenced in the body.
Remove cafPdmFieldHandle.h: extract the non-template createEnumSubsetKey
logic into a new cafAppEnum.cpp as the free function appEnumCreateSubsetKey,
allowing PdmFieldHandle to be forward-declared. This removes <functional>
and the full PdmFieldHandle class definition (including its capability<T>()
template methods) from the header.

Remove cafPdmFieldTraits.h: replace with a direct <QVariant> include and a
forward declaration of the PdmVariantEqualImpl primary template. The
AppEnum-specific pdmToVariant/pdmFromVariant/PdmVariantEqualImpl<AppEnum<T>>
overloads remain in the header. This removes 212 lines of type-trait and
container-variant template machinery from every TU that includes cafAppEnum.h
without the full PDM field stack.
Add <QIcon> to cafPdmUiItemInfo.cpp, cafPdmUiItem.cpp, and
cafPdmOptionItemInfo.cpp — these files destroy std::unique_ptr<QIcon>
values returned by IconProvider::icon(), which requires the full QIcon
definition now that cafIconProvider.h only forward-declares it.

Add cafPdmObjectHandle.h to cafPdmUiFieldHandle.cpp — PdmObjectHandle
was only forward-declared via cafPdmUiFieldSpecialization.h but the
notifyFieldChanged implementation calls methods on it.
These files were relying on transitive includes that were removed or replaced by forward declarations in recent header cleanups.
@magnesj magnesj closed this Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor PdmValueFieldSpecialization: fix isEqual inconsistency and consolidate redundant base classes

1 participant