#include "property_panel_controller.h" #include "domain/hmi_control_registry.h" #include "hmi_editor_widget.h" #include "logic_editor_widget.h" #include "logic_instruction_dialog.h" #include "services/hmi_editor_service.h" #include "services/logic_editor_service.h" #include "services/project_service.h" #include "ui_main_window.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { QString fromUtf8(const std::string &value) { return QString::fromUtf8(value.data(), static_cast(value.size())); } std::string toUtf8(const QString &value) { const QByteArray bytes = value.toUtf8(); return std::string(bytes.constData(), static_cast(bytes.size())); } } // namespace PropertyPanelController::PropertyPanelController( QWidget &parent, Ui::MainWindow &ui, ProjectService &project_service, HmiEditorService &hmi_editor_service, LogicEditorService &logic_editor_service, std::function current_page_id, std::function current_logic_id, std::string &selected_control_id, std::string &selected_logic_node_id, std::function refresh_project_ui, ResultReporter result_reporter, StatusReporter status_reporter) : parent_(parent), ui_(ui), project_service_(project_service), hmi_editor_service_(hmi_editor_service), logic_editor_service_(logic_editor_service), current_page_id_(std::move(current_page_id)), current_logic_id_(std::move(current_logic_id)), selected_control_id_(selected_control_id), selected_logic_node_id_(selected_logic_node_id), refresh_project_ui_(std::move(refresh_project_ui)), result_reporter_(std::move(result_reporter)), status_reporter_(std::move(status_reporter)) { } void PropertyPanelController::configure() { ui_.bindingAreaComboBox->setItemData(0, -1); ui_.bindingAreaComboBox->setItemData(1, 0); ui_.bindingAreaComboBox->setItemData(2, 1); ui_.buttonOperationComboBox->setItemData( 0, static_cast(HmiButtonOperation::SetOn)); ui_.buttonOperationComboBox->setItemData( 1, static_cast(HmiButtonOperation::SetOff)); ui_.buttonOperationComboBox->setItemData( 2, static_cast(HmiButtonOperation::Toggle)); ui_.buttonOperationComboBox->setItemData( 3, static_cast(HmiButtonOperation::MomentaryOn)); for (int index = 0; index < ui_.logicComparisonComboBox->count(); ++index) { ui_.logicComparisonComboBox->setItemData(index, index); } QObject::connect(ui_.applyPropertiesButton, &QPushButton::clicked, &parent_, [this] { applySelectedControlProperties(); }); QObject::connect(ui_.applyLogicPropertiesButton, &QPushButton::clicked, &parent_, [this] { applySelectedLogicNodeProperties(); }); ui_.targetPageLabel->setVisible(false); ui_.targetPageComboBox->setVisible(false); showControlProperties({}); } void PropertyPanelController::bindEditorWidgets( HmiEditorWidget &hmi_editor_widget, LogicEditorWidget &logic_editor_widget) { hmi_editor_widget_ = &hmi_editor_widget; logic_editor_widget_ = &logic_editor_widget; QObject::connect(&hmi_editor_widget, &HmiEditorWidget::controlSelected, &parent_, [this](const QString &id) { showControlProperties(toUtf8(id)); }); QObject::connect(&hmi_editor_widget, &HmiEditorWidget::controlChanged, &parent_, [this](const QString &id) { showControlProperties(toUtf8(id)); refresh_project_ui_(); }); QObject::connect(&hmi_editor_widget, &HmiEditorWidget::editorError, &parent_, [this](const QString &message) { handleHmiEditorError(message); }); QObject::connect(&logic_editor_widget, &LogicEditorWidget::nodeSelected, &parent_, [this](const QString &id) { showLogicNodeProperties(toUtf8(id)); }); QObject::connect(&logic_editor_widget, &LogicEditorWidget::graphChanged, &parent_, [this] { refresh_project_ui_(); }); QObject::connect(&logic_editor_widget, &LogicEditorWidget::editorError, &parent_, [this](const QString &message) { handleLogicEditorError(message); }); } void PropertyPanelController::showControlProperties(const std::string &control_id) { ui_.propertyStack->setCurrentWidget(ui_.hmiPropertiesPage); selected_control_id_ = control_id; const HmiControl *control = hmi_editor_service_.findControl( current_page_id_(), control_id); const bool has_control = control != nullptr; const HmiControlDescriptor *descriptor = has_control ? findHmiControlDescriptor(control->type) : nullptr; ui_.selectionValueLabel->setText( has_control ? fromUtf8(control->id) + (control->isConfigured() ? QString{} : control->type == HmiControlType::PageJump ? QObject::tr("(未配置)") : QObject::tr("(未绑定)")) : QObject::tr("未选择")); for (QWidget *widget : {static_cast(ui_.controlIdEdit), static_cast(ui_.controlTextEdit), static_cast(ui_.controlXSpinBox), static_cast(ui_.controlYSpinBox), static_cast(ui_.controlWidthSpinBox), static_cast(ui_.controlHeightSpinBox), static_cast(ui_.bindingAreaComboBox), static_cast(ui_.bindingIndexSpinBox), static_cast(ui_.targetPageComboBox), static_cast(ui_.buttonOperationComboBox), static_cast(ui_.progressMinimumSpinBox), static_cast(ui_.progressMaximumSpinBox), static_cast(ui_.progressShowValueCheckBox), static_cast(ui_.applyPropertiesButton)}) { widget->setEnabled(has_control); } if (!has_control) { ui_.bindingAreaLabel->setVisible(false); ui_.bindingAreaComboBox->setVisible(false); ui_.bindingIndexLabel->setVisible(false); ui_.bindingIndexSpinBox->setVisible(false); ui_.targetPageLabel->setVisible(false); ui_.targetPageComboBox->setVisible(false); ui_.buttonOperationLabel->setVisible(false); ui_.buttonOperationComboBox->setVisible(false); ui_.progressMinimumLabel->setVisible(false); ui_.progressMinimumSpinBox->setVisible(false); ui_.progressMaximumLabel->setVisible(false); ui_.progressMaximumSpinBox->setVisible(false); ui_.progressShowValueLabel->setVisible(false); ui_.progressShowValueCheckBox->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 = descriptor != nullptr && descriptor->bindingKind != HmiBindingKind::None; ui_.bindingAreaLabel->setVisible(has_binding); ui_.bindingAreaComboBox->setVisible(has_binding); ui_.bindingIndexLabel->setVisible(has_binding); ui_.bindingIndexSpinBox->setVisible(has_binding); const bool is_button = control->type == HmiControlType::Button; ui_.buttonOperationLabel->setVisible(is_button); ui_.buttonOperationComboBox->setVisible(is_button); ui_.buttonOperationComboBox->setEnabled(is_button); ui_.buttonOperationComboBox->setCurrentIndex( ui_.buttonOperationComboBox->findData( static_cast(control->buttonOperation))); const bool is_page_jump = control->type == HmiControlType::PageJump; ui_.targetPageLabel->setVisible(is_page_jump); ui_.targetPageComboBox->setVisible(is_page_jump); ui_.targetPageComboBox->setEnabled(is_page_jump); ui_.targetPageComboBox->clear(); if (is_page_jump) { for (const HmiPage &page : project_service_.project().hmiPages) { ui_.targetPageComboBox->addItem( fromUtf8(page.name), fromUtf8(page.id)); } const std::string target_id = control->pageJump.has_value() ? control->pageJump->targetPageId : std::string{}; ui_.targetPageComboBox->setCurrentIndex( ui_.targetPageComboBox->findData(fromUtf8(target_id))); } const bool is_progress_bar = control->type == HmiControlType::ProgressBar; ui_.progressMinimumLabel->setVisible(is_progress_bar); ui_.progressMinimumSpinBox->setVisible(is_progress_bar); ui_.progressMaximumLabel->setVisible(is_progress_bar); ui_.progressMaximumSpinBox->setVisible(is_progress_bar); ui_.progressShowValueLabel->setVisible(is_progress_bar); ui_.progressShowValueCheckBox->setVisible(is_progress_bar); ui_.progressMinimumSpinBox->setEnabled(is_progress_bar); ui_.progressMaximumSpinBox->setEnabled(is_progress_bar); ui_.progressShowValueCheckBox->setEnabled(is_progress_bar); const HmiProgressBarConfig progress_config = control->progressBar.value_or( HmiProgressBarConfig{}); ui_.progressMinimumSpinBox->setValue(progress_config.minimumValue); ui_.progressMaximumSpinBox->setValue(progress_config.maximumValue); ui_.progressShowValueCheckBox->setChecked(progress_config.showValue); } void PropertyPanelController::showLogicNodeProperties(const std::string &node_id) { ui_.propertyStack->setCurrentWidget(ui_.logicPropertiesPage); selected_logic_node_id_ = node_id; const LogicNode *node = logic_editor_service_.findNode( current_logic_id_(), node_id); const bool has_node = node != nullptr; ui_.selectionValueLabel->setText( has_node ? fromUtf8(node->id) : QObject::tr("未选择")); for (QWidget *widget : {static_cast(ui_.logicNodeIdEdit), static_cast(ui_.logicAddressSpinBox), static_cast(ui_.logicModeComboBox), static_cast(ui_.logicComparisonComboBox), static_cast(ui_.logicValueSpinBox), static_cast(ui_.logicPresetSpinBox), static_cast(ui_.applyLogicPropertiesButton)}) { widget->setEnabled(has_node); } ui_.logicNodeTypeLabel->setEnabled(has_node); ui_.logicAddressAreaLabel->setEnabled(has_node); if (!has_node) { ui_.logicNodeIdEdit->clear(); ui_.logicNodeTypeLabel->clear(); ui_.logicAddressAreaLabel->clear(); ui_.logicModeComboBox->clear(); ui_.logicPresetSpinBox->setValue(TonNodeConfig::kMinimumPresetMs); return; } ui_.logicNodeIdEdit->setText(fromUtf8(node->id)); ui_.logicModeComboBox->clear(); ui_.logicComparisonComboBox->setEnabled(false); ui_.logicValueSpinBox->setEnabled(false); ui_.logicPresetSpinBox->setEnabled(false); if (const auto *contact = std::get_if(&node->config)) { ui_.logicNodeTypeLabel->setText(QObject::tr("触点")); ui_.logicAddressAreaLabel->setText(QStringLiteral("M")); ui_.logicAddressSpinBox->setValue(contact->address.index()); ui_.logicModeComboBox->addItem( QObject::tr("常开"), static_cast(ContactMode::NormallyOpen)); ui_.logicModeComboBox->addItem( QObject::tr("常闭"), static_cast(ContactMode::NormallyClosed)); ui_.logicModeComboBox->setCurrentIndex( ui_.logicModeComboBox->findData(static_cast(contact->mode))); ui_.logicModeComboBox->setEnabled(true); } else if (const auto *edge = std::get_if(&node->config)) { ui_.logicNodeTypeLabel->setText(QObject::tr("边沿触点")); ui_.logicAddressAreaLabel->setText(QStringLiteral("M")); ui_.logicAddressSpinBox->setValue(edge->address.index()); ui_.logicModeComboBox->addItem( QObject::tr("上升沿"), static_cast(EdgeMode::Rising)); ui_.logicModeComboBox->addItem( QObject::tr("下降沿"), static_cast(EdgeMode::Falling)); ui_.logicModeComboBox->setCurrentIndex( ui_.logicModeComboBox->findData(static_cast(edge->mode))); ui_.logicModeComboBox->setEnabled(true); } else if (const auto *timer = std::get_if(&node->config)) { ui_.logicNodeTypeLabel->setText(QObject::tr("T 触点")); ui_.logicAddressAreaLabel->setText(QStringLiteral("T")); ui_.logicAddressSpinBox->setValue(timer->address.index()); ui_.logicModeComboBox->addItem( QObject::tr("常开"), static_cast(ContactMode::NormallyOpen)); ui_.logicModeComboBox->addItem( QObject::tr("常闭"), static_cast(ContactMode::NormallyClosed)); ui_.logicModeComboBox->setCurrentIndex( ui_.logicModeComboBox->findData(static_cast(timer->mode))); ui_.logicModeComboBox->setEnabled(true); } else if (const auto *counter_contact = std::get_if(&node->config)) { ui_.logicNodeTypeLabel->setText(QObject::tr("C 触点")); ui_.logicAddressAreaLabel->setText(QStringLiteral("C")); ui_.logicAddressSpinBox->setValue(counter_contact->address.index()); ui_.logicModeComboBox->addItem( QObject::tr("常开"), static_cast(ContactMode::NormallyOpen)); ui_.logicModeComboBox->addItem( QObject::tr("常闭"), static_cast(ContactMode::NormallyClosed)); ui_.logicModeComboBox->setCurrentIndex(ui_.logicModeComboBox->findData( static_cast(counter_contact->mode))); ui_.logicModeComboBox->setEnabled(true); } else if (const auto *coil = std::get_if(&node->config)) { ui_.logicNodeTypeLabel->setText(QObject::tr("线圈")); ui_.logicAddressAreaLabel->setText(QStringLiteral("M")); ui_.logicAddressSpinBox->setValue(coil->address.index()); ui_.logicModeComboBox->addItem( QObject::tr("普通"), static_cast(CoilMode::Normal)); ui_.logicModeComboBox->addItem( QObject::tr("置位"), static_cast(CoilMode::Set)); ui_.logicModeComboBox->addItem( QObject::tr("复位"), static_cast(CoilMode::Reset)); ui_.logicModeComboBox->setCurrentIndex( ui_.logicModeComboBox->findData(static_cast(coil->mode))); ui_.logicModeComboBox->setEnabled(true); } else if (const auto *compare = std::get_if(&node->config)) { ui_.logicNodeTypeLabel->setText(QObject::tr("D 值比较")); ui_.logicAddressAreaLabel->setText(QStringLiteral("D")); ui_.logicAddressSpinBox->setValue(compare->address.index()); ui_.logicModeComboBox->addItem(QObject::tr("不适用")); ui_.logicModeComboBox->setEnabled(false); ui_.logicComparisonComboBox->setEnabled(true); ui_.logicComparisonComboBox->setCurrentIndex( ui_.logicComparisonComboBox->findData( static_cast(compare->comparison))); ui_.logicValueSpinBox->setEnabled(true); ui_.logicValueSpinBox->setValue(compare->value); } else if (const auto *ton = std::get_if(&node->config)) { ui_.logicNodeTypeLabel->setText(QObject::tr("TON")); ui_.logicAddressAreaLabel->setText(QStringLiteral("T")); ui_.logicAddressSpinBox->setValue(ton->address.index()); ui_.logicModeComboBox->addItem(QObject::tr("非保持接通延时")); ui_.logicModeComboBox->setEnabled(false); ui_.logicPresetSpinBox->setEnabled(true); ui_.logicPresetSpinBox->setValue(ton->presetMs); } else if (const auto *counter = std::get_if(&node->config)) { ui_.logicNodeTypeLabel->setText( counter->mode == CounterMode::Up ? QStringLiteral("CTU") : QStringLiteral("CTD")); ui_.logicAddressAreaLabel->setText(QStringLiteral("C")); ui_.logicAddressSpinBox->setValue(counter->address.index()); ui_.logicAddressSpinBox->setEnabled(false); ui_.logicModeComboBox->addItem(QObject::tr("使用配置对话框编辑")); ui_.logicModeComboBox->setEnabled(false); } else if (const auto *move = std::get_if(&node->config)) { ui_.logicNodeTypeLabel->setText(QStringLiteral("MOVE")); ui_.logicAddressAreaLabel->setText(QStringLiteral("D")); ui_.logicAddressSpinBox->setValue(move->destination.index()); ui_.logicAddressSpinBox->setEnabled(false); ui_.logicModeComboBox->addItem(QObject::tr("使用配置对话框编辑")); ui_.logicModeComboBox->setEnabled(false); } else { const auto &arithmetic = std::get(node->config); ui_.logicNodeTypeLabel->setText( arithmetic.operation == ArithmeticOperation::Add ? QStringLiteral("ADD") : QStringLiteral("SUB")); ui_.logicAddressAreaLabel->setText(QStringLiteral("D")); ui_.logicAddressSpinBox->setValue(arithmetic.destination.index()); ui_.logicAddressSpinBox->setEnabled(false); ui_.logicModeComboBox->addItem(QObject::tr("使用配置对话框编辑")); ui_.logicModeComboBox->setEnabled(false); } if (!node->configured) { ui_.logicNodeTypeLabel->setText(QObject::tr("待配置:%1").arg( ui_.logicNodeTypeLabel->text())); } } void PropertyPanelController::addHmiControl(HmiControlType type) { const HmiEditorResult result = hmi_editor_service_.addControl( current_page_id_(), type); if (!result.succeeded) { reportFailure(QObject::tr("添加控件"), result.message); return; } hmi_editor_widget_->reloadPage(); hmi_editor_widget_->selectControl(result.id); showControlProperties(result.id); refresh_project_ui_(); const HmiControlDescriptor *descriptor = findHmiControlDescriptor(type); const QString display_name = descriptor == nullptr ? QObject::tr("未知控件") : QString::fromUtf8(descriptor->displayName); status_reporter_(QObject::tr("已添加%1").arg(display_name), 3000); } void PropertyPanelController::deleteSelectedControl() { if (hmi_editor_widget_ == nullptr) { return; } const std::vector selected_ids = hmi_editor_widget_->selectedControlIds(); if (selected_ids.empty()) { return; } const HmiEditorResult result = hmi_editor_service_.removeControls( current_page_id_(), selected_ids); 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(); const HmiControlDescriptor *descriptor = findHmiControlDescriptor(control.type); if (descriptor == nullptr || descriptor->bindingKind == HmiBindingKind::None) { control.binding.reset(); } else { const int binding_area = ui_.bindingAreaComboBox->currentData().toInt(); if (binding_area < 0) { control.binding.reset(); } else { const RegisterArea area = binding_area == 0 ? RegisterArea::M : RegisterArea::D; control.binding = RegisterAddress{ area, ui_.bindingIndexSpinBox->value()}; } } if (control.type == HmiControlType::Button) { control.buttonOperation = static_cast( ui_.buttonOperationComboBox->currentData().toInt()); } if (control.type == HmiControlType::PageJump) { control.pageJump = HmiPageJumpConfig{ toUtf8(ui_.targetPageComboBox->currentData().toString())}; } if (control.type == HmiControlType::ProgressBar) { control.progressBar = HmiProgressBarConfig{ static_cast(ui_.progressMinimumSpinBox->value()), static_cast(ui_.progressMaximumSpinBox->value()), ui_.progressShowValueCheckBox->isChecked()}; } const HmiEditorResult result = hmi_editor_service_.updateControl( current_page_id_(), selected_control_id_, control); if (!result.succeeded) { reportFailure(QObject::tr("应用属性"), result.message); return; } const std::string updated_control_id = result.id; hmi_editor_widget_->reloadPage(); selected_control_id_ = updated_control_id; hmi_editor_widget_->selectControl(updated_control_id); showControlProperties(updated_control_id); refresh_project_ui_(); status_reporter_(QObject::tr("控件属性已更新"), 3000); } void PropertyPanelController::applySelectedLogicNodeProperties() { const LogicNode *node = logic_editor_service_.findNode( current_logic_id_(), selected_logic_node_id_); if (node == nullptr) { return; } LogicNodeConfig config = node->config; if (std::holds_alternative(config)) { config = ContactNodeConfig{ RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()}, static_cast(ui_.logicModeComboBox->currentData().toInt())}; } else if (std::holds_alternative(config)) { config = EdgeContactNodeConfig{ RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()}, static_cast(ui_.logicModeComboBox->currentData().toInt())}; } else if (std::holds_alternative(config)) { config = TimerContactNodeConfig{ TimerAddress{ui_.logicAddressSpinBox->value()}, static_cast(ui_.logicModeComboBox->currentData().toInt())}; } else if (std::holds_alternative(config)) { config = CounterContactNodeConfig{ CounterAddress{ui_.logicAddressSpinBox->value()}, static_cast(ui_.logicModeComboBox->currentData().toInt())}; } else if (std::holds_alternative(config)) { config = CoilNodeConfig{ RegisterAddress{RegisterArea::M, ui_.logicAddressSpinBox->value()}, static_cast(ui_.logicModeComboBox->currentData().toInt())}; } else if (std::holds_alternative(config)) { config = CompareNodeConfig{ RegisterAddress{RegisterArea::D, ui_.logicAddressSpinBox->value()}, static_cast( ui_.logicComparisonComboBox->currentData().toInt()), static_cast(ui_.logicValueSpinBox->value())}; } else if (std::holds_alternative(config)) { config = TonNodeConfig{ TimerAddress{ui_.logicAddressSpinBox->value()}, ui_.logicPresetSpinBox->value()}; } else { LogicInstructionDialog dialog(node->config, &parent_); if (dialog.exec() != QDialog::Accepted) { return; } config = dialog.config(); } const LogicEditorResult result = logic_editor_service_.updateNodeConfig( current_logic_id_(), selected_logic_node_id_, config); if (!result.succeeded) { reportFailure(QObject::tr("应用逻辑属性"), result.message); return; } logic_editor_widget_->reloadLogic(); logic_editor_widget_->selectNode(selected_logic_node_id_); showLogicNodeProperties(selected_logic_node_id_); refresh_project_ui_(); status_reporter_(QObject::tr("逻辑节点属性已更新,节点已配置"), 3000); } void PropertyPanelController::handleHmiEditorError(const QString &message) const { status_reporter_(message, 5000); } void PropertyPanelController::handleLogicEditorError(const QString &message) const { status_reporter_(message, 5000); } void PropertyPanelController::reportFailure( const QString &action, const std::string &message) const { result_reporter_(action, fromUtf8(message), false); }