@@ -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 | |||
SOURCES += \ | |||
connection.cpp \ | |||
item.cpp \ | |||
main.cpp \ | |||
mainwindow.cpp \ | |||
mygraphicsview.cpp | |||
HEADERS += \ | |||
connection.h \ | |||
item.h \ | |||
mainwindow.h \ | |||
mygraphicsview.h | |||
@@ -1,4 +1,5 @@ | |||
#include "item.h" | |||
#include "connection.h" | |||
#include <QPainter> | |||
#include <QStyleOptionGraphicsItem> | |||
@@ -17,17 +18,59 @@ Item::Item(const QString &type, QGraphicsItem *parent) | |||
QRectF Item::boundingRect() const | |||
{ | |||
return QRectF(0, 0, 80, 40); | |||
return QRectF(-5, -5, 90, 50); | |||
} | |||
void Item::paint(QPainter *painter, | |||
const QStyleOptionGraphicsItem *, | |||
QWidget *) | |||
{ | |||
QRectF r = boundingRect().adjusted(5, 5, -5, -5); // 图元主体 | |||
painter->setBrush(color_.lighter(150)); | |||
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->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 | |||
#include <QGraphicsObject> | |||
#include <QColor> | |||
#include <QList> | |||
class Connection; | |||
class Item : public QGraphicsObject | |||
{ | |||
Q_OBJECT | |||
public: | |||
enum AnchorType { Left, Right }; | |||
explicit Item(const QString &type, QGraphicsItem *parent = nullptr); | |||
QRectF boundingRect() const override; | |||
@@ -14,9 +19,19 @@ public: | |||
const QStyleOptionGraphicsItem *option, | |||
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: | |||
QString type_; | |||
QColor color_; | |||
QList<Connection*> connections_; | |||
}; | |||
#endif // ITEM_H |
@@ -19,10 +19,7 @@ MainWindow::MainWindow(QWidget *parent) | |||
/* 1. 场景 */ | |||
m_scene = new QGraphicsScene(this); | |||
ui->graphicsView->setScene(m_scene); | |||
ui->graphicsView->setRenderHint(QPainter::Antialiasing); | |||
ui->graphicsView->setSceneRect(0, 0, 800, 600); | |||
ui->graphicsView->setAcceptDrops(true); | |||
ui->graphicsView->setFocus(); | |||
/* 2. 列表 */ | |||
ui->listWidget->setViewMode(QListView::IconMode); | |||
@@ -19,8 +19,8 @@ | |||
<rect> | |||
<x>20</x> | |||
<y>10</y> | |||
<width>161</width> | |||
<height>381</height> | |||
<width>171</width> | |||
<height>491</height> | |||
</rect> | |||
</property> | |||
</widget> | |||
@@ -29,8 +29,8 @@ | |||
<rect> | |||
<x>210</x> | |||
<y>10</y> | |||
<width>791</width> | |||
<height>381</height> | |||
<width>871</width> | |||
<height>491</height> | |||
</rect> | |||
</property> | |||
<property name="dragMode"> | |||
@@ -10,7 +10,7 @@ MyGraphicsView::MyGraphicsView(QWidget *parent) | |||
setAcceptDrops(true); | |||
viewport()->setAcceptDrops(true); | |||
setRenderHint(QPainter::Antialiasing); | |||
setFocusPolicy(Qt::StrongFocus); // 让view能接收键盘事件 | |||
setFocusPolicy(Qt::StrongFocus); | |||
} | |||
void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event) | |||
@@ -51,9 +51,18 @@ void MyGraphicsView::dropEvent(QDropEvent *event) | |||
void MyGraphicsView::keyPressEvent(QKeyEvent *event) | |||
{ | |||
if (event->key() == Qt::Key_Delete && scene()) { | |||
// 删除所有被选中的图元 | |||
QList<QGraphicsItem*> selectedItems = scene()->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); | |||
delete item; | |||
} | |||
@@ -61,3 +70,83 @@ void MyGraphicsView::keyPressEvent(QKeyEvent *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 <QMimeData> | |||
#include <QKeyEvent> | |||
#include <QMouseEvent> | |||
#include "item.h" | |||
#include "connection.h" | |||
class MyGraphicsView : public QGraphicsView | |||
{ | |||
@@ -16,9 +18,21 @@ public: | |||
protected: | |||
void dragEnterEvent(QDragEnterEvent *event) override; | |||
void dropEvent(QDropEvent *event) override; | |||
void dragMoveEvent(QDragMoveEvent *event) override; | |||
void dropEvent(QDropEvent *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 |