@@ -9,6 +9,7 @@ Connection::Connection(Item* from, Item::AnchorType fromType, | |||||
{ | { | ||||
setZValue(-1); // 在图元之下 | setZValue(-1); // 在图元之下 | ||||
setPen(QPen(Qt::black, 2)); | setPen(QPen(Qt::black, 2)); | ||||
setFlags(QGraphicsItem::ItemIsSelectable); | |||||
from_->addConnection(this); | from_->addConnection(this); | ||||
to_->addConnection(this); | to_->addConnection(this); | ||||
updatePosition(); | updatePosition(); | ||||
@@ -1,6 +1,6 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||
<!DOCTYPE QtCreatorProject> | <!DOCTYPE QtCreatorProject> | ||||
<!-- Written by QtCreator 4.11.1, 2025-08-05T20:13:55. --> | |||||
<!-- Written by QtCreator 4.11.1, 2025-08-06T13:12:15. --> | |||||
<qtcreator> | <qtcreator> | ||||
<data> | <data> | ||||
<variable>EnvironmentId</variable> | <variable>EnvironmentId</variable> | ||||
@@ -1,16 +1,13 @@ | |||||
#include "item.h" | #include "item.h" | ||||
#include "connection.h" | #include "connection.h" | ||||
#include <QMenu> | |||||
#include <QPainter> | #include <QPainter> | ||||
#include <QStyleOptionGraphicsItem> | #include <QStyleOptionGraphicsItem> | ||||
#include <QGraphicsSceneContextMenuEvent> | |||||
Item::Item(const QString &type, QGraphicsItem *parent) | Item::Item(const QString &type, QGraphicsItem *parent) | ||||
: QGraphicsObject(parent), type_(type) | : QGraphicsObject(parent), type_(type) | ||||
{ | { | ||||
if (type == "常开") color_ = Qt::blue; | |||||
else if (type == "常闭") color_ = Qt::red; | |||||
else if (type == "比较指令") color_ = Qt::green; | |||||
else color_ = Qt::darkYellow; | |||||
setFlags(QGraphicsItem::ItemIsMovable | | setFlags(QGraphicsItem::ItemIsMovable | | ||||
QGraphicsItem::ItemIsSelectable | | QGraphicsItem::ItemIsSelectable | | ||||
QGraphicsItem::ItemSendsGeometryChanges); | QGraphicsItem::ItemSendsGeometryChanges); | ||||
@@ -18,36 +15,76 @@ Item::Item(const QString &type, QGraphicsItem *parent) | |||||
QRectF Item::boundingRect() const | QRectF Item::boundingRect() const | ||||
{ | { | ||||
return QRectF(-5, -5, 90, 50); | |||||
return QRectF(-22, -15, 44, 30); | |||||
} | } | ||||
void Item::paint(QPainter *painter, | void Item::paint(QPainter *painter, | ||||
const QStyleOptionGraphicsItem *, | |||||
const QStyleOptionGraphicsItem * option, | |||||
QWidget *) | QWidget *) | ||||
{ | { | ||||
QRectF r = boundingRect().adjusted(5, 5, -5, -5); // 图元主体 | |||||
painter->setBrush(color_.lighter(150)); | |||||
painter->setPen(QPen(color_, 2)); | |||||
painter->drawRoundedRect(r, 5, 5); | |||||
painter->setRenderHint(QPainter::Antialiasing); | |||||
if (type_ == "线圈") { | |||||
// 绘制线圈样式: 两边线段+中间椭圆 | |||||
painter->drawLine(-12, 0, -5, 0); | |||||
painter->drawEllipse(QRectF(-5, -8, 10, 16)); | |||||
painter->drawLine(5, 0, 12, 0); | |||||
// 画锚点 | |||||
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->setBrush(Qt::darkGray); | |||||
painter->setPen(Qt::NoPen); | |||||
painter->drawEllipse(QPointF(-12, 0), 4, 4); // 左锚点 | |||||
painter->drawEllipse(QPointF(12, 0), 4, 4); // 右锚点 | |||||
} | |||||
else if (type_ == "常开") { | |||||
painter->drawLine(-12, 0, -4, 0); | |||||
painter->drawLine(-4, -8, -4, 8); | |||||
painter->drawLine(4, -8, 4, 8); | |||||
painter->drawLine(4, 0, 12, 0); | |||||
// 锚点 | |||||
painter->setBrush(Qt::darkGray); | |||||
painter->setPen(Qt::NoPen); | |||||
painter->drawEllipse(QPointF(-18, 0), 4, 4); // 左 | |||||
painter->drawEllipse(QPointF(18, 0), 4, 4); // 右 | |||||
} | |||||
else if (type_ == "常闭") { | |||||
painter->drawLine(-15, -10, 15, 10); // 对角线 | |||||
painter->drawLine(-12, 0, -4, 0); | |||||
painter->drawLine(-4, -8, -4, 8); | |||||
painter->drawLine(4, -8, 4, 8); | |||||
painter->drawLine(4, 0, 12, 0); | |||||
painter->setPen(Qt::black); | |||||
painter->drawText(r, Qt::AlignCenter, type_); | |||||
// 锚点 | |||||
painter->setBrush(Qt::darkGray); | |||||
painter->setPen(Qt::NoPen); | |||||
painter->drawEllipse(QPointF(-18, 0), 4, 4); // 左 | |||||
painter->drawEllipse(QPointF(18, 0), 4, 4); // 右 | |||||
} | |||||
else if (type_ == "比较") { | |||||
painter->drawRect(QRectF(-12, -8, 24, 16)); | |||||
painter->setFont(QFont("Arial", 8)); | |||||
painter->drawText(QRectF(-10, -8, 20, 16), Qt::AlignCenter, "CP"); | |||||
// 锚点 | |||||
painter->setBrush(Qt::darkGray); | |||||
painter->setPen(Qt::NoPen); | |||||
painter->drawEllipse(QPointF(-18, 0), 4, 4); | |||||
painter->drawEllipse(QPointF(18, 0), 4, 4); | |||||
} | |||||
if (option->state & QStyle::State_Selected) { | |||||
QPen pen(Qt::DashLine); | |||||
pen.setColor(Qt::blue); | |||||
pen.setWidth(2); | |||||
painter->setPen(pen); | |||||
painter->setBrush(Qt::NoBrush); | |||||
painter->drawRect(boundingRect()); | |||||
} | |||||
} | } | ||||
QPointF Item::anchorPos(AnchorType anc) const | |||||
QPointF Item::anchorPos(AnchorType type) 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(); | |||||
return mapToScene(type == Left ? QPointF(-18, 0) : QPointF(18, 0)); | |||||
} | } | ||||
void Item::addConnection(Connection* conn) | void Item::addConnection(Connection* conn) | ||||
@@ -66,6 +103,11 @@ QList<Connection *> Item::connections() | |||||
return connections_; | return connections_; | ||||
} | } | ||||
QString Item::itemType() | |||||
{ | |||||
return type_; | |||||
} | |||||
QVariant Item::itemChange(GraphicsItemChange change, const QVariant &value) | QVariant Item::itemChange(GraphicsItemChange change, const QVariant &value) | ||||
{ | { | ||||
if (change == QGraphicsItem::ItemPositionChange) { | if (change == QGraphicsItem::ItemPositionChange) { | ||||
@@ -74,3 +116,17 @@ QVariant Item::itemChange(GraphicsItemChange change, const QVariant &value) | |||||
} | } | ||||
return QGraphicsObject::itemChange(change, value); | return QGraphicsObject::itemChange(change, value); | ||||
} | } | ||||
void Item::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) | |||||
{ | |||||
QMenu menu; | |||||
QAction* copyAct = menu.addAction("复制"); | |||||
QAction* deleteAct = menu.addAction("删除"); | |||||
QAction* selected = menu.exec(event->screenPos()); | |||||
if (selected == copyAct) { | |||||
emit requestCopy(this); | |||||
} | |||||
if (selected == deleteAct) { | |||||
emit requestDelete(this); | |||||
} | |||||
} |
@@ -23,15 +23,20 @@ public: | |||||
void addConnection(Connection* conn); | void addConnection(Connection* conn); | ||||
void removeConnection(Connection* conn); | void removeConnection(Connection* conn); | ||||
QList<Connection*> connections(); | QList<Connection*> connections(); | ||||
QString itemType(); | |||||
signals: | |||||
void requestCopy(Item*); | |||||
void requestDelete(Item*); | |||||
protected: | protected: | ||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; | QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; | ||||
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; | |||||
private: | private: | ||||
QString type_; | QString type_; | ||||
QColor color_; | QColor color_; | ||||
QList<Connection*> connections_; | QList<Connection*> connections_; | ||||
}; | }; | ||||
#endif // ITEM_H | #endif // ITEM_H |
@@ -20,6 +20,7 @@ MainWindow::MainWindow(QWidget *parent) | |||||
m_scene = new QGraphicsScene(this); | m_scene = new QGraphicsScene(this); | ||||
ui->graphicsView->setScene(m_scene); | ui->graphicsView->setScene(m_scene); | ||||
ui->graphicsView->setSceneRect(0, 0, 800, 600); | ui->graphicsView->setSceneRect(0, 0, 800, 600); | ||||
ui->graphicsView->setDragMode(QGraphicsView::RubberBandDrag); | |||||
/* 2. 列表 */ | /* 2. 列表 */ | ||||
ui->listWidget->setViewMode(QListView::IconMode); | ui->listWidget->setViewMode(QListView::IconMode); | ||||
@@ -81,7 +82,7 @@ void MainWindow::createComponents() | |||||
const QVector<Comp> comps = { | const QVector<Comp> comps = { | ||||
{"常开", Qt::blue}, | {"常开", Qt::blue}, | ||||
{"常闭", Qt::red}, | {"常闭", Qt::red}, | ||||
{"比较指令", Qt::green}, | |||||
{"比较", Qt::green}, | |||||
{"线圈", Qt::darkYellow} | {"线圈", Qt::darkYellow} | ||||
}; | }; | ||||
@@ -6,7 +6,7 @@ | |||||
<rect> | <rect> | ||||
<x>0</x> | <x>0</x> | ||||
<y>0</y> | <y>0</y> | ||||
<width>1101</width> | |||||
<width>1012</width> | |||||
<height>656</height> | <height>656</height> | ||||
</rect> | </rect> | ||||
</property> | </property> | ||||
@@ -29,7 +29,7 @@ | |||||
<rect> | <rect> | ||||
<x>210</x> | <x>210</x> | ||||
<y>10</y> | <y>10</y> | ||||
<width>871</width> | |||||
<width>761</width> | |||||
<height>491</height> | <height>491</height> | ||||
</rect> | </rect> | ||||
</property> | </property> | ||||
@@ -43,7 +43,7 @@ | |||||
<rect> | <rect> | ||||
<x>0</x> | <x>0</x> | ||||
<y>0</y> | <y>0</y> | ||||
<width>1101</width> | |||||
<width>1012</width> | |||||
<height>26</height> | <height>26</height> | ||||
</rect> | </rect> | ||||
</property> | </property> | ||||
@@ -3,14 +3,17 @@ | |||||
#include <QCursor> | #include <QCursor> | ||||
#include <QGraphicsScene> | #include <QGraphicsScene> | ||||
#include <QDebug> | #include <QDebug> | ||||
#include <QMenu> | |||||
MyGraphicsView::ClipInfo MyGraphicsView::clipboard_ = {}; | |||||
MyGraphicsView::MyGraphicsView(QWidget *parent) | MyGraphicsView::MyGraphicsView(QWidget *parent) | ||||
: QGraphicsView(parent) | : QGraphicsView(parent) | ||||
{ | { | ||||
setAcceptDrops(true); | setAcceptDrops(true); | ||||
viewport()->setAcceptDrops(true); | |||||
setRenderHint(QPainter::Antialiasing); | setRenderHint(QPainter::Antialiasing); | ||||
setFocusPolicy(Qt::StrongFocus); | setFocusPolicy(Qt::StrongFocus); | ||||
setDragMode(QGraphicsView::RubberBandDrag); | |||||
} | } | ||||
void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event) | void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event) | ||||
@@ -41,9 +44,9 @@ void MyGraphicsView::dropEvent(QDropEvent *event) | |||||
QPointF scenePos = mapToScene(event->pos()); | QPointF scenePos = mapToScene(event->pos()); | ||||
Item *item = new Item(type); | Item *item = new Item(type); | ||||
item->setPos(scenePos); | item->setPos(scenePos); | ||||
if (scene()) | |||||
scene()->addItem(item); | |||||
connect(item, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy); | |||||
connect(item, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete); | |||||
scene()->addItem(item); | |||||
event->acceptProposedAction(); | event->acceptProposedAction(); | ||||
} | } | ||||
@@ -53,24 +56,69 @@ 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)) { | |||||
if (auto conn = dynamic_cast<Connection*>(item)) { | |||||
// 先通知两端节点断开 | |||||
conn->from_->removeConnection(conn); | |||||
conn->to_->removeConnection(conn); | |||||
scene()->removeItem(conn); | |||||
delete conn; | |||||
} else if (auto node = dynamic_cast<Item*>(item)) { | |||||
// 删除节点的所有连线 | |||||
QList<Connection*> conns = node->connections(); | QList<Connection*> conns = node->connections(); | ||||
for (Connection* conn : conns) { | for (Connection* conn : conns) { | ||||
scene()->removeItem(conn); | |||||
conn->from_->removeConnection(conn); | conn->from_->removeConnection(conn); | ||||
conn->to_->removeConnection(conn); | conn->to_->removeConnection(conn); | ||||
scene()->removeItem(conn); | |||||
delete conn; | delete conn; | ||||
} | } | ||||
scene()->removeItem(node); | |||||
delete node; | |||||
} | } | ||||
scene()->removeItem(item); | |||||
delete item; | |||||
} | |||||
}else if (event->matches(QKeySequence::Copy)) { | |||||
// Ctrl+C | |||||
QList<QGraphicsItem*> selectedItems = scene()->selectedItems(); | |||||
for (QGraphicsItem* item : selectedItems) { | |||||
if (auto node = dynamic_cast<Item*>(item)) { | |||||
clipboard_.input = node->itemType(); | |||||
break; | |||||
} | |||||
} | |||||
} else if (event->matches(QKeySequence::Paste)) { | |||||
// Ctrl+V | |||||
if (!clipboard_.input.isEmpty()) { | |||||
QPointF center = mapToScene(viewport()->rect().center()); | |||||
Item* newItem = new Item(clipboard_.input); | |||||
newItem->setPos(center); | |||||
connect(newItem, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy); | |||||
connect(newItem, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete); | |||||
scene()->addItem(newItem); | |||||
} | } | ||||
} else { | } else { | ||||
QGraphicsView::keyPressEvent(event); | QGraphicsView::keyPressEvent(event); | ||||
} | } | ||||
} | } | ||||
void MyGraphicsView::contextMenuEvent(QContextMenuEvent *event) | |||||
{ | |||||
QPointF scenePos = mapToScene(event->pos()); | |||||
QList<QGraphicsItem*> itemsAt = scene()->items(scenePos); | |||||
if (itemsAt.isEmpty() && !clipboard_.input.isEmpty()) { | |||||
QMenu menu; | |||||
QAction* pasteAct = menu.addAction("粘贴"); | |||||
QAction* sel = menu.exec(event->globalPos()); | |||||
if (sel == pasteAct) { | |||||
Item* newItem = new Item(clipboard_.input); | |||||
newItem->setPos(scenePos); | |||||
connect(newItem, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy); | |||||
connect(newItem, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete); | |||||
scene()->addItem(newItem); | |||||
} | |||||
} else { | |||||
QGraphicsView::contextMenuEvent(event); // 让Item自己弹出菜单 | |||||
} | |||||
} | |||||
// ----------- 连线交互 ------------ | // ----------- 连线交互 ------------ | ||||
Item* MyGraphicsView::anchorItemAt(const QPoint& viewPos, Item::AnchorType& anchorType) | Item* MyGraphicsView::anchorItemAt(const QPoint& viewPos, Item::AnchorType& anchorType) | ||||
@@ -93,6 +141,24 @@ Item* MyGraphicsView::anchorItemAt(const QPoint& viewPos, Item::AnchorType& anch | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
void MyGraphicsView::onItemRequestCopy(Item *item) | |||||
{ | |||||
clipboard_.input = item->itemType(); | |||||
} | |||||
void MyGraphicsView::onItemRequestDelete(Item *item) | |||||
{ | |||||
QList<Connection*> conns = item->connections(); | |||||
for (Connection* conn : conns) { | |||||
conn->from_->removeConnection(conn); | |||||
conn->to_->removeConnection(conn); | |||||
scene()->removeItem(conn); | |||||
delete conn; | |||||
} | |||||
scene()->removeItem(item); | |||||
delete item; | |||||
} | |||||
void MyGraphicsView::mousePressEvent(QMouseEvent *event) | void MyGraphicsView::mousePressEvent(QMouseEvent *event) | ||||
{ | { | ||||
if (event->button() == Qt::LeftButton) { | if (event->button() == Qt::LeftButton) { | ||||
@@ -21,6 +21,7 @@ protected: | |||||
void dragMoveEvent(QDragMoveEvent *event) override; | void dragMoveEvent(QDragMoveEvent *event) override; | ||||
void dropEvent(QDropEvent *event) override; | void dropEvent(QDropEvent *event) override; | ||||
void keyPressEvent(QKeyEvent *event) override; | void keyPressEvent(QKeyEvent *event) override; | ||||
void contextMenuEvent(QContextMenuEvent *event) override; | |||||
void mousePressEvent(QMouseEvent *event) override; | void mousePressEvent(QMouseEvent *event) override; | ||||
void mouseMoveEvent(QMouseEvent *event) override; | void mouseMoveEvent(QMouseEvent *event) override; | ||||
@@ -33,6 +34,15 @@ private: | |||||
Item* startItem_ = nullptr; | Item* startItem_ = nullptr; | ||||
Item::AnchorType startAnchor_; | Item::AnchorType startAnchor_; | ||||
QGraphicsLineItem* tempLine_ = nullptr; | QGraphicsLineItem* tempLine_ = nullptr; | ||||
struct ClipInfo { | |||||
QString input; | |||||
}; | |||||
static ClipInfo clipboard_; | |||||
private slots: | |||||
void onItemRequestCopy(Item*); | |||||
void onItemRequestDelete(Item*); | |||||
}; | }; | ||||
#endif // MYGRAPHICSVIEW_H | #endif // MYGRAPHICSVIEW_H |
@@ -1,6 +0,0 @@ | |||||
#include "mylistwidget.h" | |||||
MyListWidget::MyListWidget() | |||||
{ | |||||
} |
@@ -1,31 +0,0 @@ | |||||
#ifndef MYLISTWIDGET_H | |||||
#define MYLISTWIDGET_H | |||||
#include <QDrag> | |||||
#include <QListWidget> | |||||
#include <QMimeData> | |||||
class MyListWidget : public QListWidget { | |||||
public: | |||||
using QListWidget::QListWidget; | |||||
protected: | |||||
void startDrag(Qt::DropActions supportedActions) override | |||||
{ | |||||
QListWidgetItem *item = currentItem(); | |||||
if (!item) return; | |||||
QDrag *drag = new QDrag(this); | |||||
QMimeData *mimeData = new QMimeData; | |||||
QString type = item->data(Qt::UserRole).toString(); | |||||
mimeData->setData("application/x-component", type.toUtf8()); | |||||
drag->setMimeData(mimeData); | |||||
drag->setPixmap(item->icon().pixmap(iconSize())); | |||||
drag->setHotSpot(drag->pixmap().rect().center()); | |||||
drag->exec(Qt::CopyAction); | |||||
} | |||||
}; | |||||
#endif // MYLISTWIDGET_H |