25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

770 lines
24 KiB

  1. #include "hmidocument.h"
  2. #include "graphicsitems.h"
  3. #include <QGraphicsItem>
  4. #include <QGraphicsSceneHoverEvent>
  5. #include <QMouseEvent>
  6. #include <QMenu>
  7. #include <QAction>
  8. #include <QDataStream>
  9. #include <QColorDialog>
  10. #include <QDialog>
  11. #include <QFormLayout>
  12. #include <QLineEdit>
  13. #include <QPushButton>
  14. #include <QDialogButtonBox>
  15. #include <QMimeData>
  16. #include <QDragEnterEvent>
  17. #include <QDropEvent>
  18. #include <QDebug>
  19. #include <QFileInfo>
  20. #include <QVBoxLayout>
  21. #include <QJsonArray>
  22. #include <QJsonDocument>
  23. #include <QJsonObject>
  24. #include<QSpinBox>
  25. #include<QMessageBox>
  26. HMIDocument::HMIDocument(QWidget *parent)
  27. : BaseDocument(HMI, parent),
  28. m_title("未命名HMI"),
  29. m_canDrawEllipse(false),
  30. m_canDrawRectangle(false),
  31. m_lastPastePos(0, 0)
  32. {
  33. // 创建绘图场景 - 固定大小
  34. m_scene = new QGraphicsScene(this);
  35. m_scene->setSceneRect(0, 0, 1000, 600); // 固定画布尺寸
  36. m_scene->setBackgroundBrush(QBrush(Qt::lightGray));
  37. // 创建视图 - 关键修改:使用滚动区域包装
  38. m_scrollArea = new QScrollArea(this);
  39. m_scrollArea->setWidgetResizable(false); // 禁止内容自动调整大小
  40. //m_scrollArea->setFrameShape(QFrame::NoFrame); // 无边框
  41. // 创建真正的视图部件
  42. m_view = new QGraphicsView(m_scene, this);
  43. m_view->setRenderHint(QPainter::Antialiasing);
  44. m_view->setDragMode(QGraphicsView::RubberBandDrag);
  45. m_view->setAcceptDrops(true);
  46. m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 隐藏自身滚动条
  47. m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  48. // 关键:视图固定大小与场景匹配
  49. m_view->setMinimumSize(1000, 600);
  50. m_view->setMaximumSize(1000, 600);
  51. m_view->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  52. m_view->viewport()->installEventFilter(this);
  53. // 添加视图到滚动区域
  54. m_scrollArea->setWidget(m_view);
  55. m_scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  56. // 设置主布局
  57. auto layout = new QVBoxLayout(this);
  58. layout->setContentsMargins(0, 0, 0, 0);
  59. layout->addWidget(m_scrollArea);
  60. setLayout(layout);
  61. }
  62. HMIDocument::~HMIDocument()
  63. {
  64. stopSimulation();
  65. }
  66. void HMIDocument::startSimulation()
  67. {
  68. QSet<int> addresses;
  69. foreach (QGraphicsItem *item, m_scene->items()) {
  70. if (auto namedItem = dynamic_cast<NamedItem*>(item)) {
  71. bool ok;
  72. int address = namedItem->name().toInt(&ok);
  73. if (ok && address >= 0 && address <= 4000)
  74. { // 限制有效范围
  75. // 只收集指示灯(ResizableEllipse)的地址
  76. if (auto ellipseItem = dynamic_cast<ResizableEllipse*>(item))
  77. {
  78. addresses.insert(address);
  79. }
  80. else
  81. {
  82. qDebug() << "按钮不收集地址: " << item->type();
  83. }
  84. }
  85. else {
  86. qDebug() << "无效的线圈地址:" << namedItem->name()
  87. << "(图形项类型:" << item->type() << ")";
  88. }
  89. }
  90. }
  91. if (addresses.isEmpty()) {
  92. QMessageBox::warning(nullptr, "仿真启动", "没有找到有效的线圈地址绑定");
  93. return;
  94. }
  95. m_modbusSimulator = new ModbusSimulator(this);
  96. if (m_modbusSimulator)
  97. {
  98. connect(m_modbusSimulator, &ModbusSimulator::coilStatusRead,
  99. this, &HMIDocument::onCoilStatusRead);
  100. connect(m_modbusSimulator, &ModbusSimulator::errorOccurred,
  101. this, &HMIDocument::onSimulationError);
  102. }
  103. // 遍历代码结束后
  104. qDebug() << "zhxing";
  105. m_modbusSimulator->setCoilAddresses(addresses);
  106. m_modbusSimulator->startSimulation();
  107. }
  108. void HMIDocument::stopSimulation()
  109. {
  110. if (m_modbusSimulator) {
  111. m_modbusSimulator->stopSimulation();
  112. }
  113. }
  114. bool HMIDocument::isSimulationRunning() const
  115. {
  116. return m_modbusSimulator && m_modbusSimulator->isRunning();
  117. }
  118. void HMIDocument::onCoilStatusRead(int address, bool state)
  119. {
  120. foreach (QGraphicsItem *item, m_scene->items()) {
  121. if (auto namedItem = dynamic_cast<NamedItem*>(item)) {
  122. bool ok;
  123. int itemAddress = namedItem->name().toInt(&ok);
  124. if (ok && itemAddress == address) {
  125. if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) {
  126. ellipse->setBrush(state ? ellipse->onColor() : ellipse->offColor());
  127. ellipse->update();
  128. }
  129. }
  130. }
  131. }
  132. }
  133. void HMIDocument::onSimulationError(const QString &message)
  134. {
  135. QMessageBox::critical(nullptr, "仿真错误", message);
  136. }
  137. void HMIDocument::writeCoil(int address, bool state)
  138. {
  139. if (m_modbusSimulator) {
  140. m_modbusSimulator->writeCoil(address, state);
  141. }
  142. }
  143. // 事件过滤器(处理绘图、拖拽等事件)
  144. bool HMIDocument::eventFilter(QObject *obj, QEvent *event)
  145. {
  146. if (obj != m_view->viewport())
  147. {
  148. return QWidget::eventFilter(obj, event);
  149. }
  150. // 鼠标按下事件(绘制图形)
  151. if (event->type() == QEvent::MouseButtonPress)
  152. {
  153. QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
  154. setModified(true);
  155. if (mouseEvent->button() == Qt::RightButton)
  156. {
  157. showContextMenu(mouseEvent->globalPos());// 右键菜单
  158. return true;
  159. }
  160. if (mouseEvent->button() == Qt::LeftButton)
  161. {
  162. QPointF scenePos = m_view->mapToScene(mouseEvent->pos());
  163. QGraphicsItem *item = m_scene->itemAt(scenePos, m_view->transform());
  164. // 如果点击空白区域且有绘制标志,创建图形
  165. if (!item) {
  166. if (m_canDrawEllipse)
  167. {
  168. createShape("ellipse", scenePos);
  169. resetDrawFlags();
  170. }
  171. else if (m_canDrawRectangle)
  172. {
  173. createShape("rectangle", scenePos);
  174. resetDrawFlags();
  175. }
  176. }
  177. }
  178. }
  179. // 拖拽事件(从工具栏拖拽绘制)
  180. if (event->type() == QEvent::DragEnter)
  181. {
  182. QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event);
  183. dragEvent->acceptProposedAction();
  184. return true;
  185. }
  186. if (event->type() == QEvent::DragMove)
  187. {
  188. static_cast<QDragMoveEvent*>(event)->acceptProposedAction();
  189. return true;
  190. }
  191. if (event->type() == QEvent::Drop)
  192. {
  193. QDropEvent *dropEvent = static_cast<QDropEvent*>(event);
  194. const QMimeData *mimeData = dropEvent->mimeData();
  195. QPointF scenePos = m_view->mapToScene(dropEvent->pos());
  196. // 使用QMimeData
  197. if (mimeData && mimeData->hasText())
  198. {
  199. createShape(mimeData->text(), scenePos);
  200. resetDrawFlags();//重置防止拖拽后再次点击生成
  201. }
  202. dropEvent->acceptProposedAction();
  203. return true;
  204. }
  205. return QWidget::eventFilter(obj, event);
  206. }
  207. void HMIDocument::createShape(const QString& type, const QPointF &pos)
  208. {
  209. m_scene->clearSelection();
  210. if (type == "指示灯" || type == "ellipse")
  211. {
  212. ResizableEllipse *ellipse = new ResizableEllipse(pos.x(), pos.y(), 50, 50);
  213. ellipse->setBrush(QBrush(Qt::red));
  214. ellipse->setPen(QPen(Qt::black, 1));
  215. m_scene->addItem(ellipse);
  216. ellipse->setSelected(true);
  217. }
  218. else if (type == "按钮" || type == "rectangle")
  219. {
  220. ResizableRectangle *rect = new ResizableRectangle(pos.x(), pos.y(), 100, 50);
  221. rect->setBrush(QBrush(Qt::yellow));
  222. rect->setPen(QPen(Qt::black, 1));
  223. m_scene->addItem(rect);
  224. rect->setSelected(true);
  225. }
  226. setModified(true);
  227. }
  228. // 重置绘制标志
  229. void HMIDocument::resetDrawFlags()
  230. {
  231. m_canDrawEllipse = false;
  232. m_canDrawRectangle = false;
  233. }
  234. // 右键菜单
  235. void HMIDocument::showContextMenu(QPoint globalPos)
  236. {
  237. QPoint viewportPos = m_view->mapFromGlobal(globalPos);
  238. QPointF scenePos = m_view->mapToScene(viewportPos);
  239. QGraphicsItem *clickedItem = m_scene->itemAt(scenePos, m_view->transform());
  240. QMenu menu(this);
  241. if (!clickedItem)
  242. {
  243. // 空白区域:仅显示粘贴
  244. QAction *pasteAction = menu.addAction("粘贴");
  245. pasteAction->setEnabled(!m_copiedItemsData.isEmpty());
  246. connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems);
  247. }
  248. else
  249. {
  250. // 选中图形:显示基础菜单
  251. QAction *propAction = menu.addAction("属性");
  252. QAction *copyAction = menu.addAction("复制");
  253. QAction *pasteAction = menu.addAction("粘贴");
  254. QAction *deleteAction = menu.addAction("删除");
  255. pasteAction->setEnabled(!m_copiedItemsData.isEmpty());
  256. ResizableRectangle *rectItem = dynamic_cast<ResizableRectangle*>(clickedItem);
  257. connect(propAction, &QAction::triggered, this, &HMIDocument::showItemProperties);
  258. connect(copyAction, &QAction::triggered, this, &HMIDocument::copySelectedItems);
  259. connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems);
  260. connect(deleteAction, &QAction::triggered, this, &HMIDocument::deleteSelectedItems);
  261. // 仅对按钮(矩形)显示ON/OFF选项,指示灯(椭圆)不显示
  262. if (dynamic_cast<ResizableRectangle*>(clickedItem)) {
  263. QAction *onAction = menu.addAction("置为ON");
  264. QAction *offAction = menu.addAction("置为OFF");
  265. connect(onAction, &QAction::triggered, [=]() {
  266. rectItem->setBrush(rectItem->pressedColor()); // 使用 rectItem
  267. // 获取线圈地址并写入
  268. bool ok;
  269. int address = rectItem->name().toInt(&ok); // 使用 rectItem
  270. if (ok && address > 0) {
  271. writeCoil(address, true); // 写入ON状态
  272. }
  273. });
  274. connect(offAction, &QAction::triggered, [=]() {
  275. rectItem->setBrush(rectItem->releasedColor()); // 使用 rectItem
  276. // 获取线圈地址并写入
  277. bool ok;
  278. int address = rectItem->name().toInt(&ok); // 使用 rectItem
  279. if (ok && address > 0) {
  280. writeCoil(address, false); // 写入OFF状态
  281. }
  282. });
  283. }
  284. }
  285. menu.exec(globalPos + QPoint(10, 10));
  286. }
  287. // 复制选中项
  288. void HMIDocument::copySelectedItems()
  289. {
  290. m_copiedItemsData.clear();
  291. QList<QGraphicsItem*> selectedItems = m_scene->selectedItems();
  292. if (selectedItems.isEmpty()) return;
  293. foreach (QGraphicsItem *item, selectedItems)
  294. {
  295. m_copiedItemsData.append(serializeItem(item));
  296. }
  297. m_lastPastePos = selectedItems.first()->pos();
  298. setModified(true);
  299. }
  300. // 粘贴项
  301. void HMIDocument::pasteItems()
  302. {
  303. if (m_copiedItemsData.isEmpty()) return;
  304. QPointF offset(20, 20);
  305. m_lastPastePos += offset;
  306. m_scene->clearSelection();
  307. foreach (const QByteArray &itemData, m_copiedItemsData)
  308. {
  309. QGraphicsItem *newItem = deserializeItem(itemData);
  310. if (newItem)
  311. {
  312. m_scene->addItem(newItem);
  313. newItem->setPos(m_lastPastePos);
  314. newItem->setSelected(true);
  315. }
  316. }
  317. setModified(true);
  318. }
  319. // 删除选中项
  320. void HMIDocument::deleteSelectedItems()
  321. {
  322. QList<QGraphicsItem*> selectedItems = m_scene->selectedItems();
  323. foreach (QGraphicsItem *item, selectedItems) {
  324. m_scene->removeItem(item);
  325. delete item;
  326. }
  327. setModified(true);
  328. }
  329. // 显示属性对话框
  330. // 在HMIDocument::showItemProperties()方法中替换相关代码
  331. void HMIDocument::showItemProperties()
  332. {
  333. QList<QGraphicsItem*> selectedItems = m_scene->selectedItems();
  334. if (selectedItems.isEmpty()) return;
  335. QGraphicsItem *item = selectedItems.first();
  336. // 将QGraphicsItem转换为NamedItem
  337. NamedItem *namedItem = dynamic_cast<NamedItem*>(item);
  338. if (!namedItem) return;
  339. QDialog dialog(this);
  340. dialog.setWindowTitle("属性设置");
  341. QFormLayout *form = new QFormLayout(&dialog);
  342. // 线圈地址输入 - 使用SpinBox替代LineEdit
  343. QSpinBox *coilAddrSpin = new QSpinBox();
  344. coilAddrSpin->setRange(0, 4000); // 设置PLC线圈地址常见范围
  345. // 尝试从名称解析现有地址(如果之前用数字作为名称)
  346. bool isNumber;
  347. int addr = namedItem->name().toInt(&isNumber);
  348. if (isNumber) {
  349. coilAddrSpin->setValue(addr);
  350. }
  351. // 颜色选择(保持不变)
  352. QColor tempColor1, tempColor2;
  353. QPushButton *colorBtn1 = new QPushButton;
  354. QPushButton *colorBtn2 = new QPushButton;
  355. if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) {
  356. tempColor1 = ellipse->onColor();
  357. tempColor2 = ellipse->offColor();
  358. form->addRow("ON颜色:", colorBtn1);
  359. form->addRow("OFF颜色:", colorBtn2);
  360. } else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) {
  361. tempColor1 = rect->pressedColor();
  362. tempColor2 = rect->releasedColor();
  363. form->addRow("按下颜色:", colorBtn1);
  364. form->addRow("释放颜色:", colorBtn2);
  365. }
  366. // 初始化颜色按钮(保持不变)
  367. colorBtn1->setStyleSheet("background-color:" + tempColor1.name());
  368. colorBtn2->setStyleSheet("background-color:" + tempColor2.name());
  369. // 颜色选择对话框(保持不变)
  370. connect(colorBtn1, &QPushButton::clicked, [&]() {
  371. QColor c = QColorDialog::getColor(tempColor1, &dialog);
  372. if (c.isValid()) {
  373. tempColor1 = c;
  374. colorBtn1->setStyleSheet("background-color:" + c.name());
  375. }
  376. });
  377. connect(colorBtn2, &QPushButton::clicked, [&]() {
  378. QColor c = QColorDialog::getColor(tempColor2, &dialog);
  379. if (c.isValid()) {
  380. tempColor2 = c;
  381. colorBtn2->setStyleSheet("background-color:" + c.name());
  382. }
  383. });
  384. // 布局组装(将名称输入替换为线圈地址)
  385. form->addRow("线圈地址:", coilAddrSpin); // 这里是关键修改
  386. QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
  387. form->addRow(btnBox);
  388. connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
  389. connect(btnBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
  390. // 应用属性(使用SpinBox的值作为地址)
  391. if (dialog.exec() == QDialog::Accepted) {
  392. setModified(true);
  393. // 将SpinBox的值转换为字符串保存到名称属性
  394. namedItem->setName(QString::number(coilAddrSpin->value()));
  395. if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) {
  396. ellipse->setOnColor(tempColor1);
  397. ellipse->setOffColor(tempColor2);
  398. } else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) {
  399. rect->setPressedColor(tempColor1);
  400. rect->setReleasedColor(tempColor2);
  401. }
  402. item->update();
  403. }
  404. }
  405. // 序列化图形项(用于复制粘贴)
  406. QByteArray HMIDocument::serializeItem(QGraphicsItem *item)
  407. {
  408. QByteArray data;
  409. QDataStream stream(&data, QIODevice::WriteOnly);
  410. // 基础信息
  411. stream << item->type();
  412. stream << item->pos();
  413. stream << item->flags();
  414. stream << item->transform();
  415. stream << item->isVisible();
  416. // 形状信息
  417. if (auto shape = dynamic_cast<QAbstractGraphicsShapeItem*>(item))
  418. {
  419. stream << shape->pen();
  420. stream << shape->brush();
  421. stream << shape->boundingRect();
  422. }
  423. // 具体图形信息
  424. if (auto rect = dynamic_cast<ResizableRectangle*>(item))
  425. {
  426. stream << rect->rect();
  427. stream << rect->pressedColor();
  428. stream << rect->releasedColor();
  429. stream << rect->name();
  430. } else if (auto ellipse = dynamic_cast<ResizableEllipse*>(item))
  431. {
  432. stream << ellipse->rect();
  433. stream << ellipse->onColor();
  434. stream << ellipse->offColor();
  435. stream << ellipse->name();
  436. }
  437. return data;
  438. }
  439. // 反序列化图形项(用于粘贴)
  440. QGraphicsItem *HMIDocument::deserializeItem(const QByteArray &data)
  441. {
  442. QDataStream stream(data);
  443. int type;
  444. QPointF pos;
  445. QGraphicsItem::GraphicsItemFlags flags;
  446. QTransform transform;
  447. bool visible;
  448. stream >> type >> pos >> flags >> transform >> visible;
  449. QGraphicsItem *item = nullptr;
  450. if (type == QGraphicsRectItem::Type) {
  451. QPen pen;
  452. QBrush brush;
  453. QRectF boundRect;
  454. QRectF rect;
  455. QColor pressedColor, releasedColor;
  456. QString name;
  457. stream >> pen >> brush >> boundRect >> rect >> pressedColor >> releasedColor >> name;
  458. ResizableRectangle *rectItem = new ResizableRectangle(0, 0, rect.width(), rect.height());
  459. rectItem->setPen(pen);
  460. rectItem->setBrush(brush);
  461. rectItem->setPressedColor(pressedColor);
  462. rectItem->setReleasedColor(releasedColor);
  463. rectItem->setName(name);
  464. item = rectItem;
  465. } else if (type == QGraphicsEllipseItem::Type) {
  466. QPen pen;
  467. QBrush brush;
  468. QRectF boundRect;
  469. QRectF rect;
  470. QColor onColor, offColor;
  471. QString name;
  472. stream >> pen >> brush >> boundRect >> rect >> onColor >> offColor >> name;
  473. ResizableEllipse *ellipseItem = new ResizableEllipse(0, 0, rect.width(), rect.height());
  474. ellipseItem->setPen(pen);
  475. ellipseItem->setBrush(brush);
  476. ellipseItem->setOnColor(onColor);
  477. ellipseItem->setOffColor(offColor);
  478. ellipseItem->setName(name);
  479. item = ellipseItem;
  480. }
  481. if (item) {
  482. item->setFlags(flags);
  483. item->setTransform(transform);
  484. item->setVisible(visible);
  485. }
  486. return item;
  487. }
  488. void HMIDocument::startDrawingEllipse()
  489. {
  490. m_canDrawEllipse = true;
  491. m_canDrawRectangle = false;
  492. }
  493. void HMIDocument::startDrawingRectangle()
  494. {
  495. m_canDrawEllipse = false;
  496. m_canDrawRectangle = true;
  497. }
  498. bool HMIDocument::saveToFile(const QString &filePath)
  499. {
  500. QFile file(filePath);
  501. if (!file.open(QIODevice::WriteOnly)) {
  502. qWarning() << "无法打开文件进行写入:" << filePath;
  503. return false;
  504. }
  505. // 创建JSON文档结构
  506. QJsonObject docObject;
  507. docObject["title"] = m_title;
  508. docObject["sceneRect"] = QJsonArray({
  509. m_scene->sceneRect().x(),
  510. m_scene->sceneRect().y(),
  511. m_scene->sceneRect().width(),
  512. m_scene->sceneRect().height()
  513. });
  514. docObject["backgroundColor"] = m_scene->backgroundBrush().color().name();
  515. // 序列化所有图形项
  516. QJsonArray itemsArray;
  517. foreach (QGraphicsItem *item, m_scene->items()) {
  518. QJsonObject itemJson = itemToJson(item);
  519. if (!itemJson.isEmpty()) {
  520. itemsArray.append(itemJson);
  521. }
  522. }
  523. docObject["items"] = itemsArray;
  524. // 写入文件
  525. QJsonDocument doc(docObject);
  526. file.write(doc.toJson());
  527. file.close();
  528. // 更新文档状态
  529. setFilePath(filePath);
  530. setModified(false);
  531. QFileInfo fileInfo(filePath);
  532. setTitle(fileInfo.baseName());
  533. return true;
  534. }
  535. // 从文件加载文档
  536. bool HMIDocument::loadFromFile(const QString &filePath)
  537. {
  538. QFile file(filePath);
  539. if (!file.open(QIODevice::ReadOnly)) {
  540. qWarning() << "无法打开文件进行读取:" << filePath;
  541. return false;
  542. }
  543. // 读取JSON文档
  544. QByteArray data = file.readAll();
  545. QJsonDocument doc = QJsonDocument::fromJson(data);
  546. if (doc.isNull()) {
  547. qWarning() << "无效的JSON文档:" << filePath;
  548. return false;
  549. }
  550. // 解析文档
  551. QJsonObject docObject = doc.object();
  552. m_title = docObject["title"].toString();
  553. // 设置场景属性
  554. QJsonArray sceneRectArray = docObject["sceneRect"].toArray();
  555. if (sceneRectArray.size() == 4) {
  556. QRectF sceneRect(
  557. sceneRectArray[0].toDouble(),
  558. sceneRectArray[1].toDouble(),
  559. sceneRectArray[2].toDouble(),
  560. sceneRectArray[3].toDouble()
  561. );
  562. m_scene->setSceneRect(sceneRect);
  563. }
  564. if (docObject.contains("backgroundColor")) {
  565. QColor bgColor(docObject["backgroundColor"].toString());
  566. m_scene->setBackgroundBrush(QBrush(bgColor));
  567. }
  568. // 清除现有内容
  569. m_scene->clear();
  570. // 加载所有图形项
  571. QJsonArray itemsArray = docObject["items"].toArray();
  572. foreach (QJsonValue itemValue, itemsArray) {
  573. QJsonObject itemJson = itemValue.toObject();
  574. QGraphicsItem *item = jsonToItem(itemJson);
  575. if (item) {
  576. m_scene->addItem(item);
  577. }
  578. }
  579. // 更新文档状态
  580. setFilePath(filePath);
  581. setModified(false);
  582. return true;
  583. }
  584. // 将图形项转换为JSON对象
  585. QJsonObject HMIDocument::itemToJson(QGraphicsItem *item)
  586. {
  587. QJsonObject json;
  588. // 基础属性
  589. json["type"] = item->type();
  590. json["x"] = item->x();
  591. json["y"] = item->y();
  592. json["zValue"] = item->zValue();
  593. json["visible"] = item->isVisible();
  594. // 具体图形项属性
  595. if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) {
  596. json["itemType"] = "ellipse";
  597. json["rect"] = QJsonArray({
  598. ellipse->rect().x(),
  599. ellipse->rect().y(),
  600. ellipse->rect().width(),
  601. ellipse->rect().height()
  602. });
  603. json["onColor"] = ellipse->onColor().name();
  604. json["offColor"] = ellipse->offColor().name();
  605. json["currentColor"] = ellipse->brush().color().name();
  606. json["penColor"] = ellipse->pen().color().name();
  607. json["penWidth"] = ellipse->pen().width();
  608. json["name"] = ellipse->name();
  609. }
  610. else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) {
  611. json["itemType"] = "rectangle";
  612. json["rect"] = QJsonArray({
  613. rect->rect().x(),
  614. rect->rect().y(),
  615. rect->rect().width(),
  616. rect->rect().height()
  617. });
  618. json["pressedColor"] = rect->pressedColor().name();
  619. json["releasedColor"] = rect->releasedColor().name();
  620. json["currentColor"] = rect->brush().color().name();
  621. json["penColor"] = rect->pen().color().name();
  622. json["penWidth"] = rect->pen().width();
  623. json["name"] = rect->name();
  624. }
  625. return json;
  626. }
  627. // 从JSON对象创建图形项
  628. QGraphicsItem *HMIDocument::jsonToItem(const QJsonObject &json)
  629. {
  630. QString itemType = json["itemType"].toString();
  631. QGraphicsItem *item = nullptr;
  632. if (itemType == "ellipse") {
  633. QJsonArray rectArray = json["rect"].toArray();
  634. if (rectArray.size() != 4) return nullptr;
  635. ResizableEllipse *ellipse = new ResizableEllipse(
  636. rectArray[0].toDouble(),
  637. rectArray[1].toDouble(),
  638. rectArray[2].toDouble(),
  639. rectArray[3].toDouble()
  640. );
  641. ellipse->setOnColor(QColor(json["onColor"].toString()));
  642. ellipse->setOffColor(QColor(json["offColor"].toString()));
  643. ellipse->setBrush(QColor(json["currentColor"].toString()));
  644. ellipse->setPen(QPen(
  645. QColor(json["penColor"].toString()),
  646. json["penWidth"].toDouble()
  647. ));
  648. ellipse->setName(json["name"].toString());
  649. item = ellipse;
  650. }
  651. else if (itemType == "rectangle") {
  652. QJsonArray rectArray = json["rect"].toArray();
  653. if (rectArray.size() != 4) return nullptr;
  654. ResizableRectangle *rect = new ResizableRectangle(
  655. rectArray[0].toDouble(),
  656. rectArray[1].toDouble(),
  657. rectArray[2].toDouble(),
  658. rectArray[3].toDouble()
  659. );
  660. rect->setPressedColor(QColor(json["pressedColor"].toString()));
  661. rect->setReleasedColor(QColor(json["releasedColor"].toString()));
  662. rect->setBrush(QColor(json["currentColor"].toString()));
  663. rect->setPen(QPen(
  664. QColor(json["penColor"].toString()),
  665. json["penWidth"].toDouble()
  666. ));
  667. rect->setName(json["name"].toString());
  668. item = rect;
  669. }
  670. // 设置基础属性
  671. if (item) {
  672. item->setX(json["x"].toDouble());
  673. item->setY(json["y"].toDouble());
  674. item->setZValue(json["zValue"].toDouble());
  675. item->setVisible(json["visible"].toBool());
  676. }
  677. return item;
  678. }
  679. QString HMIDocument::title() const { return m_title; }
  680. void HMIDocument::setTitle(const QString &title) { m_title = title; }
  681. QGraphicsView *HMIDocument::view() const { return m_view; }
  682. QGraphicsScene *HMIDocument::scene() const { return m_scene; }
  683. void HMIDocument::setDrawEllipse(bool enable) { m_canDrawEllipse = enable; }
  684. void HMIDocument::setDrawRectangle(bool enable) { m_canDrawRectangle = enable; }
  685. void HMIDocument::markModified() { setModified(true); }