| @@ -23,6 +23,9 @@ SOURCES += \ | |||||
| src/ui/plc_connection_dialog.cpp \ | src/ui/plc_connection_dialog.cpp \ | ||||
| src/ui/free_monitor_widget.cpp \ | src/ui/free_monitor_widget.cpp \ | ||||
| src/ui/runtime_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_address.cpp \ | ||||
| src/domain/register_repository.cpp \ | src/domain/register_repository.cpp \ | ||||
| src/domain/active_register_repository.cpp \ | src/domain/active_register_repository.cpp \ | ||||
| @@ -59,6 +62,9 @@ HEADERS += \ | |||||
| src/ui/plc_connection_dialog.h \ | src/ui/plc_connection_dialog.h \ | ||||
| src/ui/free_monitor_widget.h \ | src/ui/free_monitor_widget.h \ | ||||
| src/ui/runtime_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_address.h \ | ||||
| src/domain/register_repository.h \ | src/domain/register_repository.h \ | ||||
| src/domain/active_register_repository.h \ | src/domain/active_register_repository.h \ | ||||
| @@ -21,7 +21,6 @@ QT_BEGIN_NAMESPACE | |||||
| class QAction; | class QAction; | ||||
| class QActionGroup; | class QActionGroup; | ||||
| class QLabel; | class QLabel; | ||||
| class QTimer; | |||||
| namespace Ui { | namespace Ui { | ||||
| class MainWindow; | class MainWindow; | ||||
| @@ -41,6 +40,9 @@ class LogicEditorService; | |||||
| class LogicEditorWidget; | class LogicEditorWidget; | ||||
| class RegisterMonitorService; | class RegisterMonitorService; | ||||
| class RuntimeMonitorWidget; | class RuntimeMonitorWidget; | ||||
| class ProjectWorkspaceController; | |||||
| class PropertyPanelController; | |||||
| class RuntimePanelController; | |||||
| /** | /** | ||||
| * @brief 组织主界面区域并将用户操作转交给服务层 | * @brief 组织主界面区域并将用户操作转交给服务层 | ||||
| @@ -107,15 +109,7 @@ private: | |||||
| void configureRegisterComments(); | void configureRegisterComments(); | ||||
| // 刷新工程树和当前 HMI 页面信息 | // 刷新工程树和当前 HMI 页面信息 | ||||
| void refreshProjectUi(); | void refreshProjectUi(); | ||||
| void handleProjectTreeSelection(); | |||||
| void updateProjectTreeActions(); | void updateProjectTreeActions(); | ||||
| void addPage(); | |||||
| void addLogic(); | |||||
| void renameProjectItem(); | |||||
| void deleteProjectItem(); | |||||
| void moveProjectItem(int offset); | |||||
| void setSelectedPageAsInitial(); | |||||
| void toggleSelectedLogicEnabled(); | |||||
| // 显示指定 HMI 控件的可编辑属性 | // 显示指定 HMI 控件的可编辑属性 | ||||
| void showControlProperties(const std::string &control_id); | void showControlProperties(const std::string &control_id); | ||||
| // 显示指定控制逻辑节点的可编辑属性 | // 显示指定控制逻辑节点的可编辑属性 | ||||
| @@ -124,8 +118,6 @@ private: | |||||
| void addHmiControl(HmiControlType type); | void addHmiControl(HmiControlType type); | ||||
| // 删除当前选中的 HMI 控件 | // 删除当前选中的 HMI 控件 | ||||
| void deleteSelectedControl(); | void deleteSelectedControl(); | ||||
| // 将属性表单内容应用到当前选中控件 | |||||
| void applySelectedControlProperties(); | |||||
| // 在当前光标节点后添加串联条件,没有选择时追加到网络末尾 | // 在当前光标节点后添加串联条件,没有选择时追加到网络末尾 | ||||
| void addLogicCondition(const LogicNodeConfig &config); | void addLogicCondition(const LogicNodeConfig &config); | ||||
| // 为当前选中的连续逻辑范围建立并联支路 | // 为当前选中的连续逻辑范围建立并联支路 | ||||
| @@ -138,8 +130,6 @@ private: | |||||
| void editSelectedRungComment(); | void editSelectedRungComment(); | ||||
| // 删除当前选中的逻辑节点或网络 | // 删除当前选中的逻辑节点或网络 | ||||
| void deleteSelectedLogicObject(); | void deleteSelectedLogicObject(); | ||||
| // 将逻辑属性表单内容应用到当前选中节点 | |||||
| void applySelectedLogicNodeProperties(); | |||||
| void connectPlc(); | void connectPlc(); | ||||
| void disconnectPlc(); | void disconnectPlc(); | ||||
| // 合并同一事件循环内的 PLC 状态通知,避免重复刷新和重复日志 | // 合并同一事件循环内的 PLC 状态通知,避免重复刷新和重复日志 | ||||
| @@ -195,22 +185,16 @@ private: | |||||
| RegisterMonitorService ®ister_monitor_service_; | RegisterMonitorService ®ister_monitor_service_; | ||||
| std::unique_ptr<HmiNavigationService> owned_hmi_navigation_service_; | std::unique_ptr<HmiNavigationService> owned_hmi_navigation_service_; | ||||
| HmiNavigationService *hmi_navigation_service_ = nullptr; | HmiNavigationService *hmi_navigation_service_ = nullptr; | ||||
| std::unique_ptr<ProjectWorkspaceController> project_workspace_controller_; | |||||
| std::unique_ptr<PropertyPanelController> property_panel_controller_; | |||||
| std::unique_ptr<RuntimePanelController> runtime_panel_controller_; | |||||
| QActionGroup *mode_action_group_ = nullptr; | QActionGroup *mode_action_group_ = nullptr; | ||||
| HmiEditorWidget *hmi_editor_widget_ = nullptr; | HmiEditorWidget *hmi_editor_widget_ = nullptr; | ||||
| LogicEditorWidget *logic_editor_widget_ = nullptr; | LogicEditorWidget *logic_editor_widget_ = nullptr; | ||||
| RuntimeMonitorWidget *runtime_monitor_widget_ = nullptr; | RuntimeMonitorWidget *runtime_monitor_widget_ = nullptr; | ||||
| QTimer *runtime_refresh_timer_ = nullptr; | |||||
| QLabel *mode_status_label_ = nullptr; | QLabel *mode_status_label_ = nullptr; | ||||
| QLabel *register_status_label_ = nullptr; | QLabel *register_status_label_ = nullptr; | ||||
| QLabel *executor_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_; | PlcSerialConfiguration plc_configuration_; | ||||
| std::string selected_control_id_; | std::string selected_control_id_; | ||||
| std::string selected_logic_node_id_; | std::string selected_logic_node_id_; | ||||
| @@ -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 <QAction> | |||||
| #include <QInputDialog> | |||||
| #include <QLineEdit> | |||||
| #include <QObject> | |||||
| #include <QSignalBlocker> | |||||
| #include <QTabWidget> | |||||
| #include <QTreeWidget> | |||||
| #include <QTreeWidgetItem> | |||||
| #include <QWidget> | |||||
| #include <utility> | |||||
| 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<int>(value.size())); | |||||
| } | |||||
| std::string toUtf8(const QString &value) | |||||
| { | |||||
| const QByteArray bytes = value.toUtf8(); | |||||
| return std::string(bytes.constData(), static_cast<std::size_t>(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<void(const std::string &)> show_control_properties, | |||||
| std::function<void(const std::string &)> 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<int>(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<int>(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<int>(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<int>(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<ItemKind>( | |||||
| 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<ItemKind>(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<ItemKind>( | |||||
| 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<ItemKind>( | |||||
| 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<ItemKind>( | |||||
| 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<ItemKind>(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<ItemKind>(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); | |||||
| } | |||||
| @@ -0,0 +1,104 @@ | |||||
| #pragma once | |||||
| #include "domain/hmi_model.h" | |||||
| #include "domain/runtime_state.h" | |||||
| #include <QString> | |||||
| #include <functional> | |||||
| #include <string> | |||||
| 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<void( | |||||
| const QString &action, const QString &message, bool succeeded)>; | |||||
| using StatusReporter = std::function<void( | |||||
| const QString &message, int timeout_ms)>; | |||||
| 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<void(const std::string &)> show_control_properties, | |||||
| std::function<void(const std::string &)> 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<void(const std::string &)> show_control_properties_; | |||||
| std::function<void(const std::string &)> 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; | |||||
| }; | |||||
| @@ -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 <QComboBox> | |||||
| #include <QLabel> | |||||
| #include <QPushButton> | |||||
| #include <QDialog> | |||||
| #include <QObject> | |||||
| #include <QSpinBox> | |||||
| #include <QStackedWidget> | |||||
| #include <QWidget> | |||||
| #include <cstdint> | |||||
| #include <utility> | |||||
| namespace { | |||||
| QString fromUtf8(const std::string &value) | |||||
| { | |||||
| return QString::fromUtf8(value.data(), static_cast<int>(value.size())); | |||||
| } | |||||
| std::string toUtf8(const QString &value) | |||||
| { | |||||
| const QByteArray bytes = value.toUtf8(); | |||||
| return std::string(bytes.constData(), static_cast<std::size_t>(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<std::string()> current_page_id, | |||||
| std::function<std::string()> current_logic_id, | |||||
| std::string &selected_control_id, | |||||
| std::string &selected_logic_node_id, | |||||
| std::function<void()> 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<int>(HmiButtonOperation::SetOn)); | |||||
| ui_.buttonOperationComboBox->setItemData( | |||||
| 1, static_cast<int>(HmiButtonOperation::SetOff)); | |||||
| ui_.buttonOperationComboBox->setItemData( | |||||
| 2, static_cast<int>(HmiButtonOperation::Toggle)); | |||||
| ui_.buttonOperationComboBox->setItemData( | |||||
| 3, static_cast<int>(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<QWidget *>(ui_.controlIdEdit), | |||||
| static_cast<QWidget *>(ui_.controlTextEdit), | |||||
| static_cast<QWidget *>(ui_.controlXSpinBox), | |||||
| static_cast<QWidget *>(ui_.controlYSpinBox), | |||||
| static_cast<QWidget *>(ui_.controlWidthSpinBox), | |||||
| static_cast<QWidget *>(ui_.controlHeightSpinBox), | |||||
| static_cast<QWidget *>(ui_.bindingAreaComboBox), | |||||
| static_cast<QWidget *>(ui_.bindingIndexSpinBox), | |||||
| static_cast<QWidget *>(ui_.targetPageComboBox), | |||||
| static_cast<QWidget *>(ui_.buttonOperationComboBox), | |||||
| static_cast<QWidget *>(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<int>(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<QWidget *>(ui_.logicNodeIdEdit), | |||||
| static_cast<QWidget *>(ui_.logicAddressSpinBox), | |||||
| static_cast<QWidget *>(ui_.logicModeComboBox), | |||||
| static_cast<QWidget *>(ui_.logicComparisonComboBox), | |||||
| static_cast<QWidget *>(ui_.logicValueSpinBox), | |||||
| static_cast<QWidget *>(ui_.logicPresetSpinBox), | |||||
| static_cast<QWidget *>(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<ContactNodeConfig>(&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<int>(ContactMode::NormallyOpen)); | |||||
| ui_.logicModeComboBox->addItem( | |||||
| QObject::tr("常闭"), static_cast<int>(ContactMode::NormallyClosed)); | |||||
| ui_.logicModeComboBox->setCurrentIndex( | |||||
| ui_.logicModeComboBox->findData(static_cast<int>(contact->mode))); | |||||
| ui_.logicModeComboBox->setEnabled(true); | |||||
| } | |||||
| else if (const auto *edge = std::get_if<EdgeContactNodeConfig>(&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<int>(EdgeMode::Rising)); | |||||
| ui_.logicModeComboBox->addItem( | |||||
| QObject::tr("下降沿"), static_cast<int>(EdgeMode::Falling)); | |||||
| ui_.logicModeComboBox->setCurrentIndex( | |||||
| ui_.logicModeComboBox->findData(static_cast<int>(edge->mode))); | |||||
| ui_.logicModeComboBox->setEnabled(true); | |||||
| } | |||||
| else if (const auto *timer = std::get_if<TimerContactNodeConfig>(&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<int>(ContactMode::NormallyOpen)); | |||||
| ui_.logicModeComboBox->addItem( | |||||
| QObject::tr("常闭"), static_cast<int>(ContactMode::NormallyClosed)); | |||||
| ui_.logicModeComboBox->setCurrentIndex( | |||||
| ui_.logicModeComboBox->findData(static_cast<int>(timer->mode))); | |||||
| ui_.logicModeComboBox->setEnabled(true); | |||||
| } | |||||
| else if (const auto *counter_contact = | |||||
| std::get_if<CounterContactNodeConfig>(&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<int>(ContactMode::NormallyOpen)); | |||||
| ui_.logicModeComboBox->addItem( | |||||
| QObject::tr("常闭"), static_cast<int>(ContactMode::NormallyClosed)); | |||||
| ui_.logicModeComboBox->setCurrentIndex(ui_.logicModeComboBox->findData( | |||||
| static_cast<int>(counter_contact->mode))); | |||||
| ui_.logicModeComboBox->setEnabled(true); | |||||
| } | |||||
| else if (const auto *coil = std::get_if<CoilNodeConfig>(&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<int>(CoilMode::Normal)); | |||||
| ui_.logicModeComboBox->addItem( | |||||
| QObject::tr("置位"), static_cast<int>(CoilMode::Set)); | |||||
| ui_.logicModeComboBox->addItem( | |||||
| QObject::tr("复位"), static_cast<int>(CoilMode::Reset)); | |||||
| ui_.logicModeComboBox->setCurrentIndex( | |||||
| ui_.logicModeComboBox->findData(static_cast<int>(coil->mode))); | |||||
| ui_.logicModeComboBox->setEnabled(true); | |||||
| } | |||||
| else if (const auto *compare = std::get_if<CompareNodeConfig>(&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<int>(compare->comparison))); | |||||
| ui_.logicValueSpinBox->setEnabled(true); | |||||
| ui_.logicValueSpinBox->setValue(compare->value); | |||||
| } | |||||
| else if (const auto *ton = std::get_if<TonNodeConfig>(&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<CounterNodeConfig>(&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<MoveNodeConfig>(&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<ArithmeticNodeConfig>(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<HmiButtonOperation>( | |||||
| 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<ContactNodeConfig>(config)) | |||||
| { | |||||
| config = ContactNodeConfig{ | |||||
| RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()}, | |||||
| static_cast<ContactMode>(ui_.logicModeComboBox->currentData().toInt())}; | |||||
| } | |||||
| else if (std::holds_alternative<EdgeContactNodeConfig>(config)) | |||||
| { | |||||
| config = EdgeContactNodeConfig{ | |||||
| RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()}, | |||||
| static_cast<EdgeMode>(ui_.logicModeComboBox->currentData().toInt())}; | |||||
| } | |||||
| else if (std::holds_alternative<TimerContactNodeConfig>(config)) | |||||
| { | |||||
| config = TimerContactNodeConfig{ | |||||
| TimerAddress{ui_.logicAddressSpinBox->value()}, | |||||
| static_cast<ContactMode>(ui_.logicModeComboBox->currentData().toInt())}; | |||||
| } | |||||
| else if (std::holds_alternative<CounterContactNodeConfig>(config)) | |||||
| { | |||||
| config = CounterContactNodeConfig{ | |||||
| CounterAddress{ui_.logicAddressSpinBox->value()}, | |||||
| static_cast<ContactMode>(ui_.logicModeComboBox->currentData().toInt())}; | |||||
| } | |||||
| else if (std::holds_alternative<CoilNodeConfig>(config)) | |||||
| { | |||||
| config = CoilNodeConfig{ | |||||
| RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()}, | |||||
| static_cast<CoilMode>(ui_.logicModeComboBox->currentData().toInt())}; | |||||
| } | |||||
| else if (std::holds_alternative<CompareNodeConfig>(config)) | |||||
| { | |||||
| config = CompareNodeConfig{ | |||||
| RegisterAddress{RegisterArea::D, ui_.logicAddressSpinBox->value()}, | |||||
| static_cast<ComparisonOperator>( | |||||
| ui_.logicComparisonComboBox->currentData().toInt()), | |||||
| static_cast<std::int16_t>(ui_.logicValueSpinBox->value())}; | |||||
| } | |||||
| else if (std::holds_alternative<TonNodeConfig>(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); | |||||
| } | |||||
| @@ -0,0 +1,77 @@ | |||||
| #pragma once | |||||
| #include "domain/control_logic_model.h" | |||||
| #include "domain/hmi_model.h" | |||||
| #include <QString> | |||||
| #include <functional> | |||||
| #include <string> | |||||
| 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<void( | |||||
| const QString &action, const QString &message, bool succeeded)>; | |||||
| using StatusReporter = std::function<void( | |||||
| const QString &message, int timeout_ms)>; | |||||
| PropertyPanelController( | |||||
| QWidget &parent, | |||||
| Ui::MainWindow &ui, | |||||
| ProjectService &project_service, | |||||
| HmiEditorService &hmi_editor_service, | |||||
| LogicEditorService &logic_editor_service, | |||||
| std::function<std::string()> current_page_id, | |||||
| std::function<std::string()> current_logic_id, | |||||
| std::string &selected_control_id, | |||||
| std::string &selected_logic_node_id, | |||||
| std::function<void()> 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<std::string()> current_page_id_; | |||||
| std::function<std::string()> current_logic_id_; | |||||
| std::string &selected_control_id_; | |||||
| std::string &selected_logic_node_id_; | |||||
| std::function<void()> refresh_project_ui_; | |||||
| ResultReporter result_reporter_; | |||||
| StatusReporter status_reporter_; | |||||
| HmiEditorWidget *hmi_editor_widget_ = nullptr; | |||||
| LogicEditorWidget *logic_editor_widget_ = nullptr; | |||||
| }; | |||||
| @@ -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 <QListWidget> | |||||
| #include <QLabel> | |||||
| #include <QObject> | |||||
| #include <QTimer> | |||||
| #include <QWidget> | |||||
| #include <utility> | |||||
| namespace { | |||||
| QString fromUtf8(const std::string &value) | |||||
| { | |||||
| return QString::fromUtf8(value.data(), static_cast<int>(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<std::string()> current_logic_id, | |||||
| std::function<void(const std::string &)> select_logic, | |||||
| std::function<void(const std::string &)> 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()); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,88 @@ | |||||
| #pragma once | |||||
| #include "domain/runtime_state.h" | |||||
| #include "services/plc_communication_gateway.h" | |||||
| #include <QString> | |||||
| #include <functional> | |||||
| #include <memory> | |||||
| #include <string> | |||||
| 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<void( | |||||
| const QString &message, int timeout_ms)>; | |||||
| using OutputReporter = std::function<void(const QString &message)>; | |||||
| 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<std::string()> current_logic_id, | |||||
| std::function<void(const std::string &)> select_logic, | |||||
| std::function<void(const std::string &)> 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<std::string()> current_logic_id_; | |||||
| std::function<void(const std::string &)> select_logic_; | |||||
| std::function<void(const std::string &)> show_logic_properties_; | |||||
| StatusReporter status_reporter_; | |||||
| OutputReporter output_reporter_; | |||||
| RuntimeMonitorWidget *runtime_monitor_widget_ = nullptr; | |||||
| QTimer *runtime_refresh_timer_ = nullptr; | |||||
| }; | |||||
| @@ -16,6 +16,9 @@ SOURCES += \ | |||||
| ../src/ui/plc_connection_dialog.cpp \ | ../src/ui/plc_connection_dialog.cpp \ | ||||
| ../src/ui/free_monitor_widget.cpp \ | ../src/ui/free_monitor_widget.cpp \ | ||||
| ../src/ui/runtime_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/hmi_editor_widget.cpp \ | ||||
| ../src/ui/logic_editor_widget.cpp \ | ../src/ui/logic_editor_widget.cpp \ | ||||
| ../src/ui/logic_instruction_dialog.cpp \ | ../src/ui/logic_instruction_dialog.cpp \ | ||||
| @@ -48,6 +51,9 @@ HEADERS += \ | |||||
| ../src/ui/plc_connection_dialog.h \ | ../src/ui/plc_connection_dialog.h \ | ||||
| ../src/ui/free_monitor_widget.h \ | ../src/ui/free_monitor_widget.h \ | ||||
| ../src/ui/runtime_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/hmi_editor_widget.h \ | ||||
| ../src/ui/logic_editor_widget.h \ | ../src/ui/logic_editor_widget.h \ | ||||
| ../src/ui/logic_instruction_dialog.h \ | ../src/ui/logic_instruction_dialog.h \ | ||||