您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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