@@ -0,0 +1,20 @@ | |||||
#include "connection.h" | |||||
#include <QPen> | |||||
Connection::Connection(Item* from, Item::AnchorType fromType, | |||||
Item* to, Item::AnchorType toType, QGraphicsItem* parent) | |||||
: QGraphicsLineItem(parent), | |||||
from_(from), to_(to), fromType_(fromType), toType_(toType) | |||||
{ | |||||
setZValue(-1); // 在图元之下 | |||||
setPen(QPen(Qt::black, 2)); | |||||
from_->addConnection(this); | |||||
to_->addConnection(this); | |||||
updatePosition(); | |||||
} | |||||
void Connection::updatePosition() | |||||
{ | |||||
setLine(QLineF(from_->anchorPos(fromType_), to_->anchorPos(toType_))); | |||||
} |
@@ -0,0 +1,19 @@ | |||||
#ifndef CONNECTION_H | |||||
#define CONNECTION_H | |||||
#include <QGraphicsLineItem> | |||||
#include "item.h" | |||||
class Connection : public QGraphicsLineItem | |||||
{ | |||||
public: | |||||
Connection(Item* from, Item::AnchorType fromType, | |||||
Item* to, Item::AnchorType toType, QGraphicsItem* parent = nullptr); | |||||
void updatePosition(); | |||||
Item *from_, *to_; | |||||
Item::AnchorType fromType_, toType_; | |||||
}; | |||||
#endif // CONNECTION_H |
@@ -16,12 +16,14 @@ DEFINES += QT_DEPRECATED_WARNINGS | |||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 | ||||
SOURCES += \ | SOURCES += \ | ||||
connection.cpp \ | |||||
item.cpp \ | item.cpp \ | ||||
main.cpp \ | main.cpp \ | ||||
mainwindow.cpp \ | mainwindow.cpp \ | ||||
mygraphicsview.cpp | mygraphicsview.cpp | ||||
HEADERS += \ | HEADERS += \ | ||||
connection.h \ | |||||
item.h \ | item.h \ | ||||
mainwindow.h \ | mainwindow.h \ | ||||
mygraphicsview.h | mygraphicsview.h | ||||
@@ -1,4 +1,5 @@ | |||||
#include "item.h" | #include "item.h" | ||||
#include "connection.h" | |||||
#include <QPainter> | #include <QPainter> | ||||
#include <QStyleOptionGraphicsItem> | #include <QStyleOptionGraphicsItem> | ||||
@@ -17,17 +18,59 @@ Item::Item(const QString &type, QGraphicsItem *parent) | |||||
QRectF Item::boundingRect() const | QRectF Item::boundingRect() const | ||||
{ | { | ||||
return QRectF(0, 0, 80, 40); | |||||
return QRectF(-5, -5, 90, 50); | |||||
} | } | ||||
void Item::paint(QPainter *painter, | void Item::paint(QPainter *painter, | ||||
const QStyleOptionGraphicsItem *, | const QStyleOptionGraphicsItem *, | ||||
QWidget *) | QWidget *) | ||||
{ | { | ||||
QRectF r = boundingRect().adjusted(5, 5, -5, -5); // 图元主体 | |||||
painter->setBrush(color_.lighter(150)); | painter->setBrush(color_.lighter(150)); | ||||
painter->setPen(QPen(color_, 2)); | painter->setPen(QPen(color_, 2)); | ||||
painter->drawRoundedRect(boundingRect(), 5, 5); | |||||
painter->drawRoundedRect(r, 5, 5); | |||||
// 画锚点 | |||||
painter->setBrush(Qt::darkGray); | |||||
painter->setPen(Qt::NoPen); | |||||
painter->drawEllipse(QPointF(r.left(), r.center().y()), 5, 5); // 左锚点 | |||||
painter->drawEllipse(QPointF(r.right(), r.center().y()), 5, 5); // 右锚点 | |||||
painter->setPen(Qt::black); | painter->setPen(Qt::black); | ||||
painter->drawText(boundingRect(), Qt::AlignCenter, type_); | |||||
painter->drawText(r, Qt::AlignCenter, type_); | |||||
} | |||||
QPointF Item::anchorPos(AnchorType anc) const | |||||
{ | |||||
QRectF r = boundingRect(); | |||||
switch (anc) { | |||||
case Left: return mapToScene(QPointF(r.left(), r.center().y())); | |||||
case Right: return mapToScene(QPointF(r.right(), r.center().y())); | |||||
} | |||||
return QPointF(); | |||||
} | |||||
void Item::addConnection(Connection* conn) | |||||
{ | |||||
if (!connections_.contains(conn)) | |||||
connections_.append(conn); | |||||
} | |||||
void Item::removeConnection(Connection* conn) | |||||
{ | |||||
connections_.removeAll(conn); | |||||
} | |||||
QList<Connection *> Item::connections() | |||||
{ | |||||
return connections_; | |||||
} | |||||
QVariant Item::itemChange(GraphicsItemChange change, const QVariant &value) | |||||
{ | |||||
if (change == QGraphicsItem::ItemPositionChange) { | |||||
for (Connection* conn : connections_) | |||||
conn->updatePosition(); | |||||
} | |||||
return QGraphicsObject::itemChange(change, value); | |||||
} | } |
@@ -2,11 +2,16 @@ | |||||
#define ITEM_H | #define ITEM_H | ||||
#include <QGraphicsObject> | #include <QGraphicsObject> | ||||
#include <QColor> | |||||
#include <QList> | |||||
class Connection; | |||||
class Item : public QGraphicsObject | class Item : public QGraphicsObject | ||||
{ | { | ||||
Q_OBJECT | Q_OBJECT | ||||
public: | public: | ||||
enum AnchorType { Left, Right }; | |||||
explicit Item(const QString &type, QGraphicsItem *parent = nullptr); | explicit Item(const QString &type, QGraphicsItem *parent = nullptr); | ||||
QRectF boundingRect() const override; | QRectF boundingRect() const override; | ||||
@@ -14,9 +19,19 @@ public: | |||||
const QStyleOptionGraphicsItem *option, | const QStyleOptionGraphicsItem *option, | ||||
QWidget *widget) override; | QWidget *widget) override; | ||||
QPointF anchorPos(AnchorType type) const; | |||||
void addConnection(Connection* conn); | |||||
void removeConnection(Connection* conn); | |||||
QList<Connection*> connections(); | |||||
protected: | |||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; | |||||
private: | private: | ||||
QString type_; | QString type_; | ||||
QColor color_; | QColor color_; | ||||
QList<Connection*> connections_; | |||||
}; | }; | ||||
#endif // ITEM_H | #endif // ITEM_H |
@@ -19,10 +19,7 @@ MainWindow::MainWindow(QWidget *parent) | |||||
/* 1. 场景 */ | /* 1. 场景 */ | ||||
m_scene = new QGraphicsScene(this); | m_scene = new QGraphicsScene(this); | ||||
ui->graphicsView->setScene(m_scene); | ui->graphicsView->setScene(m_scene); | ||||
ui->graphicsView->setRenderHint(QPainter::Antialiasing); | |||||
ui->graphicsView->setSceneRect(0, 0, 800, 600); | ui->graphicsView->setSceneRect(0, 0, 800, 600); | ||||
ui->graphicsView->setAcceptDrops(true); | |||||
ui->graphicsView->setFocus(); | |||||
/* 2. 列表 */ | /* 2. 列表 */ | ||||
ui->listWidget->setViewMode(QListView::IconMode); | ui->listWidget->setViewMode(QListView::IconMode); | ||||
@@ -19,8 +19,8 @@ | |||||
<rect> | <rect> | ||||
<x>20</x> | <x>20</x> | ||||
<y>10</y> | <y>10</y> | ||||
<width>161</width> | |||||
<height>381</height> | |||||
<width>171</width> | |||||
<height>491</height> | |||||
</rect> | </rect> | ||||
</property> | </property> | ||||
</widget> | </widget> | ||||
@@ -29,8 +29,8 @@ | |||||
<rect> | <rect> | ||||
<x>210</x> | <x>210</x> | ||||
<y>10</y> | <y>10</y> | ||||
<width>791</width> | |||||
<height>381</height> | |||||
<width>871</width> | |||||
<height>491</height> | |||||
</rect> | </rect> | ||||
</property> | </property> | ||||
<property name="dragMode"> | <property name="dragMode"> | ||||
@@ -10,7 +10,7 @@ MyGraphicsView::MyGraphicsView(QWidget *parent) | |||||
setAcceptDrops(true); | setAcceptDrops(true); | ||||
viewport()->setAcceptDrops(true); | viewport()->setAcceptDrops(true); | ||||
setRenderHint(QPainter::Antialiasing); | setRenderHint(QPainter::Antialiasing); | ||||
setFocusPolicy(Qt::StrongFocus); // 让view能接收键盘事件 | |||||
setFocusPolicy(Qt::StrongFocus); | |||||
} | } | ||||
void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event) | void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event) | ||||
@@ -51,9 +51,18 @@ void MyGraphicsView::dropEvent(QDropEvent *event) | |||||
void MyGraphicsView::keyPressEvent(QKeyEvent *event) | void MyGraphicsView::keyPressEvent(QKeyEvent *event) | ||||
{ | { | ||||
if (event->key() == Qt::Key_Delete && scene()) { | if (event->key() == Qt::Key_Delete && scene()) { | ||||
// 删除所有被选中的图元 | |||||
QList<QGraphicsItem*> selectedItems = scene()->selectedItems(); | QList<QGraphicsItem*> selectedItems = scene()->selectedItems(); | ||||
for (QGraphicsItem* item : selectedItems) { | for (QGraphicsItem* item : selectedItems) { | ||||
// 如果是Item,顺便删除其连线 | |||||
if (auto node = dynamic_cast<Item*>(item)) { | |||||
QList<Connection*> conns = node->connections(); | |||||
for (Connection* conn : conns) { | |||||
scene()->removeItem(conn); | |||||
conn->from_->removeConnection(conn); | |||||
conn->to_->removeConnection(conn); | |||||
delete conn; | |||||
} | |||||
} | |||||
scene()->removeItem(item); | scene()->removeItem(item); | ||||
delete item; | delete item; | ||||
} | } | ||||
@@ -61,3 +70,83 @@ void MyGraphicsView::keyPressEvent(QKeyEvent *event) | |||||
QGraphicsView::keyPressEvent(event); | QGraphicsView::keyPressEvent(event); | ||||
} | } | ||||
} | } | ||||
// ----------- 连线交互 ------------ | |||||
Item* MyGraphicsView::anchorItemAt(const QPoint& viewPos, Item::AnchorType& anchorType) | |||||
{ | |||||
QPointF scenePos = mapToScene(viewPos); | |||||
for (QGraphicsItem* item : scene()->items(scenePos)) { | |||||
if (Item* node = dynamic_cast<Item*>(item)) { | |||||
QRectF r = node->boundingRect(); | |||||
QPointF local = node->mapFromScene(scenePos); | |||||
if (QLineF(local, QPointF(r.left(), r.center().y())).length() < 10) { | |||||
anchorType = Item::Left; | |||||
return node; | |||||
} | |||||
if (QLineF(local, QPointF(r.right(), r.center().y())).length() < 10) { | |||||
anchorType = Item::Right; | |||||
return node; | |||||
} | |||||
} | |||||
} | |||||
return nullptr; | |||||
} | |||||
void MyGraphicsView::mousePressEvent(QMouseEvent *event) | |||||
{ | |||||
if (event->button() == Qt::LeftButton) { | |||||
Item::AnchorType anchor; | |||||
Item* node = anchorItemAt(event->pos(), anchor); | |||||
if (node) { | |||||
drawingConnection_ = true; | |||||
startItem_ = node; | |||||
startAnchor_ = anchor; | |||||
tempLine_ = scene()->addLine(QLineF(node->anchorPos(anchor), mapToScene(event->pos())), | |||||
QPen(Qt::darkGray, 2, Qt::DashLine)); | |||||
return; | |||||
} | |||||
} | |||||
QGraphicsView::mousePressEvent(event); | |||||
} | |||||
void MyGraphicsView::mouseMoveEvent(QMouseEvent *event) | |||||
{ | |||||
if (drawingConnection_ && tempLine_) { | |||||
QLineF l = tempLine_->line(); | |||||
l.setP2(mapToScene(event->pos())); | |||||
tempLine_->setLine(l); | |||||
return; | |||||
} | |||||
QGraphicsView::mouseMoveEvent(event); | |||||
} | |||||
void MyGraphicsView::mouseReleaseEvent(QMouseEvent *event) | |||||
{ | |||||
if (drawingConnection_ && tempLine_) { | |||||
// 检查鼠标释放点是否在某个Item boundingRect内 | |||||
QPointF scenePos = mapToScene(event->pos()); | |||||
for (QGraphicsItem* item : scene()->items(scenePos)) { | |||||
if (Item* node = dynamic_cast<Item*>(item)) { | |||||
QRectF r = node->boundingRect(); | |||||
QPointF local = node->mapFromScene(scenePos); | |||||
// 计算落点距离左右端点 | |||||
double distLeft = QLineF(local, QPointF(r.left(), r.center().y())).length(); | |||||
double distRight = QLineF(local, QPointF(r.right(), r.center().y())).length(); | |||||
Item::AnchorType anchor = (distLeft < distRight) ? Item::Left : Item::Right; | |||||
// 生成连线 | |||||
Connection* conn = new Connection(startItem_, startAnchor_, node, anchor); | |||||
scene()->addItem(conn); | |||||
break; | |||||
} | |||||
} | |||||
// 清除临时线 | |||||
scene()->removeItem(tempLine_); | |||||
delete tempLine_; | |||||
tempLine_ = nullptr; | |||||
drawingConnection_ = false; | |||||
startItem_ = nullptr; | |||||
return; | |||||
} | |||||
QGraphicsView::mouseReleaseEvent(event); | |||||
} |
@@ -6,7 +6,9 @@ | |||||
#include <QDragEnterEvent> | #include <QDragEnterEvent> | ||||
#include <QMimeData> | #include <QMimeData> | ||||
#include <QKeyEvent> | #include <QKeyEvent> | ||||
#include <QMouseEvent> | |||||
#include "item.h" | #include "item.h" | ||||
#include "connection.h" | |||||
class MyGraphicsView : public QGraphicsView | class MyGraphicsView : public QGraphicsView | ||||
{ | { | ||||
@@ -16,9 +18,21 @@ public: | |||||
protected: | protected: | ||||
void dragEnterEvent(QDragEnterEvent *event) override; | void dragEnterEvent(QDragEnterEvent *event) override; | ||||
void dropEvent(QDropEvent *event) override; | |||||
void dragMoveEvent(QDragMoveEvent *event) override; | void dragMoveEvent(QDragMoveEvent *event) override; | ||||
void dropEvent(QDropEvent *event) override; | |||||
void keyPressEvent(QKeyEvent *event) override; | void keyPressEvent(QKeyEvent *event) override; | ||||
void mousePressEvent(QMouseEvent *event) override; | |||||
void mouseMoveEvent(QMouseEvent *event) override; | |||||
void mouseReleaseEvent(QMouseEvent *event) override; | |||||
private: | |||||
Item* anchorItemAt(const QPoint& viewPos, Item::AnchorType& anchorType); | |||||
bool drawingConnection_ = false; | |||||
Item* startItem_ = nullptr; | |||||
Item::AnchorType startAnchor_; | |||||
QGraphicsLineItem* tempLine_ = nullptr; | |||||
}; | }; | ||||
#endif // MYGRAPHICSVIEW_H | #endif // MYGRAPHICSVIEW_H |