Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

654 Zeilen
20 KiB

  1. #include "document.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. HMIDocument::HMIDocument(QWidget *parent)
  21. : BaseDocument(HMI, parent),
  22. m_title("未命名HMI"),
  23. m_canDrawEllipse(false),
  24. m_canDrawRectangle(false),
  25. m_lastPastePos(0, 0)
  26. {
  27. // 创建绘图场景
  28. m_scene = new QGraphicsScene(this);
  29. m_scene->setSceneRect(0, 0, 800, 600);
  30. m_scene->setBackgroundBrush(QBrush(Qt::lightGray));
  31. // 更粗的网格线示例
  32. // QImage gridImage(40, 40, QImage::Format_ARGB32);
  33. // gridImage.fill(Qt::white);
  34. // QPainter painter(&gridImage);
  35. // painter.setPen(QPen(QColor(200, 200, 200), 1)); // 浅灰色
  36. // // 绘制水平线
  37. // painter.drawLine(0, 39, 39, 39);
  38. // // 绘制垂直线
  39. // painter.drawLine(39, 0, 39, 39);
  40. // // 每隔4个小网格绘制一条稍粗的线
  41. // painter.setPen(QPen(QColor(180, 180, 180), 1.5));
  42. // painter.drawLine(0, 39, 39, 39);
  43. // painter.drawLine(39, 0, 39, 39);
  44. // m_scene->setBackgroundBrush(QBrush(gridImage));
  45. // 创建视图
  46. m_view = new QGraphicsView(m_scene, this);
  47. m_view->setRenderHint(QPainter::Antialiasing);
  48. m_view->setDragMode(QGraphicsView::RubberBandDrag);
  49. m_view->setAcceptDrops(true);
  50. m_view->viewport()->installEventFilter(this);
  51. // 布局(让视图占满文档区域)
  52. auto layout = new QVBoxLayout(this);
  53. layout->setContentsMargins(0, 0, 0, 0);
  54. layout->addWidget(m_view);
  55. setLayout(layout);
  56. }
  57. HMIDocument::~HMIDocument()
  58. {
  59. }
  60. // 事件过滤器(处理绘图、拖拽等事件)
  61. bool HMIDocument::eventFilter(QObject *obj, QEvent *event)
  62. {
  63. if (obj != m_view->viewport()) {
  64. return QWidget::eventFilter(obj, event);
  65. }
  66. // 鼠标按下事件(绘制图形)
  67. if (event->type() == QEvent::MouseButtonPress)
  68. {
  69. QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
  70. setModified(true);
  71. if (mouseEvent->button() == Qt::RightButton)
  72. {
  73. showContextMenu(mouseEvent->globalPos());// 右键菜单
  74. return true;
  75. }
  76. if (mouseEvent->button() == Qt::LeftButton)
  77. {
  78. QPointF scenePos = m_view->mapToScene(mouseEvent->pos());
  79. QGraphicsItem *item = m_scene->itemAt(scenePos, m_view->transform());
  80. // 如果点击空白区域且有绘制标志,创建图形
  81. if (!item) {
  82. if (m_canDrawEllipse)
  83. {
  84. createShape("ellipse", scenePos);
  85. resetDrawFlags();
  86. }
  87. else if (m_canDrawRectangle)
  88. {
  89. createShape("rectangle", scenePos);
  90. resetDrawFlags();
  91. }
  92. }
  93. }
  94. }
  95. // 拖拽事件(从工具栏拖拽绘制)
  96. if (event->type() == QEvent::DragEnter)
  97. {
  98. QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event);
  99. dragEvent->acceptProposedAction();
  100. return true;
  101. }
  102. if (event->type() == QEvent::DragMove)
  103. {
  104. static_cast<QDragMoveEvent*>(event)->acceptProposedAction();
  105. return true;
  106. }
  107. if (event->type() == QEvent::Drop)
  108. {
  109. QDropEvent *dropEvent = static_cast<QDropEvent*>(event);
  110. const QMimeData *mimeData = dropEvent->mimeData();
  111. QPointF scenePos = m_view->mapToScene(dropEvent->pos());
  112. // 使用QMimeData
  113. if (mimeData && mimeData->hasText())
  114. {
  115. createShape(mimeData->text(), scenePos);
  116. resetDrawFlags();//重置防止拖拽后再次点击生成
  117. }
  118. dropEvent->acceptProposedAction();
  119. return true;
  120. }
  121. return QWidget::eventFilter(obj, event);
  122. }
  123. void HMIDocument::createShape(const QString& type, const QPointF &pos)
  124. {
  125. if (type == "指示灯" || type == "ellipse") {
  126. ResizableEllipse *ellipse = new ResizableEllipse(pos.x(), pos.y(), 50, 50);
  127. ellipse->setBrush(QBrush(Qt::red));
  128. ellipse->setPen(QPen(Qt::black, 1));
  129. m_scene->addItem(ellipse);
  130. ellipse->setSelected(true);
  131. }
  132. else if (type == "按钮" || type == "rectangle") {
  133. ResizableRectangle *rect = new ResizableRectangle(pos.x(), pos.y(), 100, 50);
  134. rect->setBrush(QBrush(Qt::yellow));
  135. rect->setPen(QPen(Qt::black, 1));
  136. m_scene->addItem(rect);
  137. rect->setSelected(true);
  138. }
  139. setModified(true);
  140. }
  141. // 重置绘制标志
  142. void HMIDocument::resetDrawFlags()
  143. {
  144. m_canDrawEllipse = false;
  145. m_canDrawRectangle = false;
  146. }
  147. // 右键菜单
  148. void HMIDocument::showContextMenu(QPoint globalPos)
  149. {
  150. QPoint viewportPos = m_view->mapFromGlobal(globalPos);
  151. QPointF scenePos = m_view->mapToScene(viewportPos);
  152. QGraphicsItem *clickedItem = m_scene->itemAt(scenePos, m_view->transform());
  153. QMenu menu(this);
  154. if (!clickedItem)
  155. {
  156. // 空白区域:仅显示粘贴
  157. QAction *pasteAction = menu.addAction("粘贴");
  158. pasteAction->setEnabled(!m_copiedItemsData.isEmpty());
  159. connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems);
  160. }
  161. else
  162. {
  163. // 选中图形:显示完整菜单
  164. QAction *propAction = menu.addAction("属性");
  165. QAction *copyAction = menu.addAction("复制");
  166. QAction *pasteAction = menu.addAction("粘贴");
  167. QAction *deleteAction = menu.addAction("删除");
  168. QAction *onAction = menu.addAction("置为ON");
  169. QAction *offAction = menu.addAction("置为OFF");
  170. pasteAction->setEnabled(!m_copiedItemsData.isEmpty());
  171. connect(propAction, &QAction::triggered, this, &HMIDocument::showItemProperties);
  172. connect(copyAction, &QAction::triggered, this, &HMIDocument::copySelectedItems);
  173. connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems);
  174. connect(deleteAction, &QAction::triggered, this, &HMIDocument::deleteSelectedItems);
  175. // ON/OFF动作(针对指示灯和按钮)
  176. connect(onAction, &QAction::triggered, [=]() {
  177. if (auto ellipse = dynamic_cast<ResizableEllipse*>(clickedItem)) {
  178. ellipse->setBrush(ellipse->onColor());
  179. } else if (auto rect = dynamic_cast<ResizableRectangle*>(clickedItem)) {
  180. rect->setBrush(rect->pressedColor());
  181. }
  182. });
  183. connect(offAction, &QAction::triggered, [=]() {
  184. if (auto ellipse = dynamic_cast<ResizableEllipse*>(clickedItem)) {
  185. ellipse->setBrush(ellipse->offColor());
  186. } else if (auto rect = dynamic_cast<ResizableRectangle*>(clickedItem)) {
  187. rect->setBrush(rect->releasedColor());
  188. }
  189. });
  190. }
  191. menu.exec(globalPos + QPoint(10, 10));
  192. }
  193. // 复制选中项
  194. void HMIDocument::copySelectedItems()
  195. {
  196. m_copiedItemsData.clear();
  197. QList<QGraphicsItem*> selectedItems = m_scene->selectedItems();
  198. if (selectedItems.isEmpty()) return;
  199. foreach (QGraphicsItem *item, selectedItems)
  200. {
  201. m_copiedItemsData.append(serializeItem(item));
  202. }
  203. m_lastPastePos = selectedItems.first()->pos();
  204. setModified(true);
  205. }
  206. // 粘贴项
  207. void HMIDocument::pasteItems()
  208. {
  209. if (m_copiedItemsData.isEmpty()) return;
  210. QPointF offset(20, 20);
  211. m_lastPastePos += offset;
  212. m_scene->clearSelection();
  213. foreach (const QByteArray &itemData, m_copiedItemsData)
  214. {
  215. QGraphicsItem *newItem = deserializeItem(itemData);
  216. if (newItem)
  217. {
  218. m_scene->addItem(newItem);
  219. newItem->setPos(m_lastPastePos);
  220. newItem->setSelected(true);
  221. }
  222. }
  223. setModified(true);
  224. }
  225. // 删除选中项
  226. void HMIDocument::deleteSelectedItems()
  227. {
  228. QList<QGraphicsItem*> selectedItems = m_scene->selectedItems();
  229. foreach (QGraphicsItem *item, selectedItems) {
  230. m_scene->removeItem(item);
  231. delete item;
  232. }
  233. setModified(true);
  234. }
  235. // 显示属性对话框
  236. void HMIDocument::showItemProperties()
  237. {
  238. QList<QGraphicsItem*> selectedItems = m_scene->selectedItems();
  239. if (selectedItems.isEmpty()) return;
  240. QGraphicsItem *item = selectedItems.first();
  241. // 将QGraphicsItem转换为NamedItem
  242. NamedItem *namedItem = dynamic_cast<NamedItem*>(item);
  243. if (!namedItem) return;
  244. QDialog dialog(this);
  245. dialog.setWindowTitle("属性设置");
  246. QFormLayout *form = new QFormLayout(&dialog);
  247. // 名称输入 - 使用NamedItem接口
  248. QLineEdit *nameEdit = new QLineEdit(namedItem->name());
  249. // 颜色选择(根据图形类型)
  250. QColor tempColor1, tempColor2;
  251. QPushButton *colorBtn1 = new QPushButton;
  252. QPushButton *colorBtn2 = new QPushButton;
  253. if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) {
  254. tempColor1 = ellipse->onColor();
  255. tempColor2 = ellipse->offColor();
  256. form->addRow("ON颜色:", colorBtn1);
  257. form->addRow("OFF颜色:", colorBtn2);
  258. } else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) {
  259. tempColor1 = rect->pressedColor();
  260. tempColor2 = rect->releasedColor();
  261. form->addRow("按下颜色:", colorBtn1);
  262. form->addRow("释放颜色:", colorBtn2);
  263. }
  264. // 初始化颜色按钮
  265. colorBtn1->setStyleSheet("background-color:" + tempColor1.name());
  266. colorBtn2->setStyleSheet("background-color:" + tempColor2.name());
  267. // 颜色选择对话框
  268. connect(colorBtn1, &QPushButton::clicked, [&]() {
  269. QColor c = QColorDialog::getColor(tempColor1, &dialog);
  270. if (c.isValid()) {
  271. tempColor1 = c;
  272. colorBtn1->setStyleSheet("background-color:" + c.name());
  273. }
  274. });
  275. connect(colorBtn2, &QPushButton::clicked, [&]() {
  276. QColor c = QColorDialog::getColor(tempColor2, &dialog);
  277. if (c.isValid()) {
  278. tempColor2 = c;
  279. colorBtn2->setStyleSheet("background-color:" + c.name());
  280. }
  281. });
  282. // 布局组装
  283. form->addRow("对象:", nameEdit);
  284. QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
  285. form->addRow(btnBox);
  286. connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
  287. connect(btnBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
  288. // 应用属性
  289. if (dialog.exec() == QDialog::Accepted) {
  290. setModified(true);
  291. // 使用NamedItem接口设置名称
  292. namedItem->setName(nameEdit->text());
  293. if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) {
  294. ellipse->setOnColor(tempColor1);
  295. ellipse->setOffColor(tempColor2);
  296. } else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) {
  297. rect->setPressedColor(tempColor1);
  298. rect->setReleasedColor(tempColor2);
  299. }
  300. item->update();
  301. }
  302. }
  303. // 序列化图形项(用于复制粘贴)
  304. QByteArray HMIDocument::serializeItem(QGraphicsItem *item)
  305. {
  306. QByteArray data;
  307. QDataStream stream(&data, QIODevice::WriteOnly);
  308. // 基础信息
  309. stream << item->type();
  310. stream << item->pos();
  311. stream << item->flags();
  312. stream << item->transform();
  313. stream << item->isVisible();
  314. // 形状信息
  315. if (auto shape = dynamic_cast<QAbstractGraphicsShapeItem*>(item))
  316. {
  317. stream << shape->pen();
  318. stream << shape->brush();
  319. stream << shape->boundingRect();
  320. }
  321. // 具体图形信息
  322. if (auto rect = dynamic_cast<ResizableRectangle*>(item))
  323. {
  324. stream << rect->rect();
  325. stream << rect->pressedColor();
  326. stream << rect->releasedColor();
  327. stream << rect->name();
  328. } else if (auto ellipse = dynamic_cast<ResizableEllipse*>(item))
  329. {
  330. stream << ellipse->rect();
  331. stream << ellipse->onColor();
  332. stream << ellipse->offColor();
  333. stream << ellipse->name();
  334. }
  335. return data;
  336. }
  337. // 反序列化图形项(用于粘贴)
  338. QGraphicsItem *HMIDocument::deserializeItem(const QByteArray &data)
  339. {
  340. QDataStream stream(data);
  341. int type;
  342. QPointF pos;
  343. QGraphicsItem::GraphicsItemFlags flags;
  344. QTransform transform;
  345. bool visible;
  346. stream >> type >> pos >> flags >> transform >> visible;
  347. QGraphicsItem *item = nullptr;
  348. if (type == QGraphicsRectItem::Type) {
  349. QPen pen;
  350. QBrush brush;
  351. QRectF boundRect;
  352. QRectF rect;
  353. QColor pressedColor, releasedColor;
  354. QString name;
  355. stream >> pen >> brush >> boundRect >> rect >> pressedColor >> releasedColor >> name;
  356. ResizableRectangle *rectItem = new ResizableRectangle(0, 0, rect.width(), rect.height());
  357. rectItem->setPen(pen);
  358. rectItem->setBrush(brush);
  359. rectItem->setPressedColor(pressedColor);
  360. rectItem->setReleasedColor(releasedColor);
  361. rectItem->setName(name);
  362. item = rectItem;
  363. } else if (type == QGraphicsEllipseItem::Type) {
  364. QPen pen;
  365. QBrush brush;
  366. QRectF boundRect;
  367. QRectF rect;
  368. QColor onColor, offColor;
  369. QString name;
  370. stream >> pen >> brush >> boundRect >> rect >> onColor >> offColor >> name;
  371. ResizableEllipse *ellipseItem = new ResizableEllipse(0, 0, rect.width(), rect.height());
  372. ellipseItem->setPen(pen);
  373. ellipseItem->setBrush(brush);
  374. ellipseItem->setOnColor(onColor);
  375. ellipseItem->setOffColor(offColor);
  376. ellipseItem->setName(name);
  377. item = ellipseItem;
  378. }
  379. if (item) {
  380. item->setFlags(flags);
  381. item->setTransform(transform);
  382. item->setVisible(visible);
  383. }
  384. return item;
  385. }
  386. void HMIDocument::startDrawingEllipse()
  387. {
  388. m_canDrawEllipse = true;
  389. m_canDrawRectangle = false;
  390. }
  391. void HMIDocument::startDrawingRectangle()
  392. {
  393. m_canDrawEllipse = false;
  394. m_canDrawRectangle = true;
  395. }
  396. bool HMIDocument::saveToFile(const QString &filePath)
  397. {
  398. QFile file(filePath);
  399. if (!file.open(QIODevice::WriteOnly)) {
  400. qWarning() << "无法打开文件进行写入:" << filePath;
  401. return false;
  402. }
  403. // 创建JSON文档结构
  404. QJsonObject docObject;
  405. docObject["title"] = m_title;
  406. docObject["sceneRect"] = QJsonArray({
  407. m_scene->sceneRect().x(),
  408. m_scene->sceneRect().y(),
  409. m_scene->sceneRect().width(),
  410. m_scene->sceneRect().height()
  411. });
  412. docObject["backgroundColor"] = m_scene->backgroundBrush().color().name();
  413. // 序列化所有图形项
  414. QJsonArray itemsArray;
  415. foreach (QGraphicsItem *item, m_scene->items()) {
  416. QJsonObject itemJson = itemToJson(item);
  417. if (!itemJson.isEmpty()) {
  418. itemsArray.append(itemJson);
  419. }
  420. }
  421. docObject["items"] = itemsArray;
  422. // 写入文件
  423. QJsonDocument doc(docObject);
  424. file.write(doc.toJson());
  425. file.close();
  426. // 更新文档状态
  427. setFilePath(filePath);
  428. setModified(false);
  429. QFileInfo fileInfo(filePath);
  430. setTitle(fileInfo.baseName());
  431. return true;
  432. }
  433. // 新增:从文件加载文档
  434. bool HMIDocument::loadFromFile(const QString &filePath)
  435. {
  436. QFile file(filePath);
  437. if (!file.open(QIODevice::ReadOnly)) {
  438. qWarning() << "无法打开文件进行读取:" << filePath;
  439. return false;
  440. }
  441. // 读取JSON文档
  442. QByteArray data = file.readAll();
  443. QJsonDocument doc = QJsonDocument::fromJson(data);
  444. if (doc.isNull()) {
  445. qWarning() << "无效的JSON文档:" << filePath;
  446. return false;
  447. }
  448. // 解析文档
  449. QJsonObject docObject = doc.object();
  450. m_title = docObject["title"].toString();
  451. // 设置场景属性
  452. QJsonArray sceneRectArray = docObject["sceneRect"].toArray();
  453. if (sceneRectArray.size() == 4) {
  454. QRectF sceneRect(
  455. sceneRectArray[0].toDouble(),
  456. sceneRectArray[1].toDouble(),
  457. sceneRectArray[2].toDouble(),
  458. sceneRectArray[3].toDouble()
  459. );
  460. m_scene->setSceneRect(sceneRect);
  461. }
  462. if (docObject.contains("backgroundColor")) {
  463. QColor bgColor(docObject["backgroundColor"].toString());
  464. m_scene->setBackgroundBrush(QBrush(bgColor));
  465. }
  466. // 清除现有内容
  467. m_scene->clear();
  468. // 加载所有图形项
  469. QJsonArray itemsArray = docObject["items"].toArray();
  470. foreach (QJsonValue itemValue, itemsArray) {
  471. QJsonObject itemJson = itemValue.toObject();
  472. QGraphicsItem *item = jsonToItem(itemJson);
  473. if (item) {
  474. m_scene->addItem(item);
  475. }
  476. }
  477. // 更新文档状态
  478. setFilePath(filePath);
  479. setModified(false);
  480. return true;
  481. }
  482. // 新增:将图形项转换为JSON对象
  483. QJsonObject HMIDocument::itemToJson(QGraphicsItem *item)
  484. {
  485. QJsonObject json;
  486. // 基础属性
  487. json["type"] = item->type();
  488. json["x"] = item->x();
  489. json["y"] = item->y();
  490. json["zValue"] = item->zValue();
  491. json["visible"] = item->isVisible();
  492. // 具体图形项属性
  493. if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) {
  494. json["itemType"] = "ellipse";
  495. json["rect"] = QJsonArray({
  496. ellipse->rect().x(),
  497. ellipse->rect().y(),
  498. ellipse->rect().width(),
  499. ellipse->rect().height()
  500. });
  501. json["onColor"] = ellipse->onColor().name();
  502. json["offColor"] = ellipse->offColor().name();
  503. json["currentColor"] = ellipse->brush().color().name();
  504. json["penColor"] = ellipse->pen().color().name();
  505. json["penWidth"] = ellipse->pen().width();
  506. json["name"] = ellipse->name();
  507. }
  508. else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) {
  509. json["itemType"] = "rectangle";
  510. json["rect"] = QJsonArray({
  511. rect->rect().x(),
  512. rect->rect().y(),
  513. rect->rect().width(),
  514. rect->rect().height()
  515. });
  516. json["pressedColor"] = rect->pressedColor().name();
  517. json["releasedColor"] = rect->releasedColor().name();
  518. json["currentColor"] = rect->brush().color().name();
  519. json["penColor"] = rect->pen().color().name();
  520. json["penWidth"] = rect->pen().width();
  521. json["name"] = rect->name();
  522. }
  523. return json;
  524. }
  525. // 新增:从JSON对象创建图形项
  526. QGraphicsItem *HMIDocument::jsonToItem(const QJsonObject &json)
  527. {
  528. QString itemType = json["itemType"].toString();
  529. QGraphicsItem *item = nullptr;
  530. if (itemType == "ellipse") {
  531. QJsonArray rectArray = json["rect"].toArray();
  532. if (rectArray.size() != 4) return nullptr;
  533. ResizableEllipse *ellipse = new ResizableEllipse(
  534. rectArray[0].toDouble(),
  535. rectArray[1].toDouble(),
  536. rectArray[2].toDouble(),
  537. rectArray[3].toDouble()
  538. );
  539. ellipse->setOnColor(QColor(json["onColor"].toString()));
  540. ellipse->setOffColor(QColor(json["offColor"].toString()));
  541. ellipse->setBrush(QColor(json["currentColor"].toString()));
  542. ellipse->setPen(QPen(
  543. QColor(json["penColor"].toString()),
  544. json["penWidth"].toDouble()
  545. ));
  546. ellipse->setName(json["name"].toString());
  547. item = ellipse;
  548. }
  549. else if (itemType == "rectangle") {
  550. QJsonArray rectArray = json["rect"].toArray();
  551. if (rectArray.size() != 4) return nullptr;
  552. ResizableRectangle *rect = new ResizableRectangle(
  553. rectArray[0].toDouble(),
  554. rectArray[1].toDouble(),
  555. rectArray[2].toDouble(),
  556. rectArray[3].toDouble()
  557. );
  558. rect->setPressedColor(QColor(json["pressedColor"].toString()));
  559. rect->setReleasedColor(QColor(json["releasedColor"].toString()));
  560. rect->setBrush(QColor(json["currentColor"].toString()));
  561. rect->setPen(QPen(
  562. QColor(json["penColor"].toString()),
  563. json["penWidth"].toDouble()
  564. ));
  565. rect->setName(json["name"].toString());
  566. item = rect;
  567. }
  568. // 设置基础属性
  569. if (item) {
  570. item->setX(json["x"].toDouble());
  571. item->setY(json["y"].toDouble());
  572. item->setZValue(json["zValue"].toDouble());
  573. item->setVisible(json["visible"].toBool());
  574. }
  575. return item;
  576. }