| @@ -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 \ | |||
| @@ -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<HmiNavigationService> owned_hmi_navigation_service_; | |||
| 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; | |||
| 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_; | |||
| @@ -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/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 \ | |||