#include "main_window.h" #include "hmi_editor_widget.h" #include "alarm_configuration_dialog.h" #include "register_comment_dialog.h" #include "logic_editor_widget.h" #include "logic_instruction_dialog.h" #include "plc_connection_dialog.h" #include "runtime_monitor_widget.h" #include "toolbar_icon_factory.h" #include "project_workspace_controller.h" #include "property_panel_controller.h" #include "runtime_panel_controller.h" #include "services/hmi_editor_service.h" #include "services/hmi_navigation_service.h" #include "services/hmi_runtime_service.h" #include "services/alarm_editor_service.h" #include "services/alarm_service.h" #include "services/logic_editor_service.h" #include "services/project_service.h" #include "services/runtime_mode_service.h" #include "services/register_monitor_service.h" #include "services/register_comment_service.h" #include "ui_main_window.h" #include "domain/project_limits.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { bool isTextEditingObject(QObject *object) { QWidget *widget = qobject_cast(object); while (widget != nullptr) { if (qobject_cast(widget) != nullptr || qobject_cast(widget) != nullptr || qobject_cast(widget) != nullptr || qobject_cast(widget) != nullptr) { return true; } widget = widget->parentWidget(); } return false; } bool isEditorShortcut(const QKeyEvent &event) { return event.matches(QKeySequence::Undo) || event.matches(QKeySequence::Redo) || (event.modifiers() == Qt::NoModifier && (event.key() == Qt::Key_Delete || event.key() == Qt::Key_Escape)); } QString modeText(ApplicationMode mode) { switch (mode) { case ApplicationMode::Editing: { return MainWindow::tr("编辑态"); } case ApplicationMode::OfflineRunning: { return MainWindow::tr("离线运行态"); } case ApplicationMode::OnlineRunning: { return MainWindow::tr("真机运行态"); } default: { return MainWindow::tr("未知状态"); } } } QString transitionErrorText(ModeTransitionError error) { switch (error) { case ModeTransitionError::MustReturnToEditing: { return MainWindow::tr("请先返回编辑态,再切换运行模式"); } case ModeTransitionError::InitialPlcReadRequired: { return MainWindow::tr("真机运行前必须连接 PLC 并完成首次读取"); } case ModeTransitionError::AlreadyInRequestedMode: { return MainWindow::tr("当前已处于所选模式"); } case ModeTransitionError::ProjectNotReady: { return MainWindow::tr("工程存在未配置或未完成的 HMI/梯形图节点,暂不能进入离线运行态"); } case ModeTransitionError::SimulationStartFailed: { return MainWindow::tr("离线逻辑执行器预检或启动失败"); } case ModeTransitionError::None: default: { return MainWindow::tr("模式切换失败"); } } } std::string toUtf8(const QString &value) { const QByteArray bytes = value.toUtf8(); return std::string(bytes.constData(), static_cast(bytes.size())); } QString fromUtf8(const std::string &value) { return QString::fromUtf8(value.data(), static_cast(value.size())); } QString plcStatusText(const RuntimeModeService &service) { switch (service.plcConnectionState()) { case PlcConnectionState::Connecting: return MainWindow::tr("PLC 正在连接"); case PlcConnectionState::Connected: return service.initialPlcReadCompleted() ? MainWindow::tr("PLC 已连接,首次读取完成") : MainWindow::tr("PLC 已连接,正在读取工程使用的 M/D 地址"); case PlcConnectionState::Recovering: return MainWindow::tr("PLC 通信已恢复,正在重新读取工程使用的 M/D 地址"); case PlcConnectionState::Faulted: { const QString error = fromUtf8(service.plcError()); return error.isEmpty() ? MainWindow::tr("PLC 通信故障") : MainWindow::tr("PLC 通信故障:%1").arg(error); } case PlcConnectionState::Disconnected: { const QString error = fromUtf8(service.plcError()); return error.isEmpty() ? MainWindow::tr("PLC 已断开") : MainWindow::tr("PLC 已断开:%1").arg(error); } default: { return MainWindow::tr("PLC 已断开"); } } } QToolButton *addToolbarMenu( QToolBar *toolbar, const QString &text, const QString &object_name, const QIcon &icon, const QList &actions) { auto *button = new QToolButton(toolbar); button->setObjectName(object_name); button->setText(text); button->setIcon(icon); button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); button->setPopupMode(QToolButton::InstantPopup); auto *menu = new QMenu(button); menu->addActions(actions); button->setMenu(menu); toolbar->addWidget(button); return button; } } // namespace MainWindow::MainWindow( RuntimeModeService &runtime_mode_service, ProjectService &project_service, HmiEditorService &hmi_editor_service, LogicEditorService &logic_editor_service, HmiRuntimeService &hmi_runtime_service, AlarmEditorService &alarm_editor_service, AlarmService &alarm_service, RegisterCommentService ®ister_comment_service, RegisterMonitorService ®ister_monitor_service, QWidget *parent) : QMainWindow(parent), ui_(std::make_unique()), runtime_mode_service_(runtime_mode_service), project_service_(project_service), hmi_editor_service_(hmi_editor_service), logic_editor_service_(logic_editor_service), hmi_runtime_service_(hmi_runtime_service), alarm_editor_service_(alarm_editor_service), alarm_service_(alarm_service), register_comment_service_(register_comment_service), register_monitor_service_(register_monitor_service), owned_hmi_navigation_service_( std::make_unique(project_service)), hmi_navigation_service_(owned_hmi_navigation_service_.get()) { initializeUi(); } MainWindow::MainWindow( RuntimeModeService &runtime_mode_service, ProjectService &project_service, HmiEditorService &hmi_editor_service, LogicEditorService &logic_editor_service, HmiRuntimeService &hmi_runtime_service, AlarmEditorService &alarm_editor_service, AlarmService &alarm_service, HmiNavigationService &hmi_navigation_service, RegisterCommentService ®ister_comment_service, RegisterMonitorService ®ister_monitor_service, QWidget *parent) : QMainWindow(parent), ui_(std::make_unique()), runtime_mode_service_(runtime_mode_service), project_service_(project_service), hmi_editor_service_(hmi_editor_service), logic_editor_service_(logic_editor_service), hmi_runtime_service_(hmi_runtime_service), alarm_editor_service_(alarm_editor_service), alarm_service_(alarm_service), register_comment_service_(register_comment_service), register_monitor_service_(register_monitor_service), hmi_navigation_service_(&hmi_navigation_service) { initializeUi(); } void MainWindow::initializeUi() { ui_->setupUi(this); qApp->installEventFilter(this); configureAppearance(); property_panel_controller_ = std::make_unique( *this, *ui_, project_service_, hmi_editor_service_, logic_editor_service_, [this] { return current_hmi_page_id_; }, [this] { return current_logic_id_; }, selected_control_id_, selected_logic_node_id_, [this] { refreshProjectUi(); }, [this](const QString &action, const QString &message, bool succeeded) { showProjectResult(action, message, succeeded); }, [this](const QString &message, int timeout_ms) { statusBar()->showMessage(message, timeout_ms); }); configureActions(); configurePropertyEditor(); configurePlcConnection(); configureAlarms(); configureRegisterComments(); configureHmiEditor(); configureLogicEditor(); configureRuntimeMonitor(); configureProjectTree(); runtime_mode_service_.setPlcStatusChangedCallback( [this] { schedulePlcStatusUpdate(); }); hmi_editor_service_.ensureDefaultPage(); logic_editor_service_.ensureDefaultLogic(); clearEditorHistories(); current_hmi_page_id_ = hmi_editor_service_.firstPageId(); current_logic_id_ = logic_editor_service_.firstLogicId(); showControlProperties({}); refreshProjectUi(); updateModeUi(tr("系统已进入编辑态")); } MainWindow::~MainWindow() { qApp->removeEventFilter(this); runtime_mode_service_.setPlcStatusChangedCallback({}); } bool MainWindow::eventFilter(QObject *watched, QEvent *event) { if (event->type() == QEvent::ShortcutOverride && isTextEditingObject(watched)) { auto *key_event = static_cast(event); if (isEditorShortcut(*key_event)) { event->accept(); return true; } } return QMainWindow::eventFilter(watched, event); } void MainWindow::closeEvent(QCloseEvent *event) { if (!confirmSaveBeforeDestructiveAction()) { event->ignore(); return; } if (runtime_panel_controller_ != nullptr) { runtime_panel_controller_->closeForApplicationExit(); } QMainWindow::closeEvent(event); } const std::string &MainWindow::currentHmiPageId() const { return current_hmi_page_id_; } const std::string &MainWindow::currentLogicId() const { return current_logic_id_; } void MainWindow::configureActions() { mode_action_group_ = new QActionGroup(this); mode_action_group_->setExclusive(true); mode_action_group_->addAction(ui_->editingModeAction); mode_action_group_->addAction(ui_->offlineModeAction); mode_action_group_->addAction(ui_->onlineModeAction); connect(ui_->editingModeAction, &QAction::triggered, this, [this] { requestMode(ApplicationMode::Editing); }); connect(ui_->offlineModeAction, &QAction::triggered, this, [this] { requestMode(ApplicationMode::OfflineRunning); }); connect(ui_->onlineModeAction, &QAction::triggered, this, [this] { requestMode(ApplicationMode::OnlineRunning); }); connect(ui_->exitAction, &QAction::triggered, this, [this] { close(); }); connect(ui_->newProjectAction, &QAction::triggered, this, &MainWindow::createNewProject); connect(ui_->saveProjectAction, &QAction::triggered, this, &MainWindow::saveProject); connect(ui_->saveAsProjectAction, &QAction::triggered, this, &MainWindow::saveProjectAs); connect(ui_->loadProjectAction, &QAction::triggered, this, &MainWindow::loadProject); connect(ui_->undoAction, &QAction::triggered, this, &MainWindow::undoActiveEditor); connect(ui_->redoAction, &QAction::triggered, this, &MainWindow::redoActiveEditor); connect(ui_->deleteSelectionAction, &QAction::triggered, this, &MainWindow::deleteActiveSelection); connect(ui_->clearSelectionAction, &QAction::triggered, this, &MainWindow::clearActiveSelection); connect(ui_->addButtonAction, &QAction::triggered, this, [this] { addHmiControl(HmiControlType::Button); }); connect(ui_->addIndicatorAction, &QAction::triggered, this, [this] { addHmiControl(HmiControlType::Indicator); }); connect(ui_->addNumericDisplayAction, &QAction::triggered, this, [this] { addHmiControl(HmiControlType::NumericDisplay); }); connect(ui_->addNumericInputAction, &QAction::triggered, this, [this] { addHmiControl(HmiControlType::NumericInput); }); connect(ui_->addProgressBarAction, &QAction::triggered, this, [this] { addHmiControl(HmiControlType::ProgressBar); }); connect(ui_->addLabelAction, &QAction::triggered, this, [this] { addHmiControl(HmiControlType::Label); }); connect(ui_->addPageJumpAction, &QAction::triggered, this, [this] { addHmiControl(HmiControlType::PageJump); }); connect(ui_->addAlarmListAction, &QAction::triggered, this, [this] { addHmiControl(HmiControlType::AlarmList); }); connect(ui_->deleteControlAction, &QAction::triggered, this, &MainWindow::deleteSelectedControl); addToolbarMenu( ui_->hmiToolBar, tr("更多控件"), QStringLiteral("hmiMoreControlsButton"), makeUiIcon(UiIcon::More), QList{ ui_->addProgressBarAction, ui_->addPageJumpAction, ui_->addAlarmListAction, ui_->configureAlarmsAction}); connect(ui_->addRungAction, &QAction::triggered, this, &MainWindow::addLogicRung); connect(ui_->insertHorizontalWireAction, &QAction::triggered, this, &MainWindow::addLogicHorizontalWire); connect(ui_->insertVerticalWireAction, &QAction::triggered, this, &MainWindow::addLogicVerticalWire); connect(ui_->deleteHorizontalWireAction, &QAction::triggered, this, &MainWindow::deleteLogicHorizontalWire); connect(ui_->deleteVerticalWireAction, &QAction::triggered, this, &MainWindow::deleteLogicVerticalWire); connect(ui_->parallelInsertAction, &QAction::triggered, this, [this] { addLogicParallelBranch(ContactNodeConfig{ RegisterAddress{RegisterArea::M, 0}, ContactMode::NormallyOpen}); }); QMenu *parallel_menu = new QMenu(this); QAction *parallel_open = parallel_menu->addAction(tr("并联常开触点")); QAction *parallel_closed = parallel_menu->addAction(tr("并联常闭触点")); QAction *parallel_rising = parallel_menu->addAction(tr("并联上升沿触点")); QAction *parallel_falling = parallel_menu->addAction(tr("并联下降沿触点")); QAction *parallel_timer = parallel_menu->addAction(tr("并联 T 触点")); QAction *parallel_counter = parallel_menu->addAction(tr("并联 C 触点")); QAction *parallel_compare = parallel_menu->addAction(tr("并联比较条件")); connect(parallel_open, &QAction::triggered, this, [this] { addLogicParallelBranch(ContactNodeConfig{ RegisterAddress{RegisterArea::M, 0}, ContactMode::NormallyOpen}); }); connect(parallel_closed, &QAction::triggered, this, [this] { addLogicParallelBranch(ContactNodeConfig{ RegisterAddress{RegisterArea::M, 0}, ContactMode::NormallyClosed}); }); connect(parallel_rising, &QAction::triggered, this, [this] { addLogicParallelBranch(EdgeContactNodeConfig{ RegisterAddress{RegisterArea::M, 0}, EdgeMode::Rising}); }); connect(parallel_falling, &QAction::triggered, this, [this] { addLogicParallelBranch(EdgeContactNodeConfig{ RegisterAddress{RegisterArea::M, 0}, EdgeMode::Falling}); }); connect(parallel_timer, &QAction::triggered, this, [this] { addLogicParallelBranch(TimerContactNodeConfig{ TimerAddress{0}, ContactMode::NormallyOpen}); }); connect(parallel_counter, &QAction::triggered, this, [this] { addLogicParallelBranch(CounterContactNodeConfig{ CounterAddress{0}, ContactMode::NormallyOpen}); }); connect(parallel_compare, &QAction::triggered, this, [this] { addLogicParallelBranch(CompareNodeConfig{ RegisterAddress{RegisterArea::D, 0}, ComparisonOperator::Equal, 0}); }); ui_->parallelInsertAction->setMenu(parallel_menu); if (QToolButton *parallel_button = qobject_cast( ui_->logicToolBar->widgetForAction(ui_->parallelInsertAction))) { parallel_button->setPopupMode(QToolButton::MenuButtonPopup); } connect(ui_->addNormallyOpenAction, &QAction::triggered, this, [this] { addLogicCondition(ContactNodeConfig{ RegisterAddress{RegisterArea::M, 0}, ContactMode::NormallyOpen}); }); connect(ui_->addNormallyClosedAction, &QAction::triggered, this, [this] { addLogicCondition(ContactNodeConfig{ RegisterAddress{RegisterArea::M, 0}, ContactMode::NormallyClosed}); }); connect(ui_->addRisingEdgeAction, &QAction::triggered, this, [this] { addLogicCondition(EdgeContactNodeConfig{ RegisterAddress{RegisterArea::M, 0}, EdgeMode::Rising}); }); connect(ui_->addFallingEdgeAction, &QAction::triggered, this, [this] { addLogicCondition(EdgeContactNodeConfig{ RegisterAddress{RegisterArea::M, 0}, EdgeMode::Falling}); }); connect(ui_->addTimerContactAction, &QAction::triggered, this, [this] { addLogicCondition(TimerContactNodeConfig{ TimerAddress{0}, ContactMode::NormallyOpen}); }); connect(ui_->addCounterContactAction, &QAction::triggered, this, [this] { addLogicCondition(CounterContactNodeConfig{ CounterAddress{0}, ContactMode::NormallyOpen}); }); connect(ui_->addNormalCoilAction, &QAction::triggered, this, [this] { setLogicOutput(CoilNodeConfig{ RegisterAddress{RegisterArea::M, 0}, CoilMode::Normal}); }); connect(ui_->addSetCoilAction, &QAction::triggered, this, [this] { setLogicOutput(CoilNodeConfig{ RegisterAddress{RegisterArea::M, 0}, CoilMode::Set}); }); connect(ui_->addResetCoilAction, &QAction::triggered, this, [this] { setLogicOutput(CoilNodeConfig{ RegisterAddress{RegisterArea::M, 0}, CoilMode::Reset}); }); connect(ui_->addTonAction, &QAction::triggered, this, [this] { setLogicOutput(TonNodeConfig{TimerAddress{0}, 1000}); }); connect(ui_->addCtuAction, &QAction::triggered, this, [this] { configureAndSetLogicOutput(CounterNodeConfig{ CounterAddress{0}, CounterMode::Up, RegisterAddress{RegisterArea::D, 0}, WordOperand{ WordOperandKind::Constant, RegisterAddress{RegisterArea::D, 0}, 10}, RegisterAddress{RegisterArea::M, 0}}); }); connect(ui_->addCtdAction, &QAction::triggered, this, [this] { configureAndSetLogicOutput(CounterNodeConfig{ CounterAddress{0}, CounterMode::Down, RegisterAddress{RegisterArea::D, 0}, WordOperand{ WordOperandKind::Constant, RegisterAddress{RegisterArea::D, 0}, 10}, RegisterAddress{RegisterArea::M, 0}}); }); connect(ui_->addMoveAction, &QAction::triggered, this, [this] { configureAndSetLogicOutput(MoveNodeConfig{ WordOperand{ WordOperandKind::Constant, RegisterAddress{RegisterArea::D, 0}, 0}, RegisterAddress{RegisterArea::D, 0}}); }); connect(ui_->addAddAction, &QAction::triggered, this, [this] { configureAndSetLogicOutput(ArithmeticNodeConfig{ ArithmeticOperation::Add, WordOperand{ WordOperandKind::Register, RegisterAddress{RegisterArea::D, 0}, 0}, WordOperand{ WordOperandKind::Constant, RegisterAddress{RegisterArea::D, 0}, 1}, RegisterAddress{RegisterArea::D, 0}}); }); connect(ui_->addSubAction, &QAction::triggered, this, [this] { configureAndSetLogicOutput(ArithmeticNodeConfig{ ArithmeticOperation::Subtract, WordOperand{ WordOperandKind::Register, RegisterAddress{RegisterArea::D, 0}, 0}, WordOperand{ WordOperandKind::Constant, RegisterAddress{RegisterArea::D, 0}, 1}, RegisterAddress{RegisterArea::D, 0}}); }); connect(ui_->addCompareAction, &QAction::triggered, this, [this] { addLogicCondition(CompareNodeConfig{ RegisterAddress{RegisterArea::D, 0}, ComparisonOperator::Equal, 0}); }); connect(ui_->deleteLogicAction, &QAction::triggered, this, &MainWindow::deleteSelectedLogicObject); connect(ui_->editRungCommentAction, &QAction::triggered, this, &MainWindow::editSelectedRungComment); addToolbarMenu( ui_->logicToolBar, tr("更多触点"), QStringLiteral("logicContactMenuButton"), makeUiIcon(UiIcon::NormallyOpenContact), QList{ ui_->addRisingEdgeAction, ui_->addFallingEdgeAction, ui_->addTimerContactAction, ui_->addCounterContactAction}); addToolbarMenu( ui_->logicToolBar, tr("更多输出"), QStringLiteral("logicOutputMenuButton"), makeUiIcon(UiIcon::Coil), QList{ui_->addSetCoilAction, ui_->addResetCoilAction}); addToolbarMenu( ui_->logicToolBar, tr("定时/计数"), QStringLiteral("logicTimerCounterMenuButton"), makeUiIcon(UiIcon::Ton), QList{ui_->addTonAction, ui_->addCtuAction, ui_->addCtdAction}); addToolbarMenu( ui_->logicToolBar, tr("数据运算"), QStringLiteral("logicDataMenuButton"), makeUiIcon(UiIcon::Move), QList{ ui_->addMoveAction, ui_->addAddAction, ui_->addSubAction, ui_->addCompareAction}); connect(ui_->editorTabWidget, &QTabWidget::currentChanged, this, [this](int index) { ui_->hmiToolBar->setVisible(index == 0); ui_->logicToolBar->setVisible(index == 1); if (index == 0) { showControlProperties(selected_control_id_); } else if (index == 1) { showLogicNodeProperties(selected_logic_node_id_); } if (index == 0 || index == 1) { refreshProjectUi(); } }); ui_->hmiToolBar->setVisible(ui_->editorTabWidget->currentIndex() == 0); ui_->logicToolBar->setVisible(ui_->editorTabWidget->currentIndex() == 1); ui_->viewMenu->addAction(ui_->projectDock->toggleViewAction()); ui_->viewMenu->addAction(ui_->propertiesDock->toggleViewAction()); ui_->viewMenu->addAction(ui_->outputDock->toggleViewAction()); ui_->viewMenu->addAction(ui_->hmiToolBar->toggleViewAction()); ui_->viewMenu->addAction(ui_->logicToolBar->toggleViewAction()); ui_->viewMenu->addSeparator(); ui_->viewMenu->addAction(ui_->modeToolBar->toggleViewAction()); } void MainWindow::configureAppearance() { setDockNestingEnabled(true); setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); ui_->editingModeAction->setIcon(makeUiIcon(UiIcon::Edit)); ui_->offlineModeAction->setIcon(makeUiIcon(UiIcon::RunOffline)); ui_->onlineModeAction->setIcon(makeUiIcon(UiIcon::RunOnline)); ui_->newProjectAction->setIcon(makeUiIcon(UiIcon::NewDocument)); ui_->saveProjectAction->setIcon(makeUiIcon(UiIcon::Save)); ui_->saveAsProjectAction->setIcon(makeUiIcon(UiIcon::SaveAs)); ui_->loadProjectAction->setIcon(makeUiIcon(UiIcon::Open)); ui_->undoAction->setIcon(makeUiIcon(UiIcon::Undo)); ui_->redoAction->setIcon(makeUiIcon(UiIcon::Redo)); ui_->deleteSelectionAction->setIcon(makeUiIcon(UiIcon::Delete)); ui_->clearSelectionAction->setIcon(makeUiIcon(UiIcon::ClearList)); ui_->exitAction->setIcon(makeUiIcon(UiIcon::Exit)); ui_->configurePlcAction->setIcon(makeUiIcon(UiIcon::PlcSettings)); ui_->disconnectPlcAction->setIcon(makeUiIcon(UiIcon::Disconnect)); ui_->configureRegisterCommentsAction->setIcon( makeUiIcon(UiIcon::RegisterComments)); ui_->addButtonAction->setIcon(makeUiIcon(UiIcon::HmiButton)); ui_->addIndicatorAction->setIcon(makeUiIcon(UiIcon::Indicator)); ui_->addNumericDisplayAction->setIcon(makeUiIcon(UiIcon::NumericDisplay)); ui_->addNumericInputAction->setIcon(makeUiIcon(UiIcon::NumericInput)); ui_->addProgressBarAction->setIcon(makeUiIcon(UiIcon::ProgressBar)); ui_->addLabelAction->setIcon(makeUiIcon(UiIcon::Text)); ui_->addPageJumpAction->setIcon(makeUiIcon(UiIcon::PageJump)); ui_->addAlarmListAction->setIcon(makeUiIcon(UiIcon::AlarmList)); ui_->configureAlarmsAction->setIcon(makeUiIcon(UiIcon::AlarmSettings)); ui_->deleteControlAction->setIcon(makeUiIcon(UiIcon::Delete)); ui_->addRungAction->setIcon(makeUiIcon(UiIcon::AddRung)); ui_->parallelInsertAction->setIcon(makeUiIcon(UiIcon::ParallelBranch)); ui_->insertHorizontalWireAction->setIcon(makeUiIcon(UiIcon::HorizontalWire)); ui_->insertVerticalWireAction->setIcon(makeUiIcon(UiIcon::VerticalWire)); ui_->deleteHorizontalWireAction->setIcon(makeUiIcon(UiIcon::Delete)); ui_->deleteVerticalWireAction->setIcon(makeUiIcon(UiIcon::Delete)); ui_->addNormallyOpenAction->setIcon(makeUiIcon(UiIcon::NormallyOpenContact)); ui_->addNormallyClosedAction->setIcon(makeUiIcon(UiIcon::NormallyClosedContact)); ui_->addRisingEdgeAction->setIcon(makeUiIcon(UiIcon::RisingEdgeContact)); ui_->addFallingEdgeAction->setIcon(makeUiIcon(UiIcon::FallingEdgeContact)); ui_->addTimerContactAction->setIcon(makeUiIcon(UiIcon::TimerContact)); ui_->addCounterContactAction->setIcon(makeUiIcon(UiIcon::CounterContact)); ui_->addNormalCoilAction->setIcon(makeUiIcon(UiIcon::Coil)); ui_->addSetCoilAction->setIcon(makeUiIcon(UiIcon::SetCoil)); ui_->addResetCoilAction->setIcon(makeUiIcon(UiIcon::ResetCoil)); ui_->addTonAction->setIcon(makeUiIcon(UiIcon::Ton)); ui_->addCtuAction->setIcon(makeUiIcon(UiIcon::Ctu)); ui_->addCtdAction->setIcon(makeUiIcon(UiIcon::Ctd)); ui_->addMoveAction->setIcon(makeUiIcon(UiIcon::Move)); ui_->addAddAction->setIcon(makeUiIcon(UiIcon::Add)); ui_->addSubAction->setIcon(makeUiIcon(UiIcon::Subtract)); ui_->addCompareAction->setIcon(makeUiIcon(UiIcon::Compare)); ui_->editRungCommentAction->setIcon(makeUiIcon(UiIcon::Comment)); ui_->deleteLogicAction->setIcon(makeUiIcon(UiIcon::Delete)); ui_->editorTabWidget->setTabIcon(0, makeUiIcon(UiIcon::HmiPage)); ui_->editorTabWidget->setTabIcon(1, makeUiIcon(UiIcon::Logic)); ui_->outputDock->setMaximumHeight(220); mode_status_label_ = new QLabel(this); mode_status_label_->setObjectName(QStringLiteral("modeStatusLabel")); mode_status_label_->setMinimumWidth(88); mode_status_label_->setAlignment(Qt::AlignCenter); register_status_label_ = new QLabel(this); register_status_label_->setObjectName(QStringLiteral("registerStatusLabel")); register_status_label_->setMinimumWidth(132); executor_status_label_ = new QLabel(this); executor_status_label_->setObjectName(QStringLiteral("executorStatusLabel")); executor_status_label_->setMinimumWidth(132); statusBar()->addPermanentWidget(mode_status_label_); statusBar()->addPermanentWidget(register_status_label_); statusBar()->addPermanentWidget(executor_status_label_); setStyleSheet(QStringLiteral( "QMainWindow { background: #f3f5f7; }" "QToolBar { background: #ffffff; border: 0; border-bottom: 1px solid #cbd2d9;" " padding: 4px; spacing: 3px; }" "QToolButton { min-height: 26px; padding: 3px 9px; border: 1px solid transparent; }" "QToolButton:hover { background: #edf2f6; border-color: #c5ced6; }" "QToolButton:checked { background: #dcece3; border-color: #75a58a; }" "QDockWidget { color: #24313b; font-weight: 600; }" "QDockWidget::title { background: #e9edf0; padding: 6px;" " border-bottom: 1px solid #cbd2d9; }" "QTreeWidget, QListWidget, QScrollArea { background: #ffffff; border: 1px solid #d5dbe0; }" "QTabWidget::pane { border: 0; background: #f3f5f7; }" "QTabBar::tab { background: #e5e9ec; padding: 7px 14px; border-right: 1px solid #cbd2d9; }" "QTabBar::tab:selected { background: #ffffff; color: #15232d; }" "QFrame#hmiCanvasPlaceholder, QFrame#logicCanvasPlaceholder { background: #ffffff;" " border: 1px solid #cbd2d9; }" "QLabel#hmiEmptyLabel, QLabel#logicEmptyLabel { color: #75838d; }" "QLabel#hmiPageTitleLabel { color: #1d2a33; font-weight: 600; }" "QLabel#hmiPageSizeLabel { color: #66747e; }")); } void MainWindow::configureHmiEditor() { QLayout *layout = ui_->hmiCanvasPlaceholder->layout(); delete ui_->hmiEmptyLabel; hmi_editor_widget_ = new HmiEditorWidget( hmi_editor_service_, hmi_runtime_service_, alarm_service_, ui_->hmiCanvasPlaceholder); hmi_editor_widget_->setMinimumHeight(320); layout->addWidget(hmi_editor_widget_); } void MainWindow::configureLogicEditor() { QLayout *layout = ui_->logicCanvasPlaceholder->layout(); delete ui_->logicEmptyLabel; logic_editor_widget_ = new LogicEditorWidget( logic_editor_service_, ui_->logicCanvasPlaceholder); logic_editor_widget_->setObjectName(QStringLiteral("logicEditorWidget")); logic_editor_widget_->setMinimumHeight(320); layout->addWidget(logic_editor_widget_); property_panel_controller_->bindEditorWidgets( *hmi_editor_widget_, *logic_editor_widget_); } void MainWindow::configureRuntimeMonitor() { runtime_panel_controller_ = std::make_unique( *this, runtime_mode_service_, project_service_, hmi_editor_service_, hmi_runtime_service_, logic_editor_service_, *hmi_navigation_service_, alarm_service_, register_monitor_service_, *hmi_editor_widget_, *logic_editor_widget_, *executor_status_label_, [this] { return current_logic_id_; }, [this](const std::string &logic_id) { current_logic_id_ = logic_id; }, [this](const std::string &node_id) { showLogicNodeProperties(node_id); }, [this](const QString &message, int timeout_ms) { statusBar()->showMessage(message, timeout_ms); }, [this](const QString &message) { appendOutputMessage(message); }, [this] { requestMode(ApplicationMode::Editing); }); runtime_panel_controller_->configure(); runtime_monitor_widget_ = runtime_panel_controller_->runtimeMonitorWidget(); } void MainWindow::configureProjectTree() { project_workspace_controller_ = std::make_unique( *this, *ui_, project_service_, hmi_editor_service_, logic_editor_service_, runtime_mode_service_, *hmi_navigation_service_, *hmi_editor_widget_, *logic_editor_widget_, *runtime_monitor_widget_, current_hmi_page_id_, current_logic_id_, [this](const std::string &control_id) { showControlProperties(control_id); }, [this](const std::string &node_id) { showLogicNodeProperties(node_id); }, [this](const QString &action, const QString &message, bool succeeded) { showProjectResult(action, message, succeeded); }, [this](const QString &message, int timeout_ms) { statusBar()->showMessage(message, timeout_ms); }, [this] { updateEditActions(); }); project_workspace_controller_->configure(); } void MainWindow::configurePropertyEditor() { property_panel_controller_->configure(); } void MainWindow::configurePlcConnection() { connect(ui_->configurePlcAction, &QAction::triggered, this, &MainWindow::connectPlc); connect(ui_->disconnectPlcAction, &QAction::triggered, this, &MainWindow::disconnectPlc); } void MainWindow::configureAlarms() { connect(ui_->configureAlarmsAction, &QAction::triggered, this, [this] { AlarmConfigurationDialog dialog(alarm_editor_service_, this); dialog.exec(); refreshProjectUi(); }); } void MainWindow::configureRegisterComments() { connect( ui_->configureRegisterCommentsAction, &QAction::triggered, this, [this] { RegisterCommentDialog dialog(register_comment_service_, this); dialog.exec(); logic_editor_widget_->reloadLogic(); refreshProjectUi(); }); } void MainWindow::undoActiveEditor() { if (!runtime_mode_service_.policy().allowsProjectEditing) { return; } if (ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab) { if (!hmi_editor_service_.undo().succeeded) { return; } selected_control_id_.clear(); showControlProperties({}); refreshProjectUi(); hmi_editor_widget_->reloadPage(); statusBar()->showMessage(tr("已撤销 HMI 编辑操作"), 3000); } else if (ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab) { if (!logic_editor_service_.undo().succeeded) { return; } selected_logic_node_id_.clear(); showLogicNodeProperties({}); refreshProjectUi(); logic_editor_widget_->reloadLogic(); statusBar()->showMessage(tr("已撤销梯形图编辑操作"), 3000); } updateEditActions(); } void MainWindow::redoActiveEditor() { if (!runtime_mode_service_.policy().allowsProjectEditing) { return; } if (ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab) { if (!hmi_editor_service_.redo().succeeded) { return; } selected_control_id_.clear(); showControlProperties({}); refreshProjectUi(); hmi_editor_widget_->reloadPage(); statusBar()->showMessage(tr("已重做 HMI 编辑操作"), 3000); } else if (ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab) { if (!logic_editor_service_.redo().succeeded) { return; } selected_logic_node_id_.clear(); showLogicNodeProperties({}); refreshProjectUi(); logic_editor_widget_->reloadLogic(); statusBar()->showMessage(tr("已重做梯形图编辑操作"), 3000); } updateEditActions(); } void MainWindow::deleteActiveSelection() { if (ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab) { deleteSelectedControl(); } else if (ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab) { deleteSelectedLogicObject(); } } void MainWindow::clearActiveSelection() { if (ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab) { hmi_editor_widget_->scene()->clearSelection(); selected_control_id_.clear(); showControlProperties({}); } else if (ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab) { logic_editor_widget_->scene()->clearSelection(); selected_logic_node_id_.clear(); showLogicNodeProperties({}); } } void MainWindow::updateEditActions() { const bool editable = runtime_mode_service_.policy().allowsProjectEditing; const bool hmi_active = ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab; const bool logic_active = ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab; ui_->undoAction->setEnabled( editable && ((hmi_active && hmi_editor_service_.canUndo()) || (logic_active && logic_editor_service_.canUndo()))); ui_->redoAction->setEnabled( editable && ((hmi_active && hmi_editor_service_.canRedo()) || (logic_active && logic_editor_service_.canRedo()))); ui_->deleteSelectionAction->setEnabled(editable && (hmi_active || logic_active)); ui_->clearSelectionAction->setEnabled(editable && (hmi_active || logic_active)); ui_->insertHorizontalWireAction->setEnabled(editable && logic_active); ui_->insertVerticalWireAction->setEnabled(editable && logic_active); ui_->deleteHorizontalWireAction->setEnabled(editable && logic_active); ui_->deleteVerticalWireAction->setEnabled(editable && logic_active); } void MainWindow::clearEditorHistories() { hmi_editor_service_.clearHistory(); logic_editor_service_.clearHistory(); if (ui_->undoAction != nullptr) { updateEditActions(); } } void MainWindow::refreshProjectUi() { project_workspace_controller_->refresh(); updateEditActions(); updateWindowTitle(); } void MainWindow::updateWindowTitle() { QString title = tr("综合平台编程器"); if (project_service_.isModified()) { title += QStringLiteral("*"); } setWindowTitle(title); } bool MainWindow::confirmSaveBeforeDestructiveAction() { if (!project_service_.isModified()) { return true; } const QString project_name = fromUtf8(project_service_.project().metadata.name); const QMessageBox::StandardButton choice = QMessageBox::warning( this, tr("工程尚未保存"), tr("工程“%1”有未保存的修改,是否先保存?").arg(project_name), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Save); if (choice == QMessageBox::Cancel) { return false; } if (choice == QMessageBox::Discard) { return true; } saveProject(); return !project_service_.isModified(); } void MainWindow::updateProjectTreeActions() { project_workspace_controller_->updateActions(); } void MainWindow::connectPlc() { PlcConnectionDialog dialog(plc_configuration_, this); if (dialog.exec() != QDialog::Accepted) { return; } plc_configuration_ = dialog.configuration(); if (plc_configuration_.portName.empty()) { showProjectResult(tr("连接 PLC"), tr("请选择或输入串口端口"), false); return; } const PlcCommunicationResult result = runtime_mode_service_.connectPlc( plc_configuration_); if (!result.succeeded) { const QString message = fromUtf8(result.message); if (result.message == runtime_mode_service_.plcError()) { statusBar()->showMessage(message, 5000); QMessageBox::warning(this, tr("连接 PLC"), message); } else { showProjectResult(tr("连接 PLC"), message, false); } return; } statusBar()->showMessage(tr("PLC 正在连接,等待首次 M/D 读取"), 5000); } void MainWindow::disconnectPlc() { runtime_mode_service_.disconnectPlc(); } void MainWindow::schedulePlcStatusUpdate() { if (plc_status_update_pending_) { return; } plc_status_update_pending_ = true; QMetaObject::invokeMethod( this, [this] { plc_status_update_pending_ = false; updateModeUi(plcStatusText(runtime_mode_service_)); }, Qt::QueuedConnection); } // 根据控件ID加载控件属性到右侧属性面板 void MainWindow::showControlProperties(const std::string &control_id) { property_panel_controller_->showControlProperties(control_id); } void MainWindow::showLogicNodeProperties(const std::string &node_id) { property_panel_controller_->showLogicNodeProperties(node_id); } void MainWindow::addHmiControl(HmiControlType type) { property_panel_controller_->addHmiControl(type); } void MainWindow::deleteSelectedControl() { property_panel_controller_->deleteSelectedControl(); } void MainWindow::addLogicCondition(const LogicNodeConfig &config) { const LogicEditorResult result = logic_editor_widget_->addCondition(config); if (!result.succeeded) { showProjectResult(tr("添加逻辑条件"), fromUtf8(result.message), false); return; } selected_logic_node_id_ = result.id; showLogicNodeProperties(result.id); refreshProjectUi(); statusBar()->showMessage(tr("已添加逻辑条件"), 3000); } void MainWindow::addLogicParallelBranch(const LogicNodeConfig &config) { const LogicEditorResult result = logic_editor_widget_->addParallelBranch(config); if (!result.succeeded) { showProjectResult(tr("建立并联支路"), fromUtf8(result.message), false); return; } selected_logic_node_id_ = result.id; showLogicNodeProperties(result.id); refreshProjectUi(); statusBar()->showMessage(tr("已建立并联支路,请配置新触点"), 3000); } void MainWindow::addLogicHorizontalWire() { const LogicEditorResult result = logic_editor_widget_->addHorizontalWire(); if (!result.succeeded) { showProjectResult(tr("插入横线"), fromUtf8(result.message), false); return; } selected_logic_node_id_.clear(); showLogicNodeProperties({}); refreshProjectUi(); statusBar()->showMessage(tr("横线已插入,可直接用触点替换"), 3000); } void MainWindow::addLogicVerticalWire() { const LogicEditorResult result = logic_editor_widget_->addVerticalWire(); if (!result.succeeded) { showProjectResult(tr("插入竖线"), fromUtf8(result.message), false); return; } selected_logic_node_id_.clear(); showLogicNodeProperties({}); refreshProjectUi(); statusBar()->showMessage(tr("已建立横线旁路和竖线连接"), 3000); } void MainWindow::deleteLogicHorizontalWire() { const LogicEditorResult result = logic_editor_widget_->deleteHorizontalWire(); if (!result.succeeded) { showProjectResult(tr("删除横线"), fromUtf8(result.message), false); return; } selected_logic_node_id_.clear(); showLogicNodeProperties({}); refreshProjectUi(); statusBar()->showMessage(tr("横线已删除"), 3000); } void MainWindow::deleteLogicVerticalWire() { const LogicEditorResult result = logic_editor_widget_->deleteVerticalWire(); if (!result.succeeded) { showProjectResult(tr("删除竖线"), fromUtf8(result.message), false); return; } selected_logic_node_id_.clear(); showLogicNodeProperties({}); refreshProjectUi(); statusBar()->showMessage(tr("竖线及对应并联支路已删除"), 3000); } void MainWindow::setLogicOutput(const LogicNodeConfig &config) { const LogicEditorResult result = logic_editor_widget_->setOutput(config); if (!result.succeeded) { showProjectResult(tr("设置逻辑输出"), fromUtf8(result.message), false); return; } selected_logic_node_id_ = result.id; showLogicNodeProperties(result.id); refreshProjectUi(); statusBar()->showMessage(tr("逻辑输出已设置"), 3000); } void MainWindow::configureAndSetLogicOutput(const LogicNodeConfig &config) { LogicInstructionDialog dialog(config, this); if (dialog.exec() != QDialog::Accepted) { return; } const LogicNodeConfig configured = dialog.config(); LogicEditorResult result = logic_editor_widget_->setOutput(configured, true); if (!result.succeeded) { showProjectResult(tr("设置逻辑输出"), fromUtf8(result.message), false); return; } const std::string node_id = result.id; selected_logic_node_id_ = node_id; showLogicNodeProperties(node_id); refreshProjectUi(); statusBar()->showMessage(tr("逻辑输出已配置"), 3000); } void MainWindow::addLogicRung() { const LogicEditorResult result = logic_editor_widget_->addRung(); if (!result.succeeded) { showProjectResult(tr("新建网络"), fromUtf8(result.message), false); return; } selected_logic_node_id_.clear(); showLogicNodeProperties({}); refreshProjectUi(); statusBar()->showMessage(tr("已新建网络"), 3000); } void MainWindow::editSelectedRungComment() { const std::string rung_id = logic_editor_widget_->selectedRungId(); if (rung_id.empty()) { showProjectResult(tr("网络注释"), tr("请先选择一个梯形图网络"), false); return; } const LadderRung *rung = logic_editor_service_.findRung( current_logic_id_, rung_id); if (rung == nullptr) { return; } bool accepted = false; const QString comment = QInputDialog::getText( this, tr("网络注释"), tr("说明"), QLineEdit::Normal, fromUtf8(rung->comment), &accepted); if (!accepted) { return; } const LogicEditorResult result = logic_editor_service_.updateRungComment( current_logic_id_, rung_id, toUtf8(comment)); if (!result.succeeded) { showProjectResult(tr("网络注释"), fromUtf8(result.message), false); return; } logic_editor_widget_->reloadLogic(); refreshProjectUi(); statusBar()->showMessage(tr("网络注释已更新"), 3000); } void MainWindow::deleteSelectedLogicObject() { const LogicEditorResult result = logic_editor_widget_->deleteSelected(); if (!result.succeeded) { return; } selected_logic_node_id_.clear(); showLogicNodeProperties({}); refreshProjectUi(); } void MainWindow::createNewProject() { if (!confirmSaveBeforeDestructiveAction()) { return; } const QString name = QInputDialog::getText( this, tr("新建工程"), tr("工程名称")); if (name.isEmpty()) { return; } const ProjectOperationResult result = project_service_.createNewProject(toUtf8(name)); if (!result.succeeded) { showProjectResult(tr("新建工程"), fromUtf8(result.message), false); return; } hmi_editor_service_.ensureDefaultPage(); logic_editor_service_.ensureDefaultLogic(); clearEditorHistories(); selected_control_id_.clear(); selected_logic_node_id_.clear(); current_hmi_page_id_ = project_service_.project().initialHmiPageId; current_logic_id_ = logic_editor_service_.firstLogicId(); refreshProjectUi(); hmi_editor_widget_->reloadPage(); logic_editor_widget_->reloadLogic(); showControlProperties({}); statusBar()->showMessage(tr("已创建新工程"), 3000); } void MainWindow::saveProject() { if (!project_service_.hasCurrentFile()) { saveProjectAs(); return; } const ProjectOperationResult result = project_service_.save(); if (!result.succeeded) { showProjectResult(tr("保存工程"), fromUtf8(result.message), false); return; } updateWindowTitle(); showProjectResult(tr("保存工程"), tr("工程已保存"), true); } void MainWindow::saveProjectAs() { const QString path = QFileDialog::getSaveFileName( this, tr("工程另存为"), {}, tr("工程文件 (*.json)")); if (path.isEmpty()) { return; } const ProjectOperationResult result = project_service_.saveAs(toUtf8(path)); if (result.succeeded) { updateWindowTitle(); } showProjectResult( tr("保存工程"), result.succeeded ? tr("工程已保存") : fromUtf8(result.message), result.succeeded); } void MainWindow::loadProject() { if (!confirmSaveBeforeDestructiveAction()) { return; } const QString path = QFileDialog::getOpenFileName( this, tr("加载工程"), {}, tr("工程文件 (*.json)")); if (path.isEmpty()) { return; } const ProjectOperationResult result = project_service_.load(toUtf8(path)); if (!result.succeeded) { showProjectResult(tr("加载工程"), fromUtf8(result.message), false); return; } clearEditorHistories(); selected_control_id_.clear(); selected_logic_node_id_.clear(); current_hmi_page_id_ = project_service_.project().initialHmiPageId; current_logic_id_ = logic_editor_service_.firstLogicId(); refreshProjectUi(); hmi_editor_widget_->reloadPage(); logic_editor_widget_->reloadLogic(); showControlProperties({}); showProjectResult(tr("加载工程"), tr("工程已加载"), true); } void MainWindow::showProjectResult( const QString &action, const QString &message, bool succeeded) { const QString output = action + QStringLiteral(": ") + message; statusBar()->showMessage(output, 5000); appendOutputMessage(output); if (!succeeded) { QMessageBox::warning(this, action, message); } } void MainWindow::appendOutputMessage(const QString &message) { while (ui_->outputList->count() >= ProjectLimits::kMaximumOutputMessages) { delete ui_->outputList->takeItem(0); } ui_->outputList->addItem(message); ui_->outputList->scrollToBottom(); } void MainWindow::requestMode(ApplicationMode requested_mode) { // UI 仅转发模式意图,合法性由服务层和领域状态机决定 ModeTransitionResult result; switch (requested_mode) { case ApplicationMode::Editing: { result = runtime_mode_service_.enterEditing(); break; } case ApplicationMode::OfflineRunning: { result = runtime_mode_service_.enterOfflineRunning(); break; } case ApplicationMode::OnlineRunning: { result = runtime_mode_service_.enterOnlineRunning(); break; } default: { restoreCurrentModeAction(); statusBar()->showMessage(tr("不支持的运行模式"), 5000); return; } } if (!result.succeeded) { restoreCurrentModeAction(); QString message = transitionErrorText(result.error); if (result.error == ModeTransitionError::InitialPlcReadRequired) { message += tr(";当前状态:%1").arg(plcStatusText(runtime_mode_service_)); } if (result.error == ModeTransitionError::SimulationStartFailed) { const LogicScanResult &error = runtime_mode_service_.simulationError(); if (!error.message.empty()) { message += QStringLiteral(": ") + fromUtf8(error.message); } } statusBar()->showMessage(message, 5000); return; } updateModeUi(tr("已进入%1").arg(modeText(runtime_mode_service_.mode()))); } void MainWindow::updateModeUi(const QString &message) { const ApplicationMode mode = runtime_mode_service_.mode(); const ModePolicy policy = runtime_mode_service_.policy(); restoreCurrentModeAction(); const bool running = mode != ApplicationMode::Editing; if (running) { if (hmi_navigation_service_->currentPageId().empty()) { const HmiNavigationResult navigation = hmi_navigation_service_->start(); if (!navigation.succeeded) { statusBar()->showMessage(fromUtf8(navigation.message), 5000); } } runtime_panel_controller_->enterRuntime( hmi_navigation_service_->currentPageId(), current_logic_id_, mode, runtime_mode_service_.plcConnectionState()); } else { hmi_navigation_service_->stop(); runtime_panel_controller_->leaveRuntime( mode, runtime_mode_service_.plcConnectionState()); } // 将同一份模式策略同步到所有可编辑入口,避免只禁用部分操作 ui_->projectDock->setEnabled(policy.allowsProjectEditing); ui_->propertiesDock->setEnabled(policy.allowsProjectEditing); hmi_editor_widget_->setEditingEnabled(policy.allowsProjectEditing); hmi_editor_widget_->setRuntimeActive( policy.usesVirtualRegisters || policy.usesPlcRegisters); logic_editor_widget_->setEditingEnabled(policy.allowsProjectEditing); if (mode == ApplicationMode::Editing) { alarm_service_.reset(); logic_editor_widget_->clearRuntimeTrace(); } ui_->hmiToolBar->setEnabled(policy.allowsProjectEditing); ui_->logicToolBar->setEnabled(policy.allowsProjectEditing); ui_->addButtonAction->setEnabled(policy.allowsProjectEditing); ui_->addIndicatorAction->setEnabled(policy.allowsProjectEditing); ui_->addNumericDisplayAction->setEnabled(policy.allowsProjectEditing); ui_->addNumericInputAction->setEnabled(policy.allowsProjectEditing); ui_->addProgressBarAction->setEnabled(policy.allowsProjectEditing); ui_->addLabelAction->setEnabled(policy.allowsProjectEditing); ui_->addPageJumpAction->setEnabled(policy.allowsProjectEditing); ui_->addAlarmListAction->setEnabled(policy.allowsProjectEditing); ui_->configureAlarmsAction->setEnabled(policy.allowsProjectEditing); ui_->configureRegisterCommentsAction->setEnabled(policy.allowsProjectEditing); ui_->deleteControlAction->setEnabled(policy.allowsProjectEditing); ui_->addNormallyOpenAction->setEnabled(policy.allowsProjectEditing); ui_->addNormallyClosedAction->setEnabled(policy.allowsProjectEditing); ui_->addRisingEdgeAction->setEnabled(policy.allowsProjectEditing); ui_->addFallingEdgeAction->setEnabled(policy.allowsProjectEditing); ui_->addTimerContactAction->setEnabled(policy.allowsProjectEditing); ui_->addCounterContactAction->setEnabled(policy.allowsProjectEditing); ui_->addNormalCoilAction->setEnabled(policy.allowsProjectEditing); ui_->addSetCoilAction->setEnabled(policy.allowsProjectEditing); ui_->addResetCoilAction->setEnabled(policy.allowsProjectEditing); ui_->addTonAction->setEnabled(policy.allowsProjectEditing); ui_->addCtuAction->setEnabled(policy.allowsProjectEditing); ui_->addCtdAction->setEnabled(policy.allowsProjectEditing); ui_->addMoveAction->setEnabled(policy.allowsProjectEditing); ui_->addAddAction->setEnabled(policy.allowsProjectEditing); ui_->addSubAction->setEnabled(policy.allowsProjectEditing); ui_->addCompareAction->setEnabled(policy.allowsProjectEditing); ui_->editRungCommentAction->setEnabled(policy.allowsProjectEditing); ui_->deleteLogicAction->setEnabled(policy.allowsProjectEditing); ui_->newProjectAction->setEnabled(policy.allowsProjectEditing); ui_->saveProjectAction->setEnabled(policy.allowsProjectEditing); ui_->saveAsProjectAction->setEnabled(policy.allowsProjectEditing); ui_->loadProjectAction->setEnabled(policy.allowsProjectEditing); updateProjectTreeActions(); updateEditActions(); mode_status_label_->setText(modeText(mode)); if (mode == ApplicationMode::Editing) { mode_status_label_->setStyleSheet(QStringLiteral( "border: 1px solid #75a58a; background: #e3f0e8; color: #205c3b; padding: 2px 8px;")); } else if (mode == ApplicationMode::OfflineRunning) { mode_status_label_->setStyleSheet(QStringLiteral( "border: 1px solid #bd8739; background: #fff0d2; color: #744b0e; padding: 2px 8px;")); } else { mode_status_label_->setStyleSheet(QStringLiteral( "border: 1px solid #4b88a8; background: #deeff7; color: #174f6c; padding: 2px 8px;")); } register_status_label_->setText( policy.usesVirtualRegisters ? tr("数据源:虚拟 M/D") : policy.usesPlcRegisters ? tr("数据源:PLC 缓存") : tr("数据源:未启用")); const PlcConnectionState plc_state = runtime_mode_service_.plcConnectionState(); const bool plc_configuration_available = plc_state == PlcConnectionState::Disconnected || plc_state == PlcConnectionState::Faulted; ui_->configurePlcAction->setEnabled( policy.allowsProjectEditing && plc_configuration_available); ui_->configurePlcAction->setText( plc_state == PlcConnectionState::Faulted ? tr("PLC 重新配置") : tr("PLC 配置")); ui_->configurePlcAction->setToolTip( plc_state == PlcConnectionState::Faulted ? tr("清理故障会话后重新配置并连接真实 PLC") : tr("配置参数并连接真实 PLC")); ui_->disconnectPlcAction->setEnabled(plc_state != PlcConnectionState::Disconnected); if (plc_state == PlcConnectionState::Connecting) { register_status_label_->setText(tr("PLC:正在连接")); } else if (plc_state == PlcConnectionState::Connected) { register_status_label_->setText( runtime_mode_service_.initialPlcReadCompleted() ? tr("PLC:已连接,首读完成") : tr("PLC:已连接,正在首读")); } else if (plc_state == PlcConnectionState::Recovering) { register_status_label_->setText(tr("PLC:通信已恢复,正在重新首读")); } else if (plc_state == PlcConnectionState::Faulted) { register_status_label_->setText(tr("PLC:通信故障")); } updateSimulationUi(false); statusBar()->showMessage(message, 4000); const bool duplicate = ui_->outputList->count() > 0 && ui_->outputList->item(ui_->outputList->count() - 1)->text() == message; if (!duplicate) { appendOutputMessage(message); } } void MainWindow::updateSimulationUi(bool report_fault) { runtime_panel_controller_->updateSimulationUi(report_fault); } // 根据当前真实运行模式,同步更新模式工具栏单选按钮选中状态 void MainWindow::restoreCurrentModeAction() { ui_->editingModeAction->setChecked(runtime_mode_service_.mode() == ApplicationMode::Editing); ui_->offlineModeAction->setChecked(runtime_mode_service_.mode() == ApplicationMode::OfflineRunning); ui_->onlineModeAction->setChecked(runtime_mode_service_.mode() == ApplicationMode::OnlineRunning); }