Codegen entity meta-model — C++ Qt
Table of Contents
This page is a segment of the Codegen org-entity meta-model hub, covering
the ores.cpp.qt facet: the MDI list window, detail dialog, controller,
and client model an entity gets in the desktop client. Unlike the
server-side facets, Qt reads across all of them by name —
:domain_class:, :save_message_type:, etc. below are just pointers
back to types C++ Domain and C++ Protocol already generated —
so this page assumes those two.
Physical model mapping
| Facet | Description |
|---|---|
| ores.cpp.qt | Qt UI: windows, dialogs, controllers, client models. |
The one thing to internalise before reading the knob tables
Eight *_convention entities (currency-like lookup entities whose
saves require a change-reason code) were regenerated without
:has_change_reason_cache: true in their Qt drawer. Nothing errored
— codegen ran clean, the build succeeded, the tests passed. The only
symptom was an empty, unusable change-reason dropdown in the save
dialog, found by hand during manual QA weeks later (task
AA97E310, PRs #1571/#1590).
That's the general failure mode for every knob in the Behavioural
group below, not a one-off bug: codegen's templates gate optional Qt
behaviour with a mustache section tag,
{{#domain_entity.qt.<knob>}}, and mustache's rule for section tags
is "false or absent renders as nothing" — there is no way for the
template to tell "author explicitly said no" apart from "author
forgot". An omitted knob is therefore indistinguishable, at
generation time, from a deliberate false; the only way to discover
you got it wrong is a human noticing the missing feature, later, by
hand. Treat every knob below as something to set on purpose, not
something safe to leave out and hope the default is right.
Worked example
book, trimmed to the knobs this page documents. book is
party-scoped with a UUID primary key, so it also exercises
parent_entity_singular and has_uuid_primary_key:
** Qt
,:PROPERTIES:
,:domain_include: ores.refdata.api/domain/book.hpp
,:domain_class: refdata::domain::book
,:protocol_include: ores.refdata.api/messaging/book_protocol.hpp
,:collection_name: books
,:item_var: book
,:key_field: name
,:has_uuid_primary_key: true
,:get_request_class: refdata::messaging::get_books_request
,:get_response_class: refdata::messaging::get_books_response
,:get_message_type: get_books_request
,:save_request_class: refdata::messaging::save_book_request
,:save_response_class: refdata::messaging::save_book_response
,:save_message_type: save_book_request
,:save_request_item_field: book
,:delete_request_class: refdata::messaging::delete_book_request
,:delete_response_class: refdata::messaging::delete_book_response
,:delete_message_type: delete_book_request
,:history_request_class: refdata::messaging::get_book_history_request
,:history_response_class: refdata::messaging::get_book_history_response
,:history_message_type: get_book_history_request
,:has_change_reason_cache: true
,:parent_entity_singular: portfolio
,:settings_group: BookListWindow
,:window_title: Books
,:icon: Book
,:END:
Feature diagram of the Behavioural group below (the non-structural
domain_entity variability; naming/wiring and overrides omitted, they
aren't real branch points):
Naming and wiring
This group is the bulk of the drawer, but conceptually the simplest: every property is a pointer to a class, header, or NATS message type that C++ Domain or C++ Protocol already generated. Get one wrong and generation still succeeds — it's just wired to the wrong class, which almost always fails at compile time. That's a much safer failure mode than the silent Behavioural gaps below, which is why this group gets a lighter treatment here.
- Domain and protocol pointers —
:domain_include:,:domain_class:,:protocol_include:,:collection_name:,:item_var:,:key_field:. - Per-verb NATS message wiring, one triple/quad per CRUD verb —
get/list (
:get_request_class:,:get_response_class:,:get_message_type:), save (adds:save_request_item_field:), delete, and history. Each triple names the request struct, response struct, and message-type identifier C++ Protocol generated for that verb. - Cross-client invalidation —
:changed_event_class:,:changed_event_include:point at C++ NATS's eventing facet output, so the Qt client knows which event to listen for to refresh itself. - MDI window chrome —
:settings_group:,:window_title:,:icon:: cosmetic, but still required per entity (no sensible default exists for a window title).
Behavioural
Every property here is, in MASD Variability terms, a feature — and
specifically MASD's non-structural kind: none of them change the
entity's own logical shape, only which generated Qt code paths exist
for it. (Contrast Keys and columns's :has_tenant_id:, which does
add a field — structural variability, a different MASD category
entirely.) Every property here is a boolean or identifier that
changes what gets generated, and every one defaults to "off"/absent
if you don't set it — the silent-omission trap described above, which
in MDE terms is this whole group's implicit presence condition (IPC)
being false, not the true default the wider literature usually
recommends as the safer choice.
- Key shape —
:has_uuid_primary_key:(default: false, treated as a text PK). Also drives:key_field_is_uuid:, which wraps the key inboost::uuids::to_string()when:key_field:equals the primary-key column. - Change tracking —
:has_change_reason_cache:(default: false, no change-reason dropdown). Settruewhenever the DB layer has achange_reason_codecolumn — see the worked bug above. - API surface —
:has_explorer_api:(queryable via the generic Explorer API),:has_pagination:(list window paginates instead of loading everything). - Import/export —
:has_csv_xml_io:turns on a whole family of paired properties (:csv_export_class:,:xml_export_class:,:xml_import_class:, and their matching_include=/=_methodproperties) naming the hand-written import/export helper classes; set them together or not at all. - Miscellany —
:has_export_macro:(DLL export macro, Windows builds),:has_version_navigation:(prev/next version controls on the detail dialog). - Parent scoping —
:parent_entity_singular:,:parent_entity_pascal:: set when the entity is only ever listed under a parent window (e.g.bookunderportfolio) rather than having its own top-level MDI entry. :has_readonly_paginated_list:(default: false) — strips every add/edit/delete/history affordance from the generated controller and MDI window: no toolbar actions, no detail-dialog wiring, no version navigation, no history dialog. Reload and pagination are untouched (:has_pagination:is independent and orthogonal). Use for a junction-backed or materialised dataset with nothing to edit (first consumer:calendar_dates, scoped bycalendar_code) — pair it with disabling this entity'sores.cpp.qt.detail_dialog_*archetypes individually via the entity's:ores.*.enabled:drawer overrides, since no detail dialog is ever wired to when this knob is set.- Parent-scoped lists —
:has_parent_scoped_list:(default: false), paired with:parent_key_field:(the protocol get-request field, e.g.calendar_code) and:parent_key_param:(the C++ member/ parameter name) – both required when the knob is set, since the parent key belongs to a different entity and neither can be derived. Only meaningful alongside:has_readonly_paginated_list:(a still-full-CRUD entity has no analogous need). When set: theClient<Entity>Modelconstructor takes the parent key and sends it on every paginated fetch alongside offset/limit; theMdiWindowconstructor threads it through to the model it creates; the controller'sshowListWindow()override still satisfiesEntityController's pure-virtual contract (opens with an empty/unset key), while the real entry point is the newopenForParent(const QString&)public method (analogous toopenAddWithParent, but filtering a read-only list's get-request rather than pre-filling a create form's foreign key – do not conflate this with:parent_entity_singular:=/ =:has_parent_relationship:above, which solves that unrelated problem).
Overrides
Both of these have sensible defaults derived from the entity's other knobs — set them only when a specific entity's generated protocol diverges from that default naming convention:
:delete_request_id_field:defaults toidswhen:has_uuid_primary_key:is true, else{pk_column}s. Currency's protocol names this fieldiso_codesinstead, so currency sets it explicitly.:history_response_data_field:defaults tohistory. Protocols predating that naming convention name the field after the entity's plural collection instead — set explicitly when regenerating one of those.
Per-field knobs
The *** Detail fields and *** Columns (Qt model) tables (one row
per entity field, not the whole-entity drawer above) carry their own
per-field properties, layered on top of the plain field=/=label=/
=type columns every field needs:
badge_key— instead of rendering this field's value as plain text, render it as a coloured badge, resolved via ores.dq'scode_domain=/=badge_definitionlookup. Applies in both the detail dialog and the list column. Omit only when the field genuinely has no DQ badge mapping — most fields.flag_source,combo_*— dropdown/combo-box wiring for a soft-FK field, so the detail dialog offers a picker instead of a free-text box. The full property set (fetch function, watcher name, sort/tooltip fields, …) isn't enumerated on this page yet; see an existing entity with a combo field, e.g.book'sbook_statuscolumn, for the complete worked shape. (Gap: this needs its own worked example on this page — flag for follow-up.)
Paste blocks
The ores.cpp.qt facet defines the following paste block kinds, one
per sub-heading below — see Paste blocks for the general mechanism
they assume.
Detail dialog hierarchy tree seam
A placeholder in the generated <Entity>DetailDialog.cpp constructor,
immediately after setupConnections(), expecting a
hierarchy_tree block. An implementing block is expected to
construct a HierarchyModelBuilder-derived model, wrap it in a
HierarchyTreeWidget, and insert it into the dialog's layout — this
is the Qt-side consumer of SQL's has_parent_id hierarchy function.
No entity implements this kind yet; it's a seam only, added ahead of
wiring party/counterparty hierarchy trees into their detail dialogs.
Detail dialog private members
Additional private member declarations immediately before the
standard generated ui_=/=clientManager_=/etc. members, expecting a
=declaration block. Use for a hand-written helper class owned by the
dialog as a pointer member. Pair with the header extra includes block
below — a forward declaration alone isn't enough for most such
members.
Detail dialog header extra includes
Additional #include directives after the standard generated
includes, before the Ui forward declaration, expecting a body
block. Needed by whatever type the private-members block above
declares.
Detail dialog extra signals
Additional signal declarations in the generated
<Entity>DetailDialog's signals: block, after the standard
generated Saved=/=Deleted=/(optional) =revertRequested signals,
expecting a declaration block. Pair with the controller header's
own signals: seam (org entity meta model's
66E078FB-D6A6-4DF6-814B-65995D948090) and the
wireDetailDialogCommon extra-connect seam below to relay a
dialog-specific signal up through the controller without hand-editing
generated code.
Controller: extra dialog-signal connections
Additional connect(detailDialog, ...) calls at the end of the
generated wireDetailDialogCommon() helper – the single place every
detail-window call site (add/edit/history-version/revert) wires a
freshly-constructed <Entity>DetailDialog, expecting a
implementation block. Use to relay a custom dialog signal (declared
via the seam above) to a matching controller signal, e.g. a
toolbar action on the dialog that a plugin's composition root needs
to react to.
Composite child-entity tables seam
A placeholder in the constructor, immediately after the hierarchy
tree seam above, expecting a child_tables_setup block. Use for a
composite parent entity — one with genuine child entities under
temporal composite versioning — to embed one editable QTableWidget
QToolBarper child entity, each its own tab. Pairs with the
entity-set load hook below (populate the tables once the parent's id
is known) and, if the children need their own bespoke NATS surface,
with C++ NATS's custom-method paste blocks and C++ Protocol's
custom message types. There is no generic facet for this — each
child entity's fields, service, and protocol differ too much between
consumers to templatize usefully with only two consumers (party,
counterparty) to date.
To use this paste block on an entity, declare it under ** Paste
blocks > *** Composite child-entity tables seam. See the following
example, from party's identifier table (abridged):
** Paste blocks *** Composite child-entity tables seam #+begin_src cpp :name child_tables_setup :implements 7E4A2C8D-9F1B-4E6A-8D3C-5B2A7E9F1C4D setupIdentifierTab(); setupContactTab(); #+end_src
Entity-set load hook
A placeholder at the end of the generated setter, after
updateUiFrom<EntityPascalShort>(), expecting a
reload_child_tables block. Use to refresh anything keyed off the
entity's id once it changes — reloading the composite child-entity
tables above, most commonly. Runs on every call to the setter,
including create-mode; implementing blocks should tolerate an
empty/nil id by showing an empty table rather than erroring.
See also
- Codegen org-entity meta-model — the hub.
- C++ Domain / C++ Protocol — the types this facet's Naming/wiring knobs point at.
- C++ NATS — the changed-event this facet listens for to invalidate its client-side cache.
- Variability — MASD's formal vocabulary for what this page's Behavioural/Overrides knobs are.