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.

447 regels
14 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. connect(item, &Item::requestSetON,this, &MyGraphicsView::onItemRequestSetON);
  49. scene()->addItem(item);
  50. event->acceptProposedAction();
  51. }
  52. void MyGraphicsView::keyPressEvent(QKeyEvent *event)
  53. {
  54. if (event->key() == Qt::Key_Delete && scene()) {
  55. QList<QGraphicsItem*> selectedItems = scene()->selectedItems();
  56. for (QGraphicsItem* item : selectedItems) {
  57. if (auto conn = dynamic_cast<Connection*>(item)) {
  58. // 先通知两端节点断开
  59. conn->from_->removeConnection(conn);
  60. conn->to_->removeConnection(conn);
  61. scene()->removeItem(conn);
  62. delete conn;
  63. } else if (auto node = dynamic_cast<Item*>(item)) {
  64. // 删除节点的所有连线
  65. QList<Connection*> conns = node->connections();
  66. for (Connection* conn : conns) {
  67. conn->from_->removeConnection(conn);
  68. conn->to_->removeConnection(conn);
  69. scene()->removeItem(conn);
  70. delete conn;
  71. }
  72. scene()->removeItem(node);
  73. delete node;
  74. }
  75. }
  76. }else if (event->matches(QKeySequence::Copy)) {
  77. // Ctrl+C
  78. clipboard_.items.clear();
  79. clipboard_.connections.clear();
  80. QList<QGraphicsItem*> selectedItems = scene()->selectedItems();
  81. // 过滤出Item类型的元件
  82. QList<Item*> items;
  83. for (QGraphicsItem* gi : selectedItems) {
  84. if (auto item = dynamic_cast<Item*>(gi)) {
  85. items.append(item);
  86. }
  87. }
  88. // 计算中心点
  89. clipboard_.center = calculateItemsCenter(selectedItems);
  90. // 保存元件信息
  91. for (Item* item : items) {
  92. CopiedItem copiedItem;
  93. copiedItem.type = item->itemType();
  94. copiedItem.pos = item->pos() - clipboard_.center;
  95. clipboard_.items.append(copiedItem);
  96. }
  97. // 保存连接关系
  98. for (QGraphicsItem* gi : selectedItems) {
  99. if (auto conn = dynamic_cast<Connection*>(gi)) {
  100. int fromIdx = items.indexOf(conn->from_);
  101. int toIdx = items.indexOf(conn->to_);
  102. if (fromIdx >= 0 && toIdx >= 0) {
  103. CopiedConnection copiedConn;
  104. copiedConn.fromIndex = fromIdx;
  105. copiedConn.toIndex = toIdx;
  106. copiedConn.fromType = conn->fromType_;
  107. copiedConn.toType = conn->toType_;
  108. clipboard_.connections.append(copiedConn);
  109. }
  110. }
  111. }
  112. } else if (event->matches(QKeySequence::Paste)) {
  113. // Ctrl+V
  114. performPaste(mapToScene(viewport()->rect().center()));
  115. } else {
  116. QGraphicsView::keyPressEvent(event);
  117. }
  118. }
  119. void MyGraphicsView::contextMenuEvent(QContextMenuEvent *event)
  120. {
  121. QPointF scenePos = mapToScene(event->pos());
  122. QList<QGraphicsItem*> itemsAt = scene()->items(scenePos);
  123. // 如果有选中的元件,显示元件自己的菜单
  124. if (!itemsAt.isEmpty()) {
  125. QGraphicsView::contextMenuEvent(event);
  126. return;
  127. }
  128. // 没有选中元件时,显示粘贴菜单(如果剪贴板有内容)
  129. QMenu menu;
  130. if (!clipboard_.items.isEmpty()) {
  131. QAction* pasteAct = menu.addAction("粘贴");
  132. QAction* sel = menu.exec(event->globalPos());
  133. if (sel == pasteAct) {
  134. performPaste(scenePos); // 在鼠标位置粘贴
  135. }
  136. }
  137. }
  138. // ----------- 连线交互 ------------
  139. Item* MyGraphicsView::anchorItemAt(const QPoint& viewPos, Item::AnchorType& anchorType)
  140. {
  141. QPointF scenePos = mapToScene(viewPos);
  142. for (QGraphicsItem* item : scene()->items(scenePos)) {
  143. if (Item* node = dynamic_cast<Item*>(item)) {
  144. QRectF r = node->boundingRect();
  145. QPointF local = node->mapFromScene(scenePos);
  146. if (QLineF(local, QPointF(r.left(), r.center().y())).length() < 10) {
  147. anchorType = Item::Left;
  148. return node;
  149. }
  150. if (QLineF(local, QPointF(r.right(), r.center().y())).length() < 10) {
  151. anchorType = Item::Right;
  152. return node;
  153. }
  154. }
  155. }
  156. return nullptr;
  157. }
  158. QPointF MyGraphicsView::calculateItemsCenter(const QList<QGraphicsItem *> &items)
  159. {
  160. QPointF center(0, 0);
  161. int count = 0;
  162. for (QGraphicsItem* item : items) {
  163. if (auto node = dynamic_cast<Item*>(item)) {
  164. center += node->pos();
  165. count++;
  166. }
  167. }
  168. return count > 0 ? center / count : QPointF(0, 0);
  169. }
  170. void MyGraphicsView::onItemRequestCopy(Item *item)
  171. {
  172. clipboard_.items.clear();
  173. clipboard_.connections.clear();
  174. CopiedItem copiedItem;
  175. copiedItem.type = item->itemType();
  176. clipboard_.items.append(copiedItem);
  177. }
  178. void MyGraphicsView::onItemRequestDelete(Item *item)
  179. {
  180. QList<Connection*> conns = item->connections();
  181. for (Connection* conn : conns) {
  182. conn->from_->removeConnection(conn);
  183. conn->to_->removeConnection(conn);
  184. scene()->removeItem(conn);
  185. delete conn;
  186. }
  187. scene()->removeItem(item);
  188. delete item;
  189. }
  190. void MyGraphicsView::onItemRequestBindRegister(Item *item)
  191. {
  192. if (!item) return;
  193. bool ok = false;
  194. QString reg = QInputDialog::getText(
  195. this,
  196. "绑定寄存器",
  197. "格式: <类型><地址>\n"
  198. "类型: D(保持寄存器), M(线圈寄存器), K(常数)\n"
  199. "示例: D100, M200, K100\n"
  200. "地址范围: 0-4000\n"
  201. "常数范围: 0-65535",
  202. QLineEdit::Normal,
  203. item->registerId(),
  204. &ok
  205. );
  206. if (ok && !reg.isEmpty()) {
  207. // 验证输入格式
  208. QRegularExpression regex("^([DMdm]\\d{1,4}|K\\d{1,5})$");
  209. QRegularExpressionMatch match = regex.match(reg);
  210. if (!match.hasMatch()) {
  211. QMessageBox::warning(this, "格式错误", "请输入有效的格式\n示例: D100, M200, K100");
  212. return;
  213. }
  214. // 提取类型和值
  215. QString type = reg.left(1).toUpper();
  216. int value = reg.mid(1).toInt();
  217. // 检查范围
  218. if (type == "K") {
  219. if (value > 65535) {
  220. QMessageBox::warning(this, "范围错误", "常数必须在0-65535范围内");
  221. return;
  222. }
  223. } else {
  224. if (value > 4000) {
  225. QMessageBox::warning(this, "范围错误", "寄存器地址必须在0-4000范围内");
  226. return;
  227. }
  228. }
  229. // 标准化格式
  230. QString newReg = QString("%1%2").arg(type).arg(value);
  231. // 对于比较元件,直接设置值
  232. if (item->itemType() == "比较" && type == "K") {
  233. // 常数直接设置值
  234. item->setRegisterId(newReg);
  235. item->setRegisterValue(newReg, value);
  236. item->update();
  237. }else {
  238. // 寄存器绑定
  239. if (type == "K")
  240. {
  241. QMessageBox::warning(this, "提示", "此元件不能绑定常数");
  242. }
  243. if (item->setRegisterId(newReg)) {
  244. item->update();
  245. emit itemBoundToRegister(item, newReg);
  246. } else {
  247. QMessageBox::warning(this, "提示", "请先重置再绑定新的寄存器");
  248. }
  249. }
  250. }
  251. }
  252. void MyGraphicsView::onItemRequestCompare(Item *item)
  253. {
  254. if (!item) return;
  255. QString compare = QInputDialog::getText(
  256. this,
  257. "绑定寄存器",
  258. "格式:>、<、=、<=、>=",
  259. QLineEdit::Normal
  260. );
  261. item->setCompare(compare);
  262. }
  263. void MyGraphicsView::onItemRequestReset(Item *item)
  264. {
  265. QStringList unboundRegs = item->resetRegister();
  266. for (const QString& reg : unboundRegs) {
  267. emit itemResetRegister(item, reg); // 解绑每个寄存器
  268. }
  269. }
  270. void MyGraphicsView::onItemRequestSetON(Item *item, bool isON)
  271. {
  272. if (isON)
  273. {
  274. emit itemSetON(item->registerId(), 1);
  275. }
  276. else
  277. {
  278. emit itemSetON(item->registerId(), 0);
  279. }
  280. }
  281. void MyGraphicsView::mousePressEvent(QMouseEvent *event)
  282. {
  283. if (event->button() == Qt::LeftButton) {
  284. Item::AnchorType anchor;
  285. Item* node = anchorItemAt(event->pos(), anchor);
  286. if (node) {
  287. drawingConnection_ = true;
  288. startItem_ = node;
  289. startAnchor_ = anchor;
  290. tempLine_ = scene()->addLine(QLineF(node->anchorPos(anchor), mapToScene(event->pos())),
  291. QPen(Qt::darkGray, 2, Qt::DashLine));
  292. return;
  293. }
  294. }
  295. QGraphicsView::mousePressEvent(event);
  296. }
  297. void MyGraphicsView::mouseMoveEvent(QMouseEvent *event)
  298. {
  299. if (drawingConnection_ && tempLine_) {
  300. QLineF l = tempLine_->line();
  301. l.setP2(mapToScene(event->pos()));
  302. tempLine_->setLine(l);
  303. return;
  304. }
  305. QGraphicsView::mouseMoveEvent(event);
  306. }
  307. void MyGraphicsView::mouseReleaseEvent(QMouseEvent *event)
  308. {
  309. if (drawingConnection_ && tempLine_) {
  310. // 检查鼠标释放点是否在某个Item boundingRect内
  311. QPointF scenePos = mapToScene(event->pos());
  312. for (QGraphicsItem* item : scene()->items(scenePos)) {
  313. if (Item* node = dynamic_cast<Item*>(item)) {
  314. QRectF r = node->boundingRect();
  315. QPointF local = node->mapFromScene(scenePos);
  316. // 计算落点距离左右端点
  317. double distLeft = QLineF(local, QPointF(r.left(), r.center().y())).length();
  318. double distRight = QLineF(local, QPointF(r.right(), r.center().y())).length();
  319. Item::AnchorType anchor = (distLeft < distRight) ? Item::Left : Item::Right;
  320. // 生成连线
  321. Connection* conn = new Connection(startItem_, startAnchor_, node, anchor);
  322. scene()->addItem(conn);
  323. break;
  324. }
  325. }
  326. // 清除临时线
  327. scene()->removeItem(tempLine_);
  328. delete tempLine_;
  329. tempLine_ = nullptr;
  330. drawingConnection_ = false;
  331. startItem_ = nullptr;
  332. return;
  333. }
  334. QGraphicsView::mouseReleaseEvent(event);
  335. }
  336. void MyGraphicsView::wheelEvent(QWheelEvent *event)
  337. {
  338. // 检查是否按下了 Ctrl 键
  339. if (event->modifiers() & Qt::ControlModifier) {
  340. // 计算缩放因子 (每滚动一步缩放 10%)
  341. const double zoomFactor = 1.1;
  342. double scaleFactor = (event->angleDelta().y() > 0) ? zoomFactor : 1.0 / zoomFactor;
  343. // 获取鼠标在场景中的位置
  344. QPointF scenePos = mapToScene(event->position().toPoint());
  345. // 执行缩放
  346. scale(scaleFactor, scaleFactor);
  347. // 调整视图中心点,使缩放以鼠标位置为中心
  348. QPointF delta = scenePos - mapToScene(viewport()->rect().center());
  349. centerOn(scenePos - delta * (scaleFactor - 1));
  350. event->accept(); // 表示事件已处理
  351. } else {
  352. // 没有按下 Ctrl 键,执行默认的滚动行为
  353. QGraphicsView::wheelEvent(event);
  354. }
  355. }
  356. void MyGraphicsView::performPaste(const QPointF &pasteCenter)
  357. {
  358. if (clipboard_.items.isEmpty()) return;
  359. QList<Item*> newItems;
  360. // 创建新元件
  361. for (const CopiedItem& copiedItem : clipboard_.items) {
  362. Item* newItem = creatItem(copiedItem.type);
  363. if (!newItem) continue;
  364. // 设置位置(相对于粘贴中心)
  365. QPointF newPos = pasteCenter + copiedItem.pos;
  366. newItem->setPos(newPos);
  367. // 连接信号
  368. connect(newItem, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy);
  369. connect(newItem, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete);
  370. connect(newItem, &Item::requestBindRegister, this, &MyGraphicsView::onItemRequestBindRegister);
  371. connect(newItem, &Item::requestCompare, this, &MyGraphicsView::onItemRequestCompare);
  372. connect(newItem, &Item::requestReset, this, &MyGraphicsView::onItemRequestReset);
  373. connect(newItem, &Item::requestSetON, this, &MyGraphicsView::onItemRequestSetON);
  374. scene()->addItem(newItem);
  375. newItems.append(newItem);
  376. }
  377. // 创建新连接
  378. for (const CopiedConnection& copiedConn : clipboard_.connections) {
  379. if (copiedConn.fromIndex < newItems.size() && copiedConn.toIndex < newItems.size()) {
  380. Connection* conn = new Connection(
  381. newItems[copiedConn.fromIndex], copiedConn.fromType,
  382. newItems[copiedConn.toIndex], copiedConn.toType);
  383. scene()->addItem(conn);
  384. }
  385. }
  386. // 选中新粘贴的元件
  387. scene()->clearSelection();
  388. for (Item* item : newItems) {
  389. item->setSelected(true);
  390. }
  391. }