综合平台编程器项目的远程存储
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

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