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

547 строки
19 KiB

  1. #include "project_workspace_controller.h"
  2. #include "hmi_editor_widget.h"
  3. #include "logic_editor_widget.h"
  4. #include "runtime_monitor_widget.h"
  5. #include "services/hmi_editor_service.h"
  6. #include "services/hmi_navigation_service.h"
  7. #include "services/logic_editor_service.h"
  8. #include "services/project_service.h"
  9. #include "services/runtime_mode_service.h"
  10. #include "ui_main_window.h"
  11. #include <QAction>
  12. #include <QDialogButtonBox>
  13. #include <QInputDialog>
  14. #include <QObject>
  15. #include <QPushButton>
  16. #include <QSignalBlocker>
  17. #include <QTabWidget>
  18. #include <QTreeWidget>
  19. #include <QTreeWidgetItem>
  20. #include <QWidget>
  21. #include <utility>
  22. namespace {
  23. constexpr int kProjectTreeKindRole = Qt::UserRole;
  24. constexpr int kProjectTreeIdRole = Qt::UserRole + 1;
  25. QString fromUtf8(const std::string &value)
  26. {
  27. return QString::fromUtf8(value.data(), static_cast<int>(value.size()));
  28. }
  29. std::string toUtf8(const QString &value)
  30. {
  31. const QByteArray bytes = value.toUtf8();
  32. return std::string(bytes.constData(), static_cast<std::size_t>(bytes.size()));
  33. }
  34. bool requestRequiredName(
  35. QWidget &parent,
  36. const QString &title,
  37. const QString &label,
  38. const QString &initial_value,
  39. QString *name)
  40. {
  41. QInputDialog dialog(&parent);
  42. dialog.setWindowTitle(title);
  43. dialog.setLabelText(label);
  44. dialog.setInputMode(QInputDialog::TextInput);
  45. dialog.setTextValue(initial_value);
  46. QDialogButtonBox *button_box = dialog.findChild<QDialogButtonBox *>();
  47. QPushButton *ok_button = button_box != nullptr
  48. ? button_box->button(QDialogButtonBox::Ok)
  49. : nullptr;
  50. if (ok_button != nullptr)
  51. {
  52. ok_button->setEnabled(!initial_value.trimmed().isEmpty());
  53. QObject::connect(
  54. &dialog,
  55. &QInputDialog::textValueChanged,
  56. &dialog,
  57. [ok_button](const QString &text) {
  58. ok_button->setEnabled(!text.trimmed().isEmpty());
  59. });
  60. }
  61. if (dialog.exec() != QDialog::Accepted)
  62. {
  63. return false;
  64. }
  65. *name = dialog.textValue().trimmed();
  66. return true;
  67. }
  68. } // namespace
  69. ProjectWorkspaceController::ProjectWorkspaceController(
  70. QWidget &parent,
  71. Ui::MainWindow &ui,
  72. ProjectService &project_service,
  73. HmiEditorService &hmi_editor_service,
  74. LogicEditorService &logic_editor_service,
  75. RuntimeModeService &runtime_mode_service,
  76. HmiNavigationService &hmi_navigation_service,
  77. HmiEditorWidget &hmi_editor_widget,
  78. LogicEditorWidget &logic_editor_widget,
  79. RuntimeMonitorWidget &runtime_monitor_widget,
  80. std::string &current_hmi_page_id,
  81. std::string &current_logic_id,
  82. std::function<void(const std::string &)> show_control_properties,
  83. std::function<void(const std::string &)> show_logic_properties,
  84. ResultReporter result_reporter,
  85. StatusReporter status_reporter,
  86. EditStateChanged edit_state_changed)
  87. : parent_(parent),
  88. ui_(ui),
  89. project_service_(project_service),
  90. hmi_editor_service_(hmi_editor_service),
  91. logic_editor_service_(logic_editor_service),
  92. runtime_mode_service_(runtime_mode_service),
  93. hmi_navigation_service_(hmi_navigation_service),
  94. hmi_editor_widget_(hmi_editor_widget),
  95. logic_editor_widget_(logic_editor_widget),
  96. runtime_monitor_widget_(runtime_monitor_widget),
  97. current_hmi_page_id_(current_hmi_page_id),
  98. current_logic_id_(current_logic_id),
  99. show_control_properties_(std::move(show_control_properties)),
  100. show_logic_properties_(std::move(show_logic_properties)),
  101. result_reporter_(std::move(result_reporter)),
  102. status_reporter_(std::move(status_reporter)),
  103. edit_state_changed_(std::move(edit_state_changed))
  104. {
  105. }
  106. void ProjectWorkspaceController::configure()
  107. {
  108. ui_.projectTree->setContextMenuPolicy(Qt::ActionsContextMenu);
  109. add_page_action_ = new QAction(QObject::tr("新建页面"), &parent_);
  110. add_page_action_->setObjectName(QStringLiteral("addPageAction"));
  111. add_logic_action_ = new QAction(QObject::tr("新建控制逻辑"), &parent_);
  112. add_logic_action_->setObjectName(QStringLiteral("addLogicAction"));
  113. rename_item_action_ = new QAction(QObject::tr("重命名"), &parent_);
  114. rename_item_action_->setObjectName(QStringLiteral("renameProjectItemAction"));
  115. delete_item_action_ = new QAction(QObject::tr("删除"), &parent_);
  116. delete_item_action_->setObjectName(QStringLiteral("deleteProjectItemAction"));
  117. move_item_up_action_ = new QAction(QObject::tr("上移"), &parent_);
  118. move_item_up_action_->setObjectName(QStringLiteral("moveProjectItemUpAction"));
  119. move_item_down_action_ = new QAction(QObject::tr("下移"), &parent_);
  120. move_item_down_action_->setObjectName(
  121. QStringLiteral("moveProjectItemDownAction"));
  122. set_initial_page_action_ = new QAction(QObject::tr("设为初始页面"), &parent_);
  123. set_initial_page_action_->setObjectName(QStringLiteral("setInitialPageAction"));
  124. toggle_logic_enabled_action_ = new QAction(
  125. QObject::tr("禁用控制逻辑"), &parent_);
  126. toggle_logic_enabled_action_->setObjectName(
  127. QStringLiteral("toggleLogicEnabledAction"));
  128. ui_.projectTree->addActions({
  129. add_page_action_,
  130. add_logic_action_,
  131. rename_item_action_,
  132. delete_item_action_,
  133. move_item_up_action_,
  134. move_item_down_action_,
  135. set_initial_page_action_,
  136. toggle_logic_enabled_action_});
  137. QObject::connect(add_page_action_, &QAction::triggered,
  138. &parent_, [this] { addPage(); });
  139. QObject::connect(add_logic_action_, &QAction::triggered,
  140. &parent_, [this] { addLogic(); });
  141. QObject::connect(rename_item_action_, &QAction::triggered,
  142. &parent_, [this] { renameSelectedItem(); });
  143. QObject::connect(delete_item_action_, &QAction::triggered,
  144. &parent_, [this] { deleteSelectedItem(); });
  145. QObject::connect(move_item_up_action_, &QAction::triggered,
  146. &parent_, [this] { moveSelectedItem(-1); });
  147. QObject::connect(move_item_down_action_, &QAction::triggered,
  148. &parent_, [this] { moveSelectedItem(1); });
  149. QObject::connect(set_initial_page_action_, &QAction::triggered,
  150. &parent_, [this] { setSelectedPageAsInitial(); });
  151. QObject::connect(toggle_logic_enabled_action_, &QAction::triggered,
  152. &parent_, [this] { toggleSelectedLogicEnabled(); });
  153. QObject::connect(ui_.projectTree, &QTreeWidget::currentItemChanged,
  154. &parent_, [this](QTreeWidgetItem *, QTreeWidgetItem *)
  155. {
  156. handleSelectionChanged();
  157. });
  158. updateActions();
  159. }
  160. void ProjectWorkspaceController::refresh()
  161. {
  162. if (hmi_editor_service_.findPage(current_hmi_page_id_) == nullptr)
  163. {
  164. const std::string &initial_id = project_service_.project().initialHmiPageId;
  165. current_hmi_page_id_ = hmi_editor_service_.findPage(initial_id) != nullptr
  166. ? initial_id : hmi_editor_service_.firstPageId();
  167. show_control_properties_({});
  168. }
  169. if (logic_editor_service_.findLogic(current_logic_id_) == nullptr)
  170. {
  171. current_logic_id_ = logic_editor_service_.firstLogicId();
  172. show_logic_properties_({});
  173. }
  174. hmi_editor_widget_.setPageId(current_hmi_page_id_);
  175. logic_editor_widget_.setLogicId(current_logic_id_);
  176. if (runtime_mode_service_.mode() != ApplicationMode::Editing)
  177. {
  178. runtime_monitor_widget_.setProjectObjects(
  179. hmi_navigation_service_.currentPageId(), current_logic_id_);
  180. }
  181. runtime_mode_service_.refreshPlcPollAddresses();
  182. const QSignalBlocker blocker(ui_.projectTree);
  183. ui_.projectTree->clear();
  184. auto *hmi_root = new QTreeWidgetItem(ui_.projectTree, {QObject::tr("HMI 页面")});
  185. hmi_root->setData(0, kProjectTreeKindRole, static_cast<int>(ItemKind::Root));
  186. QTreeWidgetItem *selected_item = nullptr;
  187. for (const HmiPage &page : project_service_.project().hmiPages)
  188. {
  189. const QString suffix = page.id == project_service_.project().initialHmiPageId
  190. ? QObject::tr("(初始)") : QString{};
  191. auto *item = new QTreeWidgetItem(
  192. hmi_root, {fromUtf8(page.name) + suffix});
  193. item->setData(0, kProjectTreeKindRole, static_cast<int>(ItemKind::HmiPage));
  194. item->setData(0, kProjectTreeIdRole, fromUtf8(page.id));
  195. if (page.id == current_hmi_page_id_
  196. && ui_.editorTabWidget->currentIndex() == 0)
  197. {
  198. selected_item = item;
  199. }
  200. }
  201. const HmiPage *current_page = hmi_editor_service_.findPage(current_hmi_page_id_);
  202. if (current_page != nullptr)
  203. {
  204. ui_.hmiPageTitleLabel->setText(fromUtf8(current_page->name));
  205. ui_.hmiPageSizeLabel->setText(
  206. QObject::tr("%1 x %2").arg(current_page->width).arg(current_page->height));
  207. }
  208. else
  209. {
  210. ui_.hmiPageTitleLabel->setText(QObject::tr("尚未创建 HMI 页面"));
  211. ui_.hmiPageSizeLabel->clear();
  212. }
  213. auto *logic_root = new QTreeWidgetItem(
  214. ui_.projectTree, {QObject::tr("控制逻辑")});
  215. logic_root->setData(0, kProjectTreeKindRole, static_cast<int>(ItemKind::Root));
  216. for (const ControlLogic &logic : project_service_.project().controlLogics)
  217. {
  218. const QString suffix = logic.enabled ? QString{} : QObject::tr("(已禁用)");
  219. auto *item = new QTreeWidgetItem(
  220. logic_root, {fromUtf8(logic.name) + suffix});
  221. item->setData(
  222. 0, kProjectTreeKindRole, static_cast<int>(ItemKind::ControlLogic));
  223. item->setData(0, kProjectTreeIdRole, fromUtf8(logic.id));
  224. if (logic.id == current_logic_id_
  225. && ui_.editorTabWidget->currentIndex() == 1)
  226. {
  227. selected_item = item;
  228. }
  229. }
  230. ui_.projectTree->expandAll();
  231. if (selected_item != nullptr)
  232. {
  233. ui_.projectTree->setCurrentItem(selected_item);
  234. }
  235. updateActions();
  236. }
  237. void ProjectWorkspaceController::handleSelectionChanged()
  238. {
  239. QTreeWidgetItem *item = ui_.projectTree->currentItem();
  240. if (item == nullptr)
  241. {
  242. updateActions();
  243. return;
  244. }
  245. const auto kind = static_cast<ItemKind>(
  246. item->data(0, kProjectTreeKindRole).toInt());
  247. const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
  248. if (kind == ItemKind::HmiPage && hmi_editor_service_.findPage(id) != nullptr)
  249. {
  250. current_hmi_page_id_ = id;
  251. hmi_editor_widget_.setPageId(id);
  252. hmi_editor_widget_.reloadPage();
  253. ui_.editorTabWidget->setCurrentWidget(ui_.hmiEditorTab);
  254. show_control_properties_({});
  255. const HmiPage *page = hmi_editor_service_.findPage(id);
  256. ui_.hmiPageTitleLabel->setText(fromUtf8(page->name));
  257. ui_.hmiPageSizeLabel->setText(
  258. QObject::tr("%1 x %2").arg(page->width).arg(page->height));
  259. }
  260. else if (kind == ItemKind::ControlLogic
  261. && logic_editor_service_.findLogic(id) != nullptr)
  262. {
  263. current_logic_id_ = id;
  264. logic_editor_widget_.setLogicId(id);
  265. logic_editor_widget_.reloadLogic();
  266. ui_.editorTabWidget->setCurrentWidget(ui_.logicEditorTab);
  267. show_logic_properties_({});
  268. }
  269. updateActions();
  270. }
  271. void ProjectWorkspaceController::updateActions()
  272. {
  273. if (add_page_action_ == nullptr)
  274. {
  275. return;
  276. }
  277. QTreeWidgetItem *item = ui_.projectTree->currentItem();
  278. const auto kind = item == nullptr ? ItemKind::Root
  279. : static_cast<ItemKind>(item->data(0, kProjectTreeKindRole).toInt());
  280. const bool page_selected = kind == ItemKind::HmiPage;
  281. const bool logic_selected = kind == ItemKind::ControlLogic;
  282. const bool editable = runtime_mode_service_.policy().allowsProjectEditing;
  283. add_page_action_->setEnabled(editable);
  284. add_logic_action_->setEnabled(editable);
  285. rename_item_action_->setEnabled(editable && (page_selected || logic_selected));
  286. delete_item_action_->setEnabled(editable && (page_selected || logic_selected));
  287. move_item_up_action_->setEnabled(editable && (page_selected || logic_selected));
  288. move_item_down_action_->setEnabled(editable && (page_selected || logic_selected));
  289. set_initial_page_action_->setEnabled(editable && page_selected);
  290. toggle_logic_enabled_action_->setEnabled(editable && logic_selected);
  291. if (logic_selected)
  292. {
  293. const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
  294. const ControlLogic *logic = logic_editor_service_.findLogic(id);
  295. toggle_logic_enabled_action_->setText(
  296. logic != nullptr && logic->enabled
  297. ? QObject::tr("禁用控制逻辑") : QObject::tr("启用控制逻辑"));
  298. }
  299. }
  300. void ProjectWorkspaceController::addPage()
  301. {
  302. QString name;
  303. if (!requestRequiredName(
  304. parent_, QObject::tr("新建页面"), QObject::tr("页面名称"), {}, &name))
  305. {
  306. return;
  307. }
  308. const HmiEditorResult result = hmi_editor_service_.addPage(toUtf8(name));
  309. if (!result.succeeded)
  310. {
  311. reportFailure(QObject::tr("新建页面"), result.message);
  312. return;
  313. }
  314. current_hmi_page_id_ = result.id;
  315. show_control_properties_({});
  316. ui_.editorTabWidget->setCurrentWidget(ui_.hmiEditorTab);
  317. refresh();
  318. hmi_editor_widget_.reloadPage();
  319. edit_state_changed_();
  320. }
  321. void ProjectWorkspaceController::addLogic()
  322. {
  323. QString name;
  324. if (!requestRequiredName(
  325. parent_, QObject::tr("新建控制逻辑"), QObject::tr("逻辑名称"), {}, &name))
  326. {
  327. return;
  328. }
  329. const LogicEditorResult result = logic_editor_service_.addLogic(toUtf8(name));
  330. if (!result.succeeded)
  331. {
  332. reportFailure(QObject::tr("新建控制逻辑"), result.message);
  333. return;
  334. }
  335. current_logic_id_ = result.id;
  336. show_logic_properties_({});
  337. ui_.editorTabWidget->setCurrentWidget(ui_.logicEditorTab);
  338. refresh();
  339. logic_editor_widget_.reloadLogic();
  340. edit_state_changed_();
  341. }
  342. void ProjectWorkspaceController::renameSelectedItem()
  343. {
  344. QTreeWidgetItem *item = ui_.projectTree->currentItem();
  345. if (item == nullptr)
  346. {
  347. return;
  348. }
  349. const auto kind = static_cast<ItemKind>(
  350. item->data(0, kProjectTreeKindRole).toInt());
  351. const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
  352. QString current_name;
  353. if (kind == ItemKind::HmiPage)
  354. {
  355. const HmiPage *page = hmi_editor_service_.findPage(id);
  356. current_name = page == nullptr ? QString{} : fromUtf8(page->name);
  357. }
  358. else if (kind == ItemKind::ControlLogic)
  359. {
  360. const ControlLogic *logic = logic_editor_service_.findLogic(id);
  361. current_name = logic == nullptr ? QString{} : fromUtf8(logic->name);
  362. }
  363. else
  364. {
  365. return;
  366. }
  367. QString name;
  368. if (!requestRequiredName(
  369. parent_, QObject::tr("重命名"), QObject::tr("新名称"), current_name, &name))
  370. {
  371. return;
  372. }
  373. if (kind == ItemKind::HmiPage)
  374. {
  375. const HmiEditorResult result = hmi_editor_service_.renamePage(id, toUtf8(name));
  376. if (!result.succeeded)
  377. {
  378. reportFailure(QObject::tr("重命名页面"), result.message);
  379. return;
  380. }
  381. }
  382. else
  383. {
  384. const LogicEditorResult result = logic_editor_service_.renameLogic(
  385. id, toUtf8(name));
  386. if (!result.succeeded)
  387. {
  388. reportFailure(QObject::tr("重命名控制逻辑"), result.message);
  389. return;
  390. }
  391. }
  392. refresh();
  393. edit_state_changed_();
  394. }
  395. void ProjectWorkspaceController::deleteSelectedItem()
  396. {
  397. QTreeWidgetItem *item = ui_.projectTree->currentItem();
  398. if (item == nullptr)
  399. {
  400. return;
  401. }
  402. const auto kind = static_cast<ItemKind>(
  403. item->data(0, kProjectTreeKindRole).toInt());
  404. const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
  405. if (kind == ItemKind::HmiPage)
  406. {
  407. const HmiEditorResult result = hmi_editor_service_.removePage(id);
  408. if (!result.succeeded)
  409. {
  410. reportFailure(QObject::tr("删除页面"), result.message);
  411. return;
  412. }
  413. }
  414. else if (kind == ItemKind::ControlLogic)
  415. {
  416. const LogicEditorResult result = logic_editor_service_.removeLogic(id);
  417. if (!result.succeeded)
  418. {
  419. reportFailure(QObject::tr("删除控制逻辑"), result.message);
  420. return;
  421. }
  422. }
  423. else
  424. {
  425. return;
  426. }
  427. refresh();
  428. show_control_properties_({});
  429. show_logic_properties_({});
  430. edit_state_changed_();
  431. }
  432. void ProjectWorkspaceController::moveSelectedItem(int offset)
  433. {
  434. QTreeWidgetItem *item = ui_.projectTree->currentItem();
  435. if (item == nullptr)
  436. {
  437. return;
  438. }
  439. const auto kind = static_cast<ItemKind>(
  440. item->data(0, kProjectTreeKindRole).toInt());
  441. const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
  442. if (kind == ItemKind::HmiPage)
  443. {
  444. const HmiEditorResult result = hmi_editor_service_.movePage(id, offset);
  445. if (!result.succeeded)
  446. {
  447. status_reporter_(fromUtf8(result.message), 4000);
  448. return;
  449. }
  450. }
  451. else if (kind == ItemKind::ControlLogic)
  452. {
  453. const LogicEditorResult result = logic_editor_service_.moveLogic(id, offset);
  454. if (!result.succeeded)
  455. {
  456. status_reporter_(fromUtf8(result.message), 4000);
  457. return;
  458. }
  459. }
  460. else
  461. {
  462. return;
  463. }
  464. refresh();
  465. edit_state_changed_();
  466. }
  467. void ProjectWorkspaceController::setSelectedPageAsInitial()
  468. {
  469. QTreeWidgetItem *item = ui_.projectTree->currentItem();
  470. if (item == nullptr
  471. || static_cast<ItemKind>(item->data(0, kProjectTreeKindRole).toInt())
  472. != ItemKind::HmiPage)
  473. {
  474. return;
  475. }
  476. const HmiEditorResult result = hmi_editor_service_.setInitialPage(
  477. toUtf8(item->data(0, kProjectTreeIdRole).toString()));
  478. if (!result.succeeded)
  479. {
  480. reportFailure(QObject::tr("设置初始页面"), result.message);
  481. return;
  482. }
  483. refresh();
  484. edit_state_changed_();
  485. }
  486. void ProjectWorkspaceController::toggleSelectedLogicEnabled()
  487. {
  488. QTreeWidgetItem *item = ui_.projectTree->currentItem();
  489. if (item == nullptr
  490. || static_cast<ItemKind>(item->data(0, kProjectTreeKindRole).toInt())
  491. != ItemKind::ControlLogic)
  492. {
  493. return;
  494. }
  495. const std::string id = toUtf8(item->data(0, kProjectTreeIdRole).toString());
  496. const ControlLogic *logic = logic_editor_service_.findLogic(id);
  497. if (logic == nullptr)
  498. {
  499. return;
  500. }
  501. const LogicEditorResult result = logic_editor_service_.setLogicEnabled(
  502. id, !logic->enabled);
  503. if (!result.succeeded)
  504. {
  505. reportFailure(QObject::tr("启停控制逻辑"), result.message);
  506. return;
  507. }
  508. refresh();
  509. edit_state_changed_();
  510. }
  511. void ProjectWorkspaceController::reportFailure(
  512. const QString &action, const std::string &message) const
  513. {
  514. result_reporter_(action, fromUtf8(message), false);
  515. }