ores.cpp.qt.detail_dialog_impl
Table of Contents
Loads the generated .ui; field get/set per widget kind. Qt UI component: model/view class or dialog wired to the service layer via request/response messages.
See the Template variable reference for the complete list of available variables and their semantics.
Extension points
<<paste:UUID>> markers in this template are paste-block injection
points — see paste blocks: injecting custom code into generated files
for the full mechanism, and its Qt-specific table for the complete
list. This template defines eight:
3F8B6C1D-4E2A-4F9B-8C3D-1A5E7F2B9D4C— top of the file, after the generated#includelines. Extra includes any of the other four seams below need.B6F2748A-9896-4B9C-BEAC-38436FD1CA79— end ofsetupConnections(). Extraconnect()wiring, e.g. a derived field kept live-synced from the fields it's computed from.9A719585-3AB1-47DE-B0B0-99AFDFE7180E— end ofsetCreateMode(), after the generatedlocked_fieldsloop. Runs last, so a paste block can override what the generated per-field lock logic just did — e.g. replace asetEnabled(false)-based lock withWidgetUtils::set_combo_locked()for a field that should stay visually normal, flag icon included, while locked.7A4AE8D5-A22D-4912-B4BF-D58215681CD2— end ofsetReadOnly(), after the generatedis_locked_after_createloop. Same rationale as thesetCreateMode()seam above.EC97923E-BC8D-4A50-9970-CBC5CBD4B732— near the end of the file, after the generated methods. Implementation bodies for whatever the header seam (F55CB64E-165A-4E15-A50A-5723C0320E97in ores.cpp.qt.detail_dialog_header) declared.69F22F14-6701-4E73-AFCC-78AC1B1FDDDF— insidevalidateInput()'sreturnchain, after the generated required-field checks, before the closing;. Extra&& (...)conditions, e.g. cross-field validation the generic required-field mechanism can't express (two fields must differ, a combination of values is invalid together).4E83FA7A-742A-4102-90AF-D337F6FB1269— insideonSaveClicked(), aftervalidateInput()passes, before the change-reason prompt and save dispatch. A synchronous (notQtConcurrent-wrapped) pre-save check that can callreturn;to abort the save with its own error dialog — e.g. rejecting a duplicate natural key before it silently versions an unrelated existing row (the SQL insert trigger can't reject this itself without breaking other upsert-by-key consumers of the same table). Can also fully handle andreturn;from an alternate save path that doesn't touch this entity's own fields at all — e.g. a companion widget's own change-reason prompt and commit, skipping the entity save entirely when it's the only thing that changed (see theon_save_successseam below for the counterpart when the entity itself does need saving too).F8EC96B9-04BA-46A1-A2D8-4E4B91DCB435— insideonSaveClicked()'s async watcher, immediately after the entity save succeeds (inside theif (result.success)branch, afternotifySaveSuccess()). The watcher lambda capturescrReasonCode=/=crCommentaryfrom the samecrSelused for the entity's own save, so a paste block here can commit a companion widget's pending changes using that same change-reason selection — one prompt shared by both writes — instead of popping a second, independent one.
Template
The full template source. Edit here and re-tangle with
compass build --direct tangle_codegen_templates to regenerate
library/templates/cpp_qt_detail_dialog.cpp.mustache.
{{! GENERATED FILE — tangled from projects/ores.codegen/library/templates/cpp_qt.org. Edit the org source. }}
{{! Template to generate Qt detail dialog source for domain entities }}
{{{cpp_license}}}
#include "ores.qt/{{domain_entity.entity_pascal}}DetailDialog.hpp"
#include <QMessageBox>
#include <QtConcurrent>
#include <QFutureWatcher>
{{#domain_entity.qt.has_text_edit_fields}}
#include <QPlainTextEdit>
{{/domain_entity.qt.has_text_edit_fields}}
{{#domain_entity.qt.has_combo_fields}}
#include "ores.qt/WidgetUtils.hpp"
#include <QComboBox>
{{/domain_entity.qt.has_combo_fields}}
{{#domain_entity.qt.has_dynamic_combo_helper_fields}}
#include "ores.qt/DynamicComboSetup.hpp"
#include "ores.qt/LookupFetcher.hpp"
{{/domain_entity.qt.has_dynamic_combo_helper_fields}}
{{#domain_entity.qt.has_uuid_primary_key}}
#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid_io.hpp>
{{/domain_entity.qt.has_uuid_primary_key}}
{{^domain_entity.qt.has_uuid_primary_key}}
{{#domain_entity.qt.has_uuid_detail_fields}}
#include <boost/uuid/uuid_io.hpp>
{{/domain_entity.qt.has_uuid_detail_fields}}
{{/domain_entity.qt.has_uuid_primary_key}}
{{#domain_entity.qt.has_uuid_detail_fields}}
#include <boost/uuid/string_generator.hpp>
{{/domain_entity.qt.has_uuid_detail_fields}}
{{#domain_entity.qt.has_date_detail_fields}}
#include "ores.platform/time/datetime.hpp"
{{/domain_entity.qt.has_date_detail_fields}}
{{^domain_entity.qt.has_date_detail_fields}}
{{#domain_entity.qt.has_as_of_combo_fields}}
#include "ores.platform/time/datetime.hpp"
{{/domain_entity.qt.has_as_of_combo_fields}}
{{/domain_entity.qt.has_date_detail_fields}}
#include "ui_{{domain_entity.entity_pascal}}DetailDialog.h"
#include "ores.qt/ChangeReasonDialog.hpp"
{{#domain_entity.qt.has_flag_icon}}
#include "ores.qt/ImageCache.hpp"
#include <QIcon>
#include <QLineEdit>
{{/domain_entity.qt.has_flag_icon}}
{{#domain_entity.qt.has_key_flag_icon}}
{{^domain_entity.qt.has_flag_icon}}
#include "ores.qt/ImageCache.hpp"
#include <QIcon>
#include <QLineEdit>
{{/domain_entity.qt.has_flag_icon}}
{{#domain_entity.qt.key_flag_accessor_is_free_fn}}
#include "ores.qt/FlagIconHelper.hpp"
{{/domain_entity.qt.key_flag_accessor_is_free_fn}}
{{/domain_entity.qt.has_key_flag_icon}}
{{#domain_entity.qt.has_combo_flag_source}}
#include "ores.qt/FlagIconHelper.hpp"
#include <algorithm>
{{/domain_entity.qt.has_combo_flag_source}}
{{#domain_entity.qt.has_combo_badge_source}}
#include "ores.qt/BadgeComboHelper.hpp"
{{/domain_entity.qt.has_combo_badge_source}}
#include "ores.qt/IconUtils.hpp"
#include "ores.qt/MessageBoxHelper.hpp"
#include "{{domain_entity.qt.protocol_include}}"
{{! see * Extension points above }}
<<paste:3F8B6C1D-4E2A-4F9B-8C3D-1A5E7F2B9D4C>>
namespace ores::qt {
using namespace ores::logging;
{{domain_entity.entity_pascal}}DetailDialog::{{domain_entity.entity_pascal}}DetailDialog(QWidget* parent)
: DetailDialogBase(parent),
ui_(new Ui::{{domain_entity.entity_pascal}}DetailDialog),
clientManager_(nullptr){{#domain_entity.qt.has_setting_gated_actions}},
{{#domain_entity.qt.setting_gated_actions}}
{{action}}_(nullptr),
{{/domain_entity.qt.setting_gated_actions}}
settingGatedActions_(nullptr){{/domain_entity.qt.has_setting_gated_actions}} {
ui_->setupUi(this);
{{#domain_entity.qt.has_combo_fields}}
WidgetUtils::setupComboBoxes(this);
{{/domain_entity.qt.has_combo_fields}}
setupUi();
{{#domain_entity.qt.has_combo_fields}}
setupCombos();
{{/domain_entity.qt.has_combo_fields}}
setupConnections();
// Hierarchy tree seam: a future :implements 9B165431-2921-4CAC-A2E8-2C186741E523
// block is expected to construct a HierarchyModelBuilder-derived model
// for this entity, wrap it in a HierarchyTreeWidget, and insert that
// widget into this dialog's layout (e.g. a dedicated tab). Left empty
// when no entity implements this kind.
<<paste:9B165431-2921-4CAC-A2E8-2C186741E523>>
// Composite child-entity tables seam: an :implements
// 7E4A2C8D-9F1B-4E6A-8D3C-5B2A7E9F1C4D block constructs one QTableWidget
// + QToolBar per embedded child entity (e.g. identifiers, contact
// information), wraps each in a tab, and inserts it into this dialog's
// tab widget. Left empty when no entity implements this kind.
<<paste:7E4A2C8D-9F1B-4E6A-8D3C-5B2A7E9F1C4D>>
}
{{domain_entity.entity_pascal}}DetailDialog::~{{domain_entity.entity_pascal}}DetailDialog() {
delete ui_;
}
QTabWidget* {{domain_entity.entity_pascal}}DetailDialog::tabWidget() const {
return ui_->tabWidget;
}
QWidget* {{domain_entity.entity_pascal}}DetailDialog::provenanceTab() const {
return ui_->provenanceTab;
}
ProvenanceWidget* {{domain_entity.entity_pascal}}DetailDialog::provenanceWidget() const {
return ui_->provenanceWidget;
}
QString {{domain_entity.entity_pascal}}DetailDialog::code() const {
return QString::fromStdString({{domain_entity.qt.key_to_string_prefix}}{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field_access}}{{/domain_entity.qt.key_field_access}}{{^domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field}}{{/domain_entity.qt.key_field_access}}{{domain_entity.qt.key_to_string_suffix}});
}
void {{domain_entity.entity_pascal}}DetailDialog::setupUi() {
ui_->saveButton->setIcon(
IconUtils::createRecoloredIcon(Icon::Save, IconUtils::DefaultIconColor));
ui_->saveButton->setEnabled(false);
ui_->deleteButton->setIcon(
IconUtils::createRecoloredIcon(Icon::Delete, IconUtils::DefaultIconColor));
ui_->closeButton->setIcon(
IconUtils::createRecoloredIcon(Icon::Dismiss, IconUtils::DefaultIconColor));
{{#domain_entity.qt.has_flag_icon}}
// Flag editor hosted in the .ui flagGroup; base class owns the button
// (also wires the inline key-field icon — see initKeyFlagField()).
initFlagButton(ui_->flagGroup->layout());
{{/domain_entity.qt.has_flag_icon}}
{{^domain_entity.qt.has_flag_icon}}
{{#domain_entity.qt.has_key_flag_icon}}
// No uploadable flag image of its own — just the derived, read-only
// inline icon on the key field.
initKeyFlagField();
{{/domain_entity.qt.has_key_flag_icon}}
{{/domain_entity.qt.has_flag_icon}}
{{#domain_entity.qt.has_toolbar}}
toolBar_ = new QToolBar(this);
toolBar_->setMovable(false);
toolBar_->setFloatable(false);
{{/domain_entity.qt.has_toolbar}}
{{#domain_entity.qt.has_version_navigation}}
revertAction_ = new QAction(tr("Revert"), this);
revertAction_->setIcon(IconUtils::createRecoloredIcon(
Icon::ArrowRotateCounterclockwise, IconUtils::DefaultIconColor));
revertAction_->setToolTip(
tr("Revert {{domain_entity.entity_singular_words}} to this historical version"));
connect(revertAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}DetailDialog::onRevertClicked);
toolBar_->addAction(revertAction_);
revertAction_->setVisible(false);
toolBar_->addSeparator();
firstVersionAction_ = new QAction(tr("First"), this);
firstVersionAction_->setIcon(
IconUtils::createRecoloredIcon(Icon::ArrowPrevious, IconUtils::DefaultIconColor));
firstVersionAction_->setToolTip(tr("First version"));
connect(firstVersionAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFirstVersionClicked);
toolBar_->addAction(firstVersionAction_);
firstVersionAction_->setVisible(false);
prevVersionAction_ = new QAction(tr("Previous"), this);
prevVersionAction_->setIcon(
IconUtils::createRecoloredIcon(Icon::ArrowLeft, IconUtils::DefaultIconColor));
prevVersionAction_->setToolTip(tr("Previous version"));
connect(prevVersionAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}DetailDialog::onPrevVersionClicked);
toolBar_->addAction(prevVersionAction_);
prevVersionAction_->setVisible(false);
nextVersionAction_ = new QAction(tr("Next"), this);
nextVersionAction_->setIcon(
IconUtils::createRecoloredIcon(Icon::ArrowRight, IconUtils::DefaultIconColor));
nextVersionAction_->setToolTip(tr("Next version"));
connect(nextVersionAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}DetailDialog::onNextVersionClicked);
toolBar_->addAction(nextVersionAction_);
nextVersionAction_->setVisible(false);
lastVersionAction_ = new QAction(tr("Last"), this);
lastVersionAction_->setIcon(
IconUtils::createRecoloredIcon(Icon::ArrowNext, IconUtils::DefaultIconColor));
lastVersionAction_->setToolTip(tr("Last version"));
connect(lastVersionAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}DetailDialog::onLastVersionClicked);
toolBar_->addAction(lastVersionAction_);
lastVersionAction_->setVisible(false);
{{/domain_entity.qt.has_version_navigation}}
{{#domain_entity.qt.has_generate_action}}
setupGenerateAction();
{{/domain_entity.qt.has_generate_action}}
{{#domain_entity.qt.has_toolbar}}
if (auto* mainLayout = qobject_cast<QVBoxLayout*>(layout()))
mainLayout->insertWidget(0, toolBar_);
{{/domain_entity.qt.has_toolbar}}
}
{{#domain_entity.qt.has_generate_action}}
void {{domain_entity.entity_pascal}}DetailDialog::setupGenerateAction() {
generateAction_ = new QAction(tr("Generate"), this);
generateAction_->setIcon(IconUtils::createRecoloredIcon(Icon::Wand, IconUtils::DefaultIconColor));
generateAction_->setToolTip(tr("Fill fields with synthetic test data"));
connect(generateAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}DetailDialog::onGenerateClicked);
toolBar_->addAction(generateAction_);
generateAction_->setVisible(false);
}
void {{domain_entity.entity_pascal}}DetailDialog::onGenerateClicked() {
BOOST_LOG_SEV(lg(), debug) << "Generate clicked in detail dialog";
try {
utility::generation::generation_context ctx;
auto generated =
{{domain_entity.component}}::{{domain_entity.generator_facet_name}}::generate_synthetic_{{domain_entity.entity_singular}}(ctx);
// The key identifies an existing record in edit mode; only a
// brand-new record (create mode) may take the generated key.
{{#domain_entity.qt.detail_fields}}
{{#is_key}}
if (!createMode_)
generated.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}};
{{/is_key}}
{{/domain_entity.qt.detail_fields}}
{{domain_entity.qt.item_var}}_ = generated;
updateUiFrom{{domain_entity.entity_pascal_short}}();
hasChanges_ = true;
updateSaveButtonState();
emit statusMessage(
tr("Generated synthetic {{domain_entity.entity_singular_words}} data - modify as needed and save"));
BOOST_LOG_SEV(lg(), info) << "Filled fields with generated {{domain_entity.entity_singular_words}}";
} catch (const std::exception& e) {
BOOST_LOG_SEV(lg(), error) << "Error generating {{domain_entity.entity_singular_words}}: " << e.what();
MessageBoxHelper::critical(this,
tr("Generation Error"),
tr("Failed to generate {{domain_entity.entity_singular_words}} data: %1").arg(e.what()));
}
}
{{/domain_entity.qt.has_generate_action}}
{{#domain_entity.qt.has_flag_icon}}
std::optional<boost::uuids::uuid>
{{domain_entity.entity_pascal}}DetailDialog::entityImageId() const {
return {{domain_entity.qt.item_var}}_.image_id;
}
{{/domain_entity.qt.has_flag_icon}}
{{#domain_entity.qt.has_key_flag_icon}}
QLineEdit* {{domain_entity.entity_pascal}}DetailDialog::keyFlagField() const {
return ui_->{{domain_entity.qt.key_flag_field}};
}
QIcon {{domain_entity.entity_pascal}}DetailDialog::keyFlagIcon(const std::string& key) const {
{{#domain_entity.qt.key_flag_accessor_is_free_fn}}
return imageCache() ? {{domain_entity.qt.key_flag_accessor}}(*imageCache(), key) : QIcon();
{{/domain_entity.qt.key_flag_accessor_is_free_fn}}
{{^domain_entity.qt.key_flag_accessor_is_free_fn}}
return imageCache() ? imageCache()->{{domain_entity.qt.key_flag_accessor}}(key) : QIcon();
{{/domain_entity.qt.key_flag_accessor_is_free_fn}}
}
{{#domain_entity.qt.key_flag_is_pair}}
QSize {{domain_entity.entity_pascal}}DetailDialog::keyFlagIconSize() const {
return currency_pair_icon_size();
}
{{/domain_entity.qt.key_flag_is_pair}}
{{/domain_entity.qt.has_key_flag_icon}}
{{#domain_entity.qt.has_combo_fields}}
void {{domain_entity.entity_pascal}}DetailDialog::setupCombos() {
{{#domain_entity.qt.detail_fields}}
{{#is_static_combo}}
ui_->{{widget}}->clear();
{{#combo_values}}
ui_->{{widget}}->addItem(tr("{{label}}"), QString("{{value}}"));
{{/combo_values}}
{{/is_static_combo}}
{{/domain_entity.qt.detail_fields}}
}
{{/domain_entity.qt.has_combo_fields}}
void {{domain_entity.entity_pascal}}DetailDialog::setupConnections() {
connect(ui_->saveButton, &QPushButton::clicked, this,
&{{domain_entity.entity_pascal}}DetailDialog::onSaveClicked);
connect(ui_->deleteButton, &QPushButton::clicked, this,
&{{domain_entity.entity_pascal}}DetailDialog::onDeleteClicked);
connect(ui_->closeButton, &QPushButton::clicked, this,
&{{domain_entity.entity_pascal}}DetailDialog::onCloseClicked);
{{#domain_entity.qt.has_flag_icon}}
connect(this, &DetailDialogBase::flagEdited, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/domain_entity.qt.has_flag_icon}}
{{#domain_entity.qt.detail_fields}}
{{#is_key}}
{{#is_line_edit}}
connect(ui_->{{widget}}, &QLineEdit::textChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onCodeChanged);
{{/is_line_edit}}
{{#is_static_combo}}
connect(ui_->{{widget}}, &QComboBox::currentIndexChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_static_combo}}
{{#is_dynamic_combo}}
connect(ui_->{{widget}}, &QComboBox::currentIndexChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_dynamic_combo}}
{{#is_flagged_combo}}
connect(ui_->{{widget}}, &QComboBox::currentIndexChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_flagged_combo}}
{{#is_spin_box}}
connect(ui_->{{widget}}, &QSpinBox::valueChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_spin_box}}
{{/is_key}}
{{^is_key}}
{{#is_line_edit}}
connect(ui_->{{widget}}, &QLineEdit::textChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_line_edit}}
{{#is_text_edit}}
connect(ui_->{{widget}}, &QPlainTextEdit::textChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_text_edit}}
{{#is_static_combo}}
connect(ui_->{{widget}}, &QComboBox::currentIndexChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_static_combo}}
{{#is_dynamic_combo}}
connect(ui_->{{widget}}, &QComboBox::currentIndexChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_dynamic_combo}}
{{#is_flagged_combo}}
connect(ui_->{{widget}}, &QComboBox::currentIndexChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_flagged_combo}}
{{#is_check_box}}
connect(ui_->{{widget}}, &QCheckBox::toggled, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_check_box}}
{{#is_spin_box}}
connect(ui_->{{widget}}, &QSpinBox::valueChanged, this,
&{{domain_entity.entity_pascal}}DetailDialog::onFieldChanged);
{{/is_spin_box}}
{{#is_colour}}
wire_colour_picker(this, ui_->{{widget}}, [this]() { onFieldChanged(); });
{{/is_colour}}
{{/is_key}}
{{/domain_entity.qt.detail_fields}}
{{! see * Extension points above }}
<<paste:B6F2748A-9896-4B9C-BEAC-38436FD1CA79>>
}
void {{domain_entity.entity_pascal}}DetailDialog::setClientManager(ClientManager* clientManager) {
clientManager_ = clientManager;
{{#domain_entity.qt.detail_fields}}
{{#is_dynamic_combo}}
{{#combo_fetch_fn}}
populate{{combo_setter_pascal}}();
{{/combo_fetch_fn}}
{{#badge_key}}
setup_badge_combo(this, ui_->{{widget}}, badgeCache(), "{{badge_key}}");
{{/badge_key}}
{{/is_dynamic_combo}}
{{#is_static_combo}}
{{#badge_key}}
setup_badge_combo(this, ui_->{{widget}}, badgeCache(), "{{badge_key}}");
{{/badge_key}}
{{/is_static_combo}}
{{/domain_entity.qt.detail_fields}}
{{#domain_entity.qt.has_setting_gated_actions}}
if (clientManager_) {
settingGatedActions_ = new SettingGatedActionController(clientManager_, this);
{{#domain_entity.qt.setting_gated_actions}}
settingGatedActions_->registerAction(
{{action}}_, "{{setting}}", [this]() { return !readOnly_; });
{{/domain_entity.qt.setting_gated_actions}}
if (clientManager_->isLoggedIn())
settingGatedActions_->refresh();
}
{{/domain_entity.qt.has_setting_gated_actions}}
{{#domain_entity.qt.detail_fields}}
{{#is_flagged_combo}}
populate{{field_pascal}}Combo();
{{/is_flagged_combo}}
{{/domain_entity.qt.detail_fields}}
}
{{#domain_entity.qt.detail_fields}}
{{#is_flagged_combo}}
{{#combo_helper}}
void {{domain_entity.entity_pascal}}DetailDialog::populate{{field_pascal}}Combo() {
{{combo_helper}}(ui_->{{widget}}, this, clientManager_, imageCache(), [this]() {
{{#is_optional_string}}
const auto& v = {{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}};
return v ? QString::fromStdString(*v) : QString();
{{/is_optional_string}}
{{^is_optional_string}}
return QString::fromStdString({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}});
{{/is_optional_string}}
}{{#flag_icon_height}}, single_flag_icon_size({{flag_icon_height}}){{/flag_icon_height}});
}
{{/combo_helper}}
{{^combo_helper}}
void {{domain_entity.entity_pascal}}DetailDialog::populate{{field_pascal}}Combo() {
if (!clientManager_ || !clientManager_->isConnected())
return;
QPointer<{{domain_entity.entity_pascal}}DetailDialog> self = this;
auto* watcher = new QFutureWatcher<std::vector<std::string>>(self);
QObject::connect(watcher, &QFutureWatcher<std::vector<std::string>>::finished, self,
[self, watcher]() {
auto codes = watcher->result();
watcher->deleteLater();
if (!self)
return;
auto* combo = self->ui_->{{widget}};
const QString previous = combo->currentText();
combo->blockSignals(true);
combo->clear();
{{#combo_allow_blank}}
combo->addItem(QString());
{{/combo_allow_blank}}
for (const auto& c : codes)
combo->addItem(QString::fromStdString(c));
// fallback is evaluated here (fetch-completion time), not at
// populate-call time, since set{{domain_entity.entity_pascal_short}}() may run before or
// after setClientManager() triggers this fetch. Preferred over
// previous: set{{domain_entity.entity_pascal_short}}() always runs synchronously before
// this dialog is shown to the user, well before this async
// fetch can complete, so by the time this callback fires the
// model's own field value is authoritative -- previous (the
// combo's transient text, empty until this first population)
// would otherwise permanently blank the selection on every
// dialog open, racing this fetch against every
// set{{domain_entity.entity_pascal_short}}() call that happened first.
{{#is_optional_string}}
const QString fallback = self->{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}
? QString::fromStdString(*self->{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}})
: QString{};
{{/is_optional_string}}
{{^is_optional_string}}
const QString fallback = QString::fromStdString(
self->{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}});
{{/is_optional_string}}
const QString to_select = !fallback.isEmpty() ? fallback : previous;
if (!to_select.isEmpty()) {
const int idx = combo->findText(to_select);
if (idx >= 0) combo->setCurrentIndex(idx);
}
combo->blockSignals(false);
if (self->imageCache())
apply_flag_icons(
combo, self->imageCache(), FlagSource::{{flag_source_pascal}}, {{flag_icon_size_expr}});
});
auto* cm = clientManager_;
watcher->setFuture(QtConcurrent::run([cm]() { return {{combo_fetch}}(cm); }));
}
{{/combo_helper}}
{{/is_flagged_combo}}
{{/domain_entity.qt.detail_fields}}
void {{domain_entity.entity_pascal}}DetailDialog::setUsername(const std::string& username) {
username_ = username;
}
void {{domain_entity.entity_pascal}}DetailDialog::set{{domain_entity.entity_pascal_short}}(
const {{domain_entity.qt.domain_class}}& {{domain_entity.qt.item_var}}) {
{{domain_entity.qt.item_var}}_ = {{domain_entity.qt.item_var}};
updateUiFrom{{domain_entity.entity_pascal_short}}();
{{#domain_entity.qt.has_as_of_combo_fields}}
// Re-resolve the as-of combos if setClientManager() already ran with a
// different (or no) {{domain_entity.entity_singular}} -- see the call-order
// note in populate*Combo() for each as-of field below.
if (clientManager_ && readOnly_) {
{{#domain_entity.qt.detail_fields}}
{{#combo_as_of_fetch_fn}}
populate{{combo_setter_pascal}}();
{{/combo_as_of_fetch_fn}}
{{/domain_entity.qt.detail_fields}}
}
{{/domain_entity.qt.has_as_of_combo_fields}}
<<paste:D5A8E2F1-6C4B-4A9D-8E3F-2B7C1A9D4E6F>>
}
void {{domain_entity.entity_pascal}}DetailDialog::setCreateMode(bool createMode) {
createMode_ = createMode;
{{#domain_entity.qt.locked_fields}}
{{#is_line_edit}}
{{#is_auto_generated_key}}
ui_->{{widget}}->setReadOnly(true);
{{/is_auto_generated_key}}
{{^is_auto_generated_key}}
ui_->{{widget}}->setReadOnly(!createMode);
{{/is_auto_generated_key}}
{{/is_line_edit}}
{{#is_text_edit}}
ui_->{{widget}}->setReadOnly(!createMode);
{{/is_text_edit}}
{{#is_static_combo}}
ui_->{{widget}}->setEnabled(createMode);
{{/is_static_combo}}
{{#is_dynamic_combo}}
ui_->{{widget}}->setEnabled(createMode);
{{/is_dynamic_combo}}
{{#is_flagged_combo}}
ui_->{{widget}}->setEnabled(createMode);
{{/is_flagged_combo}}
{{#is_check_box}}
ui_->{{widget}}->setEnabled(createMode);
{{/is_check_box}}
{{#is_colour}}
ui_->{{widget}}->setEnabled(createMode);
{{/is_colour}}
{{/domain_entity.qt.locked_fields}}
ui_->deleteButton->setVisible(!createMode);
setProvenanceEnabled(!createMode);
{{#domain_entity.qt.has_uuid_primary_key}}
if (createMode) {
{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.id_access}}{{domain_entity.qt.id_access}}{{/domain_entity.qt.id_access}}{{^domain_entity.qt.id_access}}id{{/domain_entity.qt.id_access}} = boost::uuids::random_generator()();
{{#domain_entity.qt.hidden_party_id_on_create}}
if (clientManager_)
{{domain_entity.qt.item_var}}_.party_id = clientManager_->currentPartyId();
{{/domain_entity.qt.hidden_party_id_on_create}}
}
{{/domain_entity.qt.has_uuid_primary_key}}
hasChanges_ = false;
updateSaveButtonState();
{{! see * Extension points above }}
<<paste:9A719585-3AB1-47DE-B0B0-99AFDFE7180E>>
}
void {{domain_entity.entity_pascal}}DetailDialog::markDirty() {
hasChanges_ = true;
updateSaveButtonState();
}
{{^domain_entity.qt.has_version_navigation}}
void {{domain_entity.entity_pascal}}DetailDialog::setReadOnly(bool readOnly) {
{{/domain_entity.qt.has_version_navigation}}
{{#domain_entity.qt.has_version_navigation}}
void {{domain_entity.entity_pascal}}DetailDialog::setReadOnly(bool readOnly, int versionNumber) {
historicalVersion_ = versionNumber;
{{/domain_entity.qt.has_version_navigation}}
readOnly_ = readOnly;
{{#domain_entity.qt.detail_fields}}
{{#is_locked_after_create}}
{{#is_line_edit}}
ui_->{{widget}}->setReadOnly(true);
{{/is_line_edit}}
{{#is_text_edit}}
ui_->{{widget}}->setReadOnly(true);
{{/is_text_edit}}
{{#is_static_combo}}
ui_->{{widget}}->setEnabled(false);
{{/is_static_combo}}
{{#is_dynamic_combo}}
ui_->{{widget}}->setEnabled(false);
{{/is_dynamic_combo}}
{{#is_flagged_combo}}
ui_->{{widget}}->setEnabled(false);
{{/is_flagged_combo}}
{{#is_check_box}}
ui_->{{widget}}->setEnabled(false);
{{/is_check_box}}
{{#is_colour}}
ui_->{{widget}}->setEnabled(false);
{{/is_colour}}
{{/is_locked_after_create}}
{{^is_locked_after_create}}
{{#is_line_edit}}
ui_->{{widget}}->setReadOnly(readOnly);
{{/is_line_edit}}
{{#is_text_edit}}
ui_->{{widget}}->setReadOnly(readOnly);
{{/is_text_edit}}
{{#is_static_combo}}
ui_->{{widget}}->setEnabled(!readOnly);
{{/is_static_combo}}
{{#is_dynamic_combo}}
ui_->{{widget}}->setEnabled(!readOnly);
{{/is_dynamic_combo}}
{{#is_flagged_combo}}
ui_->{{widget}}->setEnabled(!readOnly);
{{/is_flagged_combo}}
{{#is_check_box}}
ui_->{{widget}}->setEnabled(!readOnly);
{{/is_check_box}}
{{#is_colour}}
ui_->{{widget}}->setEnabled(!readOnly);
{{/is_colour}}
{{/is_locked_after_create}}
{{/domain_entity.qt.detail_fields}}
ui_->saveButton->setVisible(!readOnly);
ui_->deleteButton->setVisible(!readOnly);
{{#domain_entity.qt.has_version_navigation}}
if (revertAction_)
revertAction_->setVisible(readOnly);
{{/domain_entity.qt.has_version_navigation}}
{{#domain_entity.qt.has_as_of_combo_fields}}
// Re-resolve the as-of combos if setClientManager() already ran before
// this call established readOnly_ -- see the call-order note in
// populate*Combo() for each as-of field below.
if (clientManager_) {
{{#domain_entity.qt.detail_fields}}
{{#combo_as_of_fetch_fn}}
populate{{combo_setter_pascal}}();
{{/combo_as_of_fetch_fn}}
{{/domain_entity.qt.detail_fields}}
}
{{/domain_entity.qt.has_as_of_combo_fields}}
{{! see * Extension points above }}
<<paste:7A4AE8D5-A22D-4912-B4BF-D58215681CD2>>
}
{{#domain_entity.qt.has_version_navigation}}
void {{domain_entity.entity_pascal}}DetailDialog::setHistory(
const std::vector<{{domain_entity.qt.domain_class}}>& history, int versionNumber) {
history_ = history;
// Find index of the requested version (history is newest-first)
currentHistoryIndex_ = 0;
for (size_t i = 0; i < history_.size(); ++i) {
if (history_[i].version == versionNumber) {
currentHistoryIndex_ = static_cast<int>(i);
break;
}
}
displayCurrentVersion();
showVersionNavActions(true);
}
void {{domain_entity.entity_pascal}}DetailDialog::displayCurrentVersion() {
if (history_.empty() || currentHistoryIndex_ < 0 ||
currentHistoryIndex_ >= static_cast<int>(history_.size())) {
return;
}
const auto& version = history_[currentHistoryIndex_];
set{{domain_entity.entity_pascal_short}}(version);
setReadOnly(true, version.version);
updateVersionNavButtonStates();
}
void {{domain_entity.entity_pascal}}DetailDialog::updateVersionNavButtonStates() {
if (history_.empty()) {
showVersionNavActions(false);
return;
}
bool atOldest = (currentHistoryIndex_ == static_cast<int>(history_.size()) - 1);
bool atNewest = (currentHistoryIndex_ == 0);
if (firstVersionAction_)
firstVersionAction_->setEnabled(!atOldest); // Go to oldest
if (prevVersionAction_)
prevVersionAction_->setEnabled(!atOldest); // Go to older
if (nextVersionAction_)
nextVersionAction_->setEnabled(!atNewest); // Go to newer
if (lastVersionAction_)
lastVersionAction_->setEnabled(!atNewest); // Go to latest
}
void {{domain_entity.entity_pascal}}DetailDialog::showVersionNavActions(bool visible) {
if (firstVersionAction_)
firstVersionAction_->setVisible(visible);
if (prevVersionAction_)
prevVersionAction_->setVisible(visible);
if (nextVersionAction_)
nextVersionAction_->setVisible(visible);
if (lastVersionAction_)
lastVersionAction_->setVisible(visible);
}
void {{domain_entity.entity_pascal}}DetailDialog::onFirstVersionClicked() {
if (history_.empty())
return;
currentHistoryIndex_ = static_cast<int>(history_.size()) - 1;
displayCurrentVersion();
}
void {{domain_entity.entity_pascal}}DetailDialog::onPrevVersionClicked() {
if (history_.empty())
return;
if (currentHistoryIndex_ < static_cast<int>(history_.size()) - 1) {
++currentHistoryIndex_;
displayCurrentVersion();
}
}
void {{domain_entity.entity_pascal}}DetailDialog::onNextVersionClicked() {
if (history_.empty())
return;
if (currentHistoryIndex_ > 0) {
--currentHistoryIndex_;
displayCurrentVersion();
}
}
void {{domain_entity.entity_pascal}}DetailDialog::onLastVersionClicked() {
if (history_.empty())
return;
currentHistoryIndex_ = 0;
displayCurrentVersion();
}
void {{domain_entity.entity_pascal}}DetailDialog::onRevertClicked() {
auto reply = MessageBoxHelper::question(
this,
tr("Revert {{domain_entity.entity_title}}"),
tr("Are you sure you want to revert '%1' to version %2?\n\n"
"This will create a new version with the data from version %2.")
.arg(code())
.arg(historicalVersion_),
QMessageBox::Yes | QMessageBox::No);
if (reply != QMessageBox::Yes)
return;
emit revertRequested({{domain_entity.qt.item_var}}_);
}
{{/domain_entity.qt.has_version_navigation}}
{{#domain_entity.qt.detail_fields}}
{{#is_dynamic_combo}}
{{#combo_domain_type}}
void {{domain_entity.entity_pascal}}DetailDialog::populate{{combo_setter_pascal}}() {
BOOST_LOG_SEV(lg(), debug) << "Populating {{field}} combo";
{{#combo_as_of_fetch_fn}}
std::function<std::expected<std::vector<{{combo_domain_type}}>, QString>(ClientManager*)>
fetch = &{{combo_fetch_fn}};
if (readOnly_) {
// Resolve this historical {{domain_entity.entity_singular}} version's
// {{field}} badge as-of its own recorded_at, not against the current
// (possibly since-renamed or deleted) lookup list -- see the As-of
// lookup resolution codegen facet story. set{{domain_entity.entity_pascal_short}}()/setReadOnly()
// re-invoke this populate call if they run after setClientManager(),
// so call order between them doesn't matter.
const auto as_of = QString::fromStdString(ores::platform::time::datetime::to_db_string(
{{domain_entity.qt.item_var}}_.recorded_at));
fetch = [as_of](ClientManager* cm) { return {{combo_as_of_fetch_fn}}(cm, as_of); };
}
{{/combo_as_of_fetch_fn}}
populateDynamicCombo<{{combo_domain_type}}>(
ui_->{{widget}},
this,
clientManager_,
{{#combo_as_of_fetch_fn}}
fetch,
{{/combo_as_of_fetch_fn}}
{{^combo_as_of_fetch_fn}}
&{{combo_fetch_fn}},
{{/combo_as_of_fetch_fn}}
"{{combo_watcher_name}}",
{{#combo_display_field}}
[](const auto& t) { return QString::fromStdString(t.{{combo_display_field}}); },
{{/combo_display_field}}
{{^combo_display_field}}
[](const auto& t) { return QString::fromStdString(t.{{combo_code_field}}); },
{{/combo_display_field}}
[](const auto& t) { return QString::fromStdString(t.{{combo_tooltip_field}}); },
[](const auto& t) { return t.{{combo_sort_field}}; },
{{#is_optional_uuid}}
[this]() {
const auto& _opt = {{domain_entity.qt.item_var}}_.{{field}};
return _opt ? QString::fromStdString(boost::uuids::to_string(*_opt)) : QString{};
},
{{/is_optional_uuid}}
{{^is_optional_uuid}}
{{#is_uuid}}
[this]() { return QString::fromStdString(boost::uuids::to_string({{domain_entity.qt.item_var}}_.{{field}})); },
{{/is_uuid}}
{{^is_uuid}}
{{#is_optional_string}}
[this]() {
const auto& _opt = {{domain_entity.qt.item_var}}_.{{field}};
return _opt ? QString::fromStdString(*_opt) : QString{};
},
{{/is_optional_string}}
{{^is_optional_string}}
[this]() { return QString::fromStdString({{domain_entity.qt.item_var}}_.{{field}}); },
{{/is_optional_string}}
{{/is_uuid}}
{{/is_optional_uuid}}
[this](const QString& error) {
emit errorMessage(tr("Failed to load {{combo_label}}: %1").arg(error));
},
{{#badge_key}}
[this]() { setup_badge_combo(this, ui_->{{widget}}, badgeCache(), "{{badge_key}}"); },
{{/badge_key}}
{{^badge_key}}
[]() {},
{{/badge_key}}
QObject::tr("Loading…"),
QObject::tr("Failed to load"),
{{#is_uuid}}
[](const auto& t) { return QString::fromStdString(boost::uuids::to_string(t.{{combo_code_field}})); },
{{/is_uuid}}
{{#is_optional_uuid}}
[](const auto& t) { return QString::fromStdString(boost::uuids::to_string(t.{{combo_code_field}})); },
{{/is_optional_uuid}}
{{^is_uuid}}
{{^is_optional_uuid}}
[](const auto& t) { return QString::fromStdString(t.{{combo_code_field}}); },
{{/is_optional_uuid}}
{{/is_uuid}}
{{#is_self_referencing_combo}}
[this](const auto& t) { return t.{{domain_entity.qt.key_field}} == {{domain_entity.qt.item_var}}_.{{domain_entity.qt.key_field}}; },
{{/is_self_referencing_combo}}
{{^is_self_referencing_combo}}
[](const auto&) { return false; },
{{/is_self_referencing_combo}}
{{#combo_blank_label}}
QObject::tr("{{combo_blank_label}}"));
{{/combo_blank_label}}
{{^combo_blank_label}}
QString{});
{{/combo_blank_label}}
}
{{/combo_domain_type}}
{{^combo_domain_type}}
{{^combo_fetch_fn}}
void {{domain_entity.entity_pascal}}DetailDialog::{{combo_setter}}(
const std::vector<{{combo_type}}>& items) {
{{combo_items_member}}_ = items;
populate{{combo_setter_pascal}}();
}
void {{domain_entity.entity_pascal}}DetailDialog::populate{{combo_setter_pascal}}() {
const auto current = ui_->{{widget}}->currentData();
ui_->{{widget}}->clear();
{{#combo_is_optional}}
ui_->{{widget}}->addItem(tr("(None)"), QVariant{});
{{/combo_is_optional}}
for (const auto& item : {{combo_items_member}}_) {
ui_->{{widget}}->addItem(
QString::fromStdString(item.{{combo_display}}),
QString::fromStdString(item.{{combo_value}}));
}
if (current.isValid()) {
const int idx = ui_->{{widget}}->findData(current);
if (idx >= 0) ui_->{{widget}}->setCurrentIndex(idx);
}
}
{{/combo_fetch_fn}}
{{#combo_fetch_fn}}
{{#combo_helper}}
void {{domain_entity.entity_pascal}}DetailDialog::populate{{combo_setter_pascal}}() {
{{combo_helper}}(ui_->{{widget}}, this, clientManager_, imageCache(), [this]() {
return QString::fromStdString({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}});
});
}
{{/combo_helper}}
{{^combo_helper}}
void {{domain_entity.entity_pascal}}DetailDialog::populate{{combo_setter_pascal}}() {
if (!clientManager_ || !clientManager_->isConnected())
return;
if (findChild<QFutureWatcherBase*>("{{combo_items_member}}FetchWatcher"))
return;
QPointer<{{domain_entity.entity_pascal}}DetailDialog> self = this;
auto* cm = clientManager_;
QFuture<std::vector<{{combo_type}}>> future =
QtConcurrent::run([cm]() { return {{combo_fetch_fn}}(cm); });
auto* watcher = new QFutureWatcher<std::vector<{{combo_type}}>>(this);
watcher->setObjectName("{{combo_items_member}}FetchWatcher");
connect(watcher, &QFutureWatcher<std::vector<{{combo_type}}>>::finished, this, [self, watcher]() {
auto items = watcher->result();
watcher->deleteLater();
if (!self)
return;
// Server returns items already ordered (e.g. by display_order for a
// lookup table); not re-sorted here.
const auto current = self->ui_->{{widget}}->currentText();
self->ui_->{{widget}}->clear();
for (const auto& item : items)
self->ui_->{{widget}}->addItem(QString::fromStdString(item.{{combo_display}}));
// fallback is preferred over current: set{{domain_entity.entity_pascal_short}}() always runs
// synchronously before this dialog is shown, well before this
// async fetch can complete, so by the time this callback fires the
// model's own field value is authoritative -- current (the combo's
// transient text, empty until this first population) would
// otherwise permanently blank the selection on every dialog open.
const auto fallback =
QString::fromStdString(self->{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}});
const auto to_select = !fallback.isEmpty() ? fallback : current;
if (!to_select.isEmpty())
self->ui_->{{widget}}->setCurrentText(to_select);
{{#flag_source}}
apply_flag_icons(self->ui_->{{widget}}, self->imageCache(), FlagSource::{{flag_source}});
{{/flag_source}}
{{#badge_key}}
setup_badge_combo(self, self->ui_->{{widget}}, self->badgeCache(), "{{badge_key}}");
{{/badge_key}}
});
watcher->setFuture(future);
}
{{/combo_helper}}
{{/combo_fetch_fn}}
{{/combo_domain_type}}
{{/is_dynamic_combo}}
{{/domain_entity.qt.detail_fields}}
void {{domain_entity.entity_pascal}}DetailDialog::updateUiFrom{{domain_entity.entity_pascal_short}}() {
{{#domain_entity.qt.detail_fields}}
{{#is_line_edit}}
{{#is_optional_uuid}}
{
const auto& _opt = {{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}};
ui_->{{widget}}->setText(_opt
? QString::fromStdString(boost::uuids::to_string(*_opt))
: QString{});
}
{{/is_optional_uuid}}
{{^is_optional_uuid}}
{{#is_uuid}}
ui_->{{widget}}->setText(QString::fromStdString(
boost::uuids::to_string({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}})));
{{/is_uuid}}
{{^is_uuid}}
{{#is_optional_string}}
ui_->{{widget}}->setText({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}
? QString::fromStdString(*{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}})
: QString{});
{{/is_optional_string}}
{{^is_optional_string}}
{{#is_optional_double}}
ui_->{{widget}}->setText({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}
? QString::number(*{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}})
: QString{});
{{/is_optional_double}}
{{^is_optional_double}}
{{#is_double}}
ui_->{{widget}}->setText(QString::number({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}));
{{/is_double}}
{{^is_double}}
{{#is_date}}
ui_->{{widget}}->setText(QString::fromStdString(ores::platform::time::datetime::to_iso8601_date(
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}})));
{{/is_date}}
{{^is_date}}
ui_->{{widget}}->setText(QString::fromStdString({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}));
{{/is_date}}
{{/is_double}}
{{/is_optional_double}}
{{/is_optional_string}}
{{/is_uuid}}
{{/is_optional_uuid}}
{{/is_line_edit}}
{{#is_text_edit}}
{{#is_optional_string}}
ui_->{{widget}}->setPlainText({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}
? QString::fromStdString(*{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}})
: QString{});
{{/is_optional_string}}
{{^is_optional_string}}
ui_->{{widget}}->setPlainText(QString::fromStdString({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}));
{{/is_optional_string}}
{{/is_text_edit}}
{{#is_check_box}}
{{#is_tristate}}
ui_->{{widget}}->setCheckState({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}
? (*{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} ? Qt::Checked : Qt::Unchecked)
: Qt::PartiallyChecked);
{{/is_tristate}}
{{^is_tristate}}
ui_->{{widget}}->setChecked({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}});
{{/is_tristate}}
{{/is_check_box}}
{{#is_spin_box}}
{{#is_nullable_int}}
ui_->{{widget}}->setValue({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}.value_or(
ui_->{{widget}}->minimum()));
{{/is_nullable_int}}
{{^is_nullable_int}}
ui_->{{widget}}->setValue({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}});
{{/is_nullable_int}}
{{/is_spin_box}}
{{#is_static_combo}}
{
{{#is_optional_string}}
const auto val = {{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}
? QString::fromStdString(*{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}})
: QString{};
{{/is_optional_string}}
{{^is_optional_string}}
const auto val = QString::fromStdString(
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}});
{{/is_optional_string}}
const int idx = ui_->{{widget}}->findData(val);
if (idx >= 0) ui_->{{widget}}->setCurrentIndex(idx);
}
{{/is_static_combo}}
{{#is_dynamic_combo}}
{{#combo_fetch_fn}}
{
{{#is_optional_uuid}}
const auto& _opt = {{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}};
const auto val = _opt
? QString::fromStdString(boost::uuids::to_string(*_opt))
: QString{};
{{/is_optional_uuid}}
{{^is_optional_uuid}}
{{#is_uuid}}
const auto val = QString::fromStdString(boost::uuids::to_string(
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}));
{{/is_uuid}}
{{^is_uuid}}
{{#is_optional_string}}
const auto val = {{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}
? QString::fromStdString(*{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}})
: QString{};
{{/is_optional_string}}
{{^is_optional_string}}
const auto val = QString::fromStdString(
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}});
{{/is_optional_string}}
{{/is_uuid}}
{{/is_optional_uuid}}
const int idx = ui_->{{widget}}->findData(val);
if (idx >= 0) ui_->{{widget}}->setCurrentIndex(idx);
}
{{/combo_fetch_fn}}
{{^combo_fetch_fn}}
{
const auto val = QString::fromStdString({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}});
const int idx = ui_->{{widget}}->findData(val);
if (idx >= 0) ui_->{{widget}}->setCurrentIndex(idx);
}
{{/combo_fetch_fn}}
{{/is_dynamic_combo}}
{{#is_flagged_combo}}
{
{{#is_optional_string}}
const auto val = {{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}
? QString::fromStdString(*{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}})
: QString{};
{{/is_optional_string}}
{{^is_optional_string}}
const auto val = QString::fromStdString({{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}});
{{/is_optional_string}}
const int idx = ui_->{{widget}}->findText(val);
ui_->{{widget}}->setCurrentIndex(idx);
}
{{/is_flagged_combo}}
{{#is_colour}}
set_colour_swatch(ui_->{{widget}}, QString::fromStdString(
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}}));
{{/is_colour}}
{{/domain_entity.qt.detail_fields}}
populateProvenance({{domain_entity.qt.item_var}}_.{{#domain_entity.qt.version_access}}{{domain_entity.qt.version_access}}{{/domain_entity.qt.version_access}}{{^domain_entity.qt.version_access}}version{{/domain_entity.qt.version_access}},
{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.modified_by_access}}{{domain_entity.qt.modified_by_access}}{{/domain_entity.qt.modified_by_access}}{{^domain_entity.qt.modified_by_access}}modified_by{{/domain_entity.qt.modified_by_access}},
{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.performed_by_access}}{{domain_entity.qt.performed_by_access}}{{/domain_entity.qt.performed_by_access}}{{^domain_entity.qt.performed_by_access}}performed_by{{/domain_entity.qt.performed_by_access}},
{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.recorded_at_access}}{{domain_entity.qt.recorded_at_access}}{{/domain_entity.qt.recorded_at_access}}{{^domain_entity.qt.recorded_at_access}}recorded_at{{/domain_entity.qt.recorded_at_access}},
{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.change_reason_code_access}}{{domain_entity.qt.change_reason_code_access}}{{/domain_entity.qt.change_reason_code_access}}{{^domain_entity.qt.change_reason_code_access}}change_reason_code{{/domain_entity.qt.change_reason_code_access}},
{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.change_commentary_access}}{{domain_entity.qt.change_commentary_access}}{{/domain_entity.qt.change_commentary_access}}{{^domain_entity.qt.change_commentary_access}}change_commentary{{/domain_entity.qt.change_commentary_access}});
hasChanges_ = false;
updateSaveButtonState();
}
void {{domain_entity.entity_pascal}}DetailDialog::update{{domain_entity.entity_pascal_short}}FromUi() {
{{#domain_entity.qt.detail_fields}}
{{#is_locked_after_create}}
{{^is_uuid}}
if (createMode_) {
{{#is_line_edit}}
{{#is_date}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
ores::platform::time::datetime::from_iso8601_date(ui_->{{widget}}->text().trimmed().toStdString());
{{/is_date}}
{{^is_date}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->text().trimmed().toStdString();
{{/is_date}}
{{/is_line_edit}}
{{#is_static_combo}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->currentData().toString().trimmed().toStdString();
{{/is_static_combo}}
{{#is_dynamic_combo}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->currentData().toString().trimmed().toStdString();
{{/is_dynamic_combo}}
{{#is_flagged_combo}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->currentText().trimmed().toStdString();
{{/is_flagged_combo}}
{{#is_check_box}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->isChecked();
{{/is_check_box}}
}
{{/is_uuid}}
{{#is_uuid}}
{{#is_dynamic_combo}}
if (createMode_) {
const auto {{field}}_str = ui_->{{widget}}->currentData().toString().trimmed().toStdString();
if (!{{field}}_str.empty()) {
try {
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
boost::uuids::string_generator()({{field}}_str);
} catch (const std::exception&) {
// Leave the field unchanged; validation catches the empty/nil
// selection at save time (no combo choice made yet).
}
}
}
{{/is_dynamic_combo}}
{{/is_uuid}}
{{/is_locked_after_create}}
{{^is_locked_after_create}}
{{#is_line_edit}}
{{^is_uuid}}
{{^is_optional_uuid}}
{{#is_optional_string}}
{
const auto {{field}}_str = ui_->{{widget}}->text().trimmed().toStdString();
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
{{field}}_str.empty() ? std::nullopt : std::optional<std::string>({{field}}_str);
}
{{/is_optional_string}}
{{^is_optional_string}}
{{#is_optional_double}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
ui_->{{widget}}->text().trimmed().isEmpty()
? std::nullopt
: std::optional<double>(ui_->{{widget}}->text().trimmed().toDouble());
{{/is_optional_double}}
{{^is_optional_double}}
{{#is_double}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->text().trimmed().toDouble();
{{/is_double}}
{{^is_double}}
{{#is_date}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
ores::platform::time::datetime::from_iso8601_date(ui_->{{widget}}->text().trimmed().toStdString());
{{/is_date}}
{{^is_date}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->text().trimmed().toStdString();
{{/is_date}}
{{/is_double}}
{{/is_optional_double}}
{{/is_optional_string}}
{{/is_optional_uuid}}
{{/is_uuid}}
{{/is_line_edit}}
{{#is_text_edit}}
{{#is_optional_string}}
{
const auto {{field}}_str = ui_->{{widget}}->toPlainText().trimmed().toStdString();
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
{{field}}_str.empty() ? std::nullopt : std::optional<std::string>({{field}}_str);
}
{{/is_optional_string}}
{{^is_optional_string}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->toPlainText().trimmed().toStdString();
{{/is_optional_string}}
{{/is_text_edit}}
{{#is_check_box}}
{{#is_tristate}}
switch (ui_->{{widget}}->checkState()) {
case Qt::Checked:
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = std::optional<bool>(true);
break;
case Qt::Unchecked:
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = std::optional<bool>(false);
break;
default:
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = std::nullopt;
break;
}
{{/is_tristate}}
{{^is_tristate}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->isChecked();
{{/is_tristate}}
{{/is_check_box}}
{{#is_spin_box}}
{{#is_nullable_int}}
if (ui_->{{widget}}->value() == ui_->{{widget}}->minimum())
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = std::nullopt;
else
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->value();
{{/is_nullable_int}}
{{^is_nullable_int}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = ui_->{{widget}}->value();
{{/is_nullable_int}}
{{/is_spin_box}}
{{#is_static_combo}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
ui_->{{widget}}->currentData().toString().toStdString();
{{/is_static_combo}}
{{#is_dynamic_combo}}
{{#combo_fetch_fn}}
{{#is_optional_uuid}}
{
const auto {{field}}_str = ui_->{{widget}}->currentData().toString().trimmed().toStdString();
if ({{field}}_str.empty()) {
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = std::nullopt;
} else {
try {
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
boost::uuids::string_generator()({{field}}_str);
} catch (const std::exception&) {
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} = std::nullopt;
}
}
}
{{/is_optional_uuid}}
{{^is_optional_uuid}}
{{#is_uuid}}
{
const auto {{field}}_str = ui_->{{widget}}->currentData().toString().trimmed().toStdString();
if (!{{field}}_str.empty()) {
try {
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
boost::uuids::string_generator()({{field}}_str);
} catch (const std::exception&) {
// Leave the field unchanged; validation catches the empty/nil
// selection at save time (no combo choice made yet).
}
}
}
{{/is_uuid}}
{{^is_uuid}}
{{#is_optional_string}}
{
const auto {{field}}_str = ui_->{{widget}}->currentData().toString().trimmed().toStdString();
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
{{field}}_str.empty() ? std::nullopt : std::optional{ {{field}}_str };
}
{{/is_optional_string}}
{{^is_optional_string}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
ui_->{{widget}}->currentText().toStdString();
{{/is_optional_string}}
{{/is_uuid}}
{{/is_optional_uuid}}
{{/combo_fetch_fn}}
{{^combo_fetch_fn}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
ui_->{{widget}}->currentData().toString().toStdString();
{{/combo_fetch_fn}}
{{/is_dynamic_combo}}
{{#is_flagged_combo}}
{{#is_optional_string}}
{
const auto _txt = ui_->{{widget}}->currentText().trimmed().toStdString();
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
_txt.empty() ? std::nullopt : std::optional<std::string>(_txt);
}
{{/is_optional_string}}
{{^is_optional_string}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
ui_->{{widget}}->currentText().toStdString();
{{/is_optional_string}}
{{/is_flagged_combo}}
{{#is_colour}}
{{domain_entity.qt.item_var}}_.{{#field_access}}{{field_access}}{{/field_access}}{{^field_access}}{{field}}{{/field_access}} =
colour_swatch_value(ui_->{{widget}}).toStdString();
{{/is_colour}}
{{/is_locked_after_create}}
{{/domain_entity.qt.detail_fields}}
{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.modified_by_access}}{{domain_entity.qt.modified_by_access}}{{/domain_entity.qt.modified_by_access}}{{^domain_entity.qt.modified_by_access}}modified_by{{/domain_entity.qt.modified_by_access}} = username_;
}
{{#domain_entity.qt.has_line_edit_key}}
void {{domain_entity.entity_pascal}}DetailDialog::onCodeChanged(const QString& /* text */) {
hasChanges_ = true;
updateSaveButtonState();
}
{{/domain_entity.qt.has_line_edit_key}}
void {{domain_entity.entity_pascal}}DetailDialog::onFieldChanged() {
hasChanges_ = true;
updateSaveButtonState();
}
void {{domain_entity.entity_pascal}}DetailDialog::updateSaveButtonState() {
bool canSave = hasChanges_ && validateInput() && !readOnly_;
ui_->saveButton->setEnabled(canSave);
}
bool {{domain_entity.entity_pascal}}DetailDialog::validateInput() {
{{#domain_entity.qt.required_fields}}
const QString {{field}}_val = ui_->{{widget}}->text().trimmed();
{{/domain_entity.qt.required_fields}}
{{#domain_entity.qt.required_dynamic_combo_fields}}
const bool {{field}}_selected = ui_->{{widget}}->currentIndex() >= 0;
{{/domain_entity.qt.required_dynamic_combo_fields}}
{{#domain_entity.qt.date_fields}}
const QString {{field}}_date_val = ui_->{{widget}}->text().trimmed();
{{/domain_entity.qt.date_fields}}
return true
{{#domain_entity.qt.required_fields}}
&& !{{field}}_val.isEmpty()
{{/domain_entity.qt.required_fields}}
{{#domain_entity.qt.required_dynamic_combo_fields}}
&& {{field}}_selected
{{/domain_entity.qt.required_dynamic_combo_fields}}
{{#domain_entity.qt.date_fields}}
// A blank optional date field is valid (nothing to parse); a
// non-blank one must be a real ISO-8601 date, or the unchecked
// from_iso8601_date() parse in updateXFromUi() would throw and
// crash the app on Save.
&& ({{field}}_date_val.isEmpty() ||
ores::platform::time::datetime::is_valid_iso8601_date({{field}}_date_val.toStdString()))
{{/domain_entity.qt.date_fields}}
{{! see * Extension points above }}
<<paste:69F22F14-6701-4E73-AFCC-78AC1B1FDDDF>>
;
}
void {{domain_entity.entity_pascal}}DetailDialog::onSaveClicked() {
if (!clientManager_ || !clientManager_->isConnected()) {
MessageBoxHelper::warning(this, "Disconnected",
"Cannot save {{domain_entity.entity_singular_words}} while disconnected from server.");
return;
}
if (!validateInput()) {
MessageBoxHelper::warning(this, "Invalid Input",
"Please fill in all required fields.");
return;
}
{{! see * Extension points above }}
<<paste:4E83FA7A-742A-4102-90AF-D337F6FB1269>>
const auto crOpType = createMode_
? ChangeReasonDialog::OperationType::Create
: ChangeReasonDialog::OperationType::Amend;
const auto crSel = promptChangeReason(crOpType, hasChanges_,
createMode_ ? "system" : "common");
if (!crSel) return;
{{domain_entity.qt.item_var}}_.change_reason_code = crSel->reason_code;
{{domain_entity.qt.item_var}}_.change_commentary = crSel->commentary;
{{#domain_entity.qt.has_flag_icon}}
if (flagChanged())
{{domain_entity.qt.item_var}}_.image_id = selectedImageId();
{{/domain_entity.qt.has_flag_icon}}
update{{domain_entity.entity_pascal_short}}FromUi();
BOOST_LOG_SEV(lg(), info) << "Saving {{domain_entity.entity_singular_words}}: "
<< {{domain_entity.qt.key_to_string_prefix}}{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field_access}}{{/domain_entity.qt.key_field_access}}{{^domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field}}{{/domain_entity.qt.key_field_access}}{{domain_entity.qt.key_to_string_suffix}};
QPointer<{{domain_entity.entity_pascal}}DetailDialog> self = this;
struct SaveResult {
bool success;
std::string message;
};
auto task = [self, {{domain_entity.qt.item_var}} = {{domain_entity.qt.item_var}}_]() -> SaveResult {
if (!self || !self->clientManager_) {
return {false, "Dialog closed"};
}
{{domain_entity.qt.save_request_class}} request;
request.data = {{domain_entity.qt.item_var}};
auto response_result = self->clientManager_->
process_authenticated_request(std::move(request));
if (!response_result) {
return {false, "Failed to communicate with server"};
}
return {response_result->success, response_result->message};
};
auto* watcher = new QFutureWatcher<SaveResult>(self);
connect(watcher, &QFutureWatcher<SaveResult>::finished,
self, [self, watcher, crReasonCode = crSel->reason_code, crCommentary = crSel->commentary]() {
auto result = watcher->result();
watcher->deleteLater();
if (result.success) {
BOOST_LOG_SEV(lg(), info) << "{{domain_entity.entity_title}} saved successfully";
QString code = QString::fromStdString(
{{domain_entity.qt.key_to_string_prefix}}self->{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field_access}}{{/domain_entity.qt.key_field_access}}{{^domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field}}{{/domain_entity.qt.key_field_access}}{{domain_entity.qt.key_to_string_suffix}});
self->hasChanges_ = false;
{{#domain_entity.qt.has_flag_icon}}
self->resetFlagChanged();
{{/domain_entity.qt.has_flag_icon}}
self->updateSaveButtonState();
emit self->{{domain_entity.qt.item_var}}Saved(code);
self->notifySaveSuccess(tr("{{domain_entity.entity_title}} '%1' saved").arg(code));
{{! see * Extension points above }}
<<paste:F8EC96B9-04BA-46A1-A2D8-4E4B91DCB435>>
} else {
BOOST_LOG_SEV(lg(), error) << "Save failed: " << result.message;
QString errorMsg = QString::fromStdString(result.message);
emit self->errorMessage(errorMsg);
MessageBoxHelper::critical(self, "Save Failed", errorMsg);
}
});
QFuture<SaveResult> future = QtConcurrent::run(task);
watcher->setFuture(future);
}
void {{domain_entity.entity_pascal}}DetailDialog::onDeleteClicked() {
if (!clientManager_ || !clientManager_->isConnected()) {
MessageBoxHelper::warning(this, "Disconnected",
"Cannot delete {{domain_entity.entity_singular_words}} while disconnected from server.");
return;
}
QString code = QString::fromStdString(
{{domain_entity.qt.key_to_string_prefix}}{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field_access}}{{/domain_entity.qt.key_field_access}}{{^domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field}}{{/domain_entity.qt.key_field_access}}{{domain_entity.qt.key_to_string_suffix}});
auto reply = MessageBoxHelper::question(this, "Delete {{domain_entity.entity_title}}",
QString("Are you sure you want to delete {{domain_entity.entity_singular_words}} '%1'?").arg(code),
QMessageBox::Yes | QMessageBox::No);
if (reply != QMessageBox::Yes) {
return;
}
const auto crSel = promptChangeReason(
ChangeReasonDialog::OperationType::Delete, false, "common");
if (!crSel) return;
BOOST_LOG_SEV(lg(), info) << "Deleting {{domain_entity.entity_singular_words}}: "
<< {{domain_entity.qt.key_to_string_prefix}}{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field_access}}{{/domain_entity.qt.key_field_access}}{{^domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field}}{{/domain_entity.qt.key_field_access}}{{domain_entity.qt.key_to_string_suffix}};
QPointer<{{domain_entity.entity_pascal}}DetailDialog> self = this;
struct DeleteResult {
bool success;
std::string message;
};
{{#domain_entity.qt.has_uuid_primary_key}}
auto task = [self, id_str = boost::uuids::to_string({{domain_entity.qt.item_var}}_.{{#domain_entity.qt.id_access}}{{domain_entity.qt.id_access}}{{/domain_entity.qt.id_access}}{{^domain_entity.qt.id_access}}id{{/domain_entity.qt.id_access}})]() -> DeleteResult {
if (!self || !self->clientManager_) {
return {false, "Dialog closed"};
}
{{domain_entity.qt.delete_request_class}} request;
{{#domain_entity.qt.delete_request_id_is_plural}}
request.{{domain_entity.qt.delete_request_id_field}} = {id_str};
{{/domain_entity.qt.delete_request_id_is_plural}}
{{^domain_entity.qt.delete_request_id_is_plural}}
request.{{domain_entity.qt.delete_request_id_field}} = id_str;
{{/domain_entity.qt.delete_request_id_is_plural}}
auto response_result = self->clientManager_->
process_authenticated_request(std::move(request));
if (!response_result) {
return {false, "Failed to communicate with server"};
}
return {response_result->success, response_result->message};
};
{{/domain_entity.qt.has_uuid_primary_key}}
{{^domain_entity.qt.has_uuid_primary_key}}
auto task = [self, code = {{domain_entity.qt.key_to_string_prefix}}{{domain_entity.qt.item_var}}_.{{#domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field_access}}{{/domain_entity.qt.key_field_access}}{{^domain_entity.qt.key_field_access}}{{domain_entity.qt.key_field}}{{/domain_entity.qt.key_field_access}}{{domain_entity.qt.key_to_string_suffix}}]() -> DeleteResult {
if (!self || !self->clientManager_) {
return {false, "Dialog closed"};
}
{{domain_entity.qt.delete_request_class}} request;
{{#domain_entity.qt.delete_request_id_field}}
{{#domain_entity.qt.delete_request_id_is_plural}}
request.{{domain_entity.qt.delete_request_id_field}} = {code};
{{/domain_entity.qt.delete_request_id_is_plural}}
{{^domain_entity.qt.delete_request_id_is_plural}}
request.{{domain_entity.qt.delete_request_id_field}} = code;
{{/domain_entity.qt.delete_request_id_is_plural}}
{{/domain_entity.qt.delete_request_id_field}}
{{^domain_entity.qt.delete_request_id_field}}
request.codes = {code};
{{/domain_entity.qt.delete_request_id_field}}
auto response_result = self->clientManager_->
process_authenticated_request(std::move(request));
if (!response_result) {
return {false, "Failed to communicate with server"};
}
return {response_result->success, response_result->message};
};
{{/domain_entity.qt.has_uuid_primary_key}}
auto* watcher = new QFutureWatcher<DeleteResult>(self);
connect(watcher, &QFutureWatcher<DeleteResult>::finished,
self, [self, code, watcher]() {
auto result = watcher->result();
watcher->deleteLater();
if (result.success) {
BOOST_LOG_SEV(lg(), info) << "{{domain_entity.entity_title}} deleted successfully";
emit self->statusMessage(
QString("{{domain_entity.entity_title}} '%1' deleted").arg(code));
emit self->{{domain_entity.qt.item_var}}Deleted(code);
self->requestClose();
} else {
BOOST_LOG_SEV(lg(), error) << "Delete failed: " << result.message;
QString errorMsg = QString::fromStdString(result.message);
emit self->errorMessage(errorMsg);
MessageBoxHelper::critical(self, "Delete Failed", errorMsg);
}
});
QFuture<DeleteResult> future = QtConcurrent::run(task);
watcher->setFuture(future);
}
{{! see * Extension points above }}
<<paste:EC97923E-BC8D-4A50-9970-CBC5CBD4B732>>
}
See also
- Parent facet: ores.cpp.qt
- Template variable reference