ores.cpp.qt.mdi_window_impl
Table of Contents
List view window hosted in the main MDI area. 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. This template defines one:
18B9ED63-78C2-4259-93AC-CCDFBC88EFBD— end ofsetupToolbar(), after the generated History action. Extra toolbar actions, e.g. a cross-navigation button to a related entity's list window.
Template
The full template source. Edit here and re-tangle with
compass build --direct tangle_codegen_templates to regenerate
library/templates/cpp_qt_mdi_window.cpp.mustache.
{{! GENERATED FILE — tangled from projects/ores.codegen/library/templates/cpp_qt.org. Edit the org source. }}
{{! Template to generate Qt MDI window source for domain entities }}
{{{cpp_license}}}
#include "ores.qt/{{domain_entity.entity_pascal}}MdiWindow.hpp"
#include <QVBoxLayout>
#include <QHeaderView>
#include <QMessageBox>
#include <QtConcurrent>
#include <QFutureWatcher>
{{#domain_entity.qt.has_uuid_primary_key}}
#include <boost/uuid/uuid_io.hpp>
{{/domain_entity.qt.has_uuid_primary_key}}
#include "ores.qt/IconUtils.hpp"
#include "ores.qt/MessageBoxHelper.hpp"
#include "ores.qt/ColorConstants.hpp"
{{#domain_entity.qt.has_pair_icon_column}}
#include "ores.qt/FlagIconHelper.hpp"
{{/domain_entity.qt.has_pair_icon_column}}
{{^domain_entity.qt.has_pair_icon_column}}
{{#domain_entity.qt.has_any_flag_icon}}
#include "ores.qt/FlagIconHelper.hpp"
{{/domain_entity.qt.has_any_flag_icon}}
{{/domain_entity.qt.has_pair_icon_column}}
{{#domain_entity.qt.has_csv_xml_io}}
#include "ores.qt/ImportEntityDialog.hpp"
#include <QDesktopServices>
#include <QFile>
#include <QFileDialog>
#include <QUrl>
{{/domain_entity.qt.has_csv_xml_io}}
{{#domain_entity.qt.needs_item_delegate}}
#include "ores.qt/EntityItemDelegate.hpp"
{{/domain_entity.qt.needs_item_delegate}}
{{#domain_entity.qt.has_badge_columns}}
#include "ores.qt/BadgeCache.hpp"
{{/domain_entity.qt.has_badge_columns}}
{{#domain_entity.qt.needs_image_cache}}
#include "ores.qt/ImageCache.hpp"
{{/domain_entity.qt.needs_image_cache}}
#include "{{domain_entity.qt.protocol_include}}"
namespace ores::qt {
using namespace ores::logging;
{{domain_entity.entity_pascal}}MdiWindow::{{domain_entity.entity_pascal}}MdiWindow(
ClientManager* clientManager,
const QString& username,
{{#domain_entity.qt.has_badge_columns}}
BadgeCache* badgeCache,
{{/domain_entity.qt.has_badge_columns}}
{{#domain_entity.qt.needs_image_cache}}
ImageCache* imageCache,
{{/domain_entity.qt.needs_image_cache}}
{{#domain_entity.qt.has_parent_scoped_list}}
const QString& {{domain_entity.qt.parent_key_param}},
{{/domain_entity.qt.has_parent_scoped_list}}
QWidget* parent)
: EntityListMdiWindow(parent),
clientManager_(clientManager),
username_(username),
{{#domain_entity.qt.has_parent_scoped_list}}
{{domain_entity.qt.parent_key_param}}_({{domain_entity.qt.parent_key_param}}),
{{/domain_entity.qt.has_parent_scoped_list}}
{{#domain_entity.qt.has_badge_columns}}
badgeCache_(badgeCache),
{{/domain_entity.qt.has_badge_columns}}
{{#domain_entity.qt.needs_image_cache}}
imageCache_(imageCache),
{{/domain_entity.qt.needs_image_cache}}
toolbar_(nullptr),
tableView_(nullptr),
model_(nullptr),
proxyModel_(nullptr),
paginationWidget_(nullptr),
reloadAction_(nullptr){{^domain_entity.qt.has_readonly_paginated_list}},
addAction_(nullptr),
editAction_(nullptr),
deleteAction_(nullptr){{/domain_entity.qt.has_readonly_paginated_list}}{{#domain_entity.qt.has_csv_xml_io}},
importXMLAction_(nullptr),
exportCSVAction_(nullptr),
exportXMLAction_(nullptr){{/domain_entity.qt.has_csv_xml_io}}{{^domain_entity.qt.has_readonly_paginated_list}},
historyAction_(nullptr){{/domain_entity.qt.has_readonly_paginated_list}}{{#domain_entity.qt.has_setting_gated_actions}},
{{#domain_entity.qt.setting_gated_actions}}
{{action}}_(nullptr),
{{/domain_entity.qt.setting_gated_actions}}
settingGatedActions_(new SettingGatedActionController(clientManager, this)){{/domain_entity.qt.has_setting_gated_actions}} {
setupUi();
setupConnections();
{{#domain_entity.qt.has_setting_gated_actions}}
{{#domain_entity.qt.setting_gated_actions}}
settingGatedActions_->registerAction({{action}}_, "{{setting}}");
{{/domain_entity.qt.setting_gated_actions}}
if (clientManager_ && clientManager_->isLoggedIn())
settingGatedActions_->refresh();
{{/domain_entity.qt.has_setting_gated_actions}}
reload();
}
void {{domain_entity.entity_pascal}}MdiWindow::setupUi() {
auto* layout = new QVBoxLayout(this);
setupToolbar();
layout->addWidget(toolbar_);
layout->addWidget(loadingBar());
setupTable();
layout->addWidget(tableView_);
paginationWidget_ = new PaginationWidget(this);
layout->addWidget(paginationWidget_);
}
void {{domain_entity.entity_pascal}}MdiWindow::setupToolbar() {
toolbar_ = new QToolBar(this);
toolbar_->setMovable(false);
toolbar_->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
toolbar_->setIconSize(QSize(20, 20));
reloadAction_ = toolbar_->addAction(
IconUtils::createRecoloredIcon(
Icon::ArrowClockwise, IconUtils::DefaultIconColor),
tr("Reload"));
connect(reloadAction_, &QAction::triggered, this,
&EntityListMdiWindow::reload);
initializeStaleIndicator(reloadAction_, IconUtils::iconPath(Icon::ArrowClockwise));
{{^domain_entity.qt.has_readonly_paginated_list}}
toolbar_->addSeparator();
addAction_ = toolbar_->addAction(
IconUtils::createRecoloredIcon(
Icon::Add, IconUtils::DefaultIconColor),
tr("Add"));
addAction_->setToolTip(tr("Add new {{domain_entity.entity_singular_words}}"));
connect(addAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}MdiWindow::addNew);
editAction_ = toolbar_->addAction(
IconUtils::createRecoloredIcon(
Icon::Edit, IconUtils::DefaultIconColor),
tr("Edit"));
editAction_->setToolTip(tr("Edit selected {{domain_entity.entity_singular_words}}"));
editAction_->setEnabled(false);
connect(editAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}MdiWindow::editSelected);
deleteAction_ = toolbar_->addAction(
IconUtils::createRecoloredIcon(
Icon::Delete, IconUtils::DefaultIconColor),
tr("Delete"));
deleteAction_->setToolTip(tr("Delete selected {{domain_entity.entity_singular_words}}"));
deleteAction_->setEnabled(false);
connect(deleteAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}MdiWindow::deleteSelected);
historyAction_ = toolbar_->addAction(
IconUtils::createRecoloredIcon(
Icon::History, IconUtils::DefaultIconColor),
tr("History"));
historyAction_->setToolTip(tr("View {{domain_entity.entity_singular_words}} history"));
historyAction_->setEnabled(false);
connect(historyAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}MdiWindow::viewHistorySelected);
{{/domain_entity.qt.has_readonly_paginated_list}}
{{! see * Extension points above }}
<<paste:18B9ED63-78C2-4259-93AC-CCDFBC88EFBD>>
{{#domain_entity.qt.has_csv_xml_io}}
toolbar_->addSeparator();
importXMLAction_ = toolbar_->addAction(
IconUtils::createRecoloredIcon(
Icon::ImportOre, IconUtils::DefaultIconColor),
tr("Import XML"));
importXMLAction_->setToolTip(tr("Import {{domain_entity.entity_plural_words}} from an ORE XML file"));
connect(importXMLAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}MdiWindow::importFromXML);
exportCSVAction_ = toolbar_->addAction(
IconUtils::createRecoloredIcon(
Icon::ExportCsv, IconUtils::DefaultIconColor),
tr("Export CSV"));
exportCSVAction_->setToolTip(tr("Export {{domain_entity.entity_plural_words}} to CSV"));
connect(exportCSVAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}MdiWindow::exportToCSV);
exportXMLAction_ = toolbar_->addAction(
IconUtils::createRecoloredIcon(
Icon::ExportOre, IconUtils::DefaultIconColor),
tr("Export XML"));
exportXMLAction_->setToolTip(tr("Export {{domain_entity.entity_plural_words}} to ORE XML"));
connect(exportXMLAction_, &QAction::triggered, this,
&{{domain_entity.entity_pascal}}MdiWindow::exportToXML);
{{/domain_entity.qt.has_csv_xml_io}}
{{#domain_entity.qt.has_related_entity_shortcuts}}
toolbar_->addSeparator();
{{#domain_entity.qt.related_entity_shortcuts}}
{
auto* action = toolbar_->addAction(
IconUtils::createRecoloredIcon(Icon::{{icon}}, IconUtils::DefaultIconColor),
tr("{{label}}"));
action->setToolTip(tr("{{tooltip}}"));
connect(action, &QAction::triggered, this, [this]() {
emit show{{signal}}Requested();
});
}
{{/domain_entity.qt.related_entity_shortcuts}}
{{/domain_entity.qt.has_related_entity_shortcuts}}
}
void {{domain_entity.entity_pascal}}MdiWindow::setupTable() {
model_ = new Client{{domain_entity.entity_pascal}}Model(clientManager_,
{{#domain_entity.qt.has_parent_scoped_list}}
{{domain_entity.qt.parent_key_param}}_,
{{/domain_entity.qt.has_parent_scoped_list}}
this);
{{#domain_entity.qt.needs_image_cache}}
model_->setImageCache(imageCache_);
{{/domain_entity.qt.needs_image_cache}}
proxyModel_ = new QSortFilterProxyModel(this);
proxyModel_->setSourceModel(model_);
proxyModel_->setSortCaseSensitivity(Qt::CaseInsensitive);
tableView_ = new QTableView(this);
tableView_->setModel(proxyModel_);
tableView_->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView_->setSelectionMode(QAbstractItemView::SingleSelection);
tableView_->setSortingEnabled(true);
tableView_->setAlternatingRowColors(true);
tableView_->verticalHeader()->setVisible(false);
{{#domain_entity.qt.has_pair_icon_column}}
tableView_->setIconSize(currency_pair_icon_size());
{{/domain_entity.qt.has_pair_icon_column}}
{{^domain_entity.qt.has_pair_icon_column}}
{{#domain_entity.qt.has_any_flag_icon}}
tableView_->setIconSize(single_flag_icon_size());
{{/domain_entity.qt.has_any_flag_icon}}
{{/domain_entity.qt.has_pair_icon_column}}
{{#domain_entity.qt.needs_item_delegate}}
using cs = column_style;
auto* delegate = new EntityItemDelegate({
{{#domain_entity.qt.columns}}
{{column_style}},
{{/domain_entity.qt.columns}}
}, tableView_);
{{#domain_entity.qt.columns}}
{{#is_badge}}
delegate->set_badge_color_resolver({{column_index}}, [cache = badgeCache_](const QString& value) -> badge_color_pair {
static const badge_color_pair hardcoded_fallback{color_constants::badge_fallback,
color_constants::badge_fallback_text,
true};
if (!cache) return hardcoded_fallback;
auto* def = cache->resolve("{{badge_key}}", value.toStdString());
if (!def) {
auto* reserved = cache->fallback();
if (!reserved) return hardcoded_fallback;
return {QColor(QString::fromStdString(reserved->background_colour)),
QColor(QString::fromStdString(reserved->text_colour)),
true};
}
return {QColor(QString::fromStdString(def->background_colour)),
QColor(QString::fromStdString(def->text_colour))};
});
{{/is_badge}}
{{#self_colour}}
delegate->set_badge_color_resolver({{column_index}}, [](const QString& value) -> badge_color_pair {
QColor bg(value);
if (!bg.isValid()) bg = color_constants::badge_fallback;
const QColor fg = bg.lightnessF() > 0.5 ? QColor(Qt::black) : QColor(Qt::white);
return {bg, fg};
});
{{/self_colour}}
{{/domain_entity.qt.columns}}
tableView_->setItemDelegate(delegate);
{{#domain_entity.qt.has_badge_columns}}
if (badgeCache_) {
if (badgeCache_->isLoaded())
tableView_->viewport()->update();
connect(badgeCache_, &BadgeCache::loaded, tableView_->viewport(), [this]() {
tableView_->viewport()->update();
});
}
{{/domain_entity.qt.has_badge_columns}}
{{/domain_entity.qt.needs_item_delegate}}
initializeTableSettings(tableView_, model_,
"{{domain_entity.qt.settings_group}}",
{
{{#domain_entity.qt.hidden_columns}}
Client{{domain_entity.entity_pascal}}Model::{{enum_name}}{{^is_last}},{{/is_last}}
{{/domain_entity.qt.hidden_columns}}
},
{900, 400}, {{domain_entity.qt.qt_settings_version}});
}
void {{domain_entity.entity_pascal}}MdiWindow::setupConnections() {
connect(model_, &Client{{domain_entity.entity_pascal}}Model::dataLoaded,
this, &{{domain_entity.entity_pascal}}MdiWindow::onDataLoaded);
connect(model_, &Client{{domain_entity.entity_pascal}}Model::loadError,
this, &{{domain_entity.entity_pascal}}MdiWindow::onLoadError);
connect(tableView_->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &{{domain_entity.entity_pascal}}MdiWindow::onSelectionChanged);
connect(tableView_, &QTableView::doubleClicked,
this, &{{domain_entity.entity_pascal}}MdiWindow::onDoubleClicked);
connect(paginationWidget_, &PaginationWidget::page_size_changed,
this, [this](std::uint32_t size) {
model_->set_page_size(size);
model_->refresh();
});
connect(paginationWidget_, &PaginationWidget::load_all_requested,
this, [this]() {
const auto total = model_->total_available_count();
if (total > 0 && total <= 1000) {
model_->set_page_size(total);
paginationWidget_->reset_page();
model_->refresh();
}
});
connect(paginationWidget_, &PaginationWidget::page_requested,
this, [this](std::uint32_t offset, std::uint32_t limit) {
model_->load_page(offset, limit);
});
connectModel(model_);
}
void {{domain_entity.entity_pascal}}MdiWindow::doReload() {
BOOST_LOG_SEV(lg(), debug) << "Reloading {{domain_entity.entity_plural_words}}";
clearStaleIndicator();
emit statusChanged(tr("Loading {{domain_entity.entity_plural_words}}..."));
model_->load_page(paginationWidget_->current_offset(), paginationWidget_->page_size());
}
void {{domain_entity.entity_pascal}}MdiWindow::onDataLoaded() {
const auto loaded = model_->rowCount();
const auto total = model_->total_available_count();
emit statusChanged(tr("Loaded %1 of %2 {{domain_entity.entity_plural_words}}").arg(loaded).arg(total));
paginationWidget_->update_state(loaded, total);
paginationWidget_->set_load_all_enabled(
loaded < static_cast<int>(total) && total > 0 && total <= 1000);
}
void {{domain_entity.entity_pascal}}MdiWindow::onLoadError(const QString& error_message,
const QString& details) {
BOOST_LOG_SEV(lg(), error) << "Load error: " << error_message.toStdString();
emit errorOccurred(error_message);
MessageBoxHelper::critical(this, tr("Load Error"), error_message, details);
}
void {{domain_entity.entity_pascal}}MdiWindow::onSelectionChanged() {
{{^domain_entity.qt.has_readonly_paginated_list}}
updateActionStates();
{{/domain_entity.qt.has_readonly_paginated_list}}
}
{{^domain_entity.qt.has_readonly_paginated_list}}
void {{domain_entity.entity_pascal}}MdiWindow::onDoubleClicked(const QModelIndex& index) {
if (!index.isValid())
return;
auto sourceIndex = proxyModel_->mapToSource(index);
if (auto* {{domain_entity.qt.item_var}} = model_->get{{domain_entity.entity_pascal_short}}(sourceIndex.row())) {
emit show{{domain_entity.entity_pascal_short}}Details(*{{domain_entity.qt.item_var}});
}
}
{{/domain_entity.qt.has_readonly_paginated_list}}
{{#domain_entity.qt.has_readonly_paginated_list}}
void {{domain_entity.entity_pascal}}MdiWindow::onDoubleClicked(const QModelIndex&) {
}
{{/domain_entity.qt.has_readonly_paginated_list}}
{{^domain_entity.qt.has_readonly_paginated_list}}
void {{domain_entity.entity_pascal}}MdiWindow::updateActionStates() {
const bool hasSelection = tableView_->selectionModel()->hasSelection();
editAction_->setEnabled(hasSelection);
deleteAction_->setEnabled(hasSelection);
historyAction_->setEnabled(hasSelection);
}
void {{domain_entity.entity_pascal}}MdiWindow::addNew() {
BOOST_LOG_SEV(lg(), debug) << "Add new {{domain_entity.entity_singular_words}} requested";
emit addNewRequested();
}
void {{domain_entity.entity_pascal}}MdiWindow::editSelected() {
const auto selected = tableView_->selectionModel()->selectedRows();
if (selected.isEmpty()) {
BOOST_LOG_SEV(lg(), warn) << "Edit requested but no row selected";
return;
}
auto sourceIndex = proxyModel_->mapToSource(selected.first());
if (auto* {{domain_entity.qt.item_var}} = model_->get{{domain_entity.entity_pascal_short}}(sourceIndex.row())) {
emit show{{domain_entity.entity_pascal_short}}Details(*{{domain_entity.qt.item_var}});
}
}
void {{domain_entity.entity_pascal}}MdiWindow::viewHistorySelected() {
const auto selected = tableView_->selectionModel()->selectedRows();
if (selected.isEmpty()) {
BOOST_LOG_SEV(lg(), warn) << "View history requested but no row selected";
return;
}
auto sourceIndex = proxyModel_->mapToSource(selected.first());
if (auto* {{domain_entity.qt.item_var}} = model_->get{{domain_entity.entity_pascal_short}}(sourceIndex.row())) {
BOOST_LOG_SEV(lg(), debug) << "Emitting show{{domain_entity.entity_pascal_short}}History for code: "
<< {{domain_entity.qt.item_var}}->{{domain_entity.qt.key_field}};
emit show{{domain_entity.entity_pascal_short}}History(*{{domain_entity.qt.item_var}});
}
}
{{/domain_entity.qt.has_readonly_paginated_list}}
{{^domain_entity.qt.has_readonly_paginated_list}}
void {{domain_entity.entity_pascal}}MdiWindow::deleteSelected() {
const auto selected = tableView_->selectionModel()->selectedRows();
if (selected.isEmpty()) {
BOOST_LOG_SEV(lg(), warn) << "Delete requested but no row selected";
return;
}
if (!clientManager_->isConnected()) {
MessageBoxHelper::warning(this, "Disconnected",
"Cannot delete {{domain_entity.entity_singular_words}} while disconnected.");
return;
}
{{#domain_entity.qt.has_uuid_primary_key}}
std::vector<std::string> ids;
std::vector<std::string> codes; // For display purposes
for (const auto& index : selected) {
auto sourceIndex = proxyModel_->mapToSource(index);
if (auto* {{domain_entity.qt.item_var}} = model_->get{{domain_entity.entity_pascal_short}}(sourceIndex.row())) {
ids.push_back(boost::uuids::to_string({{domain_entity.qt.item_var}}->id));
codes.push_back({{domain_entity.qt.key_to_string_prefix}}{{domain_entity.qt.item_var}}->{{domain_entity.qt.key_field}}{{domain_entity.qt.key_to_string_suffix}});
}
}
if (ids.empty()) {
BOOST_LOG_SEV(lg(), warn) << "No valid {{domain_entity.entity_plural_words}} to delete";
return;
}
BOOST_LOG_SEV(lg(), debug) << "Delete requested for " << ids.size()
<< " {{domain_entity.entity_plural_words}}";
QString confirmMessage;
if (ids.size() == 1) {
confirmMessage = QString("Are you sure you want to delete {{domain_entity.entity_singular_words}} '%1'?")
.arg(QString::fromStdString(codes.front()));
} else {
confirmMessage = QString("Are you sure you want to delete %1 {{domain_entity.entity_plural_words}}?")
.arg(ids.size());
}
{{/domain_entity.qt.has_uuid_primary_key}}
{{^domain_entity.qt.has_uuid_primary_key}}
std::vector<std::string> codes;
for (const auto& index : selected) {
auto sourceIndex = proxyModel_->mapToSource(index);
if (auto* {{domain_entity.qt.item_var}} = model_->get{{domain_entity.entity_pascal_short}}(sourceIndex.row())) {
codes.push_back({{domain_entity.qt.item_var}}->{{domain_entity.qt.key_field}});
}
}
if (codes.empty()) {
BOOST_LOG_SEV(lg(), warn) << "No valid {{domain_entity.entity_plural_words}} to delete";
return;
}
BOOST_LOG_SEV(lg(), debug) << "Delete requested for " << codes.size()
<< " {{domain_entity.entity_plural_words}}";
QString confirmMessage;
if (codes.size() == 1) {
confirmMessage = QString("Are you sure you want to delete {{domain_entity.entity_singular_words}} '%1'?")
.arg(QString::fromStdString(codes.front()));
} else {
confirmMessage = QString("Are you sure you want to delete %1 {{domain_entity.entity_plural_words}}?")
.arg(codes.size());
}
{{/domain_entity.qt.has_uuid_primary_key}}
auto reply = MessageBoxHelper::question(this, "Delete {{domain_entity.entity_title}}",
confirmMessage, QMessageBox::Yes | QMessageBox::No);
if (reply != QMessageBox::Yes) {
BOOST_LOG_SEV(lg(), debug) << "Delete cancelled by user";
return;
}
QPointer<{{domain_entity.entity_pascal}}MdiWindow> self = this;
{{#domain_entity.qt.has_uuid_primary_key}}
using DeleteResult = std::vector<std::tuple<std::string, std::string, bool, std::string>>;
auto task = [self, ids, codes]() -> DeleteResult {
DeleteResult results;
if (!self) return {};
BOOST_LOG_SEV(lg(), debug) << "Making delete request for "
<< ids.size() << " {{domain_entity.entity_plural_words}}";
{{#domain_entity.qt.delete_is_single}}
for (std::size_t i = 0; i < ids.size(); ++i) {
{{domain_entity.qt.delete_request_class}} request;
request.{{domain_entity.qt.delete_request_id_field}} = ids[i];
request.modified_by = self->username_.toStdString();
auto response_result = self->clientManager_->process_authenticated_request(
std::move(request));
if (!response_result) {
results.push_back({ids[i], codes[i], false, "Failed to communicate with server"});
} else {
results.push_back({ids[i], codes[i], response_result->success, response_result->message});
}
}
{{/domain_entity.qt.delete_is_single}}
{{^domain_entity.qt.delete_is_single}}
{{domain_entity.qt.delete_request_class}} request;
request.ids = ids;
auto response_result = self->clientManager_->process_authenticated_request(
std::move(request));
if (!response_result) {
BOOST_LOG_SEV(lg(), error) << "Failed to send batch delete request";
for (std::size_t i = 0; i < ids.size(); ++i) {
results.push_back({ids[i], codes[i], false, "Failed to communicate with server"});
}
return results;
}
for (std::size_t i = 0; i < ids.size(); ++i) {
results.push_back({ids[i], codes[i], response_result->success, response_result->message});
}
{{/domain_entity.qt.delete_is_single}}
return results;
};
auto* watcher = new QFutureWatcher<DeleteResult>(self);
connect(watcher, &QFutureWatcher<DeleteResult>::finished,
self, [self, watcher]() {
auto results = watcher->result();
watcher->deleteLater();
int success_count = 0;
int failure_count = 0;
QString first_error;
for (const auto& [id, code, success, message] : results) {
if (success) {
BOOST_LOG_SEV(lg(), debug) << "{{domain_entity.entity_title}} deleted: " << code;
success_count++;
emit self->{{domain_entity.qt.item_var}}Deleted(QString::fromStdString(code));
} else {
BOOST_LOG_SEV(lg(), error) << "{{domain_entity.entity_title}} deletion failed: "
<< code << " - " << message;
failure_count++;
if (first_error.isEmpty()) {
first_error = QString::fromStdString(message);
}
}
}
self->model_->load_page(
self->paginationWidget_->current_offset(), self->paginationWidget_->page_size());
if (failure_count == 0) {
QString msg = success_count == 1
? "Successfully deleted 1 {{domain_entity.entity_singular_words}}"
: QString("Successfully deleted %1 {{domain_entity.entity_plural_words}}").arg(success_count);
emit self->statusChanged(msg);
} else if (success_count == 0) {
QString msg = QString("Failed to delete %1 %2: %3")
.arg(failure_count)
.arg(failure_count == 1 ? "{{domain_entity.entity_singular_words}}" : "{{domain_entity.entity_plural_words}}")
.arg(first_error);
emit self->errorOccurred(msg);
MessageBoxHelper::critical(self, "Delete Failed", msg);
} else {
QString msg = QString("Deleted %1, failed to delete %2")
.arg(success_count)
.arg(failure_count);
emit self->statusChanged(msg);
MessageBoxHelper::warning(self, "Partial Success", msg);
}
});
{{/domain_entity.qt.has_uuid_primary_key}}
{{^domain_entity.qt.has_uuid_primary_key}}
using DeleteResult = std::vector<std::pair<std::string, std::pair<bool, std::string>>>;
auto task = [self, codes]() -> DeleteResult {
DeleteResult results;
if (!self) return {};
BOOST_LOG_SEV(lg(), debug) << "Making delete request for "
<< codes.size() << " {{domain_entity.entity_plural_words}}";
{{#domain_entity.qt.delete_request_id_field}}
{{#domain_entity.qt.delete_request_id_is_plural}}
{{domain_entity.qt.delete_request_class}} request;
request.{{domain_entity.qt.delete_request_id_field}} = codes;
auto response_result = self->clientManager_->process_authenticated_request(
std::move(request));
if (!response_result) {
BOOST_LOG_SEV(lg(), error) << "Failed to send batch delete request";
for (const auto& code : codes) {
results.push_back({code, {false, "Failed to communicate with server"}});
}
return results;
}
for (const auto& code : codes) {
results.push_back({code, {response_result->success, response_result->message}});
}
{{/domain_entity.qt.delete_request_id_is_plural}}
{{^domain_entity.qt.delete_request_id_is_plural}}
for (const auto& code : codes) {
{{domain_entity.qt.delete_request_class}} request;
request.{{domain_entity.qt.delete_request_id_field}} = code;
auto response_result = self->clientManager_->process_authenticated_request(
std::move(request));
if (!response_result) {
results.push_back({code, {false, "Failed to communicate with server"}});
} else {
results.push_back({code, {response_result->success, response_result->message}});
}
}
{{/domain_entity.qt.delete_request_id_is_plural}}
{{/domain_entity.qt.delete_request_id_field}}
{{^domain_entity.qt.delete_request_id_field}}
{{domain_entity.qt.delete_request_class}} request;
request.codes = codes;
auto response_result = self->clientManager_->process_authenticated_request(
std::move(request));
if (!response_result) {
BOOST_LOG_SEV(lg(), error) << "Failed to send batch delete request";
for (const auto& code : codes) {
results.push_back({code, {false, "Failed to communicate with server"}});
}
return results;
}
for (const auto& code : codes) {
results.push_back({code, {response_result->success, response_result->message}});
}
{{/domain_entity.qt.delete_request_id_field}}
return results;
};
auto* watcher = new QFutureWatcher<DeleteResult>(self);
connect(watcher, &QFutureWatcher<DeleteResult>::finished,
self, [self, watcher]() {
auto results = watcher->result();
watcher->deleteLater();
int success_count = 0;
int failure_count = 0;
QString first_error;
for (const auto& [code, result] : results) {
if (result.first) {
BOOST_LOG_SEV(lg(), debug) << "{{domain_entity.entity_title}} deleted: " << code;
success_count++;
emit self->{{domain_entity.qt.item_var}}Deleted(QString::fromStdString(code));
} else {
BOOST_LOG_SEV(lg(), error) << "{{domain_entity.entity_title}} deletion failed: "
<< code << " - " << result.second;
failure_count++;
if (first_error.isEmpty()) {
first_error = QString::fromStdString(result.second);
}
}
}
self->model_->load_page(
self->paginationWidget_->current_offset(), self->paginationWidget_->page_size());
if (failure_count == 0) {
QString msg = success_count == 1
? "Successfully deleted 1 {{domain_entity.entity_singular_words}}"
: QString("Successfully deleted %1 {{domain_entity.entity_plural_words}}").arg(success_count);
emit self->statusChanged(msg);
} else if (success_count == 0) {
QString msg = QString("Failed to delete %1 %2: %3")
.arg(failure_count)
.arg(failure_count == 1 ? "{{domain_entity.entity_singular_words}}" : "{{domain_entity.entity_plural_words}}")
.arg(first_error);
emit self->errorOccurred(msg);
MessageBoxHelper::critical(self, "Delete Failed", msg);
} else {
QString msg = QString("Deleted %1, failed to delete %2")
.arg(success_count)
.arg(failure_count);
emit self->statusChanged(msg);
MessageBoxHelper::warning(self, "Partial Success", msg);
}
});
{{/domain_entity.qt.has_uuid_primary_key}}
QFuture<DeleteResult> future = QtConcurrent::run(task);
watcher->setFuture(future);
}
{{/domain_entity.qt.has_readonly_paginated_list}}
{{#domain_entity.qt.has_csv_xml_io}}
void {{domain_entity.entity_pascal}}MdiWindow::exportToCSV() {
if (model_->rowCount() == 0) {
QMessageBox::information(this, "No Data", "There are no {{domain_entity.entity_plural_words}} to export.");
return;
}
auto {{domain_entity.qt.collection_name}} = model_->get{{domain_entity.entity_pascal_short_plural}}();
QString fileName = QFileDialog::getSaveFileName(
this, "Export to CSV", "{{domain_entity.qt.collection_name}}.csv", "CSV Files (*.csv);;All Files (*)");
if (fileName.isEmpty())
return;
try {
std::string csvData = {{domain_entity.qt.csv_export_class}}::{{domain_entity.qt.csv_export_method}}({{domain_entity.qt.collection_name}});
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
MessageBoxHelper::critical(
this, "File Error", QString("Could not open file for writing: %1").arg(fileName));
return;
}
file.write(csvData.c_str(), csvData.length());
file.close();
QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
emit statusChanged(QString("Successfully exported {{domain_entity.entity_plural_words}} to %1").arg(fileName));
} catch (const std::exception& e) {
MessageBoxHelper::critical(
this, "Export Error", QString("Error during CSV export: %1").arg(e.what()));
}
}
void {{domain_entity.entity_pascal}}MdiWindow::exportToXML() {
if (model_->rowCount() == 0) {
QMessageBox::information(this, "No Data", "There are no {{domain_entity.entity_plural_words}} to export.");
return;
}
auto {{domain_entity.qt.collection_name}} = model_->get{{domain_entity.entity_pascal_short_plural}}();
QString fileName = QFileDialog::getSaveFileName(
this, "Export to ORE XML", "{{domain_entity.qt.collection_name}}.xml", "XML Files (*.xml);;All Files (*)");
if (fileName.isEmpty())
return;
try {
std::string xmlData = {{domain_entity.qt.xml_export_class}}::{{domain_entity.qt.xml_export_method}}({{domain_entity.qt.collection_name}});
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
MessageBoxHelper::critical(
this, "File Error", QString("Could not open file for writing: %1").arg(fileName));
return;
}
file.write(xmlData.c_str(), xmlData.length());
file.close();
QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
emit statusChanged(QString("Successfully exported {{domain_entity.entity_plural_words}} to %1").arg(fileName));
} catch (const std::exception& e) {
MessageBoxHelper::critical(
this, "Export Error", QString("Error during XML export: %1").arg(e.what()));
}
}
void {{domain_entity.entity_pascal}}MdiWindow::importFromXML() {
if (!clientManager_->isLoggedIn()) {
MessageBoxHelper::warning(
this, "Not Logged In", "Cannot import {{domain_entity.entity_plural_words}} while not logged in.");
return;
}
QString fileName = QFileDialog::getOpenFileName(
this, "Select ORE XML File to Import", QString(), "ORE XML Files (*.xml);;All Files (*)");
if (fileName.isEmpty())
return;
emit statusChanged("Parsing XML file...");
try {
std::filesystem::path path(fileName.toStdString());
auto {{domain_entity.qt.collection_name}} = {{domain_entity.qt.xml_import_class}}::{{domain_entity.qt.xml_import_method}}(path);
if ({{domain_entity.qt.collection_name}}.empty()) {
MessageBoxHelper::information(this,
"No {{domain_entity.entity_title}} Found",
"The selected XML file does not contain any {{domain_entity.entity_plural_words}}.");
emit statusChanged("Import cancelled - no {{domain_entity.entity_plural_words}} found");
return;
}
emit statusChanged(
QString("Found %1 {{domain_entity.entity_plural_words}} - opening import dialog...").arg({{domain_entity.qt.collection_name}}.size()));
std::vector<ImportEntityRow> rows;
rows.reserve({{domain_entity.qt.collection_name}}.size());
for (const auto& {{domain_entity.qt.item_var}} : {{domain_entity.qt.collection_name}}) {
const auto validation_error = {{domain_entity.qt.xml_import_class}}::{{domain_entity.qt.xml_validate_method}}({{domain_entity.qt.item_var}});
rows.push_back(
{.display_values = {
{{#domain_entity.qt.import_preview_columns}}
{{#is_string}}
QString::fromStdString({{domain_entity.qt.item_var}}.{{field}}),
{{/is_string}}
{{#is_int}}
QString::number({{domain_entity.qt.item_var}}.{{field}}),
{{/is_int}}
{{#is_bool}}
{{domain_entity.qt.item_var}}.{{field}} ? "true" : "false",
{{/is_bool}}
{{#is_double}}
QString::number({{domain_entity.qt.item_var}}.{{field}}),
{{/is_double}}
{{#is_uuid}}
QString::fromStdString(boost::uuids::to_string({{domain_entity.qt.item_var}}.{{field}})),
{{/is_uuid}}
{{/domain_entity.qt.import_preview_columns}}
},
.is_valid = validation_error.empty(),
.invalid_reason = QString::fromStdString(validation_error)});
}
auto client_manager = clientManager_;
auto username = username_;
auto {{domain_entity.qt.collection_name}}_by_row = {{domain_entity.qt.collection_name}};
auto label_of = [{{domain_entity.qt.collection_name}}_by_row](std::size_t index) {
return QString::fromStdString({{domain_entity.qt.collection_name}}_by_row[index].{{domain_entity.qt.key_field}});
};
auto import_one = [client_manager, username, {{domain_entity.qt.collection_name}}_by_row](std::size_t index) {
auto {{domain_entity.qt.item_var}}_to_import = {{domain_entity.qt.collection_name}}_by_row[index];
{{domain_entity.qt.item_var}}_to_import.modified_by = username.toStdString();
try {
auto request = {{domain_entity.qt.save_request_class}}::from({{domain_entity.qt.item_var}}_to_import);
auto response_result = client_manager->process_authenticated_request(std::move(request));
return response_result.has_value() && response_result->success;
} catch (const std::exception&) {
return false;
}
};
auto* dialog = new ImportEntityDialog(
"{{domain_entity.entity_plural_words}}",
fileName,
{
{{#domain_entity.qt.import_preview_columns}}
"{{header}}",
{{/domain_entity.qt.import_preview_columns}}
},
std::move(rows),
label_of,
import_one,
this);
connect(dialog, &ImportEntityDialog::importCompleted, this,
[this](int success_count, int total_count) {
if (success_count > 0) {
paginationWidget_->reset_page();
model_->load_page(0, paginationWidget_->page_size());
QString message = QString("Successfully imported %1 of %2 {{domain_entity.entity_plural_words}}")
.arg(success_count).arg(total_count);
emit statusChanged(message);
MessageBoxHelper::information(this, "Import Complete", message);
} else {
emit statusChanged("Import failed - no {{domain_entity.entity_plural_words}} imported");
MessageBoxHelper::warning(
this, "Import Failed", "Failed to import {{domain_entity.entity_plural_words}}. Check the log for details.");
}
});
connect(dialog, &ImportEntityDialog::importCancelled, this, [this]() {
emit statusChanged("Import cancelled");
});
if (dialog->exec() != QDialog::Accepted) {
emit statusChanged("Import cancelled");
}
dialog->deleteLater();
} catch (const std::exception& e) {
MessageBoxHelper::critical(
this, "Import Error", QString("Failed to import XML file:\n%1").arg(e.what()));
emit statusChanged("Import failed");
}
}
{{/domain_entity.qt.has_csv_xml_io}}
}
See also
- Parent facet: ores.cpp.qt
- Template variable reference