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.

301 lines
10 KiB

  1. #include "mygraphicsview.h"
  2. #include <QMimeData>
  3. #include <QCursor>
  4. #include <QGraphicsScene>
  5. #include <QDebug>
  6. #include <QMenu>
  7. #include <QInputDialog>
  8. #include <QMessageBox>
  9. #include <QRegularExpression>
  10. MyGraphicsView::ClipInfo MyGraphicsView::clipboard_ = {};
  11. MyGraphicsView::MyGraphicsView(QWidget *parent)
  12. : QGraphicsView(parent)
  13. {
  14. setAcceptDrops(true);
  15. setRenderHint(QPainter::Antialiasing);
  16. setFocusPolicy(Qt::StrongFocus);
  17. setDragMode(QGraphicsView::RubberBandDrag);
  18. }
  19. void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event)
  20. {
  21. if (event->mimeData()->hasFormat("application/x-component"))
  22. event->acceptProposedAction();
  23. else
  24. event->ignore();
  25. }
  26. void MyGraphicsView::dragMoveEvent(QDragMoveEvent *event)
  27. {
  28. if (event->mimeData()->hasFormat("application/x-component"))
  29. event->acceptProposedAction();
  30. else
  31. event->ignore();
  32. }
  33. void MyGraphicsView::dropEvent(QDropEvent *event)
  34. {
  35. if (!event->mimeData()->hasFormat("application/x-component")) {
  36. event->ignore();
  37. return;
  38. }
  39. QString type = QString::fromUtf8(event->mimeData()->data("application/x-component"));
  40. QPointF scenePos = mapToScene(event->pos());
  41. Item *item = creatItem(type);
  42. item->setPos(scenePos);
  43. connect(item, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy);
  44. connect(item, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete);
  45. connect(item, &Item::requestBindRegister, this, &MyGraphicsView::onItemRequestBindRegister);
  46. connect(item, &Item::requestCompare, this, &MyGraphicsView::onItemRequestCompare);
  47. connect(item, &Item::requestReset, this, &MyGraphicsView::onItemRequestReset);
  48. scene()->addItem(item);
  49. event->acceptProposedAction();
  50. }
  51. void MyGraphicsView::keyPressEvent(QKeyEvent *event)
  52. {
  53. if (event->key() == Qt::Key_Delete && scene()) {
  54. QList<QGraphicsItem*> selectedItems = scene()->selectedItems();
  55. for (QGraphicsItem* item : selectedItems) {
  56. if (auto conn = dynamic_cast<Connection*>(item)) {
  57. // 先通知两端节点断开
  58. conn->from_->removeConnection(conn);
  59. conn->to_->removeConnection(conn);
  60. scene()->removeItem(conn);
  61. delete conn;
  62. } else if (auto node = dynamic_cast<Item*>(item)) {
  63. // 删除节点的所有连线
  64. QList<Connection*> conns = node->connections();
  65. for (Connection* conn : conns) {
  66. conn->from_->removeConnection(conn);
  67. conn->to_->removeConnection(conn);
  68. scene()->removeItem(conn);
  69. delete conn;
  70. }
  71. scene()->removeItem(node);
  72. delete node;
  73. }
  74. }
  75. }else if (event->matches(QKeySequence::Copy)) {
  76. // Ctrl+C
  77. QList<QGraphicsItem*> selectedItems = scene()->selectedItems();
  78. for (QGraphicsItem* item : selectedItems) {
  79. if (auto node = dynamic_cast<Item*>(item)) {
  80. clipboard_.input = node->itemType();
  81. break;
  82. }
  83. }
  84. } else if (event->matches(QKeySequence::Paste)) {
  85. // Ctrl+V
  86. if (!clipboard_.input.isEmpty()) {
  87. QPointF center = mapToScene(viewport()->rect().center());
  88. Item* newItem = creatItem(clipboard_.input);
  89. newItem->setPos(center);
  90. connect(newItem, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy);
  91. connect(newItem, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete);
  92. connect(newItem, &Item::requestBindRegister, this, &MyGraphicsView::onItemRequestBindRegister);
  93. connect(newItem, &Item::requestCompare, this, &MyGraphicsView::onItemRequestCompare);
  94. connect(newItem, &Item::requestReset, this, &MyGraphicsView::onItemRequestReset);
  95. scene()->addItem(newItem);
  96. }
  97. } else {
  98. QGraphicsView::keyPressEvent(event);
  99. }
  100. }
  101. void MyGraphicsView::contextMenuEvent(QContextMenuEvent *event)
  102. {
  103. QPointF scenePos = mapToScene(event->pos());
  104. QList<QGraphicsItem*> itemsAt = scene()->items(scenePos);
  105. if (itemsAt.isEmpty() && !clipboard_.input.isEmpty()) {
  106. QMenu menu;
  107. QAction* pasteAct = menu.addAction("粘贴");
  108. QAction* sel = menu.exec(event->globalPos());
  109. if (sel == pasteAct) {
  110. Item* newItem = creatItem(clipboard_.input);
  111. newItem->setPos(scenePos);
  112. connect(newItem, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy);
  113. connect(newItem, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete);
  114. connect(newItem, &Item::requestBindRegister, this, &MyGraphicsView::onItemRequestBindRegister);
  115. connect(newItem, &Item::requestCompare, this, &MyGraphicsView::onItemRequestCompare);
  116. connect(newItem, &Item::requestReset, this, &MyGraphicsView::onItemRequestReset);
  117. scene()->addItem(newItem);
  118. }
  119. } else {
  120. QGraphicsView::contextMenuEvent(event); // 让Item自己弹出菜单
  121. }
  122. }
  123. // ----------- 连线交互 ------------
  124. Item* MyGraphicsView::anchorItemAt(const QPoint& viewPos, Item::AnchorType& anchorType)
  125. {
  126. QPointF scenePos = mapToScene(viewPos);
  127. for (QGraphicsItem* item : scene()->items(scenePos)) {
  128. if (Item* node = dynamic_cast<Item*>(item)) {
  129. QRectF r = node->boundingRect();
  130. QPointF local = node->mapFromScene(scenePos);
  131. if (QLineF(local, QPointF(r.left(), r.center().y())).length() < 10) {
  132. anchorType = Item::Left;
  133. return node;
  134. }
  135. if (QLineF(local, QPointF(r.right(), r.center().y())).length() < 10) {
  136. anchorType = Item::Right;
  137. return node;
  138. }
  139. }
  140. }
  141. return nullptr;
  142. }
  143. void MyGraphicsView::onItemRequestCopy(Item *item)
  144. {
  145. clipboard_.input = item->itemType();
  146. }
  147. void MyGraphicsView::onItemRequestDelete(Item *item)
  148. {
  149. QList<Connection*> conns = item->connections();
  150. for (Connection* conn : conns) {
  151. conn->from_->removeConnection(conn);
  152. conn->to_->removeConnection(conn);
  153. scene()->removeItem(conn);
  154. delete conn;
  155. }
  156. scene()->removeItem(item);
  157. delete item;
  158. }
  159. void MyGraphicsView::onItemRequestBindRegister(Item *item)
  160. {
  161. if (!item) return;
  162. bool ok = false;
  163. QString reg = QInputDialog::getText(
  164. this,
  165. "绑定寄存器",
  166. "格式: <类型><地址>\n"
  167. "类型: D(保持寄存器), M(线圈寄存器)\n"
  168. "示例: D100, M200\n"
  169. "地址范围: 0-4000",
  170. QLineEdit::Normal,
  171. item->registerId(),
  172. &ok
  173. );
  174. if (ok && !reg.isEmpty()) {
  175. // 验证寄存器格式
  176. QRegularExpression regex("^[DMdm]\\d{1,4}$");
  177. QRegularExpressionMatch match = regex.match(reg);
  178. if (!match.hasMatch()) {
  179. QMessageBox::warning(this, "格式错误", "请输入有效的寄存器格式\n示例: D100, M200");
  180. return;
  181. }
  182. int address = reg.mid(1).toInt();
  183. if (address > 4000) {
  184. QMessageBox::warning(this, "范围错误", "寄存器地址必须在0-4000范围内");
  185. return;
  186. }
  187. // 标准化格式 (D100)
  188. QString newReg = QString("%1%2").arg(reg[0].toUpper()).arg(address);
  189. if (item->setRegisterId(newReg))
  190. {
  191. item->update();
  192. // 发出绑定信号
  193. emit itemBoundToRegister(item, newReg);
  194. }
  195. else
  196. {
  197. QMessageBox::warning(this, "提示", "请先重置再绑定新的寄存器");
  198. }
  199. }
  200. }
  201. void MyGraphicsView::onItemRequestCompare(Item *item)
  202. {
  203. if (!item) return;
  204. QString compare = QInputDialog::getText(
  205. this,
  206. "绑定寄存器",
  207. "格式:>、<、=、<=、>=",
  208. QLineEdit::Normal
  209. );
  210. item->setCompare(compare);
  211. }
  212. void MyGraphicsView::onItemRequestReset(Item *item)
  213. {
  214. QStringList unboundRegs = item->resetRegister();
  215. for (const QString& reg : unboundRegs) {
  216. emit itemResetRegister(item, reg); // 解绑每个寄存器
  217. }
  218. }
  219. void MyGraphicsView::mousePressEvent(QMouseEvent *event)
  220. {
  221. if (event->button() == Qt::LeftButton) {
  222. Item::AnchorType anchor;
  223. Item* node = anchorItemAt(event->pos(), anchor);
  224. if (node) {
  225. drawingConnection_ = true;
  226. startItem_ = node;
  227. startAnchor_ = anchor;
  228. tempLine_ = scene()->addLine(QLineF(node->anchorPos(anchor), mapToScene(event->pos())),
  229. QPen(Qt::darkGray, 2, Qt::DashLine));
  230. return;
  231. }
  232. }
  233. QGraphicsView::mousePressEvent(event);
  234. }
  235. void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
  236. {
  237. if (drawingConnection_ && tempLine_) {
  238. QLineF l = tempLine_->line();
  239. l.setP2(mapToScene(event->pos()));
  240. tempLine_->setLine(l);
  241. return;
  242. }
  243. QGraphicsView::mouseMoveEvent(event);
  244. }
  245. void MyGraphicsView::mouseReleaseEvent(QMouseEvent *event)
  246. {
  247. if (drawingConnection_ && tempLine_) {
  248. // 检查鼠标释放点是否在某个Item boundingRect内
  249. QPointF scenePos = mapToScene(event->pos());
  250. for (QGraphicsItem* item : scene()->items(scenePos)) {
  251. if (Item* node = dynamic_cast<Item*>(item)) {
  252. QRectF r = node->boundingRect();
  253. QPointF local = node->mapFromScene(scenePos);
  254. // 计算落点距离左右端点
  255. double distLeft = QLineF(local, QPointF(r.left(), r.center().y())).length();
  256. double distRight = QLineF(local, QPointF(r.right(), r.center().y())).length();
  257. Item::AnchorType anchor = (distLeft < distRight) ? Item::Left : Item::Right;
  258. // 生成连线
  259. Connection* conn = new Connection(startItem_, startAnchor_, node, anchor);
  260. scene()->addItem(conn);
  261. break;
  262. }
  263. }
  264. // 清除临时线
  265. scene()->removeItem(tempLine_);
  266. delete tempLine_;
  267. tempLine_ = nullptr;
  268. drawingConnection_ = false;
  269. startItem_ = nullptr;
  270. return;
  271. }
  272. QGraphicsView::mouseReleaseEvent(event);
  273. }