@@ -9,6 +9,7 @@ Connection::Connection(Item* from, Item::AnchorType fromType, | |||
{ | |||
setZValue(-1); // 在图元之下 | |||
setPen(QPen(Qt::black, 2)); | |||
setFlags(QGraphicsItem::ItemIsSelectable); | |||
from_->addConnection(this); | |||
to_->addConnection(this); | |||
updatePosition(); | |||
@@ -1,6 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!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> | |||
<data> | |||
<variable>EnvironmentId</variable> | |||
@@ -1,16 +1,13 @@ | |||
#include "item.h" | |||
#include "connection.h" | |||
#include <QMenu> | |||
#include <QPainter> | |||
#include <QStyleOptionGraphicsItem> | |||
#include <QGraphicsSceneContextMenuEvent> | |||
Item::Item(const QString &type, QGraphicsItem *parent) | |||
: 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 | | |||
QGraphicsItem::ItemIsSelectable | | |||
QGraphicsItem::ItemSendsGeometryChanges); | |||
@@ -18,36 +15,76 @@ Item::Item(const QString &type, QGraphicsItem *parent) | |||
QRectF Item::boundingRect() const | |||
{ | |||
return QRectF(-5, -5, 90, 50); | |||
return QRectF(-22, -15, 44, 30); | |||
} | |||
void Item::paint(QPainter *painter, | |||
const QStyleOptionGraphicsItem *, | |||
const QStyleOptionGraphicsItem * option, | |||
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) | |||
@@ -66,6 +103,11 @@ QList<Connection *> Item::connections() | |||
return connections_; | |||
} | |||
QString Item::itemType() | |||
{ | |||
return type_; | |||
} | |||
QVariant Item::itemChange(GraphicsItemChange change, const QVariant &value) | |||
{ | |||
if (change == QGraphicsItem::ItemPositionChange) { | |||
@@ -74,3 +116,17 @@ QVariant Item::itemChange(GraphicsItemChange change, const QVariant &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 removeConnection(Connection* conn); | |||
QList<Connection*> connections(); | |||
QString itemType(); | |||
signals: | |||
void requestCopy(Item*); | |||
void requestDelete(Item*); | |||
protected: | |||
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; | |||
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; | |||
private: | |||
QString type_; | |||
QColor color_; | |||
QList<Connection*> connections_; | |||
}; | |||
#endif // ITEM_H |
@@ -20,6 +20,7 @@ MainWindow::MainWindow(QWidget *parent) | |||
m_scene = new QGraphicsScene(this); | |||
ui->graphicsView->setScene(m_scene); | |||
ui->graphicsView->setSceneRect(0, 0, 800, 600); | |||
ui->graphicsView->setDragMode(QGraphicsView::RubberBandDrag); | |||
/* 2. 列表 */ | |||
ui->listWidget->setViewMode(QListView::IconMode); | |||
@@ -81,7 +82,7 @@ void MainWindow::createComponents() | |||
const QVector<Comp> comps = { | |||
{"常开", Qt::blue}, | |||
{"常闭", Qt::red}, | |||
{"比较指令", Qt::green}, | |||
{"比较", Qt::green}, | |||
{"线圈", Qt::darkYellow} | |||
}; | |||
@@ -6,7 +6,7 @@ | |||
<rect> | |||
<x>0</x> | |||
<y>0</y> | |||
<width>1101</width> | |||
<width>1012</width> | |||
<height>656</height> | |||
</rect> | |||
</property> | |||
@@ -29,7 +29,7 @@ | |||
<rect> | |||
<x>210</x> | |||
<y>10</y> | |||
<width>871</width> | |||
<width>761</width> | |||
<height>491</height> | |||
</rect> | |||
</property> | |||
@@ -43,7 +43,7 @@ | |||
<rect> | |||
<x>0</x> | |||
<y>0</y> | |||
<width>1101</width> | |||
<width>1012</width> | |||
<height>26</height> | |||
</rect> | |||
</property> | |||
@@ -3,14 +3,17 @@ | |||
#include <QCursor> | |||
#include <QGraphicsScene> | |||
#include <QDebug> | |||
#include <QMenu> | |||
MyGraphicsView::ClipInfo MyGraphicsView::clipboard_ = {}; | |||
MyGraphicsView::MyGraphicsView(QWidget *parent) | |||
: QGraphicsView(parent) | |||
{ | |||
setAcceptDrops(true); | |||
viewport()->setAcceptDrops(true); | |||
setRenderHint(QPainter::Antialiasing); | |||
setFocusPolicy(Qt::StrongFocus); | |||
setDragMode(QGraphicsView::RubberBandDrag); | |||
} | |||
void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event) | |||
@@ -41,9 +44,9 @@ void MyGraphicsView::dropEvent(QDropEvent *event) | |||
QPointF scenePos = mapToScene(event->pos()); | |||
Item *item = new Item(type); | |||
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(); | |||
} | |||
@@ -53,24 +56,69 @@ 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)) { | |||
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(); | |||
for (Connection* conn : conns) { | |||
scene()->removeItem(conn); | |||
conn->from_->removeConnection(conn); | |||
conn->to_->removeConnection(conn); | |||
scene()->removeItem(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 { | |||
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) | |||
@@ -93,6 +141,24 @@ Item* MyGraphicsView::anchorItemAt(const QPoint& viewPos, Item::AnchorType& anch | |||
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) | |||
{ | |||
if (event->button() == Qt::LeftButton) { | |||
@@ -21,6 +21,7 @@ protected: | |||
void dragMoveEvent(QDragMoveEvent *event) override; | |||
void dropEvent(QDropEvent *event) override; | |||
void keyPressEvent(QKeyEvent *event) override; | |||
void contextMenuEvent(QContextMenuEvent *event) override; | |||
void mousePressEvent(QMouseEvent *event) override; | |||
void mouseMoveEvent(QMouseEvent *event) override; | |||
@@ -33,6 +34,15 @@ private: | |||
Item* startItem_ = nullptr; | |||
Item::AnchorType startAnchor_; | |||
QGraphicsLineItem* tempLine_ = nullptr; | |||
struct ClipInfo { | |||
QString input; | |||
}; | |||
static ClipInfo clipboard_; | |||
private slots: | |||
void onItemRequestCopy(Item*); | |||
void onItemRequestDelete(Item*); | |||
}; | |||
#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 |