|
- #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 "free_monitor_widget.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 "infrastructure/runtime_project_bundle.h"
-
- #include <QActionGroup>
- #include <QAbstractSpinBox>
- #include <QApplication>
- #include <QCloseEvent>
- #include <QDialogButtonBox>
- #include <QDockWidget>
- #include <QEvent>
- #include <QFileDialog>
- #include <QGraphicsScene>
- #include <QInputDialog>
- #include <QIcon>
- #include <QLabel>
- #include <QLineEdit>
- #include <QListWidget>
- #include <QListWidgetItem>
- #include <QKeyEvent>
- #include <QKeySequence>
- #include <QMessageBox>
- #include <QMenu>
- #include <QPlainTextEdit>
- #include <QPushButton>
- #include <QStatusBar>
- #include <QTabWidget>
- #include <QToolBar>
- #include <QToolButton>
- #include <QTextEdit>
- #include <QTimer>
- #include <QDir>
- #include <QFileInfo>
- #include <QProgressDialog>
- #include <QSignalBlocker>
-
- #include <algorithm>
-
- namespace {
-
- constexpr int kSyntaxLogicIdRole = Qt::UserRole + 1;
- constexpr int kSyntaxRungIdRole = Qt::UserRole + 2;
- constexpr int kSyntaxColumnRole = Qt::UserRole + 3;
-
- bool isTextEditingObject(QObject *object)
- {
- QWidget *widget = qobject_cast<QWidget *>(object);
- while (widget != nullptr)
- {
- if (qobject_cast<QLineEdit *>(widget) != nullptr
- || qobject_cast<QTextEdit *>(widget) != nullptr
- || qobject_cast<QPlainTextEdit *>(widget) != nullptr
- || qobject_cast<QAbstractSpinBox *>(widget) != nullptr)
- {
- return true;
- }
- widget = widget->parentWidget();
- }
- return false;
- }
-
- QString suggestedProjectFileName(QString project_name)
- {
- project_name = project_name.trimmed();
- const QString invalid_characters = QStringLiteral("<>:\"/\\|?*");
- for (QChar &character : project_name)
- {
- if (character.unicode() < 0x20U || invalid_characters.contains(character))
- {
- character = QLatin1Char('_');
- }
- }
-
- constexpr int kMaximumSuggestedBaseNameLength = 240;
- if (project_name.size() > kMaximumSuggestedBaseNameLength)
- {
- project_name.truncate(kMaximumSuggestedBaseNameLength);
- if (!project_name.isEmpty()
- && project_name.at(project_name.size() - 1).isHighSurrogate())
- {
- project_name.chop(1);
- }
- }
- while (project_name.endsWith(QLatin1Char(' '))
- || project_name.endsWith(QLatin1Char('.')))
- {
- project_name.chop(1);
- }
- if (project_name.isEmpty())
- {
- project_name = QStringLiteral("未命名工程");
- }
-
- const QString device_name = project_name.section(QLatin1Char('.'), 0, 0).toUpper();
- const bool is_reserved_device_name = device_name == QStringLiteral("CON")
- || device_name == QStringLiteral("PRN")
- || device_name == QStringLiteral("AUX")
- || device_name == QStringLiteral("NUL")
- || (device_name.size() == 4
- && (device_name.startsWith(QStringLiteral("COM"))
- || device_name.startsWith(QStringLiteral("LPT")))
- && device_name.back() >= QLatin1Char('1')
- && device_name.back() <= QLatin1Char('9'));
- if (is_reserved_device_name)
- {
- project_name.prepend(QLatin1Char('_'));
- }
- return project_name + QStringLiteral(".json");
- }
-
- bool isEditorShortcut(const QKeyEvent &event)
- {
- return event.matches(QKeySequence::Undo)
- || event.matches(QKeySequence::Redo)
- || event.matches(QKeySequence::Copy)
- || event.matches(QKeySequence::Paste)
- || (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<std::size_t>(bytes.size()));
- }
-
- QString fromUtf8(const std::string &value)
- {
- return QString::fromUtf8(value.data(), static_cast<int>(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<QAction *> &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;
- }
-
- bool copyDirectoryContents(
- const QString &source_path, const QString &destination_path, QString *error)
- {
- QDir source_directory(source_path);
- if (!source_directory.exists())
- {
- if (error != nullptr)
- {
- *error = MainWindow::tr("未找到打包目录:%1").arg(source_path);
- }
- return false;
- }
- QDir destination_directory(destination_path);
- if (!destination_directory.exists()
- && !QDir().mkpath(destination_path))
- {
- if (error != nullptr)
- {
- *error = MainWindow::tr("无法创建导出目录:%1").arg(destination_path);
- }
- return false;
- }
-
- const QFileInfoList entries = source_directory.entryInfoList(
- QDir::NoDotAndDotDot | QDir::AllEntries,
- QDir::DirsFirst | QDir::Name);
- for (const QFileInfo &entry : entries)
- {
- const QString destination = destination_directory.filePath(entry.fileName());
- if (entry.isDir())
- {
- if (!copyDirectoryContents(entry.absoluteFilePath(), destination, error))
- {
- return false;
- }
- continue;
- }
- if (QFileInfo::exists(destination)
- || !QFile::copy(entry.absoluteFilePath(), destination))
- {
- if (error != nullptr)
- {
- *error = MainWindow::tr("复制打包文件失败:%1").arg(destination);
- }
- return false;
- }
- }
- return true;
- }
-
- } // 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,
- PlcDiscoveryGateway &plc_discovery_gateway,
- const ApplicationSettingsLoadResult &application_settings_result,
- bool user_runtime_mode,
- QWidget *parent)
- : QMainWindow(parent),
- ui_(std::make_unique<Ui::MainWindow>()),
- 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),
- plc_discovery_gateway_(plc_discovery_gateway),
- application_settings_result_(application_settings_result),
- user_runtime_mode_(user_runtime_mode),
- owned_hmi_navigation_service_(
- std::make_unique<HmiNavigationService>(project_service)),
- hmi_navigation_service_(owned_hmi_navigation_service_.get()),
- plc_configuration_(application_settings_result.settings.plcDefaults)
- {
- 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,
- PlcDiscoveryGateway &plc_discovery_gateway,
- const ApplicationSettingsLoadResult &application_settings_result,
- bool user_runtime_mode,
- QWidget *parent)
- : QMainWindow(parent),
- ui_(std::make_unique<Ui::MainWindow>()),
- 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),
- plc_discovery_gateway_(plc_discovery_gateway),
- application_settings_result_(application_settings_result),
- user_runtime_mode_(user_runtime_mode),
- hmi_navigation_service_(&hmi_navigation_service),
- plc_configuration_(application_settings_result.settings.plcDefaults)
- {
- initializeUi();
- }
-
- void MainWindow::initializeUi()
- {
- ui_->setupUi(this);
- qApp->installEventFilter(this);
- configureAppearance();
- property_panel_controller_ = std::make_unique<PropertyPanelController>(
- *this,
- *ui_,
- project_service_,
- hmi_editor_service_,
- logic_editor_service_,
- application_settings_result_.settings.hmiDefaults,
- [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();
- configureDataMonitor();
- 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("系统已进入编辑态"));
- for (const std::string &message : application_settings_result_.messages)
- {
- appendOutputMessage(fromUtf8(message));
- }
- if (application_settings_result_.warningRequired)
- {
- const QString warning = fromUtf8(
- application_settings_result_.warningMessage);
- QTimer::singleShot(
- 0,
- this,
- [this, warning]
- {
- QMessageBox::warning(this, tr("应用配置"), warning);
- });
- }
- if (user_runtime_mode_)
- {
- initializeUserRuntime();
- }
- }
-
- void MainWindow::initializeUserRuntime()
- {
- setWindowTitle(fromUtf8(project_service_.project().metadata.name));
- runtime_panel_controller_->configureUserRuntimeMode(
- true,
- [this](int mode)
- {
- if (mode == static_cast<int>(ApplicationMode::OfflineRunning))
- {
- requestUserRuntimeMode(ApplicationMode::OfflineRunning);
- }
- else if (mode == static_cast<int>(ApplicationMode::OnlineRunning))
- {
- requestUserRuntimeMode(ApplicationMode::OnlineRunning);
- }
- },
- [this] { connectPlc(); },
- [this] { disconnectPlc(); });
- menuBar()->setVisible(false);
- ui_->hmiToolBar->setVisible(false);
- ui_->logicToolBar->setVisible(false);
- ui_->outputDock->setVisible(false);
- ui_->projectDock->setVisible(false);
- ui_->propertiesDock->setVisible(false);
- QTimer::singleShot(
- 0,
- this,
- [this]
- {
- if (requestMode(ApplicationMode::OfflineRunning))
- {
- hide();
- }
- else
- {
- qApp->quit();
- }
- });
- }
-
- void MainWindow::requestUserRuntimeMode(ApplicationMode requested_mode)
- {
- if (runtime_mode_service_.mode() == requested_mode)
- {
- return;
- }
- if (requested_mode == ApplicationMode::OnlineRunning
- && !runtime_mode_service_.initialPlcReadCompleted())
- {
- // 用户运行版没有可见的编辑器主窗口,失败前不能先隐藏运行监控窗口
- if (runtime_monitor_widget_ != nullptr)
- {
- runtime_monitor_widget_->setMode(
- runtime_mode_service_.mode(),
- runtime_mode_service_.plcConnectionState());
- const QString message = transitionErrorText(
- ModeTransitionError::InitialPlcReadRequired)
- + tr("\n当前状态:%1").arg(
- plcStatusText(runtime_mode_service_))
- + tr("\n请先点击“PLC 配置”连接并完成首次读取。");
- QMessageBox::warning(
- runtime_monitor_widget_,
- tr("无法切换到真机运行"),
- message);
- }
- return;
- }
- if (runtime_mode_service_.mode() != ApplicationMode::Editing)
- {
- const ModeTransitionResult editing = runtime_mode_service_.enterEditing();
- if (!editing.succeeded)
- {
- return;
- }
- updateModeUi(tr("正在切换运行模式"));
- QTimer::singleShot(
- 0,
- this,
- [this, requested_mode]
- {
- requestMode(requested_mode);
- });
- return;
- }
- requestMode(requested_mode);
- }
-
- MainWindow::~MainWindow()
- {
- qApp->removeEventFilter(this);
- register_monitor_service_.setAddressesChangedCallback({});
- runtime_mode_service_.setPlcStatusChangedCallback({});
- }
-
- bool MainWindow::eventFilter(QObject *watched, QEvent *event)
- {
- if (event->type() == QEvent::ShortcutOverride
- && isTextEditingObject(watched))
- {
- auto *key_event = static_cast<QKeyEvent *>(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);
- }
-
- 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_->exportRuntimeAction, &QAction::triggered,
- this, &MainWindow::exportRuntimeProgram);
- connect(ui_->undoAction, &QAction::triggered,
- this, &MainWindow::undoActiveEditor);
- connect(ui_->redoAction, &QAction::triggered,
- this, &MainWindow::redoActiveEditor);
- connect(ui_->copyAction, &QAction::triggered,
- this, &MainWindow::copyActiveSelection);
- connect(ui_->pasteAction, &QAction::triggered,
- this, &MainWindow::pasteActiveSelection);
- connect(ui_->deleteSelectionAction, &QAction::triggered,
- this, &MainWindow::deleteActiveSelection);
- connect(ui_->clearSelectionAction, &QAction::triggered,
- this, &MainWindow::clearActiveSelection);
- connect(ui_->syntaxCheckAction, &QAction::triggered,
- this, &MainWindow::runLogicSyntaxCheck);
- connect(ui_->doubleCoilCheckAction, &QAction::triggered,
- this, &MainWindow::runDoubleCoilCheck);
- connect(ui_->outputList, &QListWidget::itemDoubleClicked,
- this,
- [this](QListWidgetItem *item)
- {
- if (item == nullptr)
- {
- return;
- }
- const std::string logic_id = toUtf8(
- item->data(kSyntaxLogicIdRole).toString());
- const std::string rung_id = toUtf8(
- item->data(kSyntaxRungIdRole).toString());
- if (logic_id.empty() || rung_id.empty())
- {
- return;
- }
- focusLogicSyntaxLocation({
- logic_id,
- rung_id,
- 0,
- 0,
- item->data(kSyntaxColumnRole).toInt()});
- });
-
- 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_->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<QAction *>{
- ui_->addPageJumpAction,
- ui_->addAlarmListAction,
- ui_->configureAlarmsAction});
-
- connect(ui_->addRungAction, &QAction::triggered,
- this, &MainWindow::addLogicRung);
- connect(ui_->insertRungAboveAction, &QAction::triggered,
- this, &MainWindow::insertLogicRungAbove);
- connect(ui_->insertRungBelowAction, &QAction::triggered,
- this, &MainWindow::insertLogicRungBelow);
- connect(ui_->deleteRungAction, &QAction::triggered,
- this, &MainWindow::deleteLogicRung);
- connect(ui_->mouseDrawWireAction, &QAction::triggered,
- this,
- [this](bool checked)
- {
- if (checked)
- {
- const QSignalBlocker blocker(ui_->mouseEraseWireAction);
- ui_->mouseEraseWireAction->setChecked(false);
- }
- logic_editor_widget_->setMouseWireMode(
- checked
- ? LogicEditorWidget::MouseWireMode::Draw
- : ui_->mouseEraseWireAction->isChecked()
- ? LogicEditorWidget::MouseWireMode::Erase
- : LogicEditorWidget::MouseWireMode::Select);
- });
- connect(ui_->mouseEraseWireAction, &QAction::triggered,
- this,
- [this](bool checked)
- {
- if (checked)
- {
- const QSignalBlocker blocker(ui_->mouseDrawWireAction);
- ui_->mouseDrawWireAction->setChecked(false);
- }
- logic_editor_widget_->setMouseWireMode(
- checked
- ? LogicEditorWidget::MouseWireMode::Erase
- : ui_->mouseDrawWireAction->isChecked()
- ? LogicEditorWidget::MouseWireMode::Draw
- : LogicEditorWidget::MouseWireMode::Select);
- });
- 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_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_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<QToolButton *>(
- 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_->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_->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_->editNetworkCommentAction, &QAction::triggered,
- this, &MainWindow::editSelectedNetworkComment);
- addToolbarMenu(
- ui_->logicToolBar,
- tr("更多触点"),
- QStringLiteral("logicContactMenuButton"),
- makeUiIcon(UiIcon::NormallyOpenContact),
- QList<QAction *>{
- ui_->addRisingEdgeAction,
- ui_->addFallingEdgeAction});
- addToolbarMenu(
- ui_->logicToolBar,
- tr("更多输出"),
- QStringLiteral("logicOutputMenuButton"),
- makeUiIcon(UiIcon::Coil),
- QList<QAction *>{ui_->addSetCoilAction, ui_->addResetCoilAction});
- addToolbarMenu(
- ui_->logicToolBar,
- tr("数据运算"),
- QStringLiteral("logicDataMenuButton"),
- makeUiIcon(UiIcon::Move),
- QList<QAction *>{
- 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_);
- }
- else
- {
- // 数据监控页不对应 HMI 或梯形图对象
- showControlProperties({});
- }
- 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_->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_->insertRungAboveAction->setIcon(makeUiIcon(UiIcon::AddRung));
- ui_->insertRungBelowAction->setIcon(makeUiIcon(UiIcon::AddRung));
- ui_->deleteRungAction->setIcon(makeUiIcon(UiIcon::Delete));
- ui_->mouseDrawWireAction->setIcon(makeUiIcon(UiIcon::MouseDrawWire));
- ui_->mouseEraseWireAction->setIcon(makeUiIcon(UiIcon::MouseEraseWire));
- 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_->addNormalCoilAction->setIcon(makeUiIcon(UiIcon::Coil));
- ui_->addSetCoilAction->setIcon(makeUiIcon(UiIcon::SetCoil));
- ui_->addResetCoilAction->setIcon(makeUiIcon(UiIcon::ResetCoil));
- 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_->editNetworkCommentAction->setIcon(makeUiIcon(UiIcon::Comment));
- ui_->syntaxCheckAction->setIcon(makeUiIcon(UiIcon::SyntaxCheck));
- ui_->doubleCoilCheckAction->setIcon(makeUiIcon(UiIcon::Coil));
- 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_);
- connect(
- hmi_editor_widget_, &HmiEditorWidget::controlSelected,
- this, [this](const QString &) { updateEditActions(); });
- connect(
- logic_editor_widget_, &LogicEditorWidget::nodeSelected,
- this, [this](const QString &) { updateEditActions(); });
- }
-
- void MainWindow::configureRuntimeMonitor()
- {
- runtime_panel_controller_ = std::make_unique<RuntimePanelController>(
- *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]
- {
- if (user_runtime_mode_)
- {
- qApp->quit();
- }
- else
- {
- requestMode(ApplicationMode::Editing);
- }
- });
- runtime_panel_controller_->configure();
- runtime_monitor_widget_ = runtime_panel_controller_->runtimeMonitorWidget();
- }
-
- void MainWindow::configureDataMonitor()
- {
- data_monitor_widget_ = new FreeMonitorWidget(
- register_monitor_service_, ui_->dataMonitorTab);
- data_monitor_widget_->setObjectName(QStringLiteral("dataMonitorWidget"));
- ui_->dataMonitorLayout->addWidget(data_monitor_widget_);
-
- connect(data_monitor_widget_, &FreeMonitorWidget::monitorAddressesChanged,
- this,
- [this]
- {
- runtime_mode_service_.setMonitorAddresses(
- register_monitor_service_.pollAddresses(),
- register_monitor_service_.multiWordRanges());
- refreshDataMonitorUi();
- });
- connect(data_monitor_widget_, &FreeMonitorWidget::operationMessage,
- this,
- [this](const QString &message)
- {
- statusBar()->showMessage(message, 4000);
- appendOutputMessage(message);
- });
- register_monitor_service_.setAddressesChangedCallback(
- [this]
- {
- if (data_monitor_widget_ != nullptr)
- {
- data_monitor_widget_->reloadAddresses();
- }
- if (runtime_monitor_widget_ != nullptr
- && runtime_monitor_widget_->freeMonitorWidget() != nullptr)
- {
- runtime_monitor_widget_->freeMonitorWidget()->reloadAddresses();
- }
- runtime_mode_service_.setMonitorAddresses(
- register_monitor_service_.pollAddresses(),
- register_monitor_service_.multiWordRanges());
- });
- register_monitor_service_.setPollConfigurationValidator(
- [this](const std::vector<RegisterAddress> &addresses,
- const std::vector<RegisterWordRange> &ranges)
- {
- const PlcCommunicationResult result =
- runtime_mode_service_.setMonitorAddresses(addresses, ranges);
- return result.succeeded ? std::string{} : result.message;
- });
- data_monitor_refresh_timer_ = new QTimer(this);
- data_monitor_refresh_timer_->setInterval(150);
- connect(data_monitor_refresh_timer_, &QTimer::timeout,
- this, &MainWindow::refreshDataMonitorUi);
- data_monitor_refresh_timer_->start();
- refreshDataMonitorUi();
- }
-
- void MainWindow::refreshDataMonitorUi()
- {
- if (data_monitor_widget_ == nullptr)
- {
- return;
- }
- const ApplicationMode mode = runtime_mode_service_.mode();
- const bool online_connected = mode == ApplicationMode::OnlineRunning
- && runtime_mode_service_.plcConnectionState()
- == PlcConnectionState::Connected;
- data_monitor_widget_->setWriteEnabled(
- mode == ApplicationMode::Editing
- || mode == ApplicationMode::OfflineRunning
- || online_connected);
- data_monitor_widget_->refreshValues(
- mode, runtime_mode_service_.plcConnectionState());
- }
-
- void MainWindow::configureProjectTree()
- {
- project_workspace_controller_ = std::make_unique<ProjectWorkspaceController>(
- *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::copyActiveSelection()
- {
- if (!runtime_mode_service_.policy().allowsProjectEditing
- || isTextEditingObject(qApp->focusWidget()))
- {
- return;
- }
- if (ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab)
- {
- const std::vector<std::string> ids = hmi_editor_widget_->selectedControlIds();
- std::vector<HmiControl> controls;
- controls.reserve(ids.size());
- for (const std::string &id : ids)
- {
- const HmiControl *control = hmi_editor_service_.findControl(
- current_hmi_page_id_, id);
- if (control != nullptr)
- {
- controls.push_back(*control);
- }
- }
- if (controls.empty())
- {
- return;
- }
- editor_clipboard_ = HmiClipboardData{std::move(controls), 0};
- statusBar()->showMessage(tr("已复制 %1 个 HMI 控件").arg(ids.size()), 3000);
- }
- else if (ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab)
- {
- LogicClipboardCopyResult result = logic_editor_widget_->copySelection();
- if (!result.copy.succeeded)
- {
- statusBar()->showMessage(fromUtf8(result.copy.message), 5000);
- return;
- }
- const bool whole_rows =
- result.fragment.mode == LogicClipboardMode::WholeRows;
- const std::size_t object_count = result.fragment.cells.size()
- + result.fragment.outputs.size()
- + result.fragment.verticalConnections.size();
- editor_clipboard_ = LogicClipboardData{std::move(result.fragment)};
- if (whole_rows)
- {
- const auto *data = std::get_if<LogicClipboardData>(&editor_clipboard_);
- statusBar()->showMessage(
- tr("已复制 %1 行梯形图").arg(data->fragment.rows.size()),
- 3000);
- }
- else
- {
- statusBar()->showMessage(
- tr("已复制 %1 个梯形图对象").arg(object_count), 3000);
- }
- }
- updateEditActions();
- }
-
- void MainWindow::pasteActiveSelection()
- {
- if (!runtime_mode_service_.policy().allowsProjectEditing
- || isTextEditingObject(qApp->focusWidget()))
- {
- return;
- }
- if (ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab)
- {
- auto *data = std::get_if<HmiClipboardData>(&editor_clipboard_);
- if (data == nullptr)
- {
- return;
- }
- const int offset = 20 * std::min(data->pasteCount + 1, 10);
- const HmiEditorResult result = hmi_editor_service_.pasteControls(
- current_hmi_page_id_, data->controls, offset, offset);
- if (!result.succeeded)
- {
- showProjectResult(tr("粘贴 HMI 控件"), fromUtf8(result.message), false);
- return;
- }
- ++data->pasteCount;
- refreshProjectUi();
- hmi_editor_widget_->reloadPage();
- hmi_editor_widget_->selectControl(result.id);
- showControlProperties(result.id);
- statusBar()->showMessage(tr("已粘贴 HMI 控件"), 3000);
- }
- else if (ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab)
- {
- const auto *data = std::get_if<LogicClipboardData>(&editor_clipboard_);
- if (data == nullptr)
- {
- return;
- }
- const LogicClipboardPasteResult result =
- logic_editor_widget_->pasteClipboard(data->fragment);
- if (!result.edit.succeeded)
- {
- if (!result.edit.message.empty())
- {
- showProjectResult(
- tr("粘贴梯形图"), fromUtf8(result.edit.message), false);
- }
- return;
- }
- selected_logic_node_id_ = logic_editor_widget_->selectedNodeId();
- showLogicNodeProperties(selected_logic_node_id_);
- refreshProjectUi();
- 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_->clearSelection();
- selected_logic_node_id_.clear();
- showLogicNodeProperties({});
- if (logic_editor_widget_->mouseWireMode()
- != LogicEditorWidget::MouseWireMode::Select)
- {
- const QSignalBlocker draw_blocker(ui_->mouseDrawWireAction);
- const QSignalBlocker erase_blocker(ui_->mouseEraseWireAction);
- ui_->mouseDrawWireAction->setChecked(false);
- ui_->mouseEraseWireAction->setChecked(false);
- logic_editor_widget_->setMouseWireMode(
- LogicEditorWidget::MouseWireMode::Select);
- }
- }
- }
-
- 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())));
- const bool hmi_selection = hmi_active
- && !hmi_editor_widget_->selectedControlIds().empty();
- const bool logic_selection = logic_active
- && logic_editor_widget_->hasCopyableSelection();
- ui_->copyAction->setEnabled(
- editable && (hmi_selection || logic_selection));
- const bool hmi_clipboard = std::holds_alternative<HmiClipboardData>(editor_clipboard_);
- const bool logic_clipboard =
- std::holds_alternative<LogicClipboardData>(editor_clipboard_);
- ui_->pasteAction->setEnabled(editable && ((hmi_active && hmi_clipboard)
- || (logic_active && logic_clipboard)));
- 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_->insertRungAboveAction->setEnabled(editable && logic_active);
- ui_->insertRungBelowAction->setEnabled(editable && logic_active);
- ui_->deleteRungAction->setEnabled(editable && logic_active);
- ui_->deleteHorizontalWireAction->setEnabled(editable && logic_active);
- ui_->deleteVerticalWireAction->setEnabled(editable && logic_active);
- ui_->mouseDrawWireAction->setEnabled(editable && logic_active);
- ui_->mouseEraseWireAction->setEnabled(editable && logic_active);
- ui_->syntaxCheckAction->setEnabled(
- editable && !current_logic_id_.empty());
- ui_->doubleCoilCheckAction->setEnabled(
- editable && !current_logic_id_.empty());
- }
-
- 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_,
- plc_discovery_gateway_,
- [this] { runtime_mode_service_.disconnectPlc(); },
- 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(
- result.message.empty()
- ? tr("竖线连接已建立") : fromUtf8(result.message),
- 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::insertLogicRungAbove()
- {
- const LogicEditorResult result = logic_editor_widget_->insertRung(false);
- if (!result.succeeded)
- {
- showProjectResult(tr("上方插入行"), fromUtf8(result.message), false);
- return;
- }
- selected_logic_node_id_.clear();
- showLogicNodeProperties({});
- refreshProjectUi();
- statusBar()->showMessage(tr("已在当前行上方插入空白行"), 3000);
- }
-
- void MainWindow::insertLogicRungBelow()
- {
- const LogicEditorResult result = logic_editor_widget_->insertRung(true);
- if (!result.succeeded)
- {
- showProjectResult(tr("下方插入行"), fromUtf8(result.message), false);
- return;
- }
- selected_logic_node_id_.clear();
- showLogicNodeProperties({});
- refreshProjectUi();
- statusBar()->showMessage(tr("已在当前行下方插入空白行"), 3000);
- }
-
- void MainWindow::deleteLogicRung()
- {
- const LogicEditorResult result = logic_editor_widget_->deleteRung();
- if (!result.succeeded)
- {
- showProjectResult(tr("删除行"), fromUtf8(result.message), false);
- return;
- }
- selected_logic_node_id_.clear();
- showLogicNodeProperties({});
- refreshProjectUi();
- statusBar()->showMessage(tr("当前行已删除,竖线连接已重新整理"), 3000);
- }
-
- void MainWindow::editSelectedNetworkComment()
- {
- const std::string rung_id = logic_editor_widget_->selectedRungId();
- if (rung_id.empty())
- {
- showProjectResult(tr("网络注释"), tr("请先选择一个梯形图网络"), false);
- return;
- }
- const LadderRung *head = logic_editor_service_.findNetworkHeadRung(
- current_logic_id_, rung_id);
- if (head == nullptr)
- {
- return;
- }
- bool accepted = false;
- const QString comment = QInputDialog::getText(
- this,
- tr("网络注释"),
- tr("说明"),
- QLineEdit::Normal,
- fromUtf8(head->comment),
- &accepted);
- if (!accepted)
- {
- return;
- }
- const LogicEditorResult result = logic_editor_service_.updateNetworkComment(
- 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::runLogicSyntaxCheck()
- {
- const LogicSyntaxCheckResult result = logic_editor_service_.checkSyntax(
- current_logic_id_);
- reportLogicSyntaxCheck(result, tr("语法检查"));
- }
-
- void MainWindow::runDoubleCoilCheck()
- {
- const LogicSyntaxCheckResult result =
- logic_editor_service_.checkDoubleCoils(current_logic_id_);
- reportLogicSyntaxCheck(result, tr("双线圈检查"));
- }
-
- void MainWindow::reportLogicSyntaxCheck(
- const LogicSyntaxCheckResult &result, const QString &action)
- {
- if (result.changed)
- {
- logic_editor_widget_->reloadLogic();
- refreshProjectUi();
- }
- QString message = action + QStringLiteral(": ") + fromUtf8(result.message);
- if (result.removedWireCells > 0U
- || result.removedVerticalConnections > 0U)
- {
- message += tr(";已规整 %1 格横线、%2 段竖线")
- .arg(result.removedWireCells)
- .arg(result.removedVerticalConnections);
- }
- appendOutputMessage(message, result.location);
- ui_->outputDock->show();
- ui_->outputDock->raise();
- statusBar()->showMessage(message, 6000);
- if (!result.valid && result.location.has_value())
- {
- focusLogicSyntaxLocation(*result.location);
- }
- updateEditActions();
- }
-
- void MainWindow::focusLogicSyntaxLocation(
- const LogicSyntaxLocation &location)
- {
- if (logic_editor_service_.findRung(
- location.logicId, location.rungId) == nullptr)
- {
- return;
- }
- current_logic_id_ = location.logicId;
- ui_->editorTabWidget->setCurrentWidget(ui_->logicEditorTab);
- refreshProjectUi();
- logic_editor_widget_->focusSyntaxLocation(
- location.rungId, location.column);
- }
-
- 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;
- }
-
- QInputDialog name_dialog(this);
- name_dialog.setWindowTitle(tr("新建工程"));
- name_dialog.setLabelText(tr("工程名称"));
- name_dialog.setInputMode(QInputDialog::TextInput);
-
- QDialogButtonBox *button_box = name_dialog.findChild<QDialogButtonBox *>();
- QPushButton *ok_button = button_box != nullptr
- ? button_box->button(QDialogButtonBox::Ok)
- : nullptr;
- if (ok_button != nullptr)
- {
- ok_button->setEnabled(false);
- connect(&name_dialog,
- &QInputDialog::textValueChanged,
- &name_dialog,
- [ok_button](const QString &text) {
- ok_button->setEnabled(!text.trimmed().isEmpty());
- });
- }
-
- if (name_dialog.exec() != QDialog::Accepted)
- {
- return;
- }
- const QString name = name_dialog.textValue().trimmed();
- 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();
- editor_clipboard_ = std::monostate{};
- 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 suggested_file_name = suggestedProjectFileName(
- fromUtf8(project_service_.project().metadata.name));
- const QString path = QFileDialog::getSaveFileName(
- this,
- tr("工程另存为"),
- suggested_file_name,
- 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();
- editor_clipboard_ = std::monostate{};
- 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::exportRuntimeProgram()
- {
- const LogicSyntaxCheckResult syntax =
- logic_editor_service_.checkEnabledSyntax();
- if (syntax.changed || !syntax.completed || !syntax.valid)
- {
- reportLogicSyntaxCheck(syntax, tr("导出前语法检查"));
- }
- if (!syntax.completed || !syntax.valid)
- {
- return;
- }
- std::string validation_error;
- if (!project_service_.project().validateForRunning(
- project_service_.projectLimits(), &validation_error))
- {
- showProjectResult(
- tr("导出用户运行程序"),
- tr("工程还不能运行:%1").arg(fromUtf8(validation_error)),
- false);
- return;
- }
-
- QString suggested_name = suggestedProjectFileName(
- fromUtf8(project_service_.project().metadata.name));
- suggested_name.chop(QStringLiteral(".json").size());
- const QString parent_directory = QFileDialog::getExistingDirectory(
- this,
- tr("选择用户运行程序的目标父目录"),
- QDir::homePath(),
- QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
- if (parent_directory.isEmpty())
- {
- return;
- }
-
- bool name_accepted = false;
- const QString requested_name = QInputDialog::getText(
- this,
- tr("导出用户运行程序"),
- tr("请输入导出文件夹名称"),
- QLineEdit::Normal,
- suggested_name,
- &name_accepted).trimmed();
- if (!name_accepted || requested_name.isEmpty())
- {
- return;
- }
- const QString output_name = suggestedProjectFileName(requested_name).chopped(
- QStringLiteral(".json").size());
- const QString destination_directory = QDir(parent_directory).filePath(output_name);
- if (QFileInfo::exists(destination_directory))
- {
- if (!QFileInfo(destination_directory).isDir())
- {
- showProjectResult(
- tr("导出用户运行程序"),
- tr("导出目标已经存在且不是文件夹:%1").arg(destination_directory),
- false);
- return;
- }
- const QMessageBox::StandardButton answer = QMessageBox::question(
- this,
- tr("覆盖已有导出目录"),
- tr("文件夹“%1”已经存在,是否覆盖?").arg(destination_directory),
- QMessageBox::Yes | QMessageBox::No,
- QMessageBox::No);
- if (answer != QMessageBox::Yes)
- {
- return;
- }
- if (!QDir(destination_directory).removeRecursively())
- {
- showProjectResult(
- tr("导出用户运行程序"),
- tr("无法清理已有导出目录:%1").arg(destination_directory),
- false);
- return;
- }
- }
-
- const QString temp_project = QDir::tempPath()
- + QStringLiteral("/qtproxinje-runtime-")
- + QString::number(QCoreApplication::applicationPid())
- + QStringLiteral(".json");
- const ProjectOperationResult save_result = project_service_.exportAs(
- temp_project.toUtf8().toStdString());
- if (!save_result.succeeded)
- {
- showProjectResult(tr("导出用户运行程序"), fromUtf8(save_result.message), false);
- return;
- }
-
- QProgressDialog progress(
- tr("正在准备导出用户运行程序…"),
- QString(),
- 0,
- 100,
- this);
- progress.setWindowTitle(tr("导出用户运行程序"));
- progress.setWindowModality(Qt::WindowModal);
- progress.setAutoClose(false);
- progress.setAutoReset(false);
- progress.setMinimumDuration(0);
- progress.setValue(5);
- progress.show();
- QApplication::processEvents();
- ui_->exportRuntimeAction->setEnabled(false);
-
- const QString destination_executable = QDir(destination_directory).filePath(
- output_name + QStringLiteral(".exe"));
- const QString template_executable = QCoreApplication::applicationFilePath();
- const auto failExport = [this, &progress, &temp_project, &destination_directory](
- const QString &message)
- {
- QFile::remove(temp_project);
- QDir(destination_directory).removeRecursively();
- progress.setValue(0);
- progress.close();
- ui_->exportRuntimeAction->setEnabled(
- runtime_mode_service_.policy().allowsProjectEditing);
- showProjectResult(tr("导出用户运行程序"), message, false);
- };
-
- if (!QDir().mkpath(destination_directory))
- {
- failExport(tr("无法创建导出目录:%1").arg(destination_directory));
- return;
- }
-
- progress.setLabelText(tr("正在封装工程数据…"));
- progress.setValue(35);
- QApplication::processEvents();
- const RuntimeProjectBundleWriteResult bundle_result =
- RuntimeProjectBundleService::write(
- template_executable,
- temp_project,
- destination_executable);
- if (!bundle_result.succeeded)
- {
- failExport(bundle_result.message);
- return;
- }
-
- progress.setLabelText(tr("正在复制 Qt 运行库和平台插件…"));
- progress.setValue(65);
- QApplication::processEvents();
- const QString application_directory = QFileInfo(template_executable).absolutePath();
- const QStringList runtime_files = {
- QStringLiteral("Qt5Core.dll"),
- QStringLiteral("Qt5Gui.dll"),
- QStringLiteral("Qt5Network.dll"),
- QStringLiteral("Qt5SerialBus.dll"),
- QStringLiteral("Qt5SerialPort.dll"),
- QStringLiteral("Qt5Widgets.dll"),
- QStringLiteral("libgcc_s_seh-1.dll"),
- QStringLiteral("libstdc++-6.dll"),
- QStringLiteral("libwinpthread-1.dll")};
- for (const QString &runtime_file : runtime_files)
- {
- const QString source = QDir(application_directory).filePath(runtime_file);
- const QString destination = QDir(destination_directory).filePath(runtime_file);
- if (!QFileInfo::exists(source) || !QFile::copy(source, destination))
- {
- failExport(tr("复制运行库失败:%1").arg(source));
- return;
- }
- }
- QString copy_error;
- if (!copyDirectoryContents(
- QDir(application_directory).filePath(QStringLiteral("platforms")),
- QDir(destination_directory).filePath(QStringLiteral("platforms")),
- ©_error)
- || !copyDirectoryContents(
- QDir(application_directory).filePath(QStringLiteral("styles")),
- QDir(destination_directory).filePath(QStringLiteral("styles")),
- ©_error))
- {
- failExport(copy_error);
- return;
- }
-
- const RuntimeProjectBundleLoadResult verification =
- RuntimeProjectBundleService::load(destination_executable);
- if (verification.status != RuntimeProjectBundleStatus::Loaded)
- {
- failExport(verification.message.isEmpty()
- ? tr("导出结果校验失败")
- : verification.message);
- return;
- }
- QFile::remove(temp_project);
- progress.setLabelText(tr("导出完成"));
- progress.setValue(100);
- QApplication::processEvents();
- progress.close();
- ui_->exportRuntimeAction->setEnabled(
- runtime_mode_service_.policy().allowsProjectEditing);
- showProjectResult(
- tr("导出用户运行程序"),
- tr("已导出:%1").arg(destination_executable),
- 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,
- const std::optional<LogicSyntaxLocation> &location)
- {
- const int maximum = application_settings_result_
- .settings.projectLimits.maximumOutputMessages;
- while (ui_->outputList->count() >= maximum)
- {
- delete ui_->outputList->takeItem(0);
- }
- auto *item = new QListWidgetItem(message, ui_->outputList);
- if (location.has_value())
- {
- item->setData(
- kSyntaxLogicIdRole, fromUtf8(location->logicId));
- item->setData(
- kSyntaxRungIdRole, fromUtf8(location->rungId));
- item->setData(kSyntaxColumnRole, location->column);
- item->setToolTip(tr("双击定位到网络 %1,第 %2 行第 %3 列")
- .arg(location->network)
- .arg(location->row)
- .arg(location->column));
- }
- ui_->outputList->scrollToBottom();
- }
-
- bool MainWindow::requestMode(ApplicationMode requested_mode)
- {
- // UI 动作只提出目标模式,所有前置条件和仓库切换由 RuntimeModeService 决定
- // 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 false;
- }
- }
- const bool runtime_request = requested_mode == ApplicationMode::OfflineRunning
- || requested_mode == ApplicationMode::OnlineRunning;
- if (runtime_request
- && (result.succeeded
- || result.error == ModeTransitionError::ProjectNotReady))
- {
- const LogicSyntaxCheckResult &syntax =
- runtime_mode_service_.lastSyntaxCheck();
- if (syntax.completed && (syntax.changed || !syntax.valid))
- {
- reportLogicSyntaxCheck(syntax, tr("运行前语法检查"));
- }
- }
- if (!result.succeeded)
- {
- restoreCurrentModeAction();
- QString message = transitionErrorText(result.error);
- if (result.error == ModeTransitionError::InitialPlcReadRequired)
- {
- message += tr(";当前状态:%1").arg(plcStatusText(runtime_mode_service_));
- }
- if (!result.detail.empty())
- {
- message += QStringLiteral(": ") + fromUtf8(result.detail);
- }
- if (result.error == ModeTransitionError::SimulationStartFailed)
- {
- const LogicScanResult &error = requested_mode
- == ApplicationMode::OnlineRunning
- ? runtime_mode_service_.onlineLogicMonitorService().lastError()
- : runtime_mode_service_.simulationError();
- if (!error.message.empty())
- {
- message += QStringLiteral(": ") + fromUtf8(error.message);
- }
- }
- statusBar()->showMessage(message, 5000);
- return false;
- }
- updateModeUi(tr("已进入%1").arg(modeText(runtime_mode_service_.mode())));
- return true;
- }
-
- void MainWindow::updateModeUi(const QString &message)
- {
- // 按服务层策略统一启用/禁用编辑入口,避免单个按钮遗漏状态同步
- const ApplicationMode mode = runtime_mode_service_.mode();
- const ModePolicy policy = runtime_mode_service_.policy();
- register_monitor_service_.setOfflineInitialCaptureEnabled(
- mode == ApplicationMode::Editing);
- 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 (!policy.allowsProjectEditing)
- {
- const QSignalBlocker draw_blocker(ui_->mouseDrawWireAction);
- const QSignalBlocker erase_blocker(ui_->mouseEraseWireAction);
- ui_->mouseDrawWireAction->setChecked(false);
- ui_->mouseEraseWireAction->setChecked(false);
- }
- 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_->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_->addNormalCoilAction->setEnabled(policy.allowsProjectEditing);
- ui_->addSetCoilAction->setEnabled(policy.allowsProjectEditing);
- ui_->addResetCoilAction->setEnabled(policy.allowsProjectEditing);
- ui_->addMoveAction->setEnabled(policy.allowsProjectEditing);
- ui_->addAddAction->setEnabled(policy.allowsProjectEditing);
- ui_->addSubAction->setEnabled(policy.allowsProjectEditing);
- ui_->addCompareAction->setEnabled(policy.allowsProjectEditing);
- ui_->editNetworkCommentAction->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);
- ui_->exportRuntimeAction->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);
- refreshDataMonitorUi();
- 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);
- }
|