综合平台编程器项目的远程存储
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

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