@@ -26,6 +26,7 @@ void Button::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QW | |||
painter->drawPixmap(0, 0, currentSize_.width(), currentSize_.height(), customPixmap_); | |||
} | |||
else{ | |||
painter->setBrush(Qt::green); | |||
painter->drawRect(0, 0, currentSize_.width(), currentSize_.height()); | |||
} | |||
} | |||
@@ -120,9 +121,6 @@ void Button::loadImage() | |||
{ | |||
if(!imagePath_.isEmpty()){ | |||
customPixmap_ = QPixmap(imagePath_); | |||
// if (!customPixmap_.isNull()){ | |||
// customPixmap_ = customPixmap_.scaled(50, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation); | |||
// } | |||
} | |||
} | |||
@@ -1,6 +1,7 @@ | |||
#include "contact.h" | |||
#include <QPainter> | |||
#include <QStyleOptionGraphicsItem> | |||
#include <QMenu> | |||
Contact::Contact(const QString &type) : Item(type) | |||
{ | |||
@@ -45,7 +46,6 @@ void Contact::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, Q | |||
painter->drawEllipse(QPointF(-18, 0), 4, 4); // 左 | |||
painter->drawEllipse(QPointF(18, 0), 4, 4); // 右 | |||
} | |||
if (!registerId_.isEmpty()) { | |||
painter->save(); | |||
painter->setFont(QFont("Arial", 8)); | |||
@@ -69,3 +69,31 @@ bool Contact::state() const | |||
{ | |||
return registerValue_ > 0; | |||
} | |||
void Contact::addMenuActions(QMenu *menu) | |||
{ | |||
menu->addAction("置ON"); | |||
menu->addAction("置OFF"); | |||
} | |||
void Contact::handleMenuAction(QAction *action) | |||
{ | |||
if (action->text() == "复制") { | |||
emit requestCopy(this); | |||
} | |||
if (action->text() == "删除") { | |||
emit requestDelete(this); | |||
} | |||
if (action->text() == "对象"){ | |||
emit requestBindRegister(this); | |||
} | |||
if (action->text() == "重置"){ | |||
emit requestReset(this); | |||
} | |||
if (action->text() == "置ON"){ | |||
emit requestSetON(this, true); | |||
} | |||
if (action->text() == "置OFF"){ | |||
emit requestSetON(this, false); | |||
} | |||
} |
@@ -10,6 +10,8 @@ public: | |||
const QStyleOptionGraphicsItem *option, | |||
QWidget *) override; | |||
bool state() const override; | |||
void addMenuActions(QMenu *menu) override; | |||
void handleMenuAction(QAction *action) override; | |||
}; | |||
#endif // CONTACT_H |
@@ -1,6 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE QtCreatorProject> | |||
<!-- Written by QtCreator 4.11.1, 2025-08-12T22:16:53. --> | |||
<!-- Written by QtCreator 4.11.1, 2025-08-13T11:12:10. --> | |||
<qtcreator> | |||
<data> | |||
<variable>EnvironmentId</variable> | |||
@@ -121,25 +121,6 @@ void Item::handleMenuAction(QAction *action) | |||
QVariant Item::itemChange(GraphicsItemChange change, const QVariant &value) | |||
{ | |||
// if (change == QGraphicsItem::ItemPositionChange) { | |||
// for (Connection* conn : connections_) | |||
// conn->updatePosition(); | |||
// } | |||
// if (change == ItemPositionChange && scene()) { | |||
// // 获取场景(确保是 GridScene) | |||
// if (auto myScene = dynamic_cast<MyScene*>(scene())) { | |||
// if (myScene->getSnapToGrid()) { | |||
// // 获取网格大小 | |||
// int gridSize = myScene->getGridSize(); | |||
// // 对齐到网格 | |||
// QPointF newPos = value.toPointF(); | |||
// qreal xV = qRound(newPos.x() / gridSize) * gridSize; | |||
// qreal yV = qRound(newPos.y() / gridSize) * gridSize; | |||
// return QPointF(xV, yV); | |||
// } | |||
// } | |||
// } | |||
// 处理位置即将改变事件(网格对齐) | |||
if (change == ItemPositionChange && scene()) { | |||
QPointF newPos = value.toPointF(); | |||
@@ -29,7 +29,7 @@ void Light::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWi | |||
if (state()) { | |||
painter->setBrush(Qt::green); // 激活状态 | |||
} else { | |||
painter->setBrush(Qt::white); // 未激活状态 | |||
painter->setBrush(Qt::red); // 未激活状态 | |||
} | |||
painter->drawEllipse(0, 0, currentSize_.width(), currentSize_.height()); | |||
} | |||
@@ -102,6 +102,8 @@ void MainWindow::newPage(bool isPlc) | |||
registerManager, &RegisterManager::bindItem); | |||
connect(newView, &MyGraphicsView::itemResetRegister, | |||
registerManager, &RegisterManager::unbindItem); | |||
connect(newView, &MyGraphicsView::itemSetON, | |||
modbusManager, &ModbusManager::writeRegister); | |||
} else { | |||
// 创建新场景 | |||
QGraphicsScene* newScene = new QGraphicsScene(this); | |||
@@ -437,18 +439,6 @@ void MainWindow::hmiChange() | |||
void MainWindow::newProject() | |||
{ | |||
// // 检查当前tabwidget是否只有一个页面且页面内容为空 | |||
// if (currentIsPLC_ && plcScenes.size() == 1 && plcScenes[0]->items().isEmpty()) { | |||
// // 复用第一个页面 | |||
// clearScene(); | |||
// setWindowTitle("PLC编辑器"); | |||
// return; | |||
// } | |||
// if (!currentIsPLC_ && hmiScenes.size() == 1 && hmiScenes[0]->items().isEmpty()) { | |||
// clearScene(); | |||
// setWindowTitle("HMI编辑器"); | |||
// return; | |||
// } | |||
newPage(currentIsPLC_); | |||
// 更新窗口标题 | |||
@@ -476,18 +466,9 @@ void MainWindow::openProject() | |||
Project project; | |||
if (project.loadFromFile(filePath)) { | |||
// // 在当前模块新建一个页面 | |||
// newPage(currentIsPLC_); | |||
// int newPageIndex = currentPageIndex(); | |||
int newPageIndex = -1; | |||
if (currentIsPLC_ && plcScenes.size() == 1 && plcScenes[0]->items().isEmpty()) { | |||
newPageIndex = 0; | |||
} else if (!currentIsPLC_ && hmiScenes.size() == 1 && hmiScenes[0]->items().isEmpty()) { | |||
newPageIndex = 0; | |||
} else { | |||
newPage(currentIsPLC_); | |||
newPageIndex = currentPageIndex(); | |||
} | |||
// 在当前模块新建一个页面 | |||
newPage(currentIsPLC_); | |||
int newPageIndex = currentPageIndex(); | |||
// 将项目应用到新页面 | |||
applyProjectToScene(project, newPageIndex); | |||
@@ -553,7 +534,7 @@ void MainWindow::saveProject() | |||
void MainWindow::connection() | |||
{ | |||
modbusManager->connectToDevice("COM1",QSerialPort::BaudRate(9600),QSerialPort::DataBits(8),QSerialPort::EvenParity,QSerialPort::StopBits(1)); | |||
modbusManager->startSimulation(1000); | |||
modbusManager->startSimulation(); | |||
} | |||
void MainWindow::disconnection() | |||
@@ -19,7 +19,7 @@ | |||
<rect> | |||
<x>20</x> | |||
<y>500</y> | |||
<width>951</width> | |||
<width>941</width> | |||
<height>111</height> | |||
</rect> | |||
</property> | |||
@@ -28,18 +28,18 @@ | |||
<property name="geometry"> | |||
<rect> | |||
<x>20</x> | |||
<y>30</y> | |||
<width>181</width> | |||
<height>461</height> | |||
<y>40</y> | |||
<width>91</width> | |||
<height>451</height> | |||
</rect> | |||
</property> | |||
</widget> | |||
<widget class="QStackedWidget" name="stackedWidget"> | |||
<property name="geometry"> | |||
<rect> | |||
<x>210</x> | |||
<x>110</x> | |||
<y>10</y> | |||
<width>761</width> | |||
<width>861</width> | |||
<height>481</height> | |||
</rect> | |||
</property> | |||
@@ -47,9 +47,9 @@ | |||
<widget class="QTabWidget" name="plc_tab_widget"> | |||
<property name="geometry"> | |||
<rect> | |||
<x>0</x> | |||
<x>10</x> | |||
<y>0</y> | |||
<width>761</width> | |||
<width>841</width> | |||
<height>481</height> | |||
</rect> | |||
</property> | |||
@@ -62,9 +62,9 @@ | |||
<widget class="QTabWidget" name="hmi_tab_widget"> | |||
<property name="geometry"> | |||
<rect> | |||
<x>0</x> | |||
<x>10</x> | |||
<y>0</y> | |||
<width>761</width> | |||
<width>841</width> | |||
<height>481</height> | |||
</rect> | |||
</property> | |||
@@ -77,8 +77,8 @@ | |||
<widget class="QPushButton" name="btn_insert"> | |||
<property name="geometry"> | |||
<rect> | |||
<x>30</x> | |||
<y>0</y> | |||
<x>20</x> | |||
<y>10</y> | |||
<width>93</width> | |||
<height>28</height> | |||
</rect> | |||
@@ -87,6 +87,28 @@ | |||
<string>插入</string> | |||
</property> | |||
</widget> | |||
<widget class="QWidget" name="layoutWidget"> | |||
<property name="geometry"> | |||
<rect> | |||
<x>0</x> | |||
<y>0</y> | |||
<width>2</width> | |||
<height>2</height> | |||
</rect> | |||
</property> | |||
<layout class="QHBoxLayout" name="horizontalLayout"/> | |||
</widget> | |||
<widget class="QWidget" name="layoutWidget"> | |||
<property name="geometry"> | |||
<rect> | |||
<x>0</x> | |||
<y>0</y> | |||
<width>2</width> | |||
<height>2</height> | |||
</rect> | |||
</property> | |||
<layout class="QVBoxLayout" name="verticalLayout_2"/> | |||
</widget> | |||
</widget> | |||
<widget class="QMenuBar" name="menubar"> | |||
<property name="geometry"> | |||
@@ -84,27 +84,49 @@ void MyGraphicsView::keyPressEvent(QKeyEvent *event) | |||
} | |||
}else if (event->matches(QKeySequence::Copy)) { | |||
// Ctrl+C | |||
clipboard_.items.clear(); | |||
clipboard_.connections.clear(); | |||
QList<QGraphicsItem*> selectedItems = scene()->selectedItems(); | |||
for (QGraphicsItem* item : selectedItems) { | |||
if (auto node = dynamic_cast<Item*>(item)) { | |||
clipboard_.input = node->itemType(); | |||
break; | |||
// 过滤出Item类型的元件 | |||
QList<Item*> items; | |||
for (QGraphicsItem* gi : selectedItems) { | |||
if (auto item = dynamic_cast<Item*>(gi)) { | |||
items.append(item); | |||
} | |||
} | |||
// 计算中心点 | |||
clipboard_.center = calculateItemsCenter(selectedItems); | |||
// 保存元件信息 | |||
for (Item* item : items) { | |||
CopiedItem copiedItem; | |||
copiedItem.type = item->itemType(); | |||
copiedItem.pos = item->pos() - clipboard_.center; | |||
clipboard_.items.append(copiedItem); | |||
} | |||
// 保存连接关系 | |||
for (QGraphicsItem* gi : selectedItems) { | |||
if (auto conn = dynamic_cast<Connection*>(gi)) { | |||
int fromIdx = items.indexOf(conn->from_); | |||
int toIdx = items.indexOf(conn->to_); | |||
if (fromIdx >= 0 && toIdx >= 0) { | |||
CopiedConnection copiedConn; | |||
copiedConn.fromIndex = fromIdx; | |||
copiedConn.toIndex = toIdx; | |||
copiedConn.fromType = conn->fromType_; | |||
copiedConn.toType = conn->toType_; | |||
clipboard_.connections.append(copiedConn); | |||
} | |||
} | |||
} | |||
} else if (event->matches(QKeySequence::Paste)) { | |||
// Ctrl+V | |||
if (!clipboard_.input.isEmpty()) { | |||
QPointF center = mapToScene(viewport()->rect().center()); | |||
Item* newItem = creatItem(clipboard_.input); | |||
newItem->setPos(center); | |||
connect(newItem, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy); | |||
connect(newItem, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete); | |||
connect(newItem, &Item::requestBindRegister, this, &MyGraphicsView::onItemRequestBindRegister); | |||
connect(newItem, &Item::requestCompare, this, &MyGraphicsView::onItemRequestCompare); | |||
connect(newItem, &Item::requestReset, this, &MyGraphicsView::onItemRequestReset); | |||
connect(newItem, &Item::requestSetON,this, &MyGraphicsView::onItemRequestSetON); | |||
scene()->addItem(newItem); | |||
} | |||
performPaste(mapToScene(viewport()->rect().center())); | |||
} else { | |||
QGraphicsView::keyPressEvent(event); | |||
} | |||
@@ -114,23 +136,21 @@ void MyGraphicsView::contextMenuEvent(QContextMenuEvent *event) | |||
{ | |||
QPointF scenePos = mapToScene(event->pos()); | |||
QList<QGraphicsItem*> itemsAt = scene()->items(scenePos); | |||
if (itemsAt.isEmpty() && !clipboard_.input.isEmpty()) { | |||
QMenu menu; | |||
// 如果有选中的元件,显示元件自己的菜单 | |||
if (!itemsAt.isEmpty()) { | |||
QGraphicsView::contextMenuEvent(event); | |||
return; | |||
} | |||
// 没有选中元件时,显示粘贴菜单(如果剪贴板有内容) | |||
QMenu menu; | |||
if (!clipboard_.items.isEmpty()) { | |||
QAction* pasteAct = menu.addAction("粘贴"); | |||
QAction* sel = menu.exec(event->globalPos()); | |||
if (sel == pasteAct) { | |||
Item* newItem = creatItem(clipboard_.input); | |||
newItem->setPos(scenePos); | |||
connect(newItem, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy); | |||
connect(newItem, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete); | |||
connect(newItem, &Item::requestBindRegister, this, &MyGraphicsView::onItemRequestBindRegister); | |||
connect(newItem, &Item::requestCompare, this, &MyGraphicsView::onItemRequestCompare); | |||
connect(newItem, &Item::requestReset, this, &MyGraphicsView::onItemRequestReset); | |||
connect(newItem, &Item::requestSetON,this, &MyGraphicsView::onItemRequestSetON); | |||
scene()->addItem(newItem); | |||
performPaste(scenePos); // 在鼠标位置粘贴 | |||
} | |||
} else { | |||
QGraphicsView::contextMenuEvent(event); // 让Item自己弹出菜单 | |||
} | |||
} | |||
@@ -156,9 +176,29 @@ Item* MyGraphicsView::anchorItemAt(const QPoint& viewPos, Item::AnchorType& anch | |||
return nullptr; | |||
} | |||
QPointF MyGraphicsView::calculateItemsCenter(const QList<QGraphicsItem *> &items) | |||
{ | |||
QPointF center(0, 0); | |||
int count = 0; | |||
for (QGraphicsItem* item : items) { | |||
if (auto node = dynamic_cast<Item*>(item)) { | |||
center += node->pos(); | |||
count++; | |||
} | |||
} | |||
return count > 0 ? center / count : QPointF(0, 0); | |||
} | |||
void MyGraphicsView::onItemRequestCopy(Item *item) | |||
{ | |||
clipboard_.input = item->itemType(); | |||
clipboard_.items.clear(); | |||
clipboard_.connections.clear(); | |||
CopiedItem copiedItem; | |||
copiedItem.type = item->itemType(); | |||
clipboard_.items.append(copiedItem); | |||
} | |||
void MyGraphicsView::onItemRequestDelete(Item *item) | |||
@@ -360,3 +400,47 @@ void MyGraphicsView::wheelEvent(QWheelEvent *event) | |||
QGraphicsView::wheelEvent(event); | |||
} | |||
} | |||
void MyGraphicsView::performPaste(const QPointF &pasteCenter) | |||
{ | |||
if (clipboard_.items.isEmpty()) return; | |||
QList<Item*> newItems; | |||
// 创建新元件 | |||
for (const CopiedItem& copiedItem : clipboard_.items) { | |||
Item* newItem = creatItem(copiedItem.type); | |||
if (!newItem) continue; | |||
// 设置位置(相对于粘贴中心) | |||
QPointF newPos = pasteCenter + copiedItem.pos; | |||
newItem->setPos(newPos); | |||
// 连接信号 | |||
connect(newItem, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy); | |||
connect(newItem, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete); | |||
connect(newItem, &Item::requestBindRegister, this, &MyGraphicsView::onItemRequestBindRegister); | |||
connect(newItem, &Item::requestCompare, this, &MyGraphicsView::onItemRequestCompare); | |||
connect(newItem, &Item::requestReset, this, &MyGraphicsView::onItemRequestReset); | |||
connect(newItem, &Item::requestSetON, this, &MyGraphicsView::onItemRequestSetON); | |||
scene()->addItem(newItem); | |||
newItems.append(newItem); | |||
} | |||
// 创建新连接 | |||
for (const CopiedConnection& copiedConn : clipboard_.connections) { | |||
if (copiedConn.fromIndex < newItems.size() && copiedConn.toIndex < newItems.size()) { | |||
Connection* conn = new Connection( | |||
newItems[copiedConn.fromIndex], copiedConn.fromType, | |||
newItems[copiedConn.toIndex], copiedConn.toType); | |||
scene()->addItem(conn); | |||
} | |||
} | |||
// 选中新粘贴的元件 | |||
scene()->clearSelection(); | |||
for (Item* item : newItems) { | |||
item->setSelected(true); | |||
} | |||
} |
@@ -26,12 +26,21 @@ public: | |||
void mouseMoveEvent(QMouseEvent *event) override; | |||
void mouseReleaseEvent(QMouseEvent *event) override; | |||
void wheelEvent(QWheelEvent *event) override; | |||
void performPaste(const QPointF& pasteCenter); | |||
signals: | |||
void itemBoundToRegister(Item*, QString); | |||
void itemResetRegister(Item*, QString); | |||
void itemSetON(QString, quint16); | |||
public slots: | |||
void onItemRequestCopy(Item*); | |||
void onItemRequestDelete(Item*); | |||
void onItemRequestBindRegister(Item*); | |||
void onItemRequestCompare(Item*); | |||
void onItemRequestReset(Item*); | |||
void onItemRequestSetON(Item*, bool isON); | |||
private: | |||
Item* anchorItemAt(const QPoint& viewPos, Item::AnchorType& anchorType); | |||
@@ -40,18 +49,29 @@ private: | |||
Item::AnchorType startAnchor_; | |||
QGraphicsLineItem* tempLine_ = nullptr; | |||
// struct ClipInfo { | |||
// QString input; | |||
// }; | |||
struct CopiedItem { | |||
QString type; | |||
QPointF pos; | |||
}; | |||
struct CopiedConnection { | |||
int fromIndex; | |||
int toIndex; | |||
Item::AnchorType fromType; | |||
Item::AnchorType toType; | |||
}; | |||
struct ClipInfo { | |||
QString input; | |||
QList<CopiedItem> items; | |||
QList<CopiedConnection> connections; | |||
QPointF center; | |||
}; | |||
static ClipInfo clipboard_; | |||
QPointF calculateItemsCenter(const QList<QGraphicsItem*>& items); | |||
public slots: | |||
void onItemRequestCopy(Item*); | |||
void onItemRequestDelete(Item*); | |||
void onItemRequestBindRegister(Item*); | |||
void onItemRequestCompare(Item*); | |||
void onItemRequestReset(Item*); | |||
void onItemRequestSetON(Item*, bool isON); | |||
}; | |||
#endif // MYGRAPHICSVIEW_H |