From d2643d9a91c4f45c276db3831cc64ba6ba122563 Mon Sep 17 00:00:00 2001 From: suyu <1643689728@qq.com> Date: Mon, 17 Aug 2026 14:01:30 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=8B=86=E5=88=86=E4=B8=BB?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=E7=95=8C=E9=9D=A2=E6=8E=A7=E5=88=B6=E8=81=8C?= =?UTF-8?q?=E8=B4=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/integrated_platform.pro | 6 + app/src/ui/main_window.cpp | 1193 ++----------------- app/src/ui/main_window.h | 28 +- app/src/ui/project_workspace_controller.cpp | 511 ++++++++ app/src/ui/project_workspace_controller.h | 104 ++ app/src/ui/property_panel_controller.cpp | 611 ++++++++++ app/src/ui/property_panel_controller.h | 77 ++ app/src/ui/runtime_panel_controller.cpp | 255 ++++ app/src/ui/runtime_panel_controller.h | 88 ++ app/tests/main_window_tests.pro | 6 + 10 files changed, 1751 insertions(+), 1128 deletions(-) create mode 100644 app/src/ui/project_workspace_controller.cpp create mode 100644 app/src/ui/project_workspace_controller.h create mode 100644 app/src/ui/property_panel_controller.cpp create mode 100644 app/src/ui/property_panel_controller.h create mode 100644 app/src/ui/runtime_panel_controller.cpp create mode 100644 app/src/ui/runtime_panel_controller.h diff --git a/app/integrated_platform.pro b/app/integrated_platform.pro index cb78c49..819eec2 100644 --- a/app/integrated_platform.pro +++ b/app/integrated_platform.pro @@ -23,6 +23,9 @@ SOURCES += \ src/ui/plc_connection_dialog.cpp \ src/ui/free_monitor_widget.cpp \ src/ui/runtime_monitor_widget.cpp \ + src/ui/project_workspace_controller.cpp \ + src/ui/property_panel_controller.cpp \ + src/ui/runtime_panel_controller.cpp \ src/domain/register_address.cpp \ src/domain/register_repository.cpp \ src/domain/active_register_repository.cpp \ @@ -59,6 +62,9 @@ HEADERS += \ src/ui/plc_connection_dialog.h \ src/ui/free_monitor_widget.h \ src/ui/runtime_monitor_widget.h \ + src/ui/project_workspace_controller.h \ + src/ui/property_panel_controller.h \ + src/ui/runtime_panel_controller.h \ src/domain/register_address.h \ src/domain/register_repository.h \ src/domain/active_register_repository.h \ diff --git a/app/src/ui/main_window.cpp b/app/src/ui/main_window.cpp index 0e367ba..36c1744 100644 --- a/app/src/ui/main_window.cpp +++ b/app/src/ui/main_window.cpp @@ -7,7 +7,9 @@ #include "logic_instruction_dialog.h" #include "plc_connection_dialog.h" #include "runtime_monitor_widget.h" -#include "free_monitor_widget.h" +#include "project_workspace_controller.h" +#include "property_panel_controller.h" +#include "runtime_panel_controller.h" #include "services/hmi_editor_service.h" #include "services/hmi_navigation_service.h" #include "services/hmi_runtime_service.h" @@ -22,39 +24,18 @@ #include #include -#include #include #include #include -#include #include #include -#include -#include -#include #include #include -#include #include #include -#include -#include - -#include -#include namespace { -enum class ProjectTreeItemKind -{ - Root = 0, - HmiPage = 1, - ControlLogic = 2 -}; - -constexpr int kProjectTreeKindRole = Qt::UserRole; -constexpr int kProjectTreeIdRole = Qt::UserRole + 1; - QString modeText(ApplicationMode mode) { switch (mode) @@ -154,45 +135,6 @@ QString plcStatusText(const RuntimeModeService &service) } } -QString controlTypeText(HmiControlType type) -{ - switch (type) - { - case HmiControlType::Button: - { - return MainWindow::tr("按钮"); - } - case HmiControlType::Indicator: - { - return MainWindow::tr("指示灯"); - } - case HmiControlType::NumericDisplay: - { - return MainWindow::tr("数值显示"); - } - case HmiControlType::NumericInput: - { - return MainWindow::tr("数值输入"); - } - case HmiControlType::Label: - { - return MainWindow::tr("文本"); - } - case HmiControlType::PageJump: - { - return MainWindow::tr("页面跳转"); - } - case HmiControlType::AlarmList: - { - return MainWindow::tr("报警列表"); - } - default: - { - return MainWindow::tr("未知控件"); - } - } -} - } // namespace MainWindow::MainWindow( @@ -256,6 +198,25 @@ void MainWindow::initializeUi() { ui_->setupUi(this); configureAppearance(); + property_panel_controller_ = std::make_unique( + *this, + *ui_, + project_service_, + hmi_editor_service_, + logic_editor_service_, + [this] { return current_hmi_page_id_; }, + [this] { return current_logic_id_; }, + selected_control_id_, + selected_logic_node_id_, + [this] { refreshProjectUi(); }, + [this](const QString &action, const QString &message, bool succeeded) + { + showProjectResult(action, message, succeeded); + }, + [this](const QString &message, int timeout_ms) + { + statusBar()->showMessage(message, timeout_ms); + }); configureActions(); configurePropertyEditor(); configurePlcConnection(); @@ -695,55 +656,6 @@ void MainWindow::configureHmiEditor() ui_->hmiCanvasPlaceholder); hmi_editor_widget_->setMinimumHeight(320); layout->addWidget(hmi_editor_widget_); - connect(hmi_editor_widget_, &HmiEditorWidget::controlSelected, - this, - [this](const QString &id) { showControlProperties(toUtf8(id)); }); - connect(hmi_editor_widget_, &HmiEditorWidget::controlChanged, - this, - [this](const QString &id) - { - showControlProperties(toUtf8(id)); - refreshProjectUi(); - }); - connect(hmi_editor_widget_, &HmiEditorWidget::editorError, - this, - [this](const QString &message) { statusBar()->showMessage(message, 5000); }); - // 运行态定时投影仓库最新值,编辑态不触发寄存器读取 - runtime_refresh_timer_ = new QTimer(this); - runtime_refresh_timer_->setInterval(150); - connect(runtime_refresh_timer_, &QTimer::timeout, - this, - [this] - { - // 离线 / 联机运行模式 - if (runtime_mode_service_.mode() != ApplicationMode::Editing) - { - alarm_service_.refresh(); - hmi_editor_widget_->refreshRuntimeValues(); - runtime_monitor_widget_->refreshValues( - runtime_mode_service_.mode(), - runtime_mode_service_.plcConnectionState()); - updateSimulationUi(false); - } - }); - runtime_refresh_timer_->start(); - connect(&runtime_mode_service_.offlineSimulationService(), - &OfflineSimulationService::stateChanged, - this, - [this] { updateSimulationUi(true); }, - Qt::QueuedConnection); - connect(&runtime_mode_service_.offlineSimulationService(), - &OfflineSimulationService::scanCompleted, - this, - [this] - { - logic_editor_widget_->setRuntimeTrace( - runtime_mode_service_.offlineSimulationService() - .traceSnapshot().forLogic(current_logic_id_)); - runtime_monitor_widget_->setLogicTrace( - runtime_mode_service_.offlineSimulationService().traceSnapshot()); - }, - Qt::QueuedConnection); } void MainWindow::configureLogicEditor() @@ -755,147 +667,82 @@ void MainWindow::configureLogicEditor() logic_editor_widget_->setObjectName(QStringLiteral("logicEditorWidget")); logic_editor_widget_->setMinimumHeight(320); layout->addWidget(logic_editor_widget_); - connect(logic_editor_widget_, &LogicEditorWidget::nodeSelected, - this, - [this](const QString &id) - { - showLogicNodeProperties(toUtf8(id)); - }); - connect(logic_editor_widget_, &LogicEditorWidget::graphChanged, - this, &MainWindow::refreshProjectUi); - connect(logic_editor_widget_, &LogicEditorWidget::editorError, - this, - [this](const QString &message) - { - statusBar()->showMessage(message, 5000); - }); + property_panel_controller_->bindEditorWidgets( + *hmi_editor_widget_, *logic_editor_widget_); } void MainWindow::configureRuntimeMonitor() { - runtime_monitor_widget_ = new RuntimeMonitorWidget( + runtime_panel_controller_ = std::make_unique( + *this, + *ui_, + runtime_mode_service_, + project_service_, hmi_editor_service_, hmi_runtime_service_, logic_editor_service_, - project_service_, *hmi_navigation_service_, alarm_service_, register_monitor_service_, - ui_->runtimeMonitorTab); - runtime_monitor_widget_->setObjectName(QStringLiteral("runtimeMonitorWidget")); - ui_->runtimeMonitorLayout->addWidget(runtime_monitor_widget_); - connect(runtime_monitor_widget_->freeMonitorWidget(), - &FreeMonitorWidget::monitorAddressesChanged, - this, - [this] - { - runtime_mode_service_.setMonitorAddresses( - register_monitor_service_.addresses()); - runtime_monitor_widget_->refreshValues( - runtime_mode_service_.mode(), - runtime_mode_service_.plcConnectionState()); - }); - connect(runtime_monitor_widget_->freeMonitorWidget(), - &FreeMonitorWidget::operationMessage, - this, - [this](const QString &message) - { - statusBar()->showMessage(message, 4000); - }); - connect(runtime_monitor_widget_, &RuntimeMonitorWidget::navigationFailed, - this, - [this](const QString &message) - { - statusBar()->showMessage(message, 5000); - ui_->outputList->addItem(tr("页面导航失败:%1").arg(message)); - }); - ui_->editorTabWidget->setTabVisible(2, false); + *hmi_editor_widget_, + *logic_editor_widget_, + *executor_status_label_, + [this] { return current_logic_id_; }, + [this](const std::string &logic_id) { current_logic_id_ = logic_id; }, + [this](const std::string &node_id) + { + showLogicNodeProperties(node_id); + }, + [this](const QString &message, int timeout_ms) + { + statusBar()->showMessage(message, timeout_ms); + }, + [this](const QString &message) + { + ui_->outputList->addItem(message); + ui_->outputList->scrollToBottom(); + }); + runtime_panel_controller_->configure(); + runtime_monitor_widget_ = runtime_panel_controller_->runtimeMonitorWidget(); } void MainWindow::configureProjectTree() { - ui_->projectTree->setContextMenuPolicy(Qt::ActionsContextMenu); - - add_page_action_ = new QAction(tr("新建页面"), this); - add_page_action_->setObjectName(QStringLiteral("addPageAction")); - add_logic_action_ = new QAction(tr("新建控制逻辑"), this); - add_logic_action_->setObjectName(QStringLiteral("addLogicAction")); - rename_project_item_action_ = new QAction(tr("重命名"), this); - rename_project_item_action_->setObjectName( - QStringLiteral("renameProjectItemAction")); - delete_project_item_action_ = new QAction(tr("删除"), this); - delete_project_item_action_->setObjectName( - QStringLiteral("deleteProjectItemAction")); - move_project_item_up_action_ = new QAction(tr("上移"), this); - move_project_item_up_action_->setObjectName( - QStringLiteral("moveProjectItemUpAction")); - move_project_item_down_action_ = new QAction(tr("下移"), this); - move_project_item_down_action_->setObjectName( - QStringLiteral("moveProjectItemDownAction")); - set_initial_page_action_ = new QAction(tr("设为初始页面"), this); - set_initial_page_action_->setObjectName( - QStringLiteral("setInitialPageAction")); - toggle_logic_enabled_action_ = new QAction(tr("禁用控制逻辑"), this); - toggle_logic_enabled_action_->setObjectName( - QStringLiteral("toggleLogicEnabledAction")); - - ui_->projectTree->addActions({ - add_page_action_, - add_logic_action_, - rename_project_item_action_, - delete_project_item_action_, - move_project_item_up_action_, - move_project_item_down_action_, - set_initial_page_action_, - toggle_logic_enabled_action_}); - - connect(add_page_action_, &QAction::triggered, this, &MainWindow::addPage); - connect(add_logic_action_, &QAction::triggered, this, &MainWindow::addLogic); - connect(rename_project_item_action_, &QAction::triggered, - this, &MainWindow::renameProjectItem); - connect(delete_project_item_action_, &QAction::triggered, - this, &MainWindow::deleteProjectItem); - connect(move_project_item_up_action_, &QAction::triggered, - this, [this] { moveProjectItem(-1); }); - connect(move_project_item_down_action_, &QAction::triggered, - this, [this] { moveProjectItem(1); }); - connect(set_initial_page_action_, &QAction::triggered, - this, &MainWindow::setSelectedPageAsInitial); - connect(toggle_logic_enabled_action_, &QAction::triggered, - this, &MainWindow::toggleSelectedLogicEnabled); - connect(ui_->projectTree, &QTreeWidget::currentItemChanged, - this, - [this](QTreeWidgetItem *, QTreeWidgetItem *) - { - handleProjectTreeSelection(); - }); - updateProjectTreeActions(); + project_workspace_controller_ = std::make_unique( + *this, + *ui_, + project_service_, + hmi_editor_service_, + logic_editor_service_, + runtime_mode_service_, + *hmi_navigation_service_, + *hmi_editor_widget_, + *logic_editor_widget_, + *runtime_monitor_widget_, + current_hmi_page_id_, + current_logic_id_, + [this](const std::string &control_id) + { + showControlProperties(control_id); + }, + [this](const std::string &node_id) + { + showLogicNodeProperties(node_id); + }, + [this](const QString &action, const QString &message, bool succeeded) + { + showProjectResult(action, message, succeeded); + }, + [this](const QString &message, int timeout_ms) + { + statusBar()->showMessage(message, timeout_ms); + }); + project_workspace_controller_->configure(); } void MainWindow::configurePropertyEditor() { - ui_->bindingAreaComboBox->setItemData(0, -1); - ui_->bindingAreaComboBox->setItemData(1, 0); - ui_->bindingAreaComboBox->setItemData(2, 1); - ui_->buttonOperationComboBox->setItemData( - 0, static_cast(HmiButtonOperation::SetOn)); - ui_->buttonOperationComboBox->setItemData( - 1, static_cast(HmiButtonOperation::SetOff)); - ui_->buttonOperationComboBox->setItemData( - 2, static_cast(HmiButtonOperation::Toggle)); - ui_->buttonOperationComboBox->setItemData( - 3, static_cast(HmiButtonOperation::MomentaryOn)); - for (int index = 0; index < ui_->logicComparisonComboBox->count(); ++index) - { - ui_->logicComparisonComboBox->setItemData(index, index); - } - connect(ui_->applyPropertiesButton, &QPushButton::clicked, - this, &MainWindow::applySelectedControlProperties); - connect(ui_->applyLogicPropertiesButton, &QPushButton::clicked, - this, &MainWindow::applySelectedLogicNodeProperties); - ui_->targetPageLabel->setVisible(false); - ui_->targetPageComboBox->setVisible(false); - showControlProperties({}); + property_panel_controller_->configure(); } void MainWindow::configurePlcConnection() @@ -933,376 +780,12 @@ void MainWindow::configureRegisterComments() void MainWindow::refreshProjectUi() { - // 仅在当前对象消失时回退,普通刷新必须保留用户的会话选择 - if (hmi_editor_service_.findPage(current_hmi_page_id_) == nullptr) - { - const std::string &initial_id = project_service_.project().initialHmiPageId; - current_hmi_page_id_ = hmi_editor_service_.findPage(initial_id) != nullptr - ? initial_id : hmi_editor_service_.firstPageId(); - selected_control_id_.clear(); - } - if (logic_editor_service_.findLogic(current_logic_id_) == nullptr) - { - current_logic_id_ = logic_editor_service_.firstLogicId(); - selected_logic_node_id_.clear(); - } - - if (hmi_editor_widget_ != nullptr) - { - hmi_editor_widget_->setPageId(current_hmi_page_id_); - } - const HmiPage *page = hmi_editor_service_.findPage(current_hmi_page_id_); - if (logic_editor_widget_ != nullptr) - { - logic_editor_widget_->setLogicId(current_logic_id_); - } - if (runtime_monitor_widget_ != nullptr - && runtime_mode_service_.mode() != ApplicationMode::Editing) - { - runtime_monitor_widget_->setProjectObjects( - hmi_navigation_service_->currentPageId(), current_logic_id_); - } - runtime_mode_service_.refreshPlcPollAddresses(); - - const QSignalBlocker blocker(ui_->projectTree); - ui_->projectTree->clear(); - auto *hmi_root = new QTreeWidgetItem(ui_->projectTree, {tr("HMI 页面")}); - hmi_root->setData(0, kProjectTreeKindRole, - static_cast(ProjectTreeItemKind::Root)); - QTreeWidgetItem *selected_tree_item = nullptr; - for (const HmiPage &candidate : project_service_.project().hmiPages) - { - const QString initial_suffix = candidate.id - == project_service_.project().initialHmiPageId - ? tr("(初始)") : QString{}; - auto *item = new QTreeWidgetItem( - hmi_root, {fromUtf8(candidate.name) + initial_suffix}); - item->setData(0, kProjectTreeKindRole, - static_cast(ProjectTreeItemKind::HmiPage)); - item->setData(0, kProjectTreeIdRole, fromUtf8(candidate.id)); - if (candidate.id == current_hmi_page_id_ - && ui_->editorTabWidget->currentIndex() == 0) - { - selected_tree_item = item; - } - } - if (page != nullptr) - { - ui_->hmiPageTitleLabel->setText(fromUtf8(page->name)); - ui_->hmiPageSizeLabel->setText( - tr("%1 x %2").arg(page->width).arg(page->height)); - } - else - { - ui_->hmiPageTitleLabel->setText(tr("尚未创建 HMI 页面")); - ui_->hmiPageSizeLabel->clear(); - } - auto *logic_root = new QTreeWidgetItem(ui_->projectTree, {tr("控制逻辑")}); - logic_root->setData(0, kProjectTreeKindRole, - static_cast(ProjectTreeItemKind::Root)); - for (const ControlLogic &logic : project_service_.project().controlLogics) - { - const QString suffix = logic.enabled ? QString{} : tr("(已禁用)"); - auto *item = new QTreeWidgetItem( - logic_root, {fromUtf8(logic.name) + suffix}); - item->setData(0, kProjectTreeKindRole, - static_cast(ProjectTreeItemKind::ControlLogic)); - item->setData(0, kProjectTreeIdRole, fromUtf8(logic.id)); - if (logic.id == current_logic_id_ - && ui_->editorTabWidget->currentIndex() == 1) - { - selected_tree_item = item; - } - } - ui_->projectTree->expandAll(); - if (selected_tree_item != nullptr) - { - ui_->projectTree->setCurrentItem(selected_tree_item); - } - updateProjectTreeActions(); -} - -void MainWindow::handleProjectTreeSelection() -{ - QTreeWidgetItem *item = ui_->projectTree->currentItem(); - if (item == nullptr) - { - updateProjectTreeActions(); - return; - } - const auto kind = static_cast( - item->data(0, kProjectTreeKindRole).toInt()); - const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); - if (kind == ProjectTreeItemKind::HmiPage - && hmi_editor_service_.findPage(id) != nullptr) - { - current_hmi_page_id_ = id; - selected_control_id_.clear(); - hmi_editor_widget_->setPageId(id); - hmi_editor_widget_->reloadPage(); - ui_->editorTabWidget->setCurrentWidget(ui_->hmiEditorTab); - showControlProperties({}); - const HmiPage *page = hmi_editor_service_.findPage(id); - ui_->hmiPageTitleLabel->setText(fromUtf8(page->name)); - ui_->hmiPageSizeLabel->setText( - tr("%1 x %2").arg(page->width).arg(page->height)); - } - else if (kind == ProjectTreeItemKind::ControlLogic - && logic_editor_service_.findLogic(id) != nullptr) - { - current_logic_id_ = id; - selected_logic_node_id_.clear(); - logic_editor_widget_->setLogicId(id); - logic_editor_widget_->reloadLogic(); - ui_->editorTabWidget->setCurrentWidget(ui_->logicEditorTab); - showLogicNodeProperties({}); - } - updateProjectTreeActions(); + project_workspace_controller_->refresh(); } void MainWindow::updateProjectTreeActions() { - if (add_page_action_ == nullptr) - { - return; - } - QTreeWidgetItem *item = ui_->projectTree->currentItem(); - const auto kind = item == nullptr ? ProjectTreeItemKind::Root - : static_cast( - item->data(0, kProjectTreeKindRole).toInt()); - const bool page_selected = kind == ProjectTreeItemKind::HmiPage; - const bool logic_selected = kind == ProjectTreeItemKind::ControlLogic; - const bool editable = runtime_mode_service_.policy().allowsProjectEditing; - add_page_action_->setEnabled(editable); - add_logic_action_->setEnabled(editable); - rename_project_item_action_->setEnabled( - editable && (page_selected || logic_selected)); - delete_project_item_action_->setEnabled( - editable && (page_selected || logic_selected)); - move_project_item_up_action_->setEnabled( - editable && (page_selected || logic_selected)); - move_project_item_down_action_->setEnabled( - editable && (page_selected || logic_selected)); - set_initial_page_action_->setEnabled(editable && page_selected); - toggle_logic_enabled_action_->setEnabled(editable && logic_selected); - if (logic_selected) - { - const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); - const ControlLogic *logic = logic_editor_service_.findLogic(id); - toggle_logic_enabled_action_->setText( - logic != nullptr && logic->enabled - ? tr("禁用控制逻辑") : tr("启用控制逻辑")); - } -} - -void MainWindow::addPage() -{ - const QString name = QInputDialog::getText( - this, tr("新建页面"), tr("页面名称")); - if (name.isEmpty()) - { - return; - } - const HmiEditorResult result = hmi_editor_service_.addPage(toUtf8(name)); - if (!result.succeeded) - { - showProjectResult(tr("新建页面"), fromUtf8(result.message), false); - return; - } - current_hmi_page_id_ = result.id; - selected_control_id_.clear(); - ui_->editorTabWidget->setCurrentWidget(ui_->hmiEditorTab); - refreshProjectUi(); - hmi_editor_widget_->reloadPage(); -} - -void MainWindow::addLogic() -{ - const QString name = QInputDialog::getText( - this, tr("新建控制逻辑"), tr("逻辑名称")); - if (name.isEmpty()) - { - return; - } - const LogicEditorResult result = logic_editor_service_.addLogic(toUtf8(name)); - if (!result.succeeded) - { - showProjectResult(tr("新建控制逻辑"), fromUtf8(result.message), false); - return; - } - current_logic_id_ = result.id; - selected_logic_node_id_.clear(); - ui_->editorTabWidget->setCurrentWidget(ui_->logicEditorTab); - refreshProjectUi(); - logic_editor_widget_->reloadLogic(); -} - -void MainWindow::renameProjectItem() -{ - QTreeWidgetItem *item = ui_->projectTree->currentItem(); - if (item == nullptr) - { - return; - } - const auto kind = static_cast( - item->data(0, kProjectTreeKindRole).toInt()); - const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); - QString current_name; - if (kind == ProjectTreeItemKind::HmiPage) - { - const HmiPage *page = hmi_editor_service_.findPage(id); - current_name = page == nullptr ? QString{} : fromUtf8(page->name); - } - else if (kind == ProjectTreeItemKind::ControlLogic) - { - const ControlLogic *logic = logic_editor_service_.findLogic(id); - current_name = logic == nullptr ? QString{} : fromUtf8(logic->name); - } - else - { - return; - } - bool accepted = false; - const QString name = QInputDialog::getText( - this, tr("重命名"), tr("新名称"), QLineEdit::Normal, - current_name, &accepted); - if (!accepted) - { - return; - } - if (kind == ProjectTreeItemKind::HmiPage) - { - const HmiEditorResult result = hmi_editor_service_.renamePage(id, toUtf8(name)); - if (!result.succeeded) - { - showProjectResult(tr("重命名页面"), fromUtf8(result.message), false); - return; - } - } - else - { - const LogicEditorResult result = logic_editor_service_.renameLogic(id, toUtf8(name)); - if (!result.succeeded) - { - showProjectResult(tr("重命名控制逻辑"), fromUtf8(result.message), false); - return; - } - } - refreshProjectUi(); -} - -void MainWindow::deleteProjectItem() -{ - QTreeWidgetItem *item = ui_->projectTree->currentItem(); - if (item == nullptr) - { - return; - } - const auto kind = static_cast( - item->data(0, kProjectTreeKindRole).toInt()); - const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); - if (kind == ProjectTreeItemKind::HmiPage) - { - const HmiEditorResult result = hmi_editor_service_.removePage(id); - if (!result.succeeded) - { - showProjectResult(tr("删除页面"), fromUtf8(result.message), false); - return; - } - } - else if (kind == ProjectTreeItemKind::ControlLogic) - { - const LogicEditorResult result = logic_editor_service_.removeLogic(id); - if (!result.succeeded) - { - showProjectResult(tr("删除控制逻辑"), fromUtf8(result.message), false); - return; - } - } - else - { - return; - } - refreshProjectUi(); - showControlProperties({}); - showLogicNodeProperties({}); -} - -void MainWindow::moveProjectItem(int offset) -{ - QTreeWidgetItem *item = ui_->projectTree->currentItem(); - if (item == nullptr) - { - return; - } - const auto kind = static_cast( - item->data(0, kProjectTreeKindRole).toInt()); - const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); - if (kind == ProjectTreeItemKind::HmiPage) - { - const HmiEditorResult result = hmi_editor_service_.movePage(id, offset); - if (!result.succeeded) - { - statusBar()->showMessage(fromUtf8(result.message), 4000); - return; - } - } - else if (kind == ProjectTreeItemKind::ControlLogic) - { - const LogicEditorResult result = logic_editor_service_.moveLogic(id, offset); - if (!result.succeeded) - { - statusBar()->showMessage(fromUtf8(result.message), 4000); - return; - } - } - refreshProjectUi(); -} - -void MainWindow::setSelectedPageAsInitial() -{ - QTreeWidgetItem *item = ui_->projectTree->currentItem(); - if (item == nullptr - || static_cast( - item->data(0, kProjectTreeKindRole).toInt()) - != ProjectTreeItemKind::HmiPage) - { - return; - } - const HmiEditorResult result = hmi_editor_service_.setInitialPage( - toUtf8(item->data(0, kProjectTreeIdRole).toString())); - if (!result.succeeded) - { - showProjectResult(tr("设置初始页面"), fromUtf8(result.message), false); - return; - } - refreshProjectUi(); -} - -void MainWindow::toggleSelectedLogicEnabled() -{ - QTreeWidgetItem *item = ui_->projectTree->currentItem(); - if (item == nullptr - || static_cast( - item->data(0, kProjectTreeKindRole).toInt()) - != ProjectTreeItemKind::ControlLogic) - { - return; - } - const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); - const ControlLogic *logic = logic_editor_service_.findLogic(id); - if (logic == nullptr) - { - return; - } - const LogicEditorResult result = logic_editor_service_.setLogicEnabled( - id, !logic->enabled); - if (!result.succeeded) - { - showProjectResult(tr("启停控制逻辑"), fromUtf8(result.message), false); - return; - } - refreshProjectUi(); + project_workspace_controller_->updateActions(); } void MainWindow::connectPlc() @@ -1362,367 +845,22 @@ void MainWindow::schedulePlcStatusUpdate() // 根据控件ID加载控件属性到右侧属性面板 void MainWindow::showControlProperties(const std::string &control_id) { - ui_->propertyStack->setCurrentWidget(ui_->hmiPropertiesPage); - // 更新窗口全局状态:记录当前选中控件ID - selected_control_id_ = control_id; - - // 在第一页画面中,根据ID查找目标控件,获取只读模型指针 - const HmiControl *control = hmi_editor_service_.findControl( - current_hmi_page_id_, control_id); - // 判断是否查询到有效控件 - const bool has_control = control != nullptr; - - // 更新属性面板顶部状态标签,显示选中控件ID或提示未选择 - ui_->selectionValueLabel->setText( - has_control ? fromUtf8(control->id) - + (control->isConfigured() ? QString{} - : control->type == HmiControlType::PageJump - ? tr("(未配置)") : tr("(未绑定)")) - : tr("未选择")); - - // 批量控制所有属性编辑组件可用性:无选中控件时整体置灰,禁止编辑 - for (QWidget *widget : {static_cast(ui_->controlIdEdit), - static_cast(ui_->controlTextEdit), - static_cast(ui_->controlXSpinBox), - static_cast(ui_->controlYSpinBox), - static_cast(ui_->controlWidthSpinBox), - static_cast(ui_->controlHeightSpinBox), - static_cast(ui_->bindingAreaComboBox), - static_cast(ui_->bindingIndexSpinBox), - static_cast(ui_->targetPageComboBox), - static_cast(ui_->buttonOperationComboBox), - static_cast(ui_->applyPropertiesButton)}) - { - widget->setEnabled(has_control); - } - - // 未选中有效控件,无需填充属性,直接退出 - if (!has_control) - { - ui_->bindingAreaLabel->setVisible(false); - ui_->bindingAreaComboBox->setVisible(false); - ui_->bindingIndexLabel->setVisible(false); - ui_->bindingIndexSpinBox->setVisible(false); - ui_->targetPageLabel->setVisible(false); - ui_->targetPageComboBox->setVisible(false); - ui_->buttonOperationLabel->setVisible(false); - ui_->buttonOperationComboBox->setVisible(false); - return; - } - - // 将控件基础信息填充至UI输入框 - ui_->controlIdEdit->setText(fromUtf8(control->id)); - ui_->controlTextEdit->setText(fromUtf8(control->text)); - // 控件位置坐标 - ui_->controlXSpinBox->setValue(control->bounds.x); - ui_->controlYSpinBox->setValue(control->bounds.y); - // 控件宽高尺寸 - ui_->controlWidthSpinBox->setValue(control->bounds.width); - ui_->controlHeightSpinBox->setValue(control->bounds.height); - - // 根据控件类型获取默认寄存器区域:按钮/指示灯默认M区;数值类控件默认D区 - // 未绑定必须保留明确状态,避免属性编辑把显示默认值误提交为 M0/D0 - ui_->bindingAreaComboBox->setCurrentIndex(!control->binding.has_value() ? 0 - : control->binding->area() == RegisterArea::M ? 1 : 2); - ui_->bindingIndexSpinBox->setValue( - control->binding.has_value() ? control->binding->index() : 0); - const bool has_binding = control->type != HmiControlType::Label - && control->type != HmiControlType::PageJump - && control->type != HmiControlType::AlarmList; - ui_->bindingAreaLabel->setVisible(has_binding); - ui_->bindingAreaComboBox->setVisible(has_binding); - ui_->bindingIndexLabel->setVisible(has_binding); - ui_->bindingIndexSpinBox->setVisible(has_binding); - const bool is_button = control->type == HmiControlType::Button; - ui_->buttonOperationLabel->setVisible(is_button); - ui_->buttonOperationComboBox->setVisible(is_button); - ui_->buttonOperationComboBox->setEnabled(is_button); - ui_->buttonOperationComboBox->setCurrentIndex( - ui_->buttonOperationComboBox->findData( - static_cast(control->buttonOperation))); - const bool is_page_jump = control->type == HmiControlType::PageJump; - ui_->targetPageLabel->setVisible(is_page_jump); - ui_->targetPageComboBox->setVisible(is_page_jump); - ui_->targetPageComboBox->setEnabled(is_page_jump); - ui_->targetPageComboBox->clear(); - if (is_page_jump) - { - for (const HmiPage &page : project_service_.project().hmiPages) - { - ui_->targetPageComboBox->addItem( - fromUtf8(page.name), fromUtf8(page.id)); - } - const std::string target_id = control->pageJump.has_value() - ? control->pageJump->targetPageId : std::string{}; - ui_->targetPageComboBox->setCurrentIndex( - ui_->targetPageComboBox->findData(fromUtf8(target_id))); - } + property_panel_controller_->showControlProperties(control_id); } void MainWindow::showLogicNodeProperties(const std::string &node_id) { - ui_->propertyStack->setCurrentWidget(ui_->logicPropertiesPage); - selected_logic_node_id_ = node_id; - const LogicNode *node = logic_editor_service_.findNode( - current_logic_id_, node_id); - const bool has_node = node != nullptr; - ui_->selectionValueLabel->setText( - has_node ? fromUtf8(node->id) : tr("未选择")); - for (QWidget *widget : {static_cast(ui_->logicNodeIdEdit), - static_cast(ui_->logicAddressSpinBox), - static_cast(ui_->logicModeComboBox), - static_cast(ui_->logicComparisonComboBox), - static_cast(ui_->logicValueSpinBox), - static_cast(ui_->logicPresetSpinBox), - static_cast(ui_->applyLogicPropertiesButton)}) - { - widget->setEnabled(has_node); - } - ui_->logicNodeTypeLabel->setEnabled(has_node); - ui_->logicAddressAreaLabel->setEnabled(has_node); - if (!has_node) - { - ui_->logicNodeIdEdit->clear(); - ui_->logicNodeTypeLabel->clear(); - ui_->logicAddressAreaLabel->clear(); - ui_->logicModeComboBox->clear(); - ui_->logicPresetSpinBox->setValue(TonNodeConfig::kMinimumPresetMs); - return; - } - - ui_->logicNodeIdEdit->setText(fromUtf8(node->id)); - ui_->logicModeComboBox->clear(); - ui_->logicComparisonComboBox->setEnabled(false); - ui_->logicValueSpinBox->setEnabled(false); - ui_->logicPresetSpinBox->setEnabled(false); - if (const auto *contact = std::get_if(&node->config)) - { - ui_->logicNodeTypeLabel->setText(tr("触点")); - ui_->logicAddressAreaLabel->setText(QStringLiteral("M")); - ui_->logicAddressSpinBox->setValue(contact->address.index()); - ui_->logicModeComboBox->addItem( - tr("常开"), static_cast(ContactMode::NormallyOpen)); - ui_->logicModeComboBox->addItem( - tr("常闭"), static_cast(ContactMode::NormallyClosed)); - ui_->logicModeComboBox->setCurrentIndex( - ui_->logicModeComboBox->findData(static_cast(contact->mode))); - ui_->logicModeComboBox->setEnabled(true); - } - else if (const auto *edge = std::get_if(&node->config)) - { - ui_->logicNodeTypeLabel->setText(tr("边沿触点")); - ui_->logicAddressAreaLabel->setText(QStringLiteral("M")); - ui_->logicAddressSpinBox->setValue(edge->address.index()); - ui_->logicModeComboBox->addItem( - tr("上升沿"), static_cast(EdgeMode::Rising)); - ui_->logicModeComboBox->addItem( - tr("下降沿"), static_cast(EdgeMode::Falling)); - ui_->logicModeComboBox->setCurrentIndex( - ui_->logicModeComboBox->findData(static_cast(edge->mode))); - ui_->logicModeComboBox->setEnabled(true); - } - else if (const auto *timer_contact = - std::get_if(&node->config)) - { - ui_->logicNodeTypeLabel->setText(tr("T 触点")); - ui_->logicAddressAreaLabel->setText(QStringLiteral("T")); - ui_->logicAddressSpinBox->setValue(timer_contact->address.index()); - ui_->logicModeComboBox->addItem( - tr("常开"), static_cast(ContactMode::NormallyOpen)); - ui_->logicModeComboBox->addItem( - tr("常闭"), static_cast(ContactMode::NormallyClosed)); - ui_->logicModeComboBox->setCurrentIndex( - ui_->logicModeComboBox->findData( - static_cast(timer_contact->mode))); - ui_->logicModeComboBox->setEnabled(true); - } - else if (const auto *counter_contact = - std::get_if(&node->config)) - { - ui_->logicNodeTypeLabel->setText(tr("C 触点")); - ui_->logicAddressAreaLabel->setText(QStringLiteral("C")); - ui_->logicAddressSpinBox->setValue(counter_contact->address.index()); - ui_->logicModeComboBox->addItem( - tr("常开"), static_cast(ContactMode::NormallyOpen)); - ui_->logicModeComboBox->addItem( - tr("常闭"), static_cast(ContactMode::NormallyClosed)); - ui_->logicModeComboBox->setCurrentIndex( - ui_->logicModeComboBox->findData( - static_cast(counter_contact->mode))); - ui_->logicModeComboBox->setEnabled(true); - } - else if (const auto *coil = std::get_if(&node->config)) - { - ui_->logicNodeTypeLabel->setText(tr("线圈")); - ui_->logicAddressAreaLabel->setText(QStringLiteral("M")); - ui_->logicAddressSpinBox->setValue(coil->address.index()); - ui_->logicModeComboBox->addItem( - tr("普通"), static_cast(CoilMode::Normal)); - ui_->logicModeComboBox->addItem( - tr("置位"), static_cast(CoilMode::Set)); - ui_->logicModeComboBox->addItem( - tr("复位"), static_cast(CoilMode::Reset)); - ui_->logicModeComboBox->setCurrentIndex( - ui_->logicModeComboBox->findData(static_cast(coil->mode))); - ui_->logicModeComboBox->setEnabled(true); - } - else if (const auto *compare = std::get_if(&node->config)) - { - ui_->logicNodeTypeLabel->setText(tr("D 值比较")); - ui_->logicAddressAreaLabel->setText(QStringLiteral("D")); - ui_->logicAddressSpinBox->setValue(compare->address.index()); - ui_->logicModeComboBox->addItem(tr("不适用")); - ui_->logicModeComboBox->setEnabled(false); - ui_->logicComparisonComboBox->setEnabled(true); - ui_->logicComparisonComboBox->setCurrentIndex( - ui_->logicComparisonComboBox->findData( - static_cast(compare->comparison))); - ui_->logicValueSpinBox->setEnabled(true); - ui_->logicValueSpinBox->setValue(compare->value); - } - else if (const auto *ton = std::get_if(&node->config)) - { - ui_->logicNodeTypeLabel->setText(tr("TON")); - ui_->logicAddressAreaLabel->setText(QStringLiteral("T")); - ui_->logicAddressSpinBox->setValue(ton->address.index()); - ui_->logicModeComboBox->addItem(tr("非保持接通延时")); - ui_->logicModeComboBox->setEnabled(false); - ui_->logicPresetSpinBox->setEnabled(true); - ui_->logicPresetSpinBox->setValue(ton->presetMs); - } - else if (const auto *counter = std::get_if(&node->config)) - { - ui_->logicNodeTypeLabel->setText( - counter->mode == CounterMode::Up ? QStringLiteral("CTU") - : QStringLiteral("CTD")); - ui_->logicAddressAreaLabel->setText(QStringLiteral("C")); - ui_->logicAddressSpinBox->setValue(counter->address.index()); - ui_->logicAddressSpinBox->setEnabled(false); - ui_->logicModeComboBox->addItem(tr("使用配置对话框编辑")); - ui_->logicModeComboBox->setEnabled(false); - } - else if (const auto *move = std::get_if(&node->config)) - { - ui_->logicNodeTypeLabel->setText(QStringLiteral("MOVE")); - ui_->logicAddressAreaLabel->setText(QStringLiteral("D")); - ui_->logicAddressSpinBox->setValue(move->destination.index()); - ui_->logicAddressSpinBox->setEnabled(false); - ui_->logicModeComboBox->addItem(tr("使用配置对话框编辑")); - ui_->logicModeComboBox->setEnabled(false); - } - else - { - const auto &arithmetic = std::get(node->config); - ui_->logicNodeTypeLabel->setText( - arithmetic.operation == ArithmeticOperation::Add - ? QStringLiteral("ADD") : QStringLiteral("SUB")); - ui_->logicAddressAreaLabel->setText(QStringLiteral("D")); - ui_->logicAddressSpinBox->setValue(arithmetic.destination.index()); - ui_->logicAddressSpinBox->setEnabled(false); - ui_->logicModeComboBox->addItem(tr("使用配置对话框编辑")); - ui_->logicModeComboBox->setEnabled(false); - } - if (!node->configured) - { - ui_->logicNodeTypeLabel->setText(tr("待配置:%1").arg( - ui_->logicNodeTypeLabel->text())); - } + property_panel_controller_->showLogicNodeProperties(node_id); } void MainWindow::addHmiControl(HmiControlType type) { - const HmiEditorResult result = hmi_editor_service_.addControl( - current_hmi_page_id_, type); - if (!result.succeeded) - { - showProjectResult(tr("添加控件"), fromUtf8(result.message), false); - return; - } - hmi_editor_widget_->reloadPage(); - hmi_editor_widget_->selectControl(result.id); - showControlProperties(result.id); - refreshProjectUi(); - statusBar()->showMessage(tr("已添加%1").arg(controlTypeText(type)), 3000); + property_panel_controller_->addHmiControl(type); } void MainWindow::deleteSelectedControl() { - if (selected_control_id_.empty()) - { - return; - } - const HmiEditorResult result = hmi_editor_service_.removeControl( - current_hmi_page_id_, selected_control_id_); - if (!result.succeeded) - { - showProjectResult(tr("删除控件"), fromUtf8(result.message), false); - return; - } - selected_control_id_.clear(); - hmi_editor_widget_->reloadPage(); - showControlProperties({}); - refreshProjectUi(); -} - -void MainWindow::applySelectedControlProperties() -{ - const HmiControl *old_control = hmi_editor_service_.findControl( - current_hmi_page_id_, selected_control_id_); - if (old_control == nullptr) - { - return; - } - // 从现有控件复制不可见属性,避免属性面板覆盖扩展配置 - HmiControl control = *old_control; - control.id = toUtf8(ui_->controlIdEdit->text()); - control.text = toUtf8(ui_->controlTextEdit->text()); - control.bounds.x = ui_->controlXSpinBox->value(); - control.bounds.y = ui_->controlYSpinBox->value(); - control.bounds.width = ui_->controlWidthSpinBox->value(); - control.bounds.height = ui_->controlHeightSpinBox->value(); - if (control.type == HmiControlType::Label - || control.type == HmiControlType::PageJump - || control.type == HmiControlType::AlarmList) - { - control.binding.reset(); - } - else - { - const int binding_area = ui_->bindingAreaComboBox->currentData().toInt(); - if (binding_area < 0) - { - control.binding.reset(); - } - else - { - const RegisterArea area = binding_area == 0 - ? RegisterArea::M : RegisterArea::D; - control.binding = RegisterAddress{ - area, ui_->bindingIndexSpinBox->value()}; - } - } - if (control.type == HmiControlType::Button) - { - control.buttonOperation = static_cast( - ui_->buttonOperationComboBox->currentData().toInt()); - } - if (control.type == HmiControlType::PageJump) - { - control.pageJump = HmiPageJumpConfig{ - toUtf8(ui_->targetPageComboBox->currentData().toString())}; - } - const HmiEditorResult result = hmi_editor_service_.updateControl( - current_hmi_page_id_, selected_control_id_, control); - if (!result.succeeded) - { - showProjectResult(tr("应用属性"), fromUtf8(result.message), false); - return; - } - selected_control_id_ = result.id; - hmi_editor_widget_->reloadPage(); - hmi_editor_widget_->selectControl(selected_control_id_); - refreshProjectUi(); - statusBar()->showMessage(tr("控件属性已更新"), 3000); + property_panel_controller_->deleteSelectedControl(); } void MainWindow::addLogicCondition(const LogicNodeConfig &config) @@ -1860,84 +998,6 @@ void MainWindow::deleteSelectedLogicObject() refreshProjectUi(); } -void MainWindow::applySelectedLogicNodeProperties() -{ - const LogicNode *node = logic_editor_service_.findNode( - current_logic_id_, selected_logic_node_id_); - if (node == nullptr) - { - return; - } - - LogicNodeConfig config = node->config; - if (std::holds_alternative(config)) - { - config = ContactNodeConfig{ - RegisterAddress{RegisterArea::M, ui_->logicAddressSpinBox->value()}, - static_cast(ui_->logicModeComboBox->currentData().toInt())}; - } - else if (std::holds_alternative(config)) - { - config = EdgeContactNodeConfig{ - RegisterAddress{RegisterArea::M, ui_->logicAddressSpinBox->value()}, - static_cast(ui_->logicModeComboBox->currentData().toInt())}; - } - else if (std::holds_alternative(config)) - { - config = TimerContactNodeConfig{ - TimerAddress{ui_->logicAddressSpinBox->value()}, - static_cast(ui_->logicModeComboBox->currentData().toInt())}; - } - else if (std::holds_alternative(config)) - { - config = CounterContactNodeConfig{ - CounterAddress{ui_->logicAddressSpinBox->value()}, - static_cast(ui_->logicModeComboBox->currentData().toInt())}; - } - else if (std::holds_alternative(config)) - { - config = CoilNodeConfig{ - RegisterAddress{RegisterArea::M, ui_->logicAddressSpinBox->value()}, - static_cast(ui_->logicModeComboBox->currentData().toInt())}; - } - else if (std::holds_alternative(config)) - { - config = CompareNodeConfig{ - RegisterAddress{RegisterArea::D, ui_->logicAddressSpinBox->value()}, - static_cast( - ui_->logicComparisonComboBox->currentData().toInt()), - static_cast(ui_->logicValueSpinBox->value())}; - } - else if (std::holds_alternative(config)) - { - config = TonNodeConfig{ - TimerAddress{ui_->logicAddressSpinBox->value()}, - ui_->logicPresetSpinBox->value()}; - } - else - { - LogicInstructionDialog dialog(config, this); - if (dialog.exec() != QDialog::Accepted) - { - return; - } - config = dialog.config(); - } - - const LogicEditorResult result = logic_editor_service_.updateNodeConfig( - current_logic_id_, selected_logic_node_id_, config); - if (!result.succeeded) - { - showProjectResult(tr("应用逻辑属性"), fromUtf8(result.message), false); - return; - } - logic_editor_widget_->reloadLogic(); - logic_editor_widget_->selectNode(selected_logic_node_id_); - showLogicNodeProperties(selected_logic_node_id_); - refreshProjectUi(); - statusBar()->showMessage(tr("逻辑节点属性已更新,节点已配置"), 3000); -} - void MainWindow::createNewProject() { const QString name = QInputDialog::getText( @@ -2221,86 +1281,7 @@ void MainWindow::updateModeUi(const QString &message) void MainWindow::updateSimulationUi(bool report_fault) { - const bool offline = runtime_mode_service_.mode() - == ApplicationMode::OfflineRunning; - const bool online = runtime_mode_service_.mode() - == ApplicationMode::OnlineRunning; - const bool plc_connected = runtime_mode_service_.plcConnectionState() - == PlcConnectionState::Connected; - const SimulationState state = runtime_mode_service_.simulationState(); - hmi_editor_widget_->setRuntimeWriteEnabled( - (online && plc_connected) - || (offline && state == SimulationState::Running)); - if (runtime_monitor_widget_ != nullptr) - { - runtime_monitor_widget_->setHmiWriteEnabled( - (online && plc_connected) - || (offline && state == SimulationState::Running)); - runtime_monitor_widget_->refreshValues( - runtime_mode_service_.mode(), runtime_mode_service_.plcConnectionState()); - } - - switch (state) - { - case SimulationState::Running: - { - executor_status_label_->setText( - tr("逻辑执行器:运行(%1 次)") - .arg(runtime_mode_service_.successfulScanCount())); - break; - } - case SimulationState::Faulted: - { - executor_status_label_->setText(tr("逻辑执行器:故障")); - if (report_fault) - { - const LogicScanResult &error = runtime_mode_service_.simulationError(); - if (!error.logicId.empty() - && logic_editor_service_.findLogic(error.logicId) != nullptr) - { - current_logic_id_ = error.logicId; - logic_editor_widget_->setLogicId(current_logic_id_); - runtime_monitor_widget_->setProjectObjects( - hmi_navigation_service_->currentPageId(), current_logic_id_); - } - logic_editor_widget_->setRuntimeTrace( - runtime_mode_service_.offlineSimulationService() - .traceSnapshot().forLogic(current_logic_id_), - error.nodeId); - runtime_monitor_widget_->setLogicTrace( - runtime_mode_service_.offlineSimulationService().traceSnapshot(), - error.nodeId); - if (!error.nodeId.empty()) - { - logic_editor_widget_->selectNode(error.nodeId); - showLogicNodeProperties(error.nodeId); - } - QString message = tr("离线仿真异常:%1").arg(fromUtf8(error.message)); - if (!error.logicId.empty()) - { - message += tr(",逻辑 %1").arg(fromUtf8(error.logicId)); - } - if (!error.rungId.empty()) - { - message += tr(",网络 %1").arg(fromUtf8(error.rungId)); - } - if (!error.nodeId.empty()) - { - message += tr(",节点 %1").arg(fromUtf8(error.nodeId)); - } - statusBar()->showMessage(message); - ui_->outputList->addItem(message); - ui_->outputList->scrollToBottom(); - } - break; - } - case SimulationState::Stopped: - default: - { - executor_status_label_->setText(tr("逻辑执行器:停止")); - break; - } - } + runtime_panel_controller_->updateSimulationUi(report_fault); } // 根据当前真实运行模式,同步更新模式工具栏单选按钮选中状态 diff --git a/app/src/ui/main_window.h b/app/src/ui/main_window.h index dc6ab66..9166191 100644 --- a/app/src/ui/main_window.h +++ b/app/src/ui/main_window.h @@ -21,7 +21,6 @@ QT_BEGIN_NAMESPACE class QAction; class QActionGroup; class QLabel; -class QTimer; namespace Ui { class MainWindow; @@ -41,6 +40,9 @@ class LogicEditorService; class LogicEditorWidget; class RegisterMonitorService; class RuntimeMonitorWidget; +class ProjectWorkspaceController; +class PropertyPanelController; +class RuntimePanelController; /** * @brief 组织主界面区域并将用户操作转交给服务层 @@ -107,15 +109,7 @@ private: void configureRegisterComments(); // 刷新工程树和当前 HMI 页面信息 void refreshProjectUi(); - void handleProjectTreeSelection(); void updateProjectTreeActions(); - void addPage(); - void addLogic(); - void renameProjectItem(); - void deleteProjectItem(); - void moveProjectItem(int offset); - void setSelectedPageAsInitial(); - void toggleSelectedLogicEnabled(); // 显示指定 HMI 控件的可编辑属性 void showControlProperties(const std::string &control_id); // 显示指定控制逻辑节点的可编辑属性 @@ -124,8 +118,6 @@ private: void addHmiControl(HmiControlType type); // 删除当前选中的 HMI 控件 void deleteSelectedControl(); - // 将属性表单内容应用到当前选中控件 - void applySelectedControlProperties(); // 在当前光标节点后添加串联条件,没有选择时追加到网络末尾 void addLogicCondition(const LogicNodeConfig &config); // 为当前选中的连续逻辑范围建立并联支路 @@ -138,8 +130,6 @@ private: void editSelectedRungComment(); // 删除当前选中的逻辑节点或网络 void deleteSelectedLogicObject(); - // 将逻辑属性表单内容应用到当前选中节点 - void applySelectedLogicNodeProperties(); void connectPlc(); void disconnectPlc(); // 合并同一事件循环内的 PLC 状态通知,避免重复刷新和重复日志 @@ -195,22 +185,16 @@ private: RegisterMonitorService ®ister_monitor_service_; std::unique_ptr owned_hmi_navigation_service_; HmiNavigationService *hmi_navigation_service_ = nullptr; + std::unique_ptr project_workspace_controller_; + std::unique_ptr property_panel_controller_; + std::unique_ptr runtime_panel_controller_; QActionGroup *mode_action_group_ = nullptr; HmiEditorWidget *hmi_editor_widget_ = nullptr; LogicEditorWidget *logic_editor_widget_ = nullptr; RuntimeMonitorWidget *runtime_monitor_widget_ = nullptr; - QTimer *runtime_refresh_timer_ = nullptr; QLabel *mode_status_label_ = nullptr; QLabel *register_status_label_ = nullptr; QLabel *executor_status_label_ = nullptr; - QAction *add_page_action_ = nullptr; - QAction *add_logic_action_ = nullptr; - QAction *rename_project_item_action_ = nullptr; - QAction *delete_project_item_action_ = nullptr; - QAction *move_project_item_up_action_ = nullptr; - QAction *move_project_item_down_action_ = nullptr; - QAction *set_initial_page_action_ = nullptr; - QAction *toggle_logic_enabled_action_ = nullptr; PlcSerialConfiguration plc_configuration_; std::string selected_control_id_; std::string selected_logic_node_id_; diff --git a/app/src/ui/project_workspace_controller.cpp b/app/src/ui/project_workspace_controller.cpp new file mode 100644 index 0000000..66612a3 --- /dev/null +++ b/app/src/ui/project_workspace_controller.cpp @@ -0,0 +1,511 @@ +#include "project_workspace_controller.h" + +#include "hmi_editor_widget.h" +#include "logic_editor_widget.h" +#include "runtime_monitor_widget.h" +#include "services/hmi_editor_service.h" +#include "services/hmi_navigation_service.h" +#include "services/logic_editor_service.h" +#include "services/project_service.h" +#include "services/runtime_mode_service.h" +#include "ui_main_window.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +constexpr int kProjectTreeKindRole = Qt::UserRole; +constexpr int kProjectTreeIdRole = Qt::UserRole + 1; + +QString fromUtf8(const std::string &value) +{ + return QString::fromUtf8(value.data(), static_cast(value.size())); +} + +std::string toUtf8(const QString &value) +{ + const QByteArray bytes = value.toUtf8(); + return std::string(bytes.constData(), static_cast(bytes.size())); +} + +} // namespace + +ProjectWorkspaceController::ProjectWorkspaceController( + QWidget &parent, + Ui::MainWindow &ui, + ProjectService &project_service, + HmiEditorService &hmi_editor_service, + LogicEditorService &logic_editor_service, + RuntimeModeService &runtime_mode_service, + HmiNavigationService &hmi_navigation_service, + HmiEditorWidget &hmi_editor_widget, + LogicEditorWidget &logic_editor_widget, + RuntimeMonitorWidget &runtime_monitor_widget, + std::string ¤t_hmi_page_id, + std::string ¤t_logic_id, + std::function show_control_properties, + std::function show_logic_properties, + ResultReporter result_reporter, + StatusReporter status_reporter) + : parent_(parent), + ui_(ui), + project_service_(project_service), + hmi_editor_service_(hmi_editor_service), + logic_editor_service_(logic_editor_service), + runtime_mode_service_(runtime_mode_service), + hmi_navigation_service_(hmi_navigation_service), + hmi_editor_widget_(hmi_editor_widget), + logic_editor_widget_(logic_editor_widget), + runtime_monitor_widget_(runtime_monitor_widget), + current_hmi_page_id_(current_hmi_page_id), + current_logic_id_(current_logic_id), + show_control_properties_(std::move(show_control_properties)), + show_logic_properties_(std::move(show_logic_properties)), + result_reporter_(std::move(result_reporter)), + status_reporter_(std::move(status_reporter)) +{ +} + +void ProjectWorkspaceController::configure() +{ + ui_.projectTree->setContextMenuPolicy(Qt::ActionsContextMenu); + + add_page_action_ = new QAction(QObject::tr("新建页面"), &parent_); + add_page_action_->setObjectName(QStringLiteral("addPageAction")); + add_logic_action_ = new QAction(QObject::tr("新建控制逻辑"), &parent_); + add_logic_action_->setObjectName(QStringLiteral("addLogicAction")); + rename_item_action_ = new QAction(QObject::tr("重命名"), &parent_); + rename_item_action_->setObjectName(QStringLiteral("renameProjectItemAction")); + delete_item_action_ = new QAction(QObject::tr("删除"), &parent_); + delete_item_action_->setObjectName(QStringLiteral("deleteProjectItemAction")); + move_item_up_action_ = new QAction(QObject::tr("上移"), &parent_); + move_item_up_action_->setObjectName(QStringLiteral("moveProjectItemUpAction")); + move_item_down_action_ = new QAction(QObject::tr("下移"), &parent_); + move_item_down_action_->setObjectName( + QStringLiteral("moveProjectItemDownAction")); + set_initial_page_action_ = new QAction(QObject::tr("设为初始页面"), &parent_); + set_initial_page_action_->setObjectName(QStringLiteral("setInitialPageAction")); + toggle_logic_enabled_action_ = new QAction( + QObject::tr("禁用控制逻辑"), &parent_); + toggle_logic_enabled_action_->setObjectName( + QStringLiteral("toggleLogicEnabledAction")); + + ui_.projectTree->addActions({ + add_page_action_, + add_logic_action_, + rename_item_action_, + delete_item_action_, + move_item_up_action_, + move_item_down_action_, + set_initial_page_action_, + toggle_logic_enabled_action_}); + + QObject::connect(add_page_action_, &QAction::triggered, + &parent_, [this] { addPage(); }); + QObject::connect(add_logic_action_, &QAction::triggered, + &parent_, [this] { addLogic(); }); + QObject::connect(rename_item_action_, &QAction::triggered, + &parent_, [this] { renameSelectedItem(); }); + QObject::connect(delete_item_action_, &QAction::triggered, + &parent_, [this] { deleteSelectedItem(); }); + QObject::connect(move_item_up_action_, &QAction::triggered, + &parent_, [this] { moveSelectedItem(-1); }); + QObject::connect(move_item_down_action_, &QAction::triggered, + &parent_, [this] { moveSelectedItem(1); }); + QObject::connect(set_initial_page_action_, &QAction::triggered, + &parent_, [this] { setSelectedPageAsInitial(); }); + QObject::connect(toggle_logic_enabled_action_, &QAction::triggered, + &parent_, [this] { toggleSelectedLogicEnabled(); }); + QObject::connect(ui_.projectTree, &QTreeWidget::currentItemChanged, + &parent_, [this](QTreeWidgetItem *, QTreeWidgetItem *) + { + handleSelectionChanged(); + }); + updateActions(); +} + +void ProjectWorkspaceController::refresh() +{ + if (hmi_editor_service_.findPage(current_hmi_page_id_) == nullptr) + { + const std::string &initial_id = project_service_.project().initialHmiPageId; + current_hmi_page_id_ = hmi_editor_service_.findPage(initial_id) != nullptr + ? initial_id : hmi_editor_service_.firstPageId(); + show_control_properties_({}); + } + if (logic_editor_service_.findLogic(current_logic_id_) == nullptr) + { + current_logic_id_ = logic_editor_service_.firstLogicId(); + show_logic_properties_({}); + } + + hmi_editor_widget_.setPageId(current_hmi_page_id_); + logic_editor_widget_.setLogicId(current_logic_id_); + if (runtime_mode_service_.mode() != ApplicationMode::Editing) + { + runtime_monitor_widget_.setProjectObjects( + hmi_navigation_service_.currentPageId(), current_logic_id_); + } + runtime_mode_service_.refreshPlcPollAddresses(); + + const QSignalBlocker blocker(ui_.projectTree); + ui_.projectTree->clear(); + auto *hmi_root = new QTreeWidgetItem(ui_.projectTree, {QObject::tr("HMI 页面")}); + hmi_root->setData(0, kProjectTreeKindRole, static_cast(ItemKind::Root)); + QTreeWidgetItem *selected_item = nullptr; + for (const HmiPage &page : project_service_.project().hmiPages) + { + const QString suffix = page.id == project_service_.project().initialHmiPageId + ? QObject::tr("(初始)") : QString{}; + auto *item = new QTreeWidgetItem( + hmi_root, {fromUtf8(page.name) + suffix}); + item->setData(0, kProjectTreeKindRole, static_cast(ItemKind::HmiPage)); + item->setData(0, kProjectTreeIdRole, fromUtf8(page.id)); + if (page.id == current_hmi_page_id_ + && ui_.editorTabWidget->currentIndex() == 0) + { + selected_item = item; + } + } + + const HmiPage *current_page = hmi_editor_service_.findPage(current_hmi_page_id_); + if (current_page != nullptr) + { + ui_.hmiPageTitleLabel->setText(fromUtf8(current_page->name)); + ui_.hmiPageSizeLabel->setText( + QObject::tr("%1 x %2").arg(current_page->width).arg(current_page->height)); + } + else + { + ui_.hmiPageTitleLabel->setText(QObject::tr("尚未创建 HMI 页面")); + ui_.hmiPageSizeLabel->clear(); + } + + auto *logic_root = new QTreeWidgetItem( + ui_.projectTree, {QObject::tr("控制逻辑")}); + logic_root->setData(0, kProjectTreeKindRole, static_cast(ItemKind::Root)); + for (const ControlLogic &logic : project_service_.project().controlLogics) + { + const QString suffix = logic.enabled ? QString{} : QObject::tr("(已禁用)"); + auto *item = new QTreeWidgetItem( + logic_root, {fromUtf8(logic.name) + suffix}); + item->setData( + 0, kProjectTreeKindRole, static_cast(ItemKind::ControlLogic)); + item->setData(0, kProjectTreeIdRole, fromUtf8(logic.id)); + if (logic.id == current_logic_id_ + && ui_.editorTabWidget->currentIndex() == 1) + { + selected_item = item; + } + } + ui_.projectTree->expandAll(); + if (selected_item != nullptr) + { + ui_.projectTree->setCurrentItem(selected_item); + } + updateActions(); +} + +const std::string &ProjectWorkspaceController::currentHmiPageId() const +{ + return current_hmi_page_id_; +} + +const std::string &ProjectWorkspaceController::currentLogicId() const +{ + return current_logic_id_; +} + +void ProjectWorkspaceController::handleSelectionChanged() +{ + QTreeWidgetItem *item = ui_.projectTree->currentItem(); + if (item == nullptr) + { + updateActions(); + return; + } + const auto kind = static_cast( + item->data(0, kProjectTreeKindRole).toInt()); + const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); + if (kind == ItemKind::HmiPage && hmi_editor_service_.findPage(id) != nullptr) + { + current_hmi_page_id_ = id; + hmi_editor_widget_.setPageId(id); + hmi_editor_widget_.reloadPage(); + ui_.editorTabWidget->setCurrentWidget(ui_.hmiEditorTab); + show_control_properties_({}); + const HmiPage *page = hmi_editor_service_.findPage(id); + ui_.hmiPageTitleLabel->setText(fromUtf8(page->name)); + ui_.hmiPageSizeLabel->setText( + QObject::tr("%1 x %2").arg(page->width).arg(page->height)); + } + else if (kind == ItemKind::ControlLogic + && logic_editor_service_.findLogic(id) != nullptr) + { + current_logic_id_ = id; + logic_editor_widget_.setLogicId(id); + logic_editor_widget_.reloadLogic(); + ui_.editorTabWidget->setCurrentWidget(ui_.logicEditorTab); + show_logic_properties_({}); + } + updateActions(); +} + +void ProjectWorkspaceController::updateActions() +{ + if (add_page_action_ == nullptr) + { + return; + } + QTreeWidgetItem *item = ui_.projectTree->currentItem(); + const auto kind = item == nullptr ? ItemKind::Root + : static_cast(item->data(0, kProjectTreeKindRole).toInt()); + const bool page_selected = kind == ItemKind::HmiPage; + const bool logic_selected = kind == ItemKind::ControlLogic; + const bool editable = runtime_mode_service_.policy().allowsProjectEditing; + add_page_action_->setEnabled(editable); + add_logic_action_->setEnabled(editable); + rename_item_action_->setEnabled(editable && (page_selected || logic_selected)); + delete_item_action_->setEnabled(editable && (page_selected || logic_selected)); + move_item_up_action_->setEnabled(editable && (page_selected || logic_selected)); + move_item_down_action_->setEnabled(editable && (page_selected || logic_selected)); + set_initial_page_action_->setEnabled(editable && page_selected); + toggle_logic_enabled_action_->setEnabled(editable && logic_selected); + if (logic_selected) + { + const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); + const ControlLogic *logic = logic_editor_service_.findLogic(id); + toggle_logic_enabled_action_->setText( + logic != nullptr && logic->enabled + ? QObject::tr("禁用控制逻辑") : QObject::tr("启用控制逻辑")); + } +} + +void ProjectWorkspaceController::addPage() +{ + const QString name = QInputDialog::getText( + &parent_, QObject::tr("新建页面"), QObject::tr("页面名称")); + if (name.isEmpty()) + { + return; + } + const HmiEditorResult result = hmi_editor_service_.addPage(toUtf8(name)); + if (!result.succeeded) + { + reportFailure(QObject::tr("新建页面"), result.message); + return; + } + current_hmi_page_id_ = result.id; + show_control_properties_({}); + ui_.editorTabWidget->setCurrentWidget(ui_.hmiEditorTab); + refresh(); + hmi_editor_widget_.reloadPage(); +} + +void ProjectWorkspaceController::addLogic() +{ + const QString name = QInputDialog::getText( + &parent_, QObject::tr("新建控制逻辑"), QObject::tr("逻辑名称")); + if (name.isEmpty()) + { + return; + } + const LogicEditorResult result = logic_editor_service_.addLogic(toUtf8(name)); + if (!result.succeeded) + { + reportFailure(QObject::tr("新建控制逻辑"), result.message); + return; + } + current_logic_id_ = result.id; + show_logic_properties_({}); + ui_.editorTabWidget->setCurrentWidget(ui_.logicEditorTab); + refresh(); + logic_editor_widget_.reloadLogic(); +} + +void ProjectWorkspaceController::renameSelectedItem() +{ + QTreeWidgetItem *item = ui_.projectTree->currentItem(); + if (item == nullptr) + { + return; + } + const auto kind = static_cast( + item->data(0, kProjectTreeKindRole).toInt()); + const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); + QString current_name; + if (kind == ItemKind::HmiPage) + { + const HmiPage *page = hmi_editor_service_.findPage(id); + current_name = page == nullptr ? QString{} : fromUtf8(page->name); + } + else if (kind == ItemKind::ControlLogic) + { + const ControlLogic *logic = logic_editor_service_.findLogic(id); + current_name = logic == nullptr ? QString{} : fromUtf8(logic->name); + } + else + { + return; + } + bool accepted = false; + const QString name = QInputDialog::getText( + &parent_, QObject::tr("重命名"), QObject::tr("新名称"), + QLineEdit::Normal, current_name, &accepted); + if (!accepted) + { + return; + } + if (kind == ItemKind::HmiPage) + { + const HmiEditorResult result = hmi_editor_service_.renamePage(id, toUtf8(name)); + if (!result.succeeded) + { + reportFailure(QObject::tr("重命名页面"), result.message); + return; + } + } + else + { + const LogicEditorResult result = logic_editor_service_.renameLogic( + id, toUtf8(name)); + if (!result.succeeded) + { + reportFailure(QObject::tr("重命名控制逻辑"), result.message); + return; + } + } + refresh(); +} + +void ProjectWorkspaceController::deleteSelectedItem() +{ + QTreeWidgetItem *item = ui_.projectTree->currentItem(); + if (item == nullptr) + { + return; + } + const auto kind = static_cast( + item->data(0, kProjectTreeKindRole).toInt()); + const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); + if (kind == ItemKind::HmiPage) + { + const HmiEditorResult result = hmi_editor_service_.removePage(id); + if (!result.succeeded) + { + reportFailure(QObject::tr("删除页面"), result.message); + return; + } + } + else if (kind == ItemKind::ControlLogic) + { + const LogicEditorResult result = logic_editor_service_.removeLogic(id); + if (!result.succeeded) + { + reportFailure(QObject::tr("删除控制逻辑"), result.message); + return; + } + } + else + { + return; + } + refresh(); + show_control_properties_({}); + show_logic_properties_({}); +} + +void ProjectWorkspaceController::moveSelectedItem(int offset) +{ + QTreeWidgetItem *item = ui_.projectTree->currentItem(); + if (item == nullptr) + { + return; + } + const auto kind = static_cast( + item->data(0, kProjectTreeKindRole).toInt()); + const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); + if (kind == ItemKind::HmiPage) + { + const HmiEditorResult result = hmi_editor_service_.movePage(id, offset); + if (!result.succeeded) + { + status_reporter_(fromUtf8(result.message), 4000); + return; + } + } + else if (kind == ItemKind::ControlLogic) + { + const LogicEditorResult result = logic_editor_service_.moveLogic(id, offset); + if (!result.succeeded) + { + status_reporter_(fromUtf8(result.message), 4000); + return; + } + } + else + { + return; + } + refresh(); +} + +void ProjectWorkspaceController::setSelectedPageAsInitial() +{ + QTreeWidgetItem *item = ui_.projectTree->currentItem(); + if (item == nullptr + || static_cast(item->data(0, kProjectTreeKindRole).toInt()) + != ItemKind::HmiPage) + { + return; + } + const HmiEditorResult result = hmi_editor_service_.setInitialPage( + toUtf8(item->data(0, kProjectTreeIdRole).toString())); + if (!result.succeeded) + { + reportFailure(QObject::tr("设置初始页面"), result.message); + return; + } + refresh(); +} + +void ProjectWorkspaceController::toggleSelectedLogicEnabled() +{ + QTreeWidgetItem *item = ui_.projectTree->currentItem(); + if (item == nullptr + || static_cast(item->data(0, kProjectTreeKindRole).toInt()) + != ItemKind::ControlLogic) + { + return; + } + const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString()); + const ControlLogic *logic = logic_editor_service_.findLogic(id); + if (logic == nullptr) + { + return; + } + const LogicEditorResult result = logic_editor_service_.setLogicEnabled( + id, !logic->enabled); + if (!result.succeeded) + { + reportFailure(QObject::tr("启停控制逻辑"), result.message); + return; + } + refresh(); +} + +void ProjectWorkspaceController::reportFailure( + const QString &action, const std::string &message) const +{ + result_reporter_(action, fromUtf8(message), false); +} diff --git a/app/src/ui/project_workspace_controller.h b/app/src/ui/project_workspace_controller.h new file mode 100644 index 0000000..0943351 --- /dev/null +++ b/app/src/ui/project_workspace_controller.h @@ -0,0 +1,104 @@ +#pragma once + +#include "domain/hmi_model.h" +#include "domain/runtime_state.h" + +#include + +#include +#include + +class HmiEditorService; +class HmiEditorWidget; +class HmiNavigationService; +class LogicEditorService; +class LogicEditorWidget; +class ProjectService; +class RuntimeModeService; +class RuntimeMonitorWidget; +class QWidget; +class QAction; + +namespace Ui { +class MainWindow; +} + +// 组织工程树、当前页面/逻辑选择和工程对象的界面刷新 +class ProjectWorkspaceController final +{ +public: + using ResultReporter = std::function; + using StatusReporter = std::function; + + ProjectWorkspaceController( + QWidget &parent, + Ui::MainWindow &ui, + ProjectService &project_service, + HmiEditorService &hmi_editor_service, + LogicEditorService &logic_editor_service, + RuntimeModeService &runtime_mode_service, + HmiNavigationService &hmi_navigation_service, + HmiEditorWidget &hmi_editor_widget, + LogicEditorWidget &logic_editor_widget, + RuntimeMonitorWidget &runtime_monitor_widget, + std::string ¤t_hmi_page_id, + std::string ¤t_logic_id, + std::function show_control_properties, + std::function show_logic_properties, + ResultReporter result_reporter, + StatusReporter status_reporter); + + void configure(); + void refresh(); + void updateActions(); + void addPage(); + void addLogic(); + void renameSelectedItem(); + void deleteSelectedItem(); + void moveSelectedItem(int offset); + void setSelectedPageAsInitial(); + void toggleSelectedLogicEnabled(); + + const std::string ¤tHmiPageId() const; + const std::string ¤tLogicId() const; + +private: + enum class ItemKind + { + Root = 0, + HmiPage = 1, + ControlLogic = 2 + }; + + void handleSelectionChanged(); + void reportFailure( + const QString &action, const std::string &message) const; + + QWidget &parent_; + Ui::MainWindow &ui_; + ProjectService &project_service_; + HmiEditorService &hmi_editor_service_; + LogicEditorService &logic_editor_service_; + RuntimeModeService &runtime_mode_service_; + HmiNavigationService &hmi_navigation_service_; + HmiEditorWidget &hmi_editor_widget_; + LogicEditorWidget &logic_editor_widget_; + RuntimeMonitorWidget &runtime_monitor_widget_; + std::string ¤t_hmi_page_id_; + std::string ¤t_logic_id_; + std::function show_control_properties_; + std::function show_logic_properties_; + ResultReporter result_reporter_; + StatusReporter status_reporter_; + + QAction *add_page_action_ = nullptr; + QAction *add_logic_action_ = nullptr; + QAction *rename_item_action_ = nullptr; + QAction *delete_item_action_ = nullptr; + QAction *move_item_up_action_ = nullptr; + QAction *move_item_down_action_ = nullptr; + QAction *set_initial_page_action_ = nullptr; + QAction *toggle_logic_enabled_action_ = nullptr; +}; diff --git a/app/src/ui/property_panel_controller.cpp b/app/src/ui/property_panel_controller.cpp new file mode 100644 index 0000000..2d5ebb8 --- /dev/null +++ b/app/src/ui/property_panel_controller.cpp @@ -0,0 +1,611 @@ +#include "property_panel_controller.h" + +#include "hmi_editor_widget.h" +#include "logic_editor_widget.h" +#include "logic_instruction_dialog.h" +#include "services/hmi_editor_service.h" +#include "services/logic_editor_service.h" +#include "services/project_service.h" +#include "ui_main_window.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +QString fromUtf8(const std::string &value) +{ + return QString::fromUtf8(value.data(), static_cast(value.size())); +} + +std::string toUtf8(const QString &value) +{ + const QByteArray bytes = value.toUtf8(); + return std::string(bytes.constData(), static_cast(bytes.size())); +} + +QString controlTypeText(HmiControlType type) +{ + switch (type) + { + case HmiControlType::Button: + { + return QObject::tr("按钮"); + } + case HmiControlType::Indicator: + { + return QObject::tr("指示灯"); + } + case HmiControlType::NumericDisplay: + { + return QObject::tr("数值显示"); + } + case HmiControlType::NumericInput: + { + return QObject::tr("数值输入"); + } + case HmiControlType::Label: + { + return QObject::tr("文本"); + } + case HmiControlType::PageJump: + { + return QObject::tr("页面跳转"); + } + case HmiControlType::AlarmList: + { + return QObject::tr("报警列表"); + } + default: + { + return QObject::tr("未知控件"); + } + } +} + +} // namespace + +PropertyPanelController::PropertyPanelController( + QWidget &parent, + Ui::MainWindow &ui, + ProjectService &project_service, + HmiEditorService &hmi_editor_service, + LogicEditorService &logic_editor_service, + std::function current_page_id, + std::function current_logic_id, + std::string &selected_control_id, + std::string &selected_logic_node_id, + std::function refresh_project_ui, + ResultReporter result_reporter, + StatusReporter status_reporter) + : parent_(parent), + ui_(ui), + project_service_(project_service), + hmi_editor_service_(hmi_editor_service), + logic_editor_service_(logic_editor_service), + current_page_id_(std::move(current_page_id)), + current_logic_id_(std::move(current_logic_id)), + selected_control_id_(selected_control_id), + selected_logic_node_id_(selected_logic_node_id), + refresh_project_ui_(std::move(refresh_project_ui)), + result_reporter_(std::move(result_reporter)), + status_reporter_(std::move(status_reporter)) +{ +} + +void PropertyPanelController::configure() +{ + ui_.bindingAreaComboBox->setItemData(0, -1); + ui_.bindingAreaComboBox->setItemData(1, 0); + ui_.bindingAreaComboBox->setItemData(2, 1); + ui_.buttonOperationComboBox->setItemData( + 0, static_cast(HmiButtonOperation::SetOn)); + ui_.buttonOperationComboBox->setItemData( + 1, static_cast(HmiButtonOperation::SetOff)); + ui_.buttonOperationComboBox->setItemData( + 2, static_cast(HmiButtonOperation::Toggle)); + ui_.buttonOperationComboBox->setItemData( + 3, static_cast(HmiButtonOperation::MomentaryOn)); + for (int index = 0; index < ui_.logicComparisonComboBox->count(); ++index) + { + ui_.logicComparisonComboBox->setItemData(index, index); + } + QObject::connect(ui_.applyPropertiesButton, &QPushButton::clicked, + &parent_, [this] { applySelectedControlProperties(); }); + QObject::connect(ui_.applyLogicPropertiesButton, &QPushButton::clicked, + &parent_, [this] { applySelectedLogicNodeProperties(); }); + ui_.targetPageLabel->setVisible(false); + ui_.targetPageComboBox->setVisible(false); + showControlProperties({}); +} + +void PropertyPanelController::bindEditorWidgets( + HmiEditorWidget &hmi_editor_widget, + LogicEditorWidget &logic_editor_widget) +{ + hmi_editor_widget_ = &hmi_editor_widget; + logic_editor_widget_ = &logic_editor_widget; + QObject::connect(&hmi_editor_widget, &HmiEditorWidget::controlSelected, + &parent_, [this](const QString &id) + { + showControlProperties(toUtf8(id)); + }); + QObject::connect(&hmi_editor_widget, &HmiEditorWidget::controlChanged, + &parent_, [this](const QString &id) + { + showControlProperties(toUtf8(id)); + refresh_project_ui_(); + }); + QObject::connect(&hmi_editor_widget, &HmiEditorWidget::editorError, + &parent_, [this](const QString &message) + { + handleHmiEditorError(message); + }); + QObject::connect(&logic_editor_widget, &LogicEditorWidget::nodeSelected, + &parent_, [this](const QString &id) + { + showLogicNodeProperties(toUtf8(id)); + }); + QObject::connect(&logic_editor_widget, &LogicEditorWidget::graphChanged, + &parent_, [this] + { + refresh_project_ui_(); + }); + QObject::connect(&logic_editor_widget, &LogicEditorWidget::editorError, + &parent_, [this](const QString &message) + { + handleLogicEditorError(message); + }); +} + +void PropertyPanelController::showControlProperties(const std::string &control_id) +{ + ui_.propertyStack->setCurrentWidget(ui_.hmiPropertiesPage); + selected_control_id_ = control_id; + const HmiControl *control = hmi_editor_service_.findControl( + current_page_id_(), control_id); + const bool has_control = control != nullptr; + ui_.selectionValueLabel->setText( + has_control ? fromUtf8(control->id) + + (control->isConfigured() ? QString{} + : control->type == HmiControlType::PageJump + ? QObject::tr("(未配置)") : QObject::tr("(未绑定)")) + : QObject::tr("未选择")); + + for (QWidget *widget : {static_cast(ui_.controlIdEdit), + static_cast(ui_.controlTextEdit), + static_cast(ui_.controlXSpinBox), + static_cast(ui_.controlYSpinBox), + static_cast(ui_.controlWidthSpinBox), + static_cast(ui_.controlHeightSpinBox), + static_cast(ui_.bindingAreaComboBox), + static_cast(ui_.bindingIndexSpinBox), + static_cast(ui_.targetPageComboBox), + static_cast(ui_.buttonOperationComboBox), + static_cast(ui_.applyPropertiesButton)}) + { + widget->setEnabled(has_control); + } + + if (!has_control) + { + ui_.bindingAreaLabel->setVisible(false); + ui_.bindingAreaComboBox->setVisible(false); + ui_.bindingIndexLabel->setVisible(false); + ui_.bindingIndexSpinBox->setVisible(false); + ui_.targetPageLabel->setVisible(false); + ui_.targetPageComboBox->setVisible(false); + ui_.buttonOperationLabel->setVisible(false); + ui_.buttonOperationComboBox->setVisible(false); + return; + } + + ui_.controlIdEdit->setText(fromUtf8(control->id)); + ui_.controlTextEdit->setText(fromUtf8(control->text)); + ui_.controlXSpinBox->setValue(control->bounds.x); + ui_.controlYSpinBox->setValue(control->bounds.y); + ui_.controlWidthSpinBox->setValue(control->bounds.width); + ui_.controlHeightSpinBox->setValue(control->bounds.height); + ui_.bindingAreaComboBox->setCurrentIndex(!control->binding.has_value() ? 0 + : control->binding->area() == RegisterArea::M ? 1 : 2); + ui_.bindingIndexSpinBox->setValue( + control->binding.has_value() ? control->binding->index() : 0); + const bool has_binding = control->type != HmiControlType::Label + && control->type != HmiControlType::PageJump + && control->type != HmiControlType::AlarmList; + ui_.bindingAreaLabel->setVisible(has_binding); + ui_.bindingAreaComboBox->setVisible(has_binding); + ui_.bindingIndexLabel->setVisible(has_binding); + ui_.bindingIndexSpinBox->setVisible(has_binding); + + const bool is_button = control->type == HmiControlType::Button; + ui_.buttonOperationLabel->setVisible(is_button); + ui_.buttonOperationComboBox->setVisible(is_button); + ui_.buttonOperationComboBox->setEnabled(is_button); + ui_.buttonOperationComboBox->setCurrentIndex( + ui_.buttonOperationComboBox->findData( + static_cast(control->buttonOperation))); + + const bool is_page_jump = control->type == HmiControlType::PageJump; + ui_.targetPageLabel->setVisible(is_page_jump); + ui_.targetPageComboBox->setVisible(is_page_jump); + ui_.targetPageComboBox->setEnabled(is_page_jump); + ui_.targetPageComboBox->clear(); + if (is_page_jump) + { + for (const HmiPage &page : project_service_.project().hmiPages) + { + ui_.targetPageComboBox->addItem( + fromUtf8(page.name), fromUtf8(page.id)); + } + const std::string target_id = control->pageJump.has_value() + ? control->pageJump->targetPageId : std::string{}; + ui_.targetPageComboBox->setCurrentIndex( + ui_.targetPageComboBox->findData(fromUtf8(target_id))); + } +} + +void PropertyPanelController::showLogicNodeProperties(const std::string &node_id) +{ + ui_.propertyStack->setCurrentWidget(ui_.logicPropertiesPage); + selected_logic_node_id_ = node_id; + const LogicNode *node = logic_editor_service_.findNode( + current_logic_id_(), node_id); + const bool has_node = node != nullptr; + ui_.selectionValueLabel->setText( + has_node ? fromUtf8(node->id) : QObject::tr("未选择")); + for (QWidget *widget : {static_cast(ui_.logicNodeIdEdit), + static_cast(ui_.logicAddressSpinBox), + static_cast(ui_.logicModeComboBox), + static_cast(ui_.logicComparisonComboBox), + static_cast(ui_.logicValueSpinBox), + static_cast(ui_.logicPresetSpinBox), + static_cast(ui_.applyLogicPropertiesButton)}) + { + widget->setEnabled(has_node); + } + ui_.logicNodeTypeLabel->setEnabled(has_node); + ui_.logicAddressAreaLabel->setEnabled(has_node); + if (!has_node) + { + ui_.logicNodeIdEdit->clear(); + ui_.logicNodeTypeLabel->clear(); + ui_.logicAddressAreaLabel->clear(); + ui_.logicModeComboBox->clear(); + ui_.logicPresetSpinBox->setValue(TonNodeConfig::kMinimumPresetMs); + return; + } + + ui_.logicNodeIdEdit->setText(fromUtf8(node->id)); + ui_.logicModeComboBox->clear(); + ui_.logicComparisonComboBox->setEnabled(false); + ui_.logicValueSpinBox->setEnabled(false); + ui_.logicPresetSpinBox->setEnabled(false); + if (const auto *contact = std::get_if(&node->config)) + { + ui_.logicNodeTypeLabel->setText(QObject::tr("触点")); + ui_.logicAddressAreaLabel->setText(QStringLiteral("M")); + ui_.logicAddressSpinBox->setValue(contact->address.index()); + ui_.logicModeComboBox->addItem( + QObject::tr("常开"), static_cast(ContactMode::NormallyOpen)); + ui_.logicModeComboBox->addItem( + QObject::tr("常闭"), static_cast(ContactMode::NormallyClosed)); + ui_.logicModeComboBox->setCurrentIndex( + ui_.logicModeComboBox->findData(static_cast(contact->mode))); + ui_.logicModeComboBox->setEnabled(true); + } + else if (const auto *edge = std::get_if(&node->config)) + { + ui_.logicNodeTypeLabel->setText(QObject::tr("边沿触点")); + ui_.logicAddressAreaLabel->setText(QStringLiteral("M")); + ui_.logicAddressSpinBox->setValue(edge->address.index()); + ui_.logicModeComboBox->addItem( + QObject::tr("上升沿"), static_cast(EdgeMode::Rising)); + ui_.logicModeComboBox->addItem( + QObject::tr("下降沿"), static_cast(EdgeMode::Falling)); + ui_.logicModeComboBox->setCurrentIndex( + ui_.logicModeComboBox->findData(static_cast(edge->mode))); + ui_.logicModeComboBox->setEnabled(true); + } + else if (const auto *timer = std::get_if(&node->config)) + { + ui_.logicNodeTypeLabel->setText(QObject::tr("T 触点")); + ui_.logicAddressAreaLabel->setText(QStringLiteral("T")); + ui_.logicAddressSpinBox->setValue(timer->address.index()); + ui_.logicModeComboBox->addItem( + QObject::tr("常开"), static_cast(ContactMode::NormallyOpen)); + ui_.logicModeComboBox->addItem( + QObject::tr("常闭"), static_cast(ContactMode::NormallyClosed)); + ui_.logicModeComboBox->setCurrentIndex( + ui_.logicModeComboBox->findData(static_cast(timer->mode))); + ui_.logicModeComboBox->setEnabled(true); + } + else if (const auto *counter_contact = + std::get_if(&node->config)) + { + ui_.logicNodeTypeLabel->setText(QObject::tr("C 触点")); + ui_.logicAddressAreaLabel->setText(QStringLiteral("C")); + ui_.logicAddressSpinBox->setValue(counter_contact->address.index()); + ui_.logicModeComboBox->addItem( + QObject::tr("常开"), static_cast(ContactMode::NormallyOpen)); + ui_.logicModeComboBox->addItem( + QObject::tr("常闭"), static_cast(ContactMode::NormallyClosed)); + ui_.logicModeComboBox->setCurrentIndex(ui_.logicModeComboBox->findData( + static_cast(counter_contact->mode))); + ui_.logicModeComboBox->setEnabled(true); + } + else if (const auto *coil = std::get_if(&node->config)) + { + ui_.logicNodeTypeLabel->setText(QObject::tr("线圈")); + ui_.logicAddressAreaLabel->setText(QStringLiteral("M")); + ui_.logicAddressSpinBox->setValue(coil->address.index()); + ui_.logicModeComboBox->addItem( + QObject::tr("普通"), static_cast(CoilMode::Normal)); + ui_.logicModeComboBox->addItem( + QObject::tr("置位"), static_cast(CoilMode::Set)); + ui_.logicModeComboBox->addItem( + QObject::tr("复位"), static_cast(CoilMode::Reset)); + ui_.logicModeComboBox->setCurrentIndex( + ui_.logicModeComboBox->findData(static_cast(coil->mode))); + ui_.logicModeComboBox->setEnabled(true); + } + else if (const auto *compare = std::get_if(&node->config)) + { + ui_.logicNodeTypeLabel->setText(QObject::tr("D 值比较")); + ui_.logicAddressAreaLabel->setText(QStringLiteral("D")); + ui_.logicAddressSpinBox->setValue(compare->address.index()); + ui_.logicModeComboBox->addItem(QObject::tr("不适用")); + ui_.logicModeComboBox->setEnabled(false); + ui_.logicComparisonComboBox->setEnabled(true); + ui_.logicComparisonComboBox->setCurrentIndex( + ui_.logicComparisonComboBox->findData( + static_cast(compare->comparison))); + ui_.logicValueSpinBox->setEnabled(true); + ui_.logicValueSpinBox->setValue(compare->value); + } + else if (const auto *ton = std::get_if(&node->config)) + { + ui_.logicNodeTypeLabel->setText(QObject::tr("TON")); + ui_.logicAddressAreaLabel->setText(QStringLiteral("T")); + ui_.logicAddressSpinBox->setValue(ton->address.index()); + ui_.logicModeComboBox->addItem(QObject::tr("非保持接通延时")); + ui_.logicModeComboBox->setEnabled(false); + ui_.logicPresetSpinBox->setEnabled(true); + ui_.logicPresetSpinBox->setValue(ton->presetMs); + } + else if (const auto *counter = std::get_if(&node->config)) + { + ui_.logicNodeTypeLabel->setText( + counter->mode == CounterMode::Up ? QStringLiteral("CTU") + : QStringLiteral("CTD")); + ui_.logicAddressAreaLabel->setText(QStringLiteral("C")); + ui_.logicAddressSpinBox->setValue(counter->address.index()); + ui_.logicAddressSpinBox->setEnabled(false); + ui_.logicModeComboBox->addItem(QObject::tr("使用配置对话框编辑")); + ui_.logicModeComboBox->setEnabled(false); + } + else if (const auto *move = std::get_if(&node->config)) + { + ui_.logicNodeTypeLabel->setText(QStringLiteral("MOVE")); + ui_.logicAddressAreaLabel->setText(QStringLiteral("D")); + ui_.logicAddressSpinBox->setValue(move->destination.index()); + ui_.logicAddressSpinBox->setEnabled(false); + ui_.logicModeComboBox->addItem(QObject::tr("使用配置对话框编辑")); + ui_.logicModeComboBox->setEnabled(false); + } + else + { + const auto &arithmetic = std::get(node->config); + ui_.logicNodeTypeLabel->setText( + arithmetic.operation == ArithmeticOperation::Add + ? QStringLiteral("ADD") : QStringLiteral("SUB")); + ui_.logicAddressAreaLabel->setText(QStringLiteral("D")); + ui_.logicAddressSpinBox->setValue(arithmetic.destination.index()); + ui_.logicAddressSpinBox->setEnabled(false); + ui_.logicModeComboBox->addItem(QObject::tr("使用配置对话框编辑")); + ui_.logicModeComboBox->setEnabled(false); + } + if (!node->configured) + { + ui_.logicNodeTypeLabel->setText(QObject::tr("待配置:%1").arg( + ui_.logicNodeTypeLabel->text())); + } +} + +void PropertyPanelController::addHmiControl(HmiControlType type) +{ + const HmiEditorResult result = hmi_editor_service_.addControl( + current_page_id_(), type); + if (!result.succeeded) + { + reportFailure(QObject::tr("添加控件"), result.message); + return; + } + hmi_editor_widget_->reloadPage(); + hmi_editor_widget_->selectControl(result.id); + showControlProperties(result.id); + refresh_project_ui_(); + status_reporter_(QObject::tr("已添加%1").arg(controlTypeText(type)), 3000); +} + +void PropertyPanelController::deleteSelectedControl() +{ + if (selected_control_id_.empty()) + { + return; + } + const HmiEditorResult result = hmi_editor_service_.removeControl( + current_page_id_(), selected_control_id_); + if (!result.succeeded) + { + reportFailure(QObject::tr("删除控件"), result.message); + return; + } + selected_control_id_.clear(); + hmi_editor_widget_->reloadPage(); + showControlProperties({}); + refresh_project_ui_(); +} + +void PropertyPanelController::applySelectedControlProperties() +{ + const HmiControl *old_control = hmi_editor_service_.findControl( + current_page_id_(), selected_control_id_); + if (old_control == nullptr) + { + return; + } + HmiControl control = *old_control; + control.id = toUtf8(ui_.controlIdEdit->text()); + control.text = toUtf8(ui_.controlTextEdit->text()); + control.bounds.x = ui_.controlXSpinBox->value(); + control.bounds.y = ui_.controlYSpinBox->value(); + control.bounds.width = ui_.controlWidthSpinBox->value(); + control.bounds.height = ui_.controlHeightSpinBox->value(); + if (control.type == HmiControlType::Label + || control.type == HmiControlType::PageJump + || control.type == HmiControlType::AlarmList) + { + control.binding.reset(); + } + else + { + const int binding_area = ui_.bindingAreaComboBox->currentData().toInt(); + if (binding_area < 0) + { + control.binding.reset(); + } + else + { + const RegisterArea area = binding_area == 0 + ? RegisterArea::M : RegisterArea::D; + control.binding = RegisterAddress{ + area, ui_.bindingIndexSpinBox->value()}; + } + } + if (control.type == HmiControlType::Button) + { + control.buttonOperation = static_cast( + ui_.buttonOperationComboBox->currentData().toInt()); + } + if (control.type == HmiControlType::PageJump) + { + control.pageJump = HmiPageJumpConfig{ + toUtf8(ui_.targetPageComboBox->currentData().toString())}; + } + const HmiEditorResult result = hmi_editor_service_.updateControl( + current_page_id_(), selected_control_id_, control); + if (!result.succeeded) + { + reportFailure(QObject::tr("应用属性"), result.message); + return; + } + selected_control_id_ = result.id; + hmi_editor_widget_->reloadPage(); + hmi_editor_widget_->selectControl(selected_control_id_); + refresh_project_ui_(); + status_reporter_(QObject::tr("控件属性已更新"), 3000); +} + +void PropertyPanelController::applySelectedLogicNodeProperties() +{ + const LogicNode *node = logic_editor_service_.findNode( + current_logic_id_(), selected_logic_node_id_); + if (node == nullptr) + { + return; + } + + LogicNodeConfig config = node->config; + if (std::holds_alternative(config)) + { + config = ContactNodeConfig{ + RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()}, + static_cast(ui_.logicModeComboBox->currentData().toInt())}; + } + else if (std::holds_alternative(config)) + { + config = EdgeContactNodeConfig{ + RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()}, + static_cast(ui_.logicModeComboBox->currentData().toInt())}; + } + else if (std::holds_alternative(config)) + { + config = TimerContactNodeConfig{ + TimerAddress{ui_.logicAddressSpinBox->value()}, + static_cast(ui_.logicModeComboBox->currentData().toInt())}; + } + else if (std::holds_alternative(config)) + { + config = CounterContactNodeConfig{ + CounterAddress{ui_.logicAddressSpinBox->value()}, + static_cast(ui_.logicModeComboBox->currentData().toInt())}; + } + else if (std::holds_alternative(config)) + { + config = CoilNodeConfig{ + RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()}, + static_cast(ui_.logicModeComboBox->currentData().toInt())}; + } + else if (std::holds_alternative(config)) + { + config = CompareNodeConfig{ + RegisterAddress{RegisterArea::D, ui_.logicAddressSpinBox->value()}, + static_cast( + ui_.logicComparisonComboBox->currentData().toInt()), + static_cast(ui_.logicValueSpinBox->value())}; + } + else if (std::holds_alternative(config)) + { + config = TonNodeConfig{ + TimerAddress{ui_.logicAddressSpinBox->value()}, + ui_.logicPresetSpinBox->value()}; + } + else + { + LogicInstructionDialog dialog(node->config, &parent_); + if (dialog.exec() != QDialog::Accepted) + { + return; + } + config = dialog.config(); + } + + const LogicEditorResult result = logic_editor_service_.updateNodeConfig( + current_logic_id_(), selected_logic_node_id_, config); + if (!result.succeeded) + { + reportFailure(QObject::tr("应用逻辑属性"), result.message); + return; + } + logic_editor_widget_->reloadLogic(); + logic_editor_widget_->selectNode(selected_logic_node_id_); + showLogicNodeProperties(selected_logic_node_id_); + refresh_project_ui_(); + status_reporter_(QObject::tr("逻辑节点属性已更新,节点已配置"), 3000); +} + +void PropertyPanelController::handleHmiEditorError(const QString &message) const +{ + status_reporter_(message, 5000); +} + +void PropertyPanelController::handleLogicEditorError(const QString &message) const +{ + status_reporter_(message, 5000); +} + +void PropertyPanelController::reportFailure( + const QString &action, const std::string &message) const +{ + result_reporter_(action, fromUtf8(message), false); +} diff --git a/app/src/ui/property_panel_controller.h b/app/src/ui/property_panel_controller.h new file mode 100644 index 0000000..aec4f57 --- /dev/null +++ b/app/src/ui/property_panel_controller.h @@ -0,0 +1,77 @@ +#pragma once + +#include "domain/control_logic_model.h" +#include "domain/hmi_model.h" + +#include + +#include +#include + +class HmiEditorService; +class HmiEditorWidget; +class LogicEditorService; +class LogicEditorWidget; +class ProjectService; +class QWidget; + +namespace Ui { +class MainWindow; +} + +// 组织 HMI/梯形图选择状态、属性面板和编辑器信号 +class PropertyPanelController final +{ +public: + using ResultReporter = std::function; + using StatusReporter = std::function; + + PropertyPanelController( + QWidget &parent, + Ui::MainWindow &ui, + ProjectService &project_service, + HmiEditorService &hmi_editor_service, + LogicEditorService &logic_editor_service, + std::function current_page_id, + std::function current_logic_id, + std::string &selected_control_id, + std::string &selected_logic_node_id, + std::function refresh_project_ui, + ResultReporter result_reporter, + StatusReporter status_reporter); + + void configure(); + void bindEditorWidgets( + HmiEditorWidget &hmi_editor_widget, + LogicEditorWidget &logic_editor_widget); + + void showControlProperties(const std::string &control_id); + void showLogicNodeProperties(const std::string &node_id); + void addHmiControl(HmiControlType type); + void deleteSelectedControl(); + void applySelectedControlProperties(); + void applySelectedLogicNodeProperties(); + +private: + void handleHmiEditorError(const QString &message) const; + void handleLogicEditorError(const QString &message) const; + void reportFailure( + const QString &action, const std::string &message) const; + + QWidget &parent_; + Ui::MainWindow &ui_; + ProjectService &project_service_; + HmiEditorService &hmi_editor_service_; + LogicEditorService &logic_editor_service_; + std::function current_page_id_; + std::function current_logic_id_; + std::string &selected_control_id_; + std::string &selected_logic_node_id_; + std::function refresh_project_ui_; + ResultReporter result_reporter_; + StatusReporter status_reporter_; + HmiEditorWidget *hmi_editor_widget_ = nullptr; + LogicEditorWidget *logic_editor_widget_ = nullptr; +}; diff --git a/app/src/ui/runtime_panel_controller.cpp b/app/src/ui/runtime_panel_controller.cpp new file mode 100644 index 0000000..3563db2 --- /dev/null +++ b/app/src/ui/runtime_panel_controller.cpp @@ -0,0 +1,255 @@ +#include "runtime_panel_controller.h" + +#include "free_monitor_widget.h" +#include "hmi_editor_widget.h" +#include "logic_editor_widget.h" +#include "runtime_monitor_widget.h" +#include "services/alarm_service.h" +#include "services/hmi_editor_service.h" +#include "services/hmi_navigation_service.h" +#include "services/hmi_runtime_service.h" +#include "services/logic_editor_service.h" +#include "services/offline_simulation_service.h" +#include "services/project_service.h" +#include "services/register_monitor_service.h" +#include "services/runtime_mode_service.h" +#include "ui_main_window.h" + +#include +#include +#include +#include +#include + +#include + +namespace { + +QString fromUtf8(const std::string &value) +{ + return QString::fromUtf8(value.data(), static_cast(value.size())); +} + +} // namespace + +RuntimePanelController::RuntimePanelController( + QWidget &parent, + Ui::MainWindow &ui, + RuntimeModeService &runtime_mode_service, + ProjectService &project_service, + HmiEditorService &hmi_editor_service, + HmiRuntimeService &hmi_runtime_service, + LogicEditorService &logic_editor_service, + HmiNavigationService &hmi_navigation_service, + AlarmService &alarm_service, + RegisterMonitorService ®ister_monitor_service, + HmiEditorWidget &hmi_editor_widget, + LogicEditorWidget &logic_editor_widget, + QLabel &executor_status_label, + std::function current_logic_id, + std::function select_logic, + std::function show_logic_properties, + StatusReporter status_reporter, + OutputReporter output_reporter) + : parent_(parent), + ui_(ui), + runtime_mode_service_(runtime_mode_service), + project_service_(project_service), + hmi_editor_service_(hmi_editor_service), + hmi_runtime_service_(hmi_runtime_service), + logic_editor_service_(logic_editor_service), + hmi_navigation_service_(hmi_navigation_service), + alarm_service_(alarm_service), + register_monitor_service_(register_monitor_service), + hmi_editor_widget_(hmi_editor_widget), + logic_editor_widget_(logic_editor_widget), + executor_status_label_(executor_status_label), + current_logic_id_(std::move(current_logic_id)), + select_logic_(std::move(select_logic)), + show_logic_properties_(std::move(show_logic_properties)), + status_reporter_(std::move(status_reporter)), + output_reporter_(std::move(output_reporter)) +{ +} + +void RuntimePanelController::configure() +{ + runtime_monitor_widget_ = new RuntimeMonitorWidget( + hmi_editor_service_, + hmi_runtime_service_, + logic_editor_service_, + project_service_, + hmi_navigation_service_, + alarm_service_, + register_monitor_service_, + ui_.runtimeMonitorTab); + runtime_monitor_widget_->setObjectName(QStringLiteral("runtimeMonitorWidget")); + ui_.runtimeMonitorLayout->addWidget(runtime_monitor_widget_); + QObject::connect(runtime_monitor_widget_->freeMonitorWidget(), + &FreeMonitorWidget::monitorAddressesChanged, + &parent_, [this] + { + runtime_mode_service_.setMonitorAddresses( + register_monitor_service_.addresses()); + runtime_monitor_widget_->refreshValues( + runtime_mode_service_.mode(), + runtime_mode_service_.plcConnectionState()); + }); + QObject::connect(runtime_monitor_widget_->freeMonitorWidget(), + &FreeMonitorWidget::operationMessage, + &parent_, [this](const QString &message) + { + status_reporter_(message, 4000); + }); + QObject::connect(runtime_monitor_widget_, &RuntimeMonitorWidget::navigationFailed, + &parent_, [this](const QString &message) + { + status_reporter_(message, 5000); + output_reporter_(QObject::tr("页面导航失败:%1").arg(message)); + }); + ui_.editorTabWidget->setTabVisible(2, false); + + runtime_refresh_timer_ = new QTimer(&parent_); + runtime_refresh_timer_->setInterval(150); + QObject::connect(runtime_refresh_timer_, &QTimer::timeout, + &parent_, [this] { handleRuntimeTimer(); }); + runtime_refresh_timer_->start(); + QObject::connect(&runtime_mode_service_.offlineSimulationService(), + &OfflineSimulationService::stateChanged, + &parent_, [this] { handleSimulationStateChanged(); }, + Qt::QueuedConnection); + QObject::connect(&runtime_mode_service_.offlineSimulationService(), + &OfflineSimulationService::scanCompleted, + &parent_, [this] { handleScanCompleted(); }, + Qt::QueuedConnection); +} + +RuntimeMonitorWidget *RuntimePanelController::runtimeMonitorWidget() const +{ + return runtime_monitor_widget_; +} + +void RuntimePanelController::updateSimulationUi(bool report_fault) +{ + const bool offline = runtime_mode_service_.mode() + == ApplicationMode::OfflineRunning; + const bool online = runtime_mode_service_.mode() + == ApplicationMode::OnlineRunning; + const bool plc_connected = runtime_mode_service_.plcConnectionState() + == PlcConnectionState::Connected; + const SimulationState state = runtime_mode_service_.simulationState(); + const bool write_enabled = (online && plc_connected) + || (offline && state == SimulationState::Running); + hmi_editor_widget_.setRuntimeWriteEnabled(write_enabled); + if (runtime_monitor_widget_ != nullptr) + { + runtime_monitor_widget_->setHmiWriteEnabled(write_enabled); + runtime_monitor_widget_->refreshValues( + runtime_mode_service_.mode(), + runtime_mode_service_.plcConnectionState()); + } + + switch (state) + { + case SimulationState::Running: + { + executor_status_label_.setText( + QObject::tr("逻辑执行器:运行(%1 次)") + .arg(runtime_mode_service_.successfulScanCount())); + break; + } + case SimulationState::Faulted: + { + executor_status_label_.setText(QObject::tr("逻辑执行器:故障")); + if (!report_fault) + { + break; + } + const LogicScanResult &error = runtime_mode_service_.simulationError(); + if (!error.logicId.empty() + && logic_editor_service_.findLogic(error.logicId) != nullptr) + { + select_logic_(error.logicId); + } + const std::string logic_id = current_logic_id_(); + logic_editor_widget_.setLogicId(logic_id); + if (runtime_monitor_widget_ != nullptr) + { + runtime_monitor_widget_->setProjectObjects( + hmi_navigation_service_.currentPageId(), logic_id); + } + logic_editor_widget_.setRuntimeTrace( + runtime_mode_service_.offlineSimulationService() + .traceSnapshot().forLogic(logic_id), + error.nodeId); + if (runtime_monitor_widget_ != nullptr) + { + runtime_monitor_widget_->setLogicTrace( + runtime_mode_service_.offlineSimulationService().traceSnapshot(), + error.nodeId); + } + if (!error.nodeId.empty()) + { + logic_editor_widget_.selectNode(error.nodeId); + show_logic_properties_(error.nodeId); + } + QString message = QObject::tr("离线仿真异常:%1").arg(fromUtf8(error.message)); + if (!error.logicId.empty()) + { + message += QObject::tr(",逻辑 %1").arg(fromUtf8(error.logicId)); + } + if (!error.rungId.empty()) + { + message += QObject::tr(",网络 %1").arg(fromUtf8(error.rungId)); + } + if (!error.nodeId.empty()) + { + message += QObject::tr(",节点 %1").arg(fromUtf8(error.nodeId)); + } + status_reporter_(message, 0); + output_reporter_(message); + break; + } + case SimulationState::Stopped: + default: + { + executor_status_label_.setText(QObject::tr("逻辑执行器:停止")); + break; + } + } +} + +void RuntimePanelController::handleRuntimeTimer() +{ + if (runtime_mode_service_.mode() == ApplicationMode::Editing) + { + return; + } + alarm_service_.refresh(); + hmi_editor_widget_.refreshRuntimeValues(); + if (runtime_monitor_widget_ != nullptr) + { + runtime_monitor_widget_->refreshValues( + runtime_mode_service_.mode(), + runtime_mode_service_.plcConnectionState()); + } + updateSimulationUi(false); +} + +void RuntimePanelController::handleSimulationStateChanged() +{ + updateSimulationUi(true); +} + +void RuntimePanelController::handleScanCompleted() +{ + const std::string logic_id = current_logic_id_(); + logic_editor_widget_.setRuntimeTrace( + runtime_mode_service_.offlineSimulationService() + .traceSnapshot().forLogic(logic_id)); + if (runtime_monitor_widget_ != nullptr) + { + runtime_monitor_widget_->setLogicTrace( + runtime_mode_service_.offlineSimulationService().traceSnapshot()); + } +} diff --git a/app/src/ui/runtime_panel_controller.h b/app/src/ui/runtime_panel_controller.h new file mode 100644 index 0000000..491fd95 --- /dev/null +++ b/app/src/ui/runtime_panel_controller.h @@ -0,0 +1,88 @@ +#pragma once + +#include "domain/runtime_state.h" +#include "services/plc_communication_gateway.h" + +#include + +#include +#include +#include + +class AlarmService; +class HmiEditorService; +class HmiEditorWidget; +class HmiNavigationService; +class HmiRuntimeService; +class LogicEditorService; +class LogicEditorWidget; +class QLabel; +class ProjectService; +class RegisterMonitorService; +class RuntimeModeService; +class RuntimeMonitorWidget; +class QTimer; +class QWidget; + +namespace Ui { +class MainWindow; +} + +// 组织运行监控工作台、运行刷新定时器和离线执行器反馈 +class RuntimePanelController final +{ +public: + using StatusReporter = std::function; + using OutputReporter = std::function; + + RuntimePanelController( + QWidget &parent, + Ui::MainWindow &ui, + RuntimeModeService &runtime_mode_service, + ProjectService &project_service, + HmiEditorService &hmi_editor_service, + HmiRuntimeService &hmi_runtime_service, + LogicEditorService &logic_editor_service, + HmiNavigationService &hmi_navigation_service, + AlarmService &alarm_service, + RegisterMonitorService ®ister_monitor_service, + HmiEditorWidget &hmi_editor_widget, + LogicEditorWidget &logic_editor_widget, + QLabel &executor_status_label, + std::function current_logic_id, + std::function select_logic, + std::function show_logic_properties, + StatusReporter status_reporter, + OutputReporter output_reporter); + + void configure(); + void updateSimulationUi(bool report_fault); + RuntimeMonitorWidget *runtimeMonitorWidget() const; + +private: + void handleRuntimeTimer(); + void handleSimulationStateChanged(); + void handleScanCompleted(); + + QWidget &parent_; + Ui::MainWindow &ui_; + RuntimeModeService &runtime_mode_service_; + ProjectService &project_service_; + HmiEditorService &hmi_editor_service_; + HmiRuntimeService &hmi_runtime_service_; + LogicEditorService &logic_editor_service_; + HmiNavigationService &hmi_navigation_service_; + AlarmService &alarm_service_; + RegisterMonitorService ®ister_monitor_service_; + HmiEditorWidget &hmi_editor_widget_; + LogicEditorWidget &logic_editor_widget_; + QLabel &executor_status_label_; + std::function current_logic_id_; + std::function select_logic_; + std::function show_logic_properties_; + StatusReporter status_reporter_; + OutputReporter output_reporter_; + RuntimeMonitorWidget *runtime_monitor_widget_ = nullptr; + QTimer *runtime_refresh_timer_ = nullptr; +}; diff --git a/app/tests/main_window_tests.pro b/app/tests/main_window_tests.pro index 5644f6f..a0d6c22 100644 --- a/app/tests/main_window_tests.pro +++ b/app/tests/main_window_tests.pro @@ -16,6 +16,9 @@ SOURCES += \ ../src/ui/plc_connection_dialog.cpp \ ../src/ui/free_monitor_widget.cpp \ ../src/ui/runtime_monitor_widget.cpp \ + ../src/ui/project_workspace_controller.cpp \ + ../src/ui/property_panel_controller.cpp \ + ../src/ui/runtime_panel_controller.cpp \ ../src/ui/hmi_editor_widget.cpp \ ../src/ui/logic_editor_widget.cpp \ ../src/ui/logic_instruction_dialog.cpp \ @@ -48,6 +51,9 @@ HEADERS += \ ../src/ui/plc_connection_dialog.h \ ../src/ui/free_monitor_widget.h \ ../src/ui/runtime_monitor_widget.h \ + ../src/ui/project_workspace_controller.h \ + ../src/ui/property_panel_controller.h \ + ../src/ui/runtime_panel_controller.h \ ../src/ui/hmi_editor_widget.h \ ../src/ui/logic_editor_widget.h \ ../src/ui/logic_instruction_dialog.h \