Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

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