综合平台编程器项目的远程存储
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 

1697 satır
59 KiB

  1. #include "main_window.h"
  2. #include "hmi_editor_widget.h"
  3. #include "alarm_configuration_dialog.h"
  4. #include "register_comment_dialog.h"
  5. #include "logic_editor_widget.h"
  6. #include "logic_instruction_dialog.h"
  7. #include "plc_connection_dialog.h"
  8. #include "runtime_monitor_widget.h"
  9. #include "toolbar_icon_factory.h"
  10. #include "project_workspace_controller.h"
  11. #include "property_panel_controller.h"
  12. #include "runtime_panel_controller.h"
  13. #include "services/hmi_editor_service.h"
  14. #include "services/hmi_navigation_service.h"
  15. #include "services/hmi_runtime_service.h"
  16. #include "services/alarm_editor_service.h"
  17. #include "services/alarm_service.h"
  18. #include "services/logic_editor_service.h"
  19. #include "services/project_service.h"
  20. #include "services/runtime_mode_service.h"
  21. #include "services/register_monitor_service.h"
  22. #include "services/register_comment_service.h"
  23. #include "ui_main_window.h"
  24. #include "domain/project_limits.h"
  25. #include <QActionGroup>
  26. #include <QAbstractSpinBox>
  27. #include <QApplication>
  28. #include <QCloseEvent>
  29. #include <QEvent>
  30. #include <QFileDialog>
  31. #include <QGraphicsScene>
  32. #include <QInputDialog>
  33. #include <QIcon>
  34. #include <QLabel>
  35. #include <QLineEdit>
  36. #include <QKeyEvent>
  37. #include <QKeySequence>
  38. #include <QMessageBox>
  39. #include <QMenu>
  40. #include <QPlainTextEdit>
  41. #include <QStatusBar>
  42. #include <QTabWidget>
  43. #include <QToolBar>
  44. #include <QToolButton>
  45. #include <QTextEdit>
  46. namespace {
  47. bool isTextEditingObject(QObject *object)
  48. {
  49. QWidget *widget = qobject_cast<QWidget *>(object);
  50. while (widget != nullptr)
  51. {
  52. if (qobject_cast<QLineEdit *>(widget) != nullptr
  53. || qobject_cast<QTextEdit *>(widget) != nullptr
  54. || qobject_cast<QPlainTextEdit *>(widget) != nullptr
  55. || qobject_cast<QAbstractSpinBox *>(widget) != nullptr)
  56. {
  57. return true;
  58. }
  59. widget = widget->parentWidget();
  60. }
  61. return false;
  62. }
  63. bool isEditorShortcut(const QKeyEvent &event)
  64. {
  65. return event.matches(QKeySequence::Undo)
  66. || event.matches(QKeySequence::Redo)
  67. || (event.modifiers() == Qt::NoModifier
  68. && (event.key() == Qt::Key_Delete || event.key() == Qt::Key_Escape));
  69. }
  70. QString modeText(ApplicationMode mode)
  71. {
  72. switch (mode)
  73. {
  74. case ApplicationMode::Editing:
  75. {
  76. return MainWindow::tr("编辑态");
  77. }
  78. case ApplicationMode::OfflineRunning:
  79. {
  80. return MainWindow::tr("离线运行态");
  81. }
  82. case ApplicationMode::OnlineRunning:
  83. {
  84. return MainWindow::tr("真机运行态");
  85. }
  86. default:
  87. {
  88. return MainWindow::tr("未知状态");
  89. }
  90. }
  91. }
  92. QString transitionErrorText(ModeTransitionError error)
  93. {
  94. switch (error)
  95. {
  96. case ModeTransitionError::MustReturnToEditing:
  97. {
  98. return MainWindow::tr("请先返回编辑态,再切换运行模式");
  99. }
  100. case ModeTransitionError::InitialPlcReadRequired:
  101. {
  102. return MainWindow::tr("真机运行前必须连接 PLC 并完成首次读取");
  103. }
  104. case ModeTransitionError::AlreadyInRequestedMode:
  105. {
  106. return MainWindow::tr("当前已处于所选模式");
  107. }
  108. case ModeTransitionError::ProjectNotReady:
  109. {
  110. return MainWindow::tr("工程存在未配置或未完成的 HMI/梯形图节点,暂不能进入离线运行态");
  111. }
  112. case ModeTransitionError::SimulationStartFailed:
  113. {
  114. return MainWindow::tr("离线逻辑执行器预检或启动失败");
  115. }
  116. case ModeTransitionError::None:
  117. default:
  118. {
  119. return MainWindow::tr("模式切换失败");
  120. }
  121. }
  122. }
  123. std::string toUtf8(const QString &value)
  124. {
  125. const QByteArray bytes = value.toUtf8();
  126. return std::string(bytes.constData(), static_cast<std::size_t>(bytes.size()));
  127. }
  128. QString fromUtf8(const std::string &value)
  129. {
  130. return QString::fromUtf8(value.data(), static_cast<int>(value.size()));
  131. }
  132. QString plcStatusText(const RuntimeModeService &service)
  133. {
  134. switch (service.plcConnectionState())
  135. {
  136. case PlcConnectionState::Connecting:
  137. return MainWindow::tr("PLC 正在连接");
  138. case PlcConnectionState::Connected:
  139. return service.initialPlcReadCompleted()
  140. ? MainWindow::tr("PLC 已连接,首次读取完成")
  141. : MainWindow::tr("PLC 已连接,正在读取工程使用的 M/D 地址");
  142. case PlcConnectionState::Recovering:
  143. return MainWindow::tr("PLC 通信已恢复,正在重新读取工程使用的 M/D 地址");
  144. case PlcConnectionState::Faulted:
  145. {
  146. const QString error = fromUtf8(service.plcError());
  147. return error.isEmpty()
  148. ? MainWindow::tr("PLC 通信故障")
  149. : MainWindow::tr("PLC 通信故障:%1").arg(error);
  150. }
  151. case PlcConnectionState::Disconnected:
  152. {
  153. const QString error = fromUtf8(service.plcError());
  154. return error.isEmpty()
  155. ? MainWindow::tr("PLC 已断开")
  156. : MainWindow::tr("PLC 已断开:%1").arg(error);
  157. }
  158. default:
  159. {
  160. return MainWindow::tr("PLC 已断开");
  161. }
  162. }
  163. }
  164. QToolButton *addToolbarMenu(
  165. QToolBar *toolbar,
  166. const QString &text,
  167. const QString &object_name,
  168. const QIcon &icon,
  169. const QList<QAction *> &actions)
  170. {
  171. auto *button = new QToolButton(toolbar);
  172. button->setObjectName(object_name);
  173. button->setText(text);
  174. button->setIcon(icon);
  175. button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
  176. button->setPopupMode(QToolButton::InstantPopup);
  177. auto *menu = new QMenu(button);
  178. menu->addActions(actions);
  179. button->setMenu(menu);
  180. toolbar->addWidget(button);
  181. return button;
  182. }
  183. } // namespace
  184. MainWindow::MainWindow(
  185. RuntimeModeService &runtime_mode_service,
  186. ProjectService &project_service,
  187. HmiEditorService &hmi_editor_service,
  188. LogicEditorService &logic_editor_service,
  189. HmiRuntimeService &hmi_runtime_service,
  190. AlarmEditorService &alarm_editor_service,
  191. AlarmService &alarm_service,
  192. RegisterCommentService &register_comment_service,
  193. RegisterMonitorService &register_monitor_service,
  194. QWidget *parent)
  195. : QMainWindow(parent),
  196. ui_(std::make_unique<Ui::MainWindow>()),
  197. runtime_mode_service_(runtime_mode_service),
  198. project_service_(project_service),
  199. hmi_editor_service_(hmi_editor_service),
  200. logic_editor_service_(logic_editor_service),
  201. hmi_runtime_service_(hmi_runtime_service),
  202. alarm_editor_service_(alarm_editor_service),
  203. alarm_service_(alarm_service),
  204. register_comment_service_(register_comment_service),
  205. register_monitor_service_(register_monitor_service),
  206. owned_hmi_navigation_service_(
  207. std::make_unique<HmiNavigationService>(project_service)),
  208. hmi_navigation_service_(owned_hmi_navigation_service_.get())
  209. {
  210. initializeUi();
  211. }
  212. MainWindow::MainWindow(
  213. RuntimeModeService &runtime_mode_service,
  214. ProjectService &project_service,
  215. HmiEditorService &hmi_editor_service,
  216. LogicEditorService &logic_editor_service,
  217. HmiRuntimeService &hmi_runtime_service,
  218. AlarmEditorService &alarm_editor_service,
  219. AlarmService &alarm_service,
  220. HmiNavigationService &hmi_navigation_service,
  221. RegisterCommentService &register_comment_service,
  222. RegisterMonitorService &register_monitor_service,
  223. QWidget *parent)
  224. : QMainWindow(parent),
  225. ui_(std::make_unique<Ui::MainWindow>()),
  226. runtime_mode_service_(runtime_mode_service),
  227. project_service_(project_service),
  228. hmi_editor_service_(hmi_editor_service),
  229. logic_editor_service_(logic_editor_service),
  230. hmi_runtime_service_(hmi_runtime_service),
  231. alarm_editor_service_(alarm_editor_service),
  232. alarm_service_(alarm_service),
  233. register_comment_service_(register_comment_service),
  234. register_monitor_service_(register_monitor_service),
  235. hmi_navigation_service_(&hmi_navigation_service)
  236. {
  237. initializeUi();
  238. }
  239. void MainWindow::initializeUi()
  240. {
  241. ui_->setupUi(this);
  242. qApp->installEventFilter(this);
  243. configureAppearance();
  244. property_panel_controller_ = std::make_unique<PropertyPanelController>(
  245. *this,
  246. *ui_,
  247. project_service_,
  248. hmi_editor_service_,
  249. logic_editor_service_,
  250. [this] { return current_hmi_page_id_; },
  251. [this] { return current_logic_id_; },
  252. selected_control_id_,
  253. selected_logic_node_id_,
  254. [this] { refreshProjectUi(); },
  255. [this](const QString &action, const QString &message, bool succeeded)
  256. {
  257. showProjectResult(action, message, succeeded);
  258. },
  259. [this](const QString &message, int timeout_ms)
  260. {
  261. statusBar()->showMessage(message, timeout_ms);
  262. });
  263. configureActions();
  264. configurePropertyEditor();
  265. configurePlcConnection();
  266. configureAlarms();
  267. configureRegisterComments();
  268. configureHmiEditor();
  269. configureLogicEditor();
  270. configureRuntimeMonitor();
  271. configureProjectTree();
  272. runtime_mode_service_.setPlcStatusChangedCallback(
  273. [this] { schedulePlcStatusUpdate(); });
  274. hmi_editor_service_.ensureDefaultPage();
  275. logic_editor_service_.ensureDefaultLogic();
  276. clearEditorHistories();
  277. current_hmi_page_id_ = hmi_editor_service_.firstPageId();
  278. current_logic_id_ = logic_editor_service_.firstLogicId();
  279. showControlProperties({});
  280. refreshProjectUi();
  281. updateModeUi(tr("系统已进入编辑态"));
  282. }
  283. MainWindow::~MainWindow()
  284. {
  285. qApp->removeEventFilter(this);
  286. runtime_mode_service_.setPlcStatusChangedCallback({});
  287. }
  288. bool MainWindow::eventFilter(QObject *watched, QEvent *event)
  289. {
  290. if (event->type() == QEvent::ShortcutOverride
  291. && isTextEditingObject(watched))
  292. {
  293. auto *key_event = static_cast<QKeyEvent *>(event);
  294. if (isEditorShortcut(*key_event))
  295. {
  296. event->accept();
  297. return true;
  298. }
  299. }
  300. return QMainWindow::eventFilter(watched, event);
  301. }
  302. void MainWindow::closeEvent(QCloseEvent *event)
  303. {
  304. if (!confirmSaveBeforeDestructiveAction())
  305. {
  306. event->ignore();
  307. return;
  308. }
  309. if (runtime_panel_controller_ != nullptr)
  310. {
  311. runtime_panel_controller_->closeForApplicationExit();
  312. }
  313. QMainWindow::closeEvent(event);
  314. }
  315. const std::string &MainWindow::currentHmiPageId() const
  316. {
  317. return current_hmi_page_id_;
  318. }
  319. const std::string &MainWindow::currentLogicId() const
  320. {
  321. return current_logic_id_;
  322. }
  323. void MainWindow::configureActions()
  324. {
  325. mode_action_group_ = new QActionGroup(this);
  326. mode_action_group_->setExclusive(true);
  327. mode_action_group_->addAction(ui_->editingModeAction);
  328. mode_action_group_->addAction(ui_->offlineModeAction);
  329. mode_action_group_->addAction(ui_->onlineModeAction);
  330. connect(ui_->editingModeAction, &QAction::triggered, this,
  331. [this] { requestMode(ApplicationMode::Editing); });
  332. connect(ui_->offlineModeAction, &QAction::triggered, this,
  333. [this] { requestMode(ApplicationMode::OfflineRunning); });
  334. connect(ui_->onlineModeAction, &QAction::triggered, this,
  335. [this] { requestMode(ApplicationMode::OnlineRunning); });
  336. connect(ui_->exitAction, &QAction::triggered, this, [this] { close(); });
  337. connect(ui_->newProjectAction, &QAction::triggered, this, &MainWindow::createNewProject);
  338. connect(ui_->saveProjectAction, &QAction::triggered, this, &MainWindow::saveProject);
  339. connect(ui_->saveAsProjectAction, &QAction::triggered, this, &MainWindow::saveProjectAs);
  340. connect(ui_->loadProjectAction, &QAction::triggered, this, &MainWindow::loadProject);
  341. connect(ui_->undoAction, &QAction::triggered,
  342. this, &MainWindow::undoActiveEditor);
  343. connect(ui_->redoAction, &QAction::triggered,
  344. this, &MainWindow::redoActiveEditor);
  345. connect(ui_->deleteSelectionAction, &QAction::triggered,
  346. this, &MainWindow::deleteActiveSelection);
  347. connect(ui_->clearSelectionAction, &QAction::triggered,
  348. this, &MainWindow::clearActiveSelection);
  349. connect(ui_->addButtonAction, &QAction::triggered, this,
  350. [this] { addHmiControl(HmiControlType::Button); });
  351. connect(ui_->addIndicatorAction, &QAction::triggered, this,
  352. [this] { addHmiControl(HmiControlType::Indicator); });
  353. connect(ui_->addNumericDisplayAction, &QAction::triggered, this,
  354. [this] { addHmiControl(HmiControlType::NumericDisplay); });
  355. connect(ui_->addNumericInputAction, &QAction::triggered, this,
  356. [this] { addHmiControl(HmiControlType::NumericInput); });
  357. connect(ui_->addProgressBarAction, &QAction::triggered, this,
  358. [this] { addHmiControl(HmiControlType::ProgressBar); });
  359. connect(ui_->addLabelAction, &QAction::triggered, this,
  360. [this] { addHmiControl(HmiControlType::Label); });
  361. connect(ui_->addPageJumpAction, &QAction::triggered, this,
  362. [this] { addHmiControl(HmiControlType::PageJump); });
  363. connect(ui_->addAlarmListAction, &QAction::triggered, this,
  364. [this] { addHmiControl(HmiControlType::AlarmList); });
  365. connect(ui_->deleteControlAction, &QAction::triggered,
  366. this, &MainWindow::deleteSelectedControl);
  367. addToolbarMenu(
  368. ui_->hmiToolBar,
  369. tr("更多控件"),
  370. QStringLiteral("hmiMoreControlsButton"),
  371. makeUiIcon(UiIcon::More),
  372. QList<QAction *>{
  373. ui_->addProgressBarAction,
  374. ui_->addPageJumpAction,
  375. ui_->addAlarmListAction,
  376. ui_->configureAlarmsAction});
  377. connect(ui_->addRungAction, &QAction::triggered,
  378. this, &MainWindow::addLogicRung);
  379. connect(ui_->insertHorizontalWireAction, &QAction::triggered,
  380. this, &MainWindow::addLogicHorizontalWire);
  381. connect(ui_->insertVerticalWireAction, &QAction::triggered,
  382. this, &MainWindow::addLogicVerticalWire);
  383. connect(ui_->deleteHorizontalWireAction, &QAction::triggered,
  384. this, &MainWindow::deleteLogicHorizontalWire);
  385. connect(ui_->deleteVerticalWireAction, &QAction::triggered,
  386. this, &MainWindow::deleteLogicVerticalWire);
  387. connect(ui_->parallelInsertAction, &QAction::triggered,
  388. this,
  389. [this]
  390. {
  391. addLogicParallelBranch(ContactNodeConfig{
  392. RegisterAddress{RegisterArea::M, 0},
  393. ContactMode::NormallyOpen});
  394. });
  395. QMenu *parallel_menu = new QMenu(this);
  396. QAction *parallel_open = parallel_menu->addAction(tr("并联常开触点"));
  397. QAction *parallel_closed = parallel_menu->addAction(tr("并联常闭触点"));
  398. QAction *parallel_rising = parallel_menu->addAction(tr("并联上升沿触点"));
  399. QAction *parallel_falling = parallel_menu->addAction(tr("并联下降沿触点"));
  400. QAction *parallel_timer = parallel_menu->addAction(tr("并联 T 触点"));
  401. QAction *parallel_counter = parallel_menu->addAction(tr("并联 C 触点"));
  402. QAction *parallel_compare = parallel_menu->addAction(tr("并联比较条件"));
  403. connect(parallel_open, &QAction::triggered,
  404. this,
  405. [this]
  406. {
  407. addLogicParallelBranch(ContactNodeConfig{
  408. RegisterAddress{RegisterArea::M, 0},
  409. ContactMode::NormallyOpen});
  410. });
  411. connect(parallel_closed, &QAction::triggered,
  412. this,
  413. [this]
  414. {
  415. addLogicParallelBranch(ContactNodeConfig{
  416. RegisterAddress{RegisterArea::M, 0},
  417. ContactMode::NormallyClosed});
  418. });
  419. connect(parallel_rising, &QAction::triggered,
  420. this,
  421. [this]
  422. {
  423. addLogicParallelBranch(EdgeContactNodeConfig{
  424. RegisterAddress{RegisterArea::M, 0}, EdgeMode::Rising});
  425. });
  426. connect(parallel_falling, &QAction::triggered,
  427. this,
  428. [this]
  429. {
  430. addLogicParallelBranch(EdgeContactNodeConfig{
  431. RegisterAddress{RegisterArea::M, 0}, EdgeMode::Falling});
  432. });
  433. connect(parallel_timer, &QAction::triggered,
  434. this,
  435. [this]
  436. {
  437. addLogicParallelBranch(TimerContactNodeConfig{
  438. TimerAddress{0}, ContactMode::NormallyOpen});
  439. });
  440. connect(parallel_counter, &QAction::triggered,
  441. this,
  442. [this]
  443. {
  444. addLogicParallelBranch(CounterContactNodeConfig{
  445. CounterAddress{0}, ContactMode::NormallyOpen});
  446. });
  447. connect(parallel_compare, &QAction::triggered,
  448. this,
  449. [this]
  450. {
  451. addLogicParallelBranch(CompareNodeConfig{
  452. RegisterAddress{RegisterArea::D, 0},
  453. ComparisonOperator::Equal,
  454. 0});
  455. });
  456. ui_->parallelInsertAction->setMenu(parallel_menu);
  457. if (QToolButton *parallel_button = qobject_cast<QToolButton *>(
  458. ui_->logicToolBar->widgetForAction(ui_->parallelInsertAction)))
  459. {
  460. parallel_button->setPopupMode(QToolButton::MenuButtonPopup);
  461. }
  462. connect(ui_->addNormallyOpenAction, &QAction::triggered,
  463. this,
  464. [this]
  465. {
  466. addLogicCondition(ContactNodeConfig{
  467. RegisterAddress{RegisterArea::M, 0},
  468. ContactMode::NormallyOpen});
  469. });
  470. connect(ui_->addNormallyClosedAction, &QAction::triggered,
  471. this,
  472. [this]
  473. {
  474. addLogicCondition(ContactNodeConfig{
  475. RegisterAddress{RegisterArea::M, 0},
  476. ContactMode::NormallyClosed});
  477. });
  478. connect(ui_->addRisingEdgeAction, &QAction::triggered,
  479. this,
  480. [this]
  481. {
  482. addLogicCondition(EdgeContactNodeConfig{
  483. RegisterAddress{RegisterArea::M, 0}, EdgeMode::Rising});
  484. });
  485. connect(ui_->addFallingEdgeAction, &QAction::triggered,
  486. this,
  487. [this]
  488. {
  489. addLogicCondition(EdgeContactNodeConfig{
  490. RegisterAddress{RegisterArea::M, 0}, EdgeMode::Falling});
  491. });
  492. connect(ui_->addTimerContactAction, &QAction::triggered,
  493. this,
  494. [this]
  495. {
  496. addLogicCondition(TimerContactNodeConfig{
  497. TimerAddress{0}, ContactMode::NormallyOpen});
  498. });
  499. connect(ui_->addCounterContactAction, &QAction::triggered,
  500. this,
  501. [this]
  502. {
  503. addLogicCondition(CounterContactNodeConfig{
  504. CounterAddress{0}, ContactMode::NormallyOpen});
  505. });
  506. connect(ui_->addNormalCoilAction, &QAction::triggered,
  507. this,
  508. [this]
  509. {
  510. setLogicOutput(CoilNodeConfig{
  511. RegisterAddress{RegisterArea::M, 0},
  512. CoilMode::Normal});
  513. });
  514. connect(ui_->addSetCoilAction, &QAction::triggered,
  515. this,
  516. [this]
  517. {
  518. setLogicOutput(CoilNodeConfig{
  519. RegisterAddress{RegisterArea::M, 0},
  520. CoilMode::Set});
  521. });
  522. connect(ui_->addResetCoilAction, &QAction::triggered,
  523. this,
  524. [this]
  525. {
  526. setLogicOutput(CoilNodeConfig{
  527. RegisterAddress{RegisterArea::M, 0},
  528. CoilMode::Reset});
  529. });
  530. connect(ui_->addTonAction, &QAction::triggered,
  531. this,
  532. [this]
  533. {
  534. setLogicOutput(TonNodeConfig{TimerAddress{0}, 1000});
  535. });
  536. connect(ui_->addCtuAction, &QAction::triggered,
  537. this,
  538. [this]
  539. {
  540. configureAndSetLogicOutput(CounterNodeConfig{
  541. CounterAddress{0},
  542. CounterMode::Up,
  543. RegisterAddress{RegisterArea::D, 0},
  544. WordOperand{
  545. WordOperandKind::Constant,
  546. RegisterAddress{RegisterArea::D, 0},
  547. 10},
  548. RegisterAddress{RegisterArea::M, 0}});
  549. });
  550. connect(ui_->addCtdAction, &QAction::triggered,
  551. this,
  552. [this]
  553. {
  554. configureAndSetLogicOutput(CounterNodeConfig{
  555. CounterAddress{0},
  556. CounterMode::Down,
  557. RegisterAddress{RegisterArea::D, 0},
  558. WordOperand{
  559. WordOperandKind::Constant,
  560. RegisterAddress{RegisterArea::D, 0},
  561. 10},
  562. RegisterAddress{RegisterArea::M, 0}});
  563. });
  564. connect(ui_->addMoveAction, &QAction::triggered,
  565. this,
  566. [this]
  567. {
  568. configureAndSetLogicOutput(MoveNodeConfig{
  569. WordOperand{
  570. WordOperandKind::Constant,
  571. RegisterAddress{RegisterArea::D, 0},
  572. 0},
  573. RegisterAddress{RegisterArea::D, 0}});
  574. });
  575. connect(ui_->addAddAction, &QAction::triggered,
  576. this,
  577. [this]
  578. {
  579. configureAndSetLogicOutput(ArithmeticNodeConfig{
  580. ArithmeticOperation::Add,
  581. WordOperand{
  582. WordOperandKind::Register,
  583. RegisterAddress{RegisterArea::D, 0},
  584. 0},
  585. WordOperand{
  586. WordOperandKind::Constant,
  587. RegisterAddress{RegisterArea::D, 0},
  588. 1},
  589. RegisterAddress{RegisterArea::D, 0}});
  590. });
  591. connect(ui_->addSubAction, &QAction::triggered,
  592. this,
  593. [this]
  594. {
  595. configureAndSetLogicOutput(ArithmeticNodeConfig{
  596. ArithmeticOperation::Subtract,
  597. WordOperand{
  598. WordOperandKind::Register,
  599. RegisterAddress{RegisterArea::D, 0},
  600. 0},
  601. WordOperand{
  602. WordOperandKind::Constant,
  603. RegisterAddress{RegisterArea::D, 0},
  604. 1},
  605. RegisterAddress{RegisterArea::D, 0}});
  606. });
  607. connect(ui_->addCompareAction, &QAction::triggered,
  608. this,
  609. [this]
  610. {
  611. addLogicCondition(CompareNodeConfig{
  612. RegisterAddress{RegisterArea::D, 0},
  613. ComparisonOperator::Equal,
  614. 0});
  615. });
  616. connect(ui_->deleteLogicAction, &QAction::triggered,
  617. this, &MainWindow::deleteSelectedLogicObject);
  618. connect(ui_->editRungCommentAction, &QAction::triggered,
  619. this, &MainWindow::editSelectedRungComment);
  620. addToolbarMenu(
  621. ui_->logicToolBar,
  622. tr("更多触点"),
  623. QStringLiteral("logicContactMenuButton"),
  624. makeUiIcon(UiIcon::NormallyOpenContact),
  625. QList<QAction *>{
  626. ui_->addRisingEdgeAction,
  627. ui_->addFallingEdgeAction,
  628. ui_->addTimerContactAction,
  629. ui_->addCounterContactAction});
  630. addToolbarMenu(
  631. ui_->logicToolBar,
  632. tr("更多输出"),
  633. QStringLiteral("logicOutputMenuButton"),
  634. makeUiIcon(UiIcon::Coil),
  635. QList<QAction *>{ui_->addSetCoilAction, ui_->addResetCoilAction});
  636. addToolbarMenu(
  637. ui_->logicToolBar,
  638. tr("定时/计数"),
  639. QStringLiteral("logicTimerCounterMenuButton"),
  640. makeUiIcon(UiIcon::Ton),
  641. QList<QAction *>{ui_->addTonAction, ui_->addCtuAction, ui_->addCtdAction});
  642. addToolbarMenu(
  643. ui_->logicToolBar,
  644. tr("数据运算"),
  645. QStringLiteral("logicDataMenuButton"),
  646. makeUiIcon(UiIcon::Move),
  647. QList<QAction *>{
  648. ui_->addMoveAction,
  649. ui_->addAddAction,
  650. ui_->addSubAction,
  651. ui_->addCompareAction});
  652. connect(ui_->editorTabWidget, &QTabWidget::currentChanged,
  653. this,
  654. [this](int index)
  655. {
  656. ui_->hmiToolBar->setVisible(index == 0);
  657. ui_->logicToolBar->setVisible(index == 1);
  658. if (index == 0)
  659. {
  660. showControlProperties(selected_control_id_);
  661. }
  662. else if (index == 1)
  663. {
  664. showLogicNodeProperties(selected_logic_node_id_);
  665. }
  666. if (index == 0 || index == 1)
  667. {
  668. refreshProjectUi();
  669. }
  670. });
  671. ui_->hmiToolBar->setVisible(ui_->editorTabWidget->currentIndex() == 0);
  672. ui_->logicToolBar->setVisible(ui_->editorTabWidget->currentIndex() == 1);
  673. ui_->viewMenu->addAction(ui_->projectDock->toggleViewAction());
  674. ui_->viewMenu->addAction(ui_->propertiesDock->toggleViewAction());
  675. ui_->viewMenu->addAction(ui_->outputDock->toggleViewAction());
  676. ui_->viewMenu->addAction(ui_->hmiToolBar->toggleViewAction());
  677. ui_->viewMenu->addAction(ui_->logicToolBar->toggleViewAction());
  678. ui_->viewMenu->addSeparator();
  679. ui_->viewMenu->addAction(ui_->modeToolBar->toggleViewAction());
  680. }
  681. void MainWindow::configureAppearance()
  682. {
  683. setDockNestingEnabled(true);
  684. setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
  685. setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
  686. ui_->editingModeAction->setIcon(makeUiIcon(UiIcon::Edit));
  687. ui_->offlineModeAction->setIcon(makeUiIcon(UiIcon::RunOffline));
  688. ui_->onlineModeAction->setIcon(makeUiIcon(UiIcon::RunOnline));
  689. ui_->newProjectAction->setIcon(makeUiIcon(UiIcon::NewDocument));
  690. ui_->saveProjectAction->setIcon(makeUiIcon(UiIcon::Save));
  691. ui_->saveAsProjectAction->setIcon(makeUiIcon(UiIcon::SaveAs));
  692. ui_->loadProjectAction->setIcon(makeUiIcon(UiIcon::Open));
  693. ui_->undoAction->setIcon(makeUiIcon(UiIcon::Undo));
  694. ui_->redoAction->setIcon(makeUiIcon(UiIcon::Redo));
  695. ui_->deleteSelectionAction->setIcon(makeUiIcon(UiIcon::Delete));
  696. ui_->clearSelectionAction->setIcon(makeUiIcon(UiIcon::ClearList));
  697. ui_->exitAction->setIcon(makeUiIcon(UiIcon::Exit));
  698. ui_->configurePlcAction->setIcon(makeUiIcon(UiIcon::PlcSettings));
  699. ui_->disconnectPlcAction->setIcon(makeUiIcon(UiIcon::Disconnect));
  700. ui_->configureRegisterCommentsAction->setIcon(
  701. makeUiIcon(UiIcon::RegisterComments));
  702. ui_->addButtonAction->setIcon(makeUiIcon(UiIcon::HmiButton));
  703. ui_->addIndicatorAction->setIcon(makeUiIcon(UiIcon::Indicator));
  704. ui_->addNumericDisplayAction->setIcon(makeUiIcon(UiIcon::NumericDisplay));
  705. ui_->addNumericInputAction->setIcon(makeUiIcon(UiIcon::NumericInput));
  706. ui_->addProgressBarAction->setIcon(makeUiIcon(UiIcon::ProgressBar));
  707. ui_->addLabelAction->setIcon(makeUiIcon(UiIcon::Text));
  708. ui_->addPageJumpAction->setIcon(makeUiIcon(UiIcon::PageJump));
  709. ui_->addAlarmListAction->setIcon(makeUiIcon(UiIcon::AlarmList));
  710. ui_->configureAlarmsAction->setIcon(makeUiIcon(UiIcon::AlarmSettings));
  711. ui_->deleteControlAction->setIcon(makeUiIcon(UiIcon::Delete));
  712. ui_->addRungAction->setIcon(makeUiIcon(UiIcon::AddRung));
  713. ui_->parallelInsertAction->setIcon(makeUiIcon(UiIcon::ParallelBranch));
  714. ui_->insertHorizontalWireAction->setIcon(makeUiIcon(UiIcon::HorizontalWire));
  715. ui_->insertVerticalWireAction->setIcon(makeUiIcon(UiIcon::VerticalWire));
  716. ui_->deleteHorizontalWireAction->setIcon(makeUiIcon(UiIcon::Delete));
  717. ui_->deleteVerticalWireAction->setIcon(makeUiIcon(UiIcon::Delete));
  718. ui_->addNormallyOpenAction->setIcon(makeUiIcon(UiIcon::NormallyOpenContact));
  719. ui_->addNormallyClosedAction->setIcon(makeUiIcon(UiIcon::NormallyClosedContact));
  720. ui_->addRisingEdgeAction->setIcon(makeUiIcon(UiIcon::RisingEdgeContact));
  721. ui_->addFallingEdgeAction->setIcon(makeUiIcon(UiIcon::FallingEdgeContact));
  722. ui_->addTimerContactAction->setIcon(makeUiIcon(UiIcon::TimerContact));
  723. ui_->addCounterContactAction->setIcon(makeUiIcon(UiIcon::CounterContact));
  724. ui_->addNormalCoilAction->setIcon(makeUiIcon(UiIcon::Coil));
  725. ui_->addSetCoilAction->setIcon(makeUiIcon(UiIcon::SetCoil));
  726. ui_->addResetCoilAction->setIcon(makeUiIcon(UiIcon::ResetCoil));
  727. ui_->addTonAction->setIcon(makeUiIcon(UiIcon::Ton));
  728. ui_->addCtuAction->setIcon(makeUiIcon(UiIcon::Ctu));
  729. ui_->addCtdAction->setIcon(makeUiIcon(UiIcon::Ctd));
  730. ui_->addMoveAction->setIcon(makeUiIcon(UiIcon::Move));
  731. ui_->addAddAction->setIcon(makeUiIcon(UiIcon::Add));
  732. ui_->addSubAction->setIcon(makeUiIcon(UiIcon::Subtract));
  733. ui_->addCompareAction->setIcon(makeUiIcon(UiIcon::Compare));
  734. ui_->editRungCommentAction->setIcon(makeUiIcon(UiIcon::Comment));
  735. ui_->deleteLogicAction->setIcon(makeUiIcon(UiIcon::Delete));
  736. ui_->editorTabWidget->setTabIcon(0, makeUiIcon(UiIcon::HmiPage));
  737. ui_->editorTabWidget->setTabIcon(1, makeUiIcon(UiIcon::Logic));
  738. ui_->outputDock->setMaximumHeight(220);
  739. mode_status_label_ = new QLabel(this);
  740. mode_status_label_->setObjectName(QStringLiteral("modeStatusLabel"));
  741. mode_status_label_->setMinimumWidth(88);
  742. mode_status_label_->setAlignment(Qt::AlignCenter);
  743. register_status_label_ = new QLabel(this);
  744. register_status_label_->setObjectName(QStringLiteral("registerStatusLabel"));
  745. register_status_label_->setMinimumWidth(132);
  746. executor_status_label_ = new QLabel(this);
  747. executor_status_label_->setObjectName(QStringLiteral("executorStatusLabel"));
  748. executor_status_label_->setMinimumWidth(132);
  749. statusBar()->addPermanentWidget(mode_status_label_);
  750. statusBar()->addPermanentWidget(register_status_label_);
  751. statusBar()->addPermanentWidget(executor_status_label_);
  752. setStyleSheet(QStringLiteral(
  753. "QMainWindow { background: #f3f5f7; }"
  754. "QToolBar { background: #ffffff; border: 0; border-bottom: 1px solid #cbd2d9;"
  755. " padding: 4px; spacing: 3px; }"
  756. "QToolButton { min-height: 26px; padding: 3px 9px; border: 1px solid transparent; }"
  757. "QToolButton:hover { background: #edf2f6; border-color: #c5ced6; }"
  758. "QToolButton:checked { background: #dcece3; border-color: #75a58a; }"
  759. "QDockWidget { color: #24313b; font-weight: 600; }"
  760. "QDockWidget::title { background: #e9edf0; padding: 6px;"
  761. " border-bottom: 1px solid #cbd2d9; }"
  762. "QTreeWidget, QListWidget, QScrollArea { background: #ffffff; border: 1px solid #d5dbe0; }"
  763. "QTabWidget::pane { border: 0; background: #f3f5f7; }"
  764. "QTabBar::tab { background: #e5e9ec; padding: 7px 14px; border-right: 1px solid #cbd2d9; }"
  765. "QTabBar::tab:selected { background: #ffffff; color: #15232d; }"
  766. "QFrame#hmiCanvasPlaceholder, QFrame#logicCanvasPlaceholder { background: #ffffff;"
  767. " border: 1px solid #cbd2d9; }"
  768. "QLabel#hmiEmptyLabel, QLabel#logicEmptyLabel { color: #75838d; }"
  769. "QLabel#hmiPageTitleLabel { color: #1d2a33; font-weight: 600; }"
  770. "QLabel#hmiPageSizeLabel { color: #66747e; }"));
  771. }
  772. void MainWindow::configureHmiEditor()
  773. {
  774. QLayout *layout = ui_->hmiCanvasPlaceholder->layout();
  775. delete ui_->hmiEmptyLabel;
  776. hmi_editor_widget_ = new HmiEditorWidget(
  777. hmi_editor_service_,
  778. hmi_runtime_service_,
  779. alarm_service_,
  780. ui_->hmiCanvasPlaceholder);
  781. hmi_editor_widget_->setMinimumHeight(320);
  782. layout->addWidget(hmi_editor_widget_);
  783. }
  784. void MainWindow::configureLogicEditor()
  785. {
  786. QLayout *layout = ui_->logicCanvasPlaceholder->layout();
  787. delete ui_->logicEmptyLabel;
  788. logic_editor_widget_ = new LogicEditorWidget(
  789. logic_editor_service_, ui_->logicCanvasPlaceholder);
  790. logic_editor_widget_->setObjectName(QStringLiteral("logicEditorWidget"));
  791. logic_editor_widget_->setMinimumHeight(320);
  792. layout->addWidget(logic_editor_widget_);
  793. property_panel_controller_->bindEditorWidgets(
  794. *hmi_editor_widget_, *logic_editor_widget_);
  795. }
  796. void MainWindow::configureRuntimeMonitor()
  797. {
  798. runtime_panel_controller_ = std::make_unique<RuntimePanelController>(
  799. *this,
  800. runtime_mode_service_,
  801. project_service_,
  802. hmi_editor_service_,
  803. hmi_runtime_service_,
  804. logic_editor_service_,
  805. *hmi_navigation_service_,
  806. alarm_service_,
  807. register_monitor_service_,
  808. *hmi_editor_widget_,
  809. *logic_editor_widget_,
  810. *executor_status_label_,
  811. [this] { return current_logic_id_; },
  812. [this](const std::string &logic_id) { current_logic_id_ = logic_id; },
  813. [this](const std::string &node_id)
  814. {
  815. showLogicNodeProperties(node_id);
  816. },
  817. [this](const QString &message, int timeout_ms)
  818. {
  819. statusBar()->showMessage(message, timeout_ms);
  820. },
  821. [this](const QString &message)
  822. {
  823. appendOutputMessage(message);
  824. },
  825. [this]
  826. {
  827. requestMode(ApplicationMode::Editing);
  828. });
  829. runtime_panel_controller_->configure();
  830. runtime_monitor_widget_ = runtime_panel_controller_->runtimeMonitorWidget();
  831. }
  832. void MainWindow::configureProjectTree()
  833. {
  834. project_workspace_controller_ = std::make_unique<ProjectWorkspaceController>(
  835. *this,
  836. *ui_,
  837. project_service_,
  838. hmi_editor_service_,
  839. logic_editor_service_,
  840. runtime_mode_service_,
  841. *hmi_navigation_service_,
  842. *hmi_editor_widget_,
  843. *logic_editor_widget_,
  844. *runtime_monitor_widget_,
  845. current_hmi_page_id_,
  846. current_logic_id_,
  847. [this](const std::string &control_id)
  848. {
  849. showControlProperties(control_id);
  850. },
  851. [this](const std::string &node_id)
  852. {
  853. showLogicNodeProperties(node_id);
  854. },
  855. [this](const QString &action, const QString &message, bool succeeded)
  856. {
  857. showProjectResult(action, message, succeeded);
  858. },
  859. [this](const QString &message, int timeout_ms)
  860. {
  861. statusBar()->showMessage(message, timeout_ms);
  862. },
  863. [this]
  864. {
  865. updateEditActions();
  866. });
  867. project_workspace_controller_->configure();
  868. }
  869. void MainWindow::configurePropertyEditor()
  870. {
  871. property_panel_controller_->configure();
  872. }
  873. void MainWindow::configurePlcConnection()
  874. {
  875. connect(ui_->configurePlcAction, &QAction::triggered, this, &MainWindow::connectPlc);
  876. connect(ui_->disconnectPlcAction, &QAction::triggered, this, &MainWindow::disconnectPlc);
  877. }
  878. void MainWindow::configureAlarms()
  879. {
  880. connect(ui_->configureAlarmsAction, &QAction::triggered,
  881. this,
  882. [this]
  883. {
  884. AlarmConfigurationDialog dialog(alarm_editor_service_, this);
  885. dialog.exec();
  886. refreshProjectUi();
  887. });
  888. }
  889. void MainWindow::configureRegisterComments()
  890. {
  891. connect(
  892. ui_->configureRegisterCommentsAction,
  893. &QAction::triggered,
  894. this,
  895. [this]
  896. {
  897. RegisterCommentDialog dialog(register_comment_service_, this);
  898. dialog.exec();
  899. logic_editor_widget_->reloadLogic();
  900. refreshProjectUi();
  901. });
  902. }
  903. void MainWindow::undoActiveEditor()
  904. {
  905. if (!runtime_mode_service_.policy().allowsProjectEditing)
  906. {
  907. return;
  908. }
  909. if (ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab)
  910. {
  911. if (!hmi_editor_service_.undo().succeeded)
  912. {
  913. return;
  914. }
  915. selected_control_id_.clear();
  916. showControlProperties({});
  917. refreshProjectUi();
  918. hmi_editor_widget_->reloadPage();
  919. statusBar()->showMessage(tr("已撤销 HMI 编辑操作"), 3000);
  920. }
  921. else if (ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab)
  922. {
  923. if (!logic_editor_service_.undo().succeeded)
  924. {
  925. return;
  926. }
  927. selected_logic_node_id_.clear();
  928. showLogicNodeProperties({});
  929. refreshProjectUi();
  930. logic_editor_widget_->reloadLogic();
  931. statusBar()->showMessage(tr("已撤销梯形图编辑操作"), 3000);
  932. }
  933. updateEditActions();
  934. }
  935. void MainWindow::redoActiveEditor()
  936. {
  937. if (!runtime_mode_service_.policy().allowsProjectEditing)
  938. {
  939. return;
  940. }
  941. if (ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab)
  942. {
  943. if (!hmi_editor_service_.redo().succeeded)
  944. {
  945. return;
  946. }
  947. selected_control_id_.clear();
  948. showControlProperties({});
  949. refreshProjectUi();
  950. hmi_editor_widget_->reloadPage();
  951. statusBar()->showMessage(tr("已重做 HMI 编辑操作"), 3000);
  952. }
  953. else if (ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab)
  954. {
  955. if (!logic_editor_service_.redo().succeeded)
  956. {
  957. return;
  958. }
  959. selected_logic_node_id_.clear();
  960. showLogicNodeProperties({});
  961. refreshProjectUi();
  962. logic_editor_widget_->reloadLogic();
  963. statusBar()->showMessage(tr("已重做梯形图编辑操作"), 3000);
  964. }
  965. updateEditActions();
  966. }
  967. void MainWindow::deleteActiveSelection()
  968. {
  969. if (ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab)
  970. {
  971. deleteSelectedControl();
  972. }
  973. else if (ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab)
  974. {
  975. deleteSelectedLogicObject();
  976. }
  977. }
  978. void MainWindow::clearActiveSelection()
  979. {
  980. if (ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab)
  981. {
  982. hmi_editor_widget_->scene()->clearSelection();
  983. selected_control_id_.clear();
  984. showControlProperties({});
  985. }
  986. else if (ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab)
  987. {
  988. logic_editor_widget_->scene()->clearSelection();
  989. selected_logic_node_id_.clear();
  990. showLogicNodeProperties({});
  991. }
  992. }
  993. void MainWindow::updateEditActions()
  994. {
  995. const bool editable = runtime_mode_service_.policy().allowsProjectEditing;
  996. const bool hmi_active = ui_->editorTabWidget->currentWidget() == ui_->hmiEditorTab;
  997. const bool logic_active =
  998. ui_->editorTabWidget->currentWidget() == ui_->logicEditorTab;
  999. ui_->undoAction->setEnabled(
  1000. editable && ((hmi_active && hmi_editor_service_.canUndo())
  1001. || (logic_active && logic_editor_service_.canUndo())));
  1002. ui_->redoAction->setEnabled(
  1003. editable && ((hmi_active && hmi_editor_service_.canRedo())
  1004. || (logic_active && logic_editor_service_.canRedo())));
  1005. ui_->deleteSelectionAction->setEnabled(editable && (hmi_active || logic_active));
  1006. ui_->clearSelectionAction->setEnabled(editable && (hmi_active || logic_active));
  1007. ui_->insertHorizontalWireAction->setEnabled(editable && logic_active);
  1008. ui_->insertVerticalWireAction->setEnabled(editable && logic_active);
  1009. ui_->deleteHorizontalWireAction->setEnabled(editable && logic_active);
  1010. ui_->deleteVerticalWireAction->setEnabled(editable && logic_active);
  1011. }
  1012. void MainWindow::clearEditorHistories()
  1013. {
  1014. hmi_editor_service_.clearHistory();
  1015. logic_editor_service_.clearHistory();
  1016. if (ui_->undoAction != nullptr)
  1017. {
  1018. updateEditActions();
  1019. }
  1020. }
  1021. void MainWindow::refreshProjectUi()
  1022. {
  1023. project_workspace_controller_->refresh();
  1024. updateEditActions();
  1025. updateWindowTitle();
  1026. }
  1027. void MainWindow::updateWindowTitle()
  1028. {
  1029. QString title = tr("综合平台编程器");
  1030. if (project_service_.isModified())
  1031. {
  1032. title += QStringLiteral("*");
  1033. }
  1034. setWindowTitle(title);
  1035. }
  1036. bool MainWindow::confirmSaveBeforeDestructiveAction()
  1037. {
  1038. if (!project_service_.isModified())
  1039. {
  1040. return true;
  1041. }
  1042. const QString project_name = fromUtf8(project_service_.project().metadata.name);
  1043. const QMessageBox::StandardButton choice = QMessageBox::warning(
  1044. this,
  1045. tr("工程尚未保存"),
  1046. tr("工程“%1”有未保存的修改,是否先保存?").arg(project_name),
  1047. QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
  1048. QMessageBox::Save);
  1049. if (choice == QMessageBox::Cancel)
  1050. {
  1051. return false;
  1052. }
  1053. if (choice == QMessageBox::Discard)
  1054. {
  1055. return true;
  1056. }
  1057. saveProject();
  1058. return !project_service_.isModified();
  1059. }
  1060. void MainWindow::updateProjectTreeActions()
  1061. {
  1062. project_workspace_controller_->updateActions();
  1063. }
  1064. void MainWindow::connectPlc()
  1065. {
  1066. PlcConnectionDialog dialog(plc_configuration_, this);
  1067. if (dialog.exec() != QDialog::Accepted)
  1068. {
  1069. return;
  1070. }
  1071. plc_configuration_ = dialog.configuration();
  1072. if (plc_configuration_.portName.empty())
  1073. {
  1074. showProjectResult(tr("连接 PLC"), tr("请选择或输入串口端口"), false);
  1075. return;
  1076. }
  1077. const PlcCommunicationResult result = runtime_mode_service_.connectPlc(
  1078. plc_configuration_);
  1079. if (!result.succeeded)
  1080. {
  1081. const QString message = fromUtf8(result.message);
  1082. if (result.message == runtime_mode_service_.plcError())
  1083. {
  1084. statusBar()->showMessage(message, 5000);
  1085. QMessageBox::warning(this, tr("连接 PLC"), message);
  1086. }
  1087. else
  1088. {
  1089. showProjectResult(tr("连接 PLC"), message, false);
  1090. }
  1091. return;
  1092. }
  1093. statusBar()->showMessage(tr("PLC 正在连接,等待首次 M/D 读取"), 5000);
  1094. }
  1095. void MainWindow::disconnectPlc()
  1096. {
  1097. runtime_mode_service_.disconnectPlc();
  1098. }
  1099. void MainWindow::schedulePlcStatusUpdate()
  1100. {
  1101. if (plc_status_update_pending_)
  1102. {
  1103. return;
  1104. }
  1105. plc_status_update_pending_ = true;
  1106. QMetaObject::invokeMethod(
  1107. this,
  1108. [this]
  1109. {
  1110. plc_status_update_pending_ = false;
  1111. updateModeUi(plcStatusText(runtime_mode_service_));
  1112. },
  1113. Qt::QueuedConnection);
  1114. }
  1115. // 根据控件ID加载控件属性到右侧属性面板
  1116. void MainWindow::showControlProperties(const std::string &control_id)
  1117. {
  1118. property_panel_controller_->showControlProperties(control_id);
  1119. }
  1120. void MainWindow::showLogicNodeProperties(const std::string &node_id)
  1121. {
  1122. property_panel_controller_->showLogicNodeProperties(node_id);
  1123. }
  1124. void MainWindow::addHmiControl(HmiControlType type)
  1125. {
  1126. property_panel_controller_->addHmiControl(type);
  1127. }
  1128. void MainWindow::deleteSelectedControl()
  1129. {
  1130. property_panel_controller_->deleteSelectedControl();
  1131. }
  1132. void MainWindow::addLogicCondition(const LogicNodeConfig &config)
  1133. {
  1134. const LogicEditorResult result = logic_editor_widget_->addCondition(config);
  1135. if (!result.succeeded)
  1136. {
  1137. showProjectResult(tr("添加逻辑条件"), fromUtf8(result.message), false);
  1138. return;
  1139. }
  1140. selected_logic_node_id_ = result.id;
  1141. showLogicNodeProperties(result.id);
  1142. refreshProjectUi();
  1143. statusBar()->showMessage(tr("已添加逻辑条件"), 3000);
  1144. }
  1145. void MainWindow::addLogicParallelBranch(const LogicNodeConfig &config)
  1146. {
  1147. const LogicEditorResult result = logic_editor_widget_->addParallelBranch(config);
  1148. if (!result.succeeded)
  1149. {
  1150. showProjectResult(tr("建立并联支路"), fromUtf8(result.message), false);
  1151. return;
  1152. }
  1153. selected_logic_node_id_ = result.id;
  1154. showLogicNodeProperties(result.id);
  1155. refreshProjectUi();
  1156. statusBar()->showMessage(tr("已建立并联支路,请配置新触点"), 3000);
  1157. }
  1158. void MainWindow::addLogicHorizontalWire()
  1159. {
  1160. const LogicEditorResult result = logic_editor_widget_->addHorizontalWire();
  1161. if (!result.succeeded)
  1162. {
  1163. showProjectResult(tr("插入横线"), fromUtf8(result.message), false);
  1164. return;
  1165. }
  1166. selected_logic_node_id_.clear();
  1167. showLogicNodeProperties({});
  1168. refreshProjectUi();
  1169. statusBar()->showMessage(tr("横线已插入,可直接用触点替换"), 3000);
  1170. }
  1171. void MainWindow::addLogicVerticalWire()
  1172. {
  1173. const LogicEditorResult result = logic_editor_widget_->addVerticalWire();
  1174. if (!result.succeeded)
  1175. {
  1176. showProjectResult(tr("插入竖线"), fromUtf8(result.message), false);
  1177. return;
  1178. }
  1179. selected_logic_node_id_.clear();
  1180. showLogicNodeProperties({});
  1181. refreshProjectUi();
  1182. statusBar()->showMessage(tr("已建立横线旁路和竖线连接"), 3000);
  1183. }
  1184. void MainWindow::deleteLogicHorizontalWire()
  1185. {
  1186. const LogicEditorResult result = logic_editor_widget_->deleteHorizontalWire();
  1187. if (!result.succeeded)
  1188. {
  1189. showProjectResult(tr("删除横线"), fromUtf8(result.message), false);
  1190. return;
  1191. }
  1192. selected_logic_node_id_.clear();
  1193. showLogicNodeProperties({});
  1194. refreshProjectUi();
  1195. statusBar()->showMessage(tr("横线已删除"), 3000);
  1196. }
  1197. void MainWindow::deleteLogicVerticalWire()
  1198. {
  1199. const LogicEditorResult result = logic_editor_widget_->deleteVerticalWire();
  1200. if (!result.succeeded)
  1201. {
  1202. showProjectResult(tr("删除竖线"), fromUtf8(result.message), false);
  1203. return;
  1204. }
  1205. selected_logic_node_id_.clear();
  1206. showLogicNodeProperties({});
  1207. refreshProjectUi();
  1208. statusBar()->showMessage(tr("竖线及对应并联支路已删除"), 3000);
  1209. }
  1210. void MainWindow::setLogicOutput(const LogicNodeConfig &config)
  1211. {
  1212. const LogicEditorResult result = logic_editor_widget_->setOutput(config);
  1213. if (!result.succeeded)
  1214. {
  1215. showProjectResult(tr("设置逻辑输出"), fromUtf8(result.message), false);
  1216. return;
  1217. }
  1218. selected_logic_node_id_ = result.id;
  1219. showLogicNodeProperties(result.id);
  1220. refreshProjectUi();
  1221. statusBar()->showMessage(tr("逻辑输出已设置"), 3000);
  1222. }
  1223. void MainWindow::configureAndSetLogicOutput(const LogicNodeConfig &config)
  1224. {
  1225. LogicInstructionDialog dialog(config, this);
  1226. if (dialog.exec() != QDialog::Accepted)
  1227. {
  1228. return;
  1229. }
  1230. const LogicNodeConfig configured = dialog.config();
  1231. LogicEditorResult result = logic_editor_widget_->setOutput(configured, true);
  1232. if (!result.succeeded)
  1233. {
  1234. showProjectResult(tr("设置逻辑输出"), fromUtf8(result.message), false);
  1235. return;
  1236. }
  1237. const std::string node_id = result.id;
  1238. selected_logic_node_id_ = node_id;
  1239. showLogicNodeProperties(node_id);
  1240. refreshProjectUi();
  1241. statusBar()->showMessage(tr("逻辑输出已配置"), 3000);
  1242. }
  1243. void MainWindow::addLogicRung()
  1244. {
  1245. const LogicEditorResult result = logic_editor_widget_->addRung();
  1246. if (!result.succeeded)
  1247. {
  1248. showProjectResult(tr("新建网络"), fromUtf8(result.message), false);
  1249. return;
  1250. }
  1251. selected_logic_node_id_.clear();
  1252. showLogicNodeProperties({});
  1253. refreshProjectUi();
  1254. statusBar()->showMessage(tr("已新建网络"), 3000);
  1255. }
  1256. void MainWindow::editSelectedRungComment()
  1257. {
  1258. const std::string rung_id = logic_editor_widget_->selectedRungId();
  1259. if (rung_id.empty())
  1260. {
  1261. showProjectResult(tr("网络注释"), tr("请先选择一个梯形图网络"), false);
  1262. return;
  1263. }
  1264. const LadderRung *rung = logic_editor_service_.findRung(
  1265. current_logic_id_, rung_id);
  1266. if (rung == nullptr)
  1267. {
  1268. return;
  1269. }
  1270. bool accepted = false;
  1271. const QString comment = QInputDialog::getText(
  1272. this,
  1273. tr("网络注释"),
  1274. tr("说明"),
  1275. QLineEdit::Normal,
  1276. fromUtf8(rung->comment),
  1277. &accepted);
  1278. if (!accepted)
  1279. {
  1280. return;
  1281. }
  1282. const LogicEditorResult result = logic_editor_service_.updateRungComment(
  1283. current_logic_id_, rung_id, toUtf8(comment));
  1284. if (!result.succeeded)
  1285. {
  1286. showProjectResult(tr("网络注释"), fromUtf8(result.message), false);
  1287. return;
  1288. }
  1289. logic_editor_widget_->reloadLogic();
  1290. refreshProjectUi();
  1291. statusBar()->showMessage(tr("网络注释已更新"), 3000);
  1292. }
  1293. void MainWindow::deleteSelectedLogicObject()
  1294. {
  1295. const LogicEditorResult result = logic_editor_widget_->deleteSelected();
  1296. if (!result.succeeded)
  1297. {
  1298. return;
  1299. }
  1300. selected_logic_node_id_.clear();
  1301. showLogicNodeProperties({});
  1302. refreshProjectUi();
  1303. }
  1304. void MainWindow::createNewProject()
  1305. {
  1306. if (!confirmSaveBeforeDestructiveAction())
  1307. {
  1308. return;
  1309. }
  1310. const QString name = QInputDialog::getText(
  1311. this, tr("新建工程"), tr("工程名称"));
  1312. if (name.isEmpty())
  1313. {
  1314. return;
  1315. }
  1316. const ProjectOperationResult result = project_service_.createNewProject(toUtf8(name));
  1317. if (!result.succeeded)
  1318. {
  1319. showProjectResult(tr("新建工程"), fromUtf8(result.message), false);
  1320. return;
  1321. }
  1322. hmi_editor_service_.ensureDefaultPage();
  1323. logic_editor_service_.ensureDefaultLogic();
  1324. clearEditorHistories();
  1325. selected_control_id_.clear();
  1326. selected_logic_node_id_.clear();
  1327. current_hmi_page_id_ = project_service_.project().initialHmiPageId;
  1328. current_logic_id_ = logic_editor_service_.firstLogicId();
  1329. refreshProjectUi();
  1330. hmi_editor_widget_->reloadPage();
  1331. logic_editor_widget_->reloadLogic();
  1332. showControlProperties({});
  1333. statusBar()->showMessage(tr("已创建新工程"), 3000);
  1334. }
  1335. void MainWindow::saveProject()
  1336. {
  1337. if (!project_service_.hasCurrentFile())
  1338. {
  1339. saveProjectAs();
  1340. return;
  1341. }
  1342. const ProjectOperationResult result = project_service_.save();
  1343. if (!result.succeeded)
  1344. {
  1345. showProjectResult(tr("保存工程"), fromUtf8(result.message), false);
  1346. return;
  1347. }
  1348. updateWindowTitle();
  1349. showProjectResult(tr("保存工程"), tr("工程已保存"), true);
  1350. }
  1351. void MainWindow::saveProjectAs()
  1352. {
  1353. const QString path = QFileDialog::getSaveFileName(
  1354. this, tr("工程另存为"), {}, tr("工程文件 (*.json)"));
  1355. if (path.isEmpty())
  1356. {
  1357. return;
  1358. }
  1359. const ProjectOperationResult result = project_service_.saveAs(toUtf8(path));
  1360. if (result.succeeded)
  1361. {
  1362. updateWindowTitle();
  1363. }
  1364. showProjectResult(
  1365. tr("保存工程"),
  1366. result.succeeded ? tr("工程已保存") : fromUtf8(result.message),
  1367. result.succeeded);
  1368. }
  1369. void MainWindow::loadProject()
  1370. {
  1371. if (!confirmSaveBeforeDestructiveAction())
  1372. {
  1373. return;
  1374. }
  1375. const QString path = QFileDialog::getOpenFileName(
  1376. this, tr("加载工程"), {}, tr("工程文件 (*.json)"));
  1377. if (path.isEmpty())
  1378. {
  1379. return;
  1380. }
  1381. const ProjectOperationResult result = project_service_.load(toUtf8(path));
  1382. if (!result.succeeded)
  1383. {
  1384. showProjectResult(tr("加载工程"), fromUtf8(result.message), false);
  1385. return;
  1386. }
  1387. clearEditorHistories();
  1388. selected_control_id_.clear();
  1389. selected_logic_node_id_.clear();
  1390. current_hmi_page_id_ = project_service_.project().initialHmiPageId;
  1391. current_logic_id_ = logic_editor_service_.firstLogicId();
  1392. refreshProjectUi();
  1393. hmi_editor_widget_->reloadPage();
  1394. logic_editor_widget_->reloadLogic();
  1395. showControlProperties({});
  1396. showProjectResult(tr("加载工程"), tr("工程已加载"), true);
  1397. }
  1398. void MainWindow::showProjectResult(
  1399. const QString &action, const QString &message, bool succeeded)
  1400. {
  1401. const QString output = action + QStringLiteral(": ") + message;
  1402. statusBar()->showMessage(output, 5000);
  1403. appendOutputMessage(output);
  1404. if (!succeeded)
  1405. {
  1406. QMessageBox::warning(this, action, message);
  1407. }
  1408. }
  1409. void MainWindow::appendOutputMessage(const QString &message)
  1410. {
  1411. while (ui_->outputList->count() >= ProjectLimits::kMaximumOutputMessages)
  1412. {
  1413. delete ui_->outputList->takeItem(0);
  1414. }
  1415. ui_->outputList->addItem(message);
  1416. ui_->outputList->scrollToBottom();
  1417. }
  1418. void MainWindow::requestMode(ApplicationMode requested_mode)
  1419. {
  1420. // UI 仅转发模式意图,合法性由服务层和领域状态机决定
  1421. ModeTransitionResult result;
  1422. switch (requested_mode)
  1423. {
  1424. case ApplicationMode::Editing:
  1425. {
  1426. result = runtime_mode_service_.enterEditing();
  1427. break;
  1428. }
  1429. case ApplicationMode::OfflineRunning:
  1430. {
  1431. result = runtime_mode_service_.enterOfflineRunning();
  1432. break;
  1433. }
  1434. case ApplicationMode::OnlineRunning:
  1435. {
  1436. result = runtime_mode_service_.enterOnlineRunning();
  1437. break;
  1438. }
  1439. default:
  1440. {
  1441. restoreCurrentModeAction();
  1442. statusBar()->showMessage(tr("不支持的运行模式"), 5000);
  1443. return;
  1444. }
  1445. }
  1446. if (!result.succeeded)
  1447. {
  1448. restoreCurrentModeAction();
  1449. QString message = transitionErrorText(result.error);
  1450. if (result.error == ModeTransitionError::InitialPlcReadRequired)
  1451. {
  1452. message += tr(";当前状态:%1").arg(plcStatusText(runtime_mode_service_));
  1453. }
  1454. if (result.error == ModeTransitionError::SimulationStartFailed)
  1455. {
  1456. const LogicScanResult &error = runtime_mode_service_.simulationError();
  1457. if (!error.message.empty())
  1458. {
  1459. message += QStringLiteral(": ") + fromUtf8(error.message);
  1460. }
  1461. }
  1462. statusBar()->showMessage(message, 5000);
  1463. return;
  1464. }
  1465. updateModeUi(tr("已进入%1").arg(modeText(runtime_mode_service_.mode())));
  1466. }
  1467. void MainWindow::updateModeUi(const QString &message)
  1468. {
  1469. const ApplicationMode mode = runtime_mode_service_.mode();
  1470. const ModePolicy policy = runtime_mode_service_.policy();
  1471. restoreCurrentModeAction();
  1472. const bool running = mode != ApplicationMode::Editing;
  1473. if (running)
  1474. {
  1475. if (hmi_navigation_service_->currentPageId().empty())
  1476. {
  1477. const HmiNavigationResult navigation = hmi_navigation_service_->start();
  1478. if (!navigation.succeeded)
  1479. {
  1480. statusBar()->showMessage(fromUtf8(navigation.message), 5000);
  1481. }
  1482. }
  1483. runtime_panel_controller_->enterRuntime(
  1484. hmi_navigation_service_->currentPageId(),
  1485. current_logic_id_,
  1486. mode,
  1487. runtime_mode_service_.plcConnectionState());
  1488. }
  1489. else
  1490. {
  1491. hmi_navigation_service_->stop();
  1492. runtime_panel_controller_->leaveRuntime(
  1493. mode, runtime_mode_service_.plcConnectionState());
  1494. }
  1495. // 将同一份模式策略同步到所有可编辑入口,避免只禁用部分操作
  1496. ui_->projectDock->setEnabled(policy.allowsProjectEditing);
  1497. ui_->propertiesDock->setEnabled(policy.allowsProjectEditing);
  1498. hmi_editor_widget_->setEditingEnabled(policy.allowsProjectEditing);
  1499. hmi_editor_widget_->setRuntimeActive(
  1500. policy.usesVirtualRegisters || policy.usesPlcRegisters);
  1501. logic_editor_widget_->setEditingEnabled(policy.allowsProjectEditing);
  1502. if (mode == ApplicationMode::Editing)
  1503. {
  1504. alarm_service_.reset();
  1505. logic_editor_widget_->clearRuntimeTrace();
  1506. }
  1507. ui_->hmiToolBar->setEnabled(policy.allowsProjectEditing);
  1508. ui_->logicToolBar->setEnabled(policy.allowsProjectEditing);
  1509. ui_->addButtonAction->setEnabled(policy.allowsProjectEditing);
  1510. ui_->addIndicatorAction->setEnabled(policy.allowsProjectEditing);
  1511. ui_->addNumericDisplayAction->setEnabled(policy.allowsProjectEditing);
  1512. ui_->addNumericInputAction->setEnabled(policy.allowsProjectEditing);
  1513. ui_->addProgressBarAction->setEnabled(policy.allowsProjectEditing);
  1514. ui_->addLabelAction->setEnabled(policy.allowsProjectEditing);
  1515. ui_->addPageJumpAction->setEnabled(policy.allowsProjectEditing);
  1516. ui_->addAlarmListAction->setEnabled(policy.allowsProjectEditing);
  1517. ui_->configureAlarmsAction->setEnabled(policy.allowsProjectEditing);
  1518. ui_->configureRegisterCommentsAction->setEnabled(policy.allowsProjectEditing);
  1519. ui_->deleteControlAction->setEnabled(policy.allowsProjectEditing);
  1520. ui_->addNormallyOpenAction->setEnabled(policy.allowsProjectEditing);
  1521. ui_->addNormallyClosedAction->setEnabled(policy.allowsProjectEditing);
  1522. ui_->addRisingEdgeAction->setEnabled(policy.allowsProjectEditing);
  1523. ui_->addFallingEdgeAction->setEnabled(policy.allowsProjectEditing);
  1524. ui_->addTimerContactAction->setEnabled(policy.allowsProjectEditing);
  1525. ui_->addCounterContactAction->setEnabled(policy.allowsProjectEditing);
  1526. ui_->addNormalCoilAction->setEnabled(policy.allowsProjectEditing);
  1527. ui_->addSetCoilAction->setEnabled(policy.allowsProjectEditing);
  1528. ui_->addResetCoilAction->setEnabled(policy.allowsProjectEditing);
  1529. ui_->addTonAction->setEnabled(policy.allowsProjectEditing);
  1530. ui_->addCtuAction->setEnabled(policy.allowsProjectEditing);
  1531. ui_->addCtdAction->setEnabled(policy.allowsProjectEditing);
  1532. ui_->addMoveAction->setEnabled(policy.allowsProjectEditing);
  1533. ui_->addAddAction->setEnabled(policy.allowsProjectEditing);
  1534. ui_->addSubAction->setEnabled(policy.allowsProjectEditing);
  1535. ui_->addCompareAction->setEnabled(policy.allowsProjectEditing);
  1536. ui_->editRungCommentAction->setEnabled(policy.allowsProjectEditing);
  1537. ui_->deleteLogicAction->setEnabled(policy.allowsProjectEditing);
  1538. ui_->newProjectAction->setEnabled(policy.allowsProjectEditing);
  1539. ui_->saveProjectAction->setEnabled(policy.allowsProjectEditing);
  1540. ui_->saveAsProjectAction->setEnabled(policy.allowsProjectEditing);
  1541. ui_->loadProjectAction->setEnabled(policy.allowsProjectEditing);
  1542. updateProjectTreeActions();
  1543. updateEditActions();
  1544. mode_status_label_->setText(modeText(mode));
  1545. if (mode == ApplicationMode::Editing)
  1546. {
  1547. mode_status_label_->setStyleSheet(QStringLiteral(
  1548. "border: 1px solid #75a58a; background: #e3f0e8; color: #205c3b; padding: 2px 8px;"));
  1549. }
  1550. else if (mode == ApplicationMode::OfflineRunning)
  1551. {
  1552. mode_status_label_->setStyleSheet(QStringLiteral(
  1553. "border: 1px solid #bd8739; background: #fff0d2; color: #744b0e; padding: 2px 8px;"));
  1554. }
  1555. else
  1556. {
  1557. mode_status_label_->setStyleSheet(QStringLiteral(
  1558. "border: 1px solid #4b88a8; background: #deeff7; color: #174f6c; padding: 2px 8px;"));
  1559. }
  1560. register_status_label_->setText(
  1561. policy.usesVirtualRegisters ? tr("数据源:虚拟 M/D")
  1562. : policy.usesPlcRegisters ? tr("数据源:PLC 缓存") : tr("数据源:未启用"));
  1563. const PlcConnectionState plc_state = runtime_mode_service_.plcConnectionState();
  1564. const bool plc_configuration_available = plc_state == PlcConnectionState::Disconnected
  1565. || plc_state == PlcConnectionState::Faulted;
  1566. ui_->configurePlcAction->setEnabled(
  1567. policy.allowsProjectEditing && plc_configuration_available);
  1568. ui_->configurePlcAction->setText(
  1569. plc_state == PlcConnectionState::Faulted ? tr("PLC 重新配置") : tr("PLC 配置"));
  1570. ui_->configurePlcAction->setToolTip(
  1571. plc_state == PlcConnectionState::Faulted
  1572. ? tr("清理故障会话后重新配置并连接真实 PLC")
  1573. : tr("配置参数并连接真实 PLC"));
  1574. ui_->disconnectPlcAction->setEnabled(plc_state != PlcConnectionState::Disconnected);
  1575. if (plc_state == PlcConnectionState::Connecting)
  1576. {
  1577. register_status_label_->setText(tr("PLC:正在连接"));
  1578. }
  1579. else if (plc_state == PlcConnectionState::Connected)
  1580. {
  1581. register_status_label_->setText(
  1582. runtime_mode_service_.initialPlcReadCompleted()
  1583. ? tr("PLC:已连接,首读完成") : tr("PLC:已连接,正在首读"));
  1584. }
  1585. else if (plc_state == PlcConnectionState::Recovering)
  1586. {
  1587. register_status_label_->setText(tr("PLC:通信已恢复,正在重新首读"));
  1588. }
  1589. else if (plc_state == PlcConnectionState::Faulted)
  1590. {
  1591. register_status_label_->setText(tr("PLC:通信故障"));
  1592. }
  1593. updateSimulationUi(false);
  1594. statusBar()->showMessage(message, 4000);
  1595. const bool duplicate = ui_->outputList->count() > 0
  1596. && ui_->outputList->item(ui_->outputList->count() - 1)->text() == message;
  1597. if (!duplicate)
  1598. {
  1599. appendOutputMessage(message);
  1600. }
  1601. }
  1602. void MainWindow::updateSimulationUi(bool report_fault)
  1603. {
  1604. runtime_panel_controller_->updateSimulationUi(report_fault);
  1605. }
  1606. // 根据当前真实运行模式,同步更新模式工具栏单选按钮选中状态
  1607. void MainWindow::restoreCurrentModeAction()
  1608. {
  1609. ui_->editingModeAction->setChecked(runtime_mode_service_.mode()
  1610. == ApplicationMode::Editing);
  1611. ui_->offlineModeAction->setChecked(runtime_mode_service_.mode()
  1612. == ApplicationMode::OfflineRunning);
  1613. ui_->onlineModeAction->setChecked(runtime_mode_service_.mode()
  1614. == ApplicationMode::OnlineRunning);
  1615. }