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.

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