From bd342a9c346017578b3f16c84788d7ed30513322 Mon Sep 17 00:00:00 2001 From: email <15737449156@163.com> Date: Thu, 7 Aug 2025 21:31:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=B0=E5=BB=BA=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=8A=9F=E8=83=BD=EF=BC=8CHMI=E6=96=B0=E5=BB=BA?= =?UTF-8?q?=E4=B8=8EPLC=E6=96=B0=E5=BB=BA=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IntegratedPlatform.pro.user | 2 +- IntegratedPlatform/hmiwidget.h | 33 +- IntegratedPlatform/mainwindow.cpp | 74 ++- IntegratedPlatform/mainwindow.h | 7 +- untitled/document.cpp | 408 +++++++++++++ untitled/document.h | 92 +++ untitled/graphicsitems.cpp | 26 + untitled/graphicsitems.h | 143 +++++ untitled/hmieditor.cpp | 73 +++ untitled/hmieditor.h | 45 ++ untitled/images/T-常开触点-01.png | Bin 0 -> 1471 bytes untitled/images/T-常闭触点-01-01.png | Bin 0 -> 5305 bytes untitled/images/大于等于.png | Bin 0 -> 5931 bytes untitled/images/按钮.png | Bin 0 -> 4172 bytes untitled/images/灯泡.png | Bin 0 -> 8642 bytes untitled/images/线-圈-圈.png | Bin 0 -> 7606 bytes untitled/main.cpp | 15 + untitled/mainwindow.cpp | 215 +++++++ untitled/mainwindow.h | 40 ++ untitled/mainwindow.ui | 22 + untitled/plceditor.cpp | 72 +++ untitled/plceditor.h | 40 ++ untitled/untitled.pro | 35 ++ untitled/untitled.pro.user | 563 ++++++++++++++++++ 24 files changed, 1899 insertions(+), 6 deletions(-) create mode 100644 untitled/document.cpp create mode 100644 untitled/document.h create mode 100644 untitled/graphicsitems.cpp create mode 100644 untitled/graphicsitems.h create mode 100644 untitled/hmieditor.cpp create mode 100644 untitled/hmieditor.h create mode 100644 untitled/images/T-常开触点-01.png create mode 100644 untitled/images/T-常闭触点-01-01.png create mode 100644 untitled/images/大于等于.png create mode 100644 untitled/images/按钮.png create mode 100644 untitled/images/灯泡.png create mode 100644 untitled/images/线-圈-圈.png create mode 100644 untitled/main.cpp create mode 100644 untitled/mainwindow.cpp create mode 100644 untitled/mainwindow.h create mode 100644 untitled/mainwindow.ui create mode 100644 untitled/plceditor.cpp create mode 100644 untitled/plceditor.h create mode 100644 untitled/untitled.pro create mode 100644 untitled/untitled.pro.user diff --git a/IntegratedPlatform/IntegratedPlatform.pro.user b/IntegratedPlatform/IntegratedPlatform.pro.user index 7a0b1c7..13aadfc 100644 --- a/IntegratedPlatform/IntegratedPlatform.pro.user +++ b/IntegratedPlatform/IntegratedPlatform.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/IntegratedPlatform/hmiwidget.h b/IntegratedPlatform/hmiwidget.h index 52da599..91b5ba5 100644 --- a/IntegratedPlatform/hmiwidget.h +++ b/IntegratedPlatform/hmiwidget.h @@ -30,12 +30,43 @@ protected: class ResizableRectangle : public ResizableShape { public: ResizableRectangle(qreal x, qreal y, qreal w, qreal h); -}; + QColor pressedColor() const { return m_pressedColor; } + void setPressedColor(const QColor& color) { m_pressedColor = color; } + + QColor releasedColor() const { return m_releasedColor; } + void setReleasedColor(const QColor& color) { m_releasedColor = color; } + + QString name() const { return m_name; } + void setName(const QString& name) { m_name = name; } +private: + QColor m_pressedColor = Qt::green; // 按钮按下时的颜色 + QColor m_releasedColor = Qt::yellow; // 按钮松开时的颜色 + QString m_name = "按钮"; // 默认名称 +}; +class NamedItem { +public: + virtual QString name() const = 0; + virtual void setName(const QString& name) = 0; + virtual ~NamedItem() = default; +}; // 可调整大小的椭圆 class ResizableEllipse : public ResizableShape { public: ResizableEllipse(qreal x, qreal y, qreal w, qreal h); + QColor onColor() const { return m_onColor; } + void setOnColor(const QColor& color) { m_onColor = color; } + + QColor offColor() const { return m_offColor; } + void setOffColor(const QColor& color) { m_offColor = color; } + + QString name() const { return m_name; } + void setName(const QString& name) { m_name = name; } + +private: + QColor m_onColor = Qt::green; // 默认开状态颜色 + QColor m_offColor = Qt::red; // 默认关状态颜色 + QString m_name = "指示灯"; // 默认名称 }; #endif // HMIWIDGET_H diff --git a/IntegratedPlatform/mainwindow.cpp b/IntegratedPlatform/mainwindow.cpp index fcd025c..c3b5c0e 100644 --- a/IntegratedPlatform/mainwindow.cpp +++ b/IntegratedPlatform/mainwindow.cpp @@ -5,6 +5,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) @@ -29,7 +35,6 @@ MainWindow::MainWindow(QWidget *parent) ui->actiondelete->setShortcut(QKeySequence::Delete);//设置快捷键 ui->actioncopy->setShortcut(QKeySequence::Copy); ui->actionpaste->setShortcut(QKeySequence::Paste); - ui->treeWidget->topLevelItem(0)->setIcon(0, QIcon("../two/IntegratedPlatform/images/T-常开触点-01.png")); ui->treeWidget->topLevelItem(1)->setIcon(0, QIcon("../two/IntegratedPlatform/images/T-常闭触点-01-01.png")); ui->treeWidget->topLevelItem(2)->setIcon(0, QIcon("../two/IntegratedPlatform/images/大于等于.png")); @@ -255,7 +260,71 @@ void MainWindow::onActionPasteTriggered() } } } +//属性功能 +void MainWindow::onActionPropertiesTriggered() +{ + QList selectedItems = scene->selectedItems(); + if (selectedItems.isEmpty()) return; + QGraphicsItem* selectedItem = selectedItems.first(); + QDialog dialog(this);// 创建对话框 + dialog.setWindowTitle("属性设置"); + QFormLayout* formLayout = new QFormLayout(&dialog); + + QColor tempOnColor = Qt::green;//临时变量存储选择的颜色 + QColor tempOffColor = Qt::red;//临时变量存储选择的颜色 + + QLineEdit* nameEdit = new QLineEdit(&dialog);//对象输入框 + if (auto namedItem = dynamic_cast(selectedItem)) + { + nameEdit->setText(namedItem->name()); + } + + // 开状态颜色选择 + QPushButton* onColorBtn = new QPushButton("", &dialog); + onColorBtn->setStyleSheet("background-color:" + tempOnColor.name());; + connect(onColorBtn, &QPushButton::clicked, [&]() + { + QColor newColor = QColorDialog::getColor(tempOnColor, &dialog, "选择ON状态颜色"); + tempOnColor = newColor; + onColorBtn->setStyleSheet("background-color:" + tempOnColor.name());; + }); + // 关状态颜色选择 + QPushButton* offColorBtn = new QPushButton("", &dialog); + offColorBtn->setStyleSheet("background-color:"+tempOffColor.name()); + connect(offColorBtn, &QPushButton::clicked, [&]() + { + QColor newColor = QColorDialog::getColor(tempOffColor, &dialog, "选择OFF状态颜色"); + tempOffColor = newColor; + offColorBtn->setStyleSheet("background-color:"+tempOffColor.name()); + }); + formLayout->addRow("对象:", nameEdit); + formLayout->addRow("ON颜色:", onColorBtn); + formLayout->addRow("OFF颜色:", offColorBtn); + QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog); + formLayout->addRow(buttonBox); + connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + if (dialog.exec() == QDialog::Accepted)//显示对话框并应用更改 + { + QString newName = nameEdit->text();// 应用新属性到图形项 + if (auto ellipse = dynamic_cast(selectedItem)) + { + ellipse->setOnColor(tempOnColor); + ellipse->setOffColor(tempOffColor); + ellipse->setName(newName); + ellipse->setBrush(tempOnColor); // 应用开状态颜色 + } + else if (auto rect = dynamic_cast(selectedItem)) + { + rect->setPressedColor(tempOnColor); + rect->setReleasedColor(tempOffColor); + rect->setName(newName); + rect->setBrush(tempOffColor); // 应用关状态颜色 + } + selectedItem->update(); + } +} void MainWindow::createEllipse(const QPointF &pos) { ResizableEllipse *ellipse = new ResizableEllipse(pos.x(), pos.y(), 50, 50); @@ -305,9 +374,12 @@ void MainWindow::showContextMenu(QPoint pos) QAction* deleteAction = contextMenu.addAction("删除"); QAction* onAction = contextMenu.addAction("置为ON"); QAction* offAction = contextMenu.addAction("置为OFF"); + pasteAction->setEnabled(!copiedItemsData.isEmpty()); // 连接动作到现有功能 connect(deleteAction, &QAction::triggered, this, &MainWindow::onActionDeleteTriggered); connect(copyAction, &QAction::triggered, this, &MainWindow::onActionCopyTriggered); connect(pasteAction, &QAction::triggered, this, &MainWindow::onActionPasteTriggered); + connect(propertiesAction, &QAction::triggered, this, &MainWindow::onActionPropertiesTriggered); + contextMenu.exec(pos+QPoint(10, 10)); } diff --git a/IntegratedPlatform/mainwindow.h b/IntegratedPlatform/mainwindow.h index d68e16c..d294672 100644 --- a/IntegratedPlatform/mainwindow.h +++ b/IntegratedPlatform/mainwindow.h @@ -22,9 +22,10 @@ private slots: void onActionDeleteTriggered(); // 添加删除功能的槽函数 void onActionCopyTriggered();//复制功能槽函数 void onActionPasteTriggered();//粘贴功能槽函数 - void createEllipse(const QPointF &pos); // 创建指示灯(圆形) - void createRectangle(const QPointF &pos); // 创建按钮(矩形) - void resetDrawFlags(); // 重置绘制标志 + void onActionPropertiesTriggered();//属性页 + void createEllipse(const QPointF &pos);// 创建指示灯(圆形) + void createRectangle(const QPointF &pos);// 创建按钮(矩形) + void resetDrawFlags();// 重置绘制标志 void showContextMenu(QPoint pos); protected: bool eventFilter(QObject *obj, QEvent *event) override; diff --git a/untitled/document.cpp b/untitled/document.cpp new file mode 100644 index 0000000..3bbb386 --- /dev/null +++ b/untitled/document.cpp @@ -0,0 +1,408 @@ +#include "document.h" +#include "graphicsitems.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +HMIDocument::HMIDocument(QWidget *parent) + : BaseDocument(HMI, parent), + m_title("未命名HMI"), + m_canDrawEllipse(false), + m_canDrawRectangle(false), + m_lastPastePos(0, 0) +{ + // 创建绘图场景 + m_scene = new QGraphicsScene(this); + m_scene->setSceneRect(0, 0, 800, 600); + m_scene->setBackgroundBrush(QBrush(Qt::lightGray)); + + // 创建视图 + m_view = new QGraphicsView(m_scene, this); + m_view->setRenderHint(QPainter::Antialiasing); + m_view->setDragMode(QGraphicsView::RubberBandDrag); + m_view->setAcceptDrops(true); + m_view->viewport()->installEventFilter(this); + + // 布局(让视图占满文档区域) + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_view); + setLayout(layout); +} + +HMIDocument::~HMIDocument() +{ +} + +// 事件过滤器(处理绘图、拖拽等事件) +bool HMIDocument::eventFilter(QObject *obj, QEvent *event) +{ + if (obj != m_view->viewport()) { + return QWidget::eventFilter(obj, event); + } + + // 鼠标按下事件(绘制图形) + if (event->type() == QEvent::MouseButtonPress) + { + QMouseEvent *mouseEvent = static_cast(event); + + if (mouseEvent->button() == Qt::RightButton) + { + // 右键菜单 + showContextMenu(mouseEvent->globalPos()); + return true; + } + if (mouseEvent->button() == Qt::LeftButton) + { + QPointF scenePos = m_view->mapToScene(mouseEvent->pos()); + QGraphicsItem *item = m_scene->itemAt(scenePos, m_view->transform()); + + // 如果点击空白区域且有绘制标志,创建图形 + if (!item) { + if (m_canDrawEllipse) { + createEllipse(scenePos); + resetDrawFlags(); + } else if (m_canDrawRectangle) { + createRectangle(scenePos); + resetDrawFlags(); + } + m_scene->clearSelection(); // 清除选择 + return true; + } + } + } + + // 拖拽事件(从工具栏拖拽绘制) + if (event->type() == QEvent::DragEnter) { + QDragEnterEvent *dragEvent = static_cast(event); + dragEvent->acceptProposedAction(); + return true; + } + if (event->type() == QEvent::DragMove) { + static_cast(event)->acceptProposedAction(); + return true; + } + if (event->type() == QEvent::Drop) { + QDropEvent *dropEvent = static_cast(event); + const QMimeData *mimeData = dropEvent->mimeData(); // 显式获取mimeData + QPointF scenePos = m_view->mapToScene(dropEvent->pos()); + + // 使用QMimeData + if (mimeData && mimeData->hasText()) { + QString type = mimeData->text(); + if (type == "指示灯") { + createEllipse(scenePos); + } else if (type == "按钮") { + createRectangle(scenePos); + } + } + dropEvent->acceptProposedAction(); + return true; + } + + return QWidget::eventFilter(obj, event); +} + +// 创建椭圆(指示灯) +void HMIDocument::createEllipse(const QPointF &pos) +{ + ResizableEllipse *ellipse = new ResizableEllipse(pos.x(), pos.y(), 50, 50); + ellipse->setBrush(QBrush(Qt::red)); + ellipse->setPen(QPen(Qt::black, 1)); + m_scene->addItem(ellipse); + ellipse->setSelected(true); +} + +// 创建矩形(按钮) +void HMIDocument::createRectangle(const QPointF &pos) +{ + ResizableRectangle *rect = new ResizableRectangle(pos.x(), pos.y(), 100, 50); + rect->setBrush(QBrush(Qt::yellow)); + rect->setPen(QPen(Qt::black, 1)); + m_scene->addItem(rect); + rect->setSelected(true); +} + +// 重置绘制标志 +void HMIDocument::resetDrawFlags() +{ + m_canDrawEllipse = false; + m_canDrawRectangle = false; +} + +// 右键菜单 +void HMIDocument::showContextMenu(QPoint globalPos) +{ + QPoint viewportPos = m_view->mapFromGlobal(globalPos); + QPointF scenePos = m_view->mapToScene(viewportPos); + QGraphicsItem *clickedItem = m_scene->itemAt(scenePos, m_view->transform()); + + QMenu menu(this); + + if (!clickedItem) { + // 空白区域:仅显示粘贴 + QAction *pasteAction = menu.addAction("粘贴"); + pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); + connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); + } else { + // 选中图形:显示完整菜单 + QAction *propAction = menu.addAction("属性"); + QAction *copyAction = menu.addAction("复制"); + QAction *pasteAction = menu.addAction("粘贴"); + QAction *deleteAction = menu.addAction("删除"); + QAction *onAction = menu.addAction("置为ON"); + QAction *offAction = menu.addAction("置为OFF"); + + pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); + + connect(propAction, &QAction::triggered, this, &HMIDocument::showItemProperties); + connect(copyAction, &QAction::triggered, this, &HMIDocument::copySelectedItems); + connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); + connect(deleteAction, &QAction::triggered, this, &HMIDocument::deleteSelectedItems); + + // ON/OFF动作(针对指示灯和按钮) + connect(onAction, &QAction::triggered, [=]() { + if (auto ellipse = dynamic_cast(clickedItem)) { + ellipse->setBrush(ellipse->onColor()); + } else if (auto rect = dynamic_cast(clickedItem)) { + rect->setBrush(rect->pressedColor()); + } + }); + connect(offAction, &QAction::triggered, [=]() { + if (auto ellipse = dynamic_cast(clickedItem)) { + ellipse->setBrush(ellipse->offColor()); + } else if (auto rect = dynamic_cast(clickedItem)) { + rect->setBrush(rect->releasedColor()); + } + }); + } + + menu.exec(globalPos + QPoint(10, 10)); +} + +// 复制选中项 +void HMIDocument::copySelectedItems() +{ + m_copiedItemsData.clear(); + QList selectedItems = m_scene->selectedItems(); + if (selectedItems.isEmpty()) return; + + foreach (QGraphicsItem *item, selectedItems) { + m_copiedItemsData.append(serializeItem(item)); + } + m_lastPastePos = selectedItems.first()->pos(); +} + +// 粘贴项 +void HMIDocument::pasteItems() +{ + if (m_copiedItemsData.isEmpty()) return; + + QPointF offset(20, 20); + m_lastPastePos += offset; + m_scene->clearSelection(); + + foreach (const QByteArray &itemData, m_copiedItemsData) { + QGraphicsItem *newItem = deserializeItem(itemData); + if (newItem) { + m_scene->addItem(newItem); + newItem->setPos(m_lastPastePos); + newItem->setSelected(true); + } + } +} + +// 删除选中项 +void HMIDocument::deleteSelectedItems() +{ + QList selectedItems = m_scene->selectedItems(); + foreach (QGraphicsItem *item, selectedItems) { + m_scene->removeItem(item); + delete item; + } +} + +// 显示属性对话框 +void HMIDocument::showItemProperties() +{ + QList selectedItems = m_scene->selectedItems(); + if (selectedItems.isEmpty()) return; + QGraphicsItem *item = selectedItems.first(); + + // 将QGraphicsItem转换为NamedItem + NamedItem *namedItem = dynamic_cast(item); + if (!namedItem) return; + + QDialog dialog(this); + dialog.setWindowTitle("属性设置"); + QFormLayout *form = new QFormLayout(&dialog); + + // 名称输入 - 使用NamedItem接口 + QLineEdit *nameEdit = new QLineEdit(namedItem->name()); + + // 颜色选择(根据图形类型) + QColor tempColor1, tempColor2; + QPushButton *colorBtn1 = new QPushButton; + QPushButton *colorBtn2 = new QPushButton; + + if (auto ellipse = dynamic_cast(item)) { + tempColor1 = ellipse->onColor(); + tempColor2 = ellipse->offColor(); + form->addRow("ON颜色:", colorBtn1); + form->addRow("OFF颜色:", colorBtn2); + } else if (auto rect = dynamic_cast(item)) { + tempColor1 = rect->pressedColor(); + tempColor2 = rect->releasedColor(); + form->addRow("按下颜色:", colorBtn1); + form->addRow("释放颜色:", colorBtn2); + } + + // 初始化颜色按钮 + colorBtn1->setStyleSheet("background-color:" + tempColor1.name()); + colorBtn2->setStyleSheet("background-color:" + tempColor2.name()); + + // 颜色选择对话框 + connect(colorBtn1, &QPushButton::clicked, [&]() { + QColor c = QColorDialog::getColor(tempColor1, &dialog); + if (c.isValid()) { + tempColor1 = c; + colorBtn1->setStyleSheet("background-color:" + c.name()); + } + }); + connect(colorBtn2, &QPushButton::clicked, [&]() { + QColor c = QColorDialog::getColor(tempColor2, &dialog); + if (c.isValid()) { + tempColor2 = c; + colorBtn2->setStyleSheet("background-color:" + c.name()); + } + }); + + // 布局组装 + form->addRow("名称:", nameEdit); + QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + form->addRow(btnBox); + connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(btnBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + // 应用属性 + if (dialog.exec() == QDialog::Accepted) { + // 使用NamedItem接口设置名称 + namedItem->setName(nameEdit->text()); + + if (auto ellipse = dynamic_cast(item)) { + ellipse->setOnColor(tempColor1); + ellipse->setOffColor(tempColor2); + } else if (auto rect = dynamic_cast(item)) { + rect->setPressedColor(tempColor1); + rect->setReleasedColor(tempColor2); + } + item->update(); + } +} + +// 序列化图形项(用于复制粘贴) +QByteArray HMIDocument::serializeItem(QGraphicsItem *item) +{ + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + + // 基础信息 + stream << item->type(); + stream << item->pos(); + stream << item->flags(); + stream << item->transform(); + stream << item->isVisible(); + + // 形状信息 + if (auto shape = dynamic_cast(item)) { + stream << shape->pen(); + stream << shape->brush(); + stream << shape->boundingRect(); + } + + // 具体图形信息 + if (auto rect = dynamic_cast(item)) { + stream << rect->rect(); + stream << rect->pressedColor(); + stream << rect->releasedColor(); + stream << rect->name(); + } else if (auto ellipse = dynamic_cast(item)) { + stream << ellipse->rect(); + stream << ellipse->onColor(); + stream << ellipse->offColor(); + stream << ellipse->name(); + } + + return data; +} + +// 反序列化图形项(用于粘贴) +QGraphicsItem *HMIDocument::deserializeItem(const QByteArray &data) +{ + QDataStream stream(data); + int type; + QPointF pos; + QGraphicsItem::GraphicsItemFlags flags; + QTransform transform; + bool visible; + + stream >> type >> pos >> flags >> transform >> visible; + + QGraphicsItem *item = nullptr; + if (type == QGraphicsRectItem::Type) { + QPen pen; + QBrush brush; + QRectF boundRect; + QRectF rect; + QColor pressedColor, releasedColor; + QString name; + + stream >> pen >> brush >> boundRect >> rect >> pressedColor >> releasedColor >> name; + + ResizableRectangle *rectItem = new ResizableRectangle(0, 0, rect.width(), rect.height()); + rectItem->setPen(pen); + rectItem->setBrush(brush); + rectItem->setPressedColor(pressedColor); + rectItem->setReleasedColor(releasedColor); + rectItem->setName(name); + item = rectItem; + } else if (type == QGraphicsEllipseItem::Type) { + QPen pen; + QBrush brush; + QRectF boundRect; + QRectF rect; + QColor onColor, offColor; + QString name; + + stream >> pen >> brush >> boundRect >> rect >> onColor >> offColor >> name; + + ResizableEllipse *ellipseItem = new ResizableEllipse(0, 0, rect.width(), rect.height()); + ellipseItem->setPen(pen); + ellipseItem->setBrush(brush); + ellipseItem->setOnColor(onColor); + ellipseItem->setOffColor(offColor); + ellipseItem->setName(name); + item = ellipseItem; + } + + if (item) { + item->setFlags(flags); + item->setTransform(transform); + item->setVisible(visible); + } + + return item; +} diff --git a/untitled/document.h b/untitled/document.h new file mode 100644 index 0000000..8c0a012 --- /dev/null +++ b/untitled/document.h @@ -0,0 +1,92 @@ +#ifndef DOCUMENT_H +#define DOCUMENT_H + +#include +#include +#include +#include +#include +#include + +// 前向声明 +class ResizableRectangle; +class ResizableEllipse; +class NamedItem; + +// 文档基类 +class BaseDocument : public QWidget +{ + Q_OBJECT +public: + enum DocumentType { HMI, PLC }; + BaseDocument(DocumentType type, QWidget *parent = nullptr) + : QWidget(parent), m_type(type) {} + virtual ~BaseDocument() = default; + DocumentType type() const { return m_type; } + virtual QString title() const = 0; + +protected: + DocumentType m_type; +}; + +// HMI文档类(包含绘图相关核心功能) +class HMIDocument : public BaseDocument +{ + Q_OBJECT +public: + HMIDocument(QWidget *parent = nullptr); + ~HMIDocument() override; + + QString title() const override { return m_title; } + void setTitle(const QString &title) { m_title = title; } + QGraphicsView *view() const { return m_view; } + QGraphicsScene *scene() const { return m_scene; } + + // 绘图控制 + void setDrawEllipse(bool enable) { m_canDrawEllipse = enable; } + void setDrawRectangle(bool enable) { m_canDrawRectangle = enable; } + + // 编辑功能 + void copySelectedItems(); + void pasteItems(); + void deleteSelectedItems(); + void showItemProperties(); + +signals: + void titleChanged(const QString &title); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + // 绘图相关 + void createEllipse(const QPointF &pos); + void createRectangle(const QPointF &pos); + void resetDrawFlags(); + void showContextMenu(QPoint globalPos); + + // 图形项序列化/反序列化 + QByteArray serializeItem(QGraphicsItem *item); + QGraphicsItem *deserializeItem(const QByteArray &data); + + QString m_title; + QGraphicsScene *m_scene; + QGraphicsView *m_view; + bool m_canDrawEllipse; + bool m_canDrawRectangle; + + // 复制粘贴相关 + QList m_copiedItemsData; + QPointF m_lastPastePos; +}; + +// PLC文档类(预留,暂为空实现) +class PLCDocument : public BaseDocument +{ + Q_OBJECT +public: + PLCDocument(QWidget *parent = nullptr) : BaseDocument(PLC, parent) {} + QString title() const override { return "PLC文档"; } +}; + +#endif // DOCUMENT_H diff --git a/untitled/graphicsitems.cpp b/untitled/graphicsitems.cpp new file mode 100644 index 0000000..28f7e29 --- /dev/null +++ b/untitled/graphicsitems.cpp @@ -0,0 +1,26 @@ +#include "graphicsitems.h" +#include + +// 可调整大小的矩形(按钮)实现 +ResizableRectangle::ResizableRectangle(qreal x, qreal y, qreal w, qreal h) + : ResizableShape(x, y, w, h) +{ + m_name = "按钮"; + m_pressedColor = Qt::green; + m_releasedColor = Qt::yellow; + this->setBrush(m_releasedColor); +} + +// 可调整大小的椭圆(指示灯)实现 +ResizableEllipse::ResizableEllipse(qreal x, qreal y, qreal w, qreal h) + : ResizableShape(x, y, w, h) +{ + m_name = "指示灯"; + m_onColor = Qt::green; + m_offColor = Qt::red; + this->setBrush(m_onColor); +} + +// 显式实例化模板类 +template class ResizableShape; +template class ResizableShape; diff --git a/untitled/graphicsitems.h b/untitled/graphicsitems.h new file mode 100644 index 0000000..6e5fd61 --- /dev/null +++ b/untitled/graphicsitems.h @@ -0,0 +1,143 @@ +#ifndef GRAPHICSITEMS_H +#define GRAPHICSITEMS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 命名项接口(用于属性设置) +class NamedItem +{ +public: + virtual QString name() const = 0; + virtual void setName(const QString &name) = 0; + virtual ~NamedItem() = default; +}; + +// 可调整大小的图形基类(模板) +template +class ResizableShape : public BaseShape, public NamedItem +{ +protected: + bool m_resizing; + QString m_name; + Qt::CursorShape m_currentCursor; + +public: + ResizableShape(qreal x, qreal y, qreal w, qreal h) + : BaseShape(x, y, w, h), m_resizing(false), m_currentCursor(Qt::SizeAllCursor) + { + this->setFlag(QGraphicsItem::ItemIsMovable, true); + this->setFlag(QGraphicsItem::ItemIsSelectable, true); + this->setAcceptHoverEvents(true); + } + + // 事件处理(调整大小逻辑) + void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override + { + if (isInResizeArea(event->pos())) { + m_currentCursor = Qt::SizeFDiagCursor; + } else { + m_currentCursor = Qt::SizeAllCursor; + } + BaseShape::hoverMoveEvent(event); + } + + void mousePressEvent(QGraphicsSceneMouseEvent *event) override + { + if (isInResizeArea(event->pos())) { + m_resizing = true; + m_currentCursor = Qt::SizeFDiagCursor; + } else { + m_resizing = false; + m_currentCursor = Qt::SizeAllCursor; + BaseShape::mousePressEvent(event); + } + } + + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override + { + if (m_resizing) { + QRectF newRect = this->rect(); + newRect.setBottomRight(event->pos()); + this->setRect(newRect); + } else { + BaseShape::mouseMoveEvent(event); + } + } + + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override + { + m_resizing = false; + m_currentCursor = Qt::SizeAllCursor; + BaseShape::mouseReleaseEvent(event); + } + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override + { + BaseShape::paint(painter, option, widget); + } + + // 命名项接口实现 + QString name() const override { return m_name; } + void setName(const QString &name) override { m_name = name; } + + // 光标获取方法 + Qt::CursorShape currentCursor() const { return m_currentCursor; } + +protected: + bool isInResizeArea(const QPointF &pos) const + { + QRectF r = this->rect(); + QRectF resizeArea(r.bottomRight() - QPointF(20, 20), r.bottomRight()); + return resizeArea.contains(pos); + } +}; + +// 可调整大小的矩形(按钮) +class ResizableRectangle : public ResizableShape +{ +public: + ResizableRectangle(qreal x, qreal y, qreal w, qreal h); + + QColor pressedColor() const { return m_pressedColor; } + void setPressedColor(const QColor &color) { m_pressedColor = color; } + + QColor releasedColor() const { return m_releasedColor; } + void setReleasedColor(const QColor &color) { + m_releasedColor = color; + this->setBrush(m_releasedColor); // 更新显示 + } + +private: + QColor m_pressedColor; // 按下颜色 + QColor m_releasedColor; // 释放颜色 +}; + +// 可调整大小的椭圆(指示灯) +class ResizableEllipse : public ResizableShape +{ +public: + ResizableEllipse(qreal x, qreal y, qreal w, qreal h); + + QColor onColor() const { return m_onColor; } + void setOnColor(const QColor &color) { + m_onColor = color; + this->setBrush(m_onColor); // 更新显示 + } + + QColor offColor() const { return m_offColor; } + void setOffColor(const QColor &color) { m_offColor = color; } + +private: + QColor m_onColor; // 开状态颜色 + QColor m_offColor; // 关状态颜色 +}; + +#endif // GRAPHICSITEMS_H diff --git a/untitled/hmieditor.cpp b/untitled/hmieditor.cpp new file mode 100644 index 0000000..abee2c5 --- /dev/null +++ b/untitled/hmieditor.cpp @@ -0,0 +1,73 @@ +#include "hmieditor.h" +#include +#include + +void Shape::draw(QPainter& painter) const { + painter.setPen(QPen(Qt::blue, 2)); + painter.setBrush(QBrush(Qt::lightGray, Qt::DiagCrossPattern)); + + if (type == Circle) { + painter.drawEllipse(rect); + } else if (type == Rectangle) { + painter.drawRect(rect); + } +} +HMIEditor::HMIEditor(QWidget *parent) + : QWidget(parent), currentShapeType(Shape::Rectangle), isDrawing(false) +{ + setMinimumSize(400, 300); + setStyleSheet("background-color: white;"); +} + +void HMIEditor::onDrawCircle() { + currentShapeType = Shape::Circle; +} + +void HMIEditor::onDrawRect() { + currentShapeType = Shape::Rectangle; +} + +void HMIEditor::paintEvent(QPaintEvent *event) { + Q_UNUSED(event); + QPainter painter(this); + + // 绘制所有已完成的图形 + for (const auto& shape : shapes) { + shape.draw(painter); + } + + // 绘制正在绘制的图形 + if (isDrawing) { + painter.setPen(QPen(Qt::red, 2, Qt::DashLine)); + if (currentShapeType == Shape::Circle) { + painter.drawEllipse(currentDrawingRect); + } else { + painter.drawRect(currentDrawingRect); + } + } +} + +void HMIEditor::mousePressEvent(QMouseEvent *event) { + if (event->button() == Qt::LeftButton) { + isDrawing = true; + currentDrawingRect.setTopLeft(event->pos()); + currentDrawingRect.setBottomRight(event->pos()); + update(); + } +} + +void HMIEditor::mouseReleaseEvent(QMouseEvent *event) { + if (event->button() == Qt::LeftButton && isDrawing) { + isDrawing = false; + currentDrawingRect.setBottomRight(event->pos()); + shapes.emplace_back(currentShapeType, currentDrawingRect); + update(); + } +} + +void HMIEditor::mouseMoveEvent(QMouseEvent *event) { + if (isDrawing) { + currentDrawingRect.setBottomRight(event->pos()); + update(); + } +} diff --git a/untitled/hmieditor.h b/untitled/hmieditor.h new file mode 100644 index 0000000..34a9589 --- /dev/null +++ b/untitled/hmieditor.h @@ -0,0 +1,45 @@ +#ifndef HMIEDITOR_H +#define HMIEDITOR_H + +#include +#include +#include +#include + +// 图形元素基类 +class Shape { +public: + enum Type { Circle, Rectangle }; + + Shape(Type type, const QRect& rect) : type(type), rect(rect) {} + virtual void draw(QPainter& painter) const; + + Type type; + QRect rect; +}; + +class HMIEditor : public QWidget +{ + Q_OBJECT + +public: + explicit HMIEditor(QWidget *parent = nullptr); + +public slots: + void onDrawCircle(); + void onDrawRect(); + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + +private: + std::vector shapes; + Shape::Type currentShapeType; + bool isDrawing; + QRect currentDrawingRect; +}; + +#endif // HMIEDITOR_H diff --git a/untitled/images/T-常开触点-01.png b/untitled/images/T-常开触点-01.png new file mode 100644 index 0000000000000000000000000000000000000000..91b4566e6b5b57a73ac68ffb2437ea7e45ebe8b2 GIT binary patch literal 1471 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-ahlfwj`p#WAEJ z?(OA+zRZpcERJtPd>A@B6trDfQdk!XFc{uQDfqP5UhZmLzUi))H|=8M?DPHEpO@_2 z_y2F*Uzxifc^lnTKK%LgvCMk@e*XLRj&g-j|6>FcJRAd$=`c2(@mn3?*r34bD!G}3 z!^nDV5-Veqh)C)gA%zofwseWIa0CTRG4yUw*nC+lY*f=|K#Zn{(X25N%7}M@PvoyG XnWz*wZ5ubRL}u`G^>bP0l+XkKI@;}_ literal 0 HcmV?d00001 diff --git a/untitled/images/T-常闭触点-01-01.png b/untitled/images/T-常闭触点-01-01.png new file mode 100644 index 0000000000000000000000000000000000000000..6a2a0f583ec48da5bbd0375661beb5115b033b04 GIT binary patch literal 5305 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 literal 0 HcmV?d00001 diff --git a/untitled/images/按钮.png b/untitled/images/按钮.png new file mode 100644 index 0000000000000000000000000000000000000000..3df91d9bf67171d35040978a46a0cd525fd17e4e GIT binary patch literal 4172 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 W*{~#(P?ovh10YjF3sMEqmGK`Dy)U%@ literal 0 HcmV?d00001 diff --git a/untitled/images/灯泡.png b/untitled/images/灯泡.png new file mode 100644 index 0000000000000000000000000000000000000000..2030e661bb22a0e399d49bcb67d11f4eb1628249 GIT binary patch literal 8642 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 bN!;V5SWv?(yAEno20&3(O{PZ5BK-dWbUJ&7 literal 0 HcmV?d00001 diff --git a/untitled/images/线-圈-圈.png b/untitled/images/线-圈-圈.png new file mode 100644 index 0000000000000000000000000000000000000000..0962b1bfb01ce928fd0c55af41b5908687a9cf8e GIT binary patch literal 7606 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 O1;|M$qpBoL(EkNp{vzrC literal 0 HcmV?d00001 diff --git a/untitled/main.cpp b/untitled/main.cpp new file mode 100644 index 0000000..b0f00a2 --- /dev/null +++ b/untitled/main.cpp @@ -0,0 +1,15 @@ +#include "mainwindow.h" +#include +#include +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + // 设置应用程序信息 + a.setApplicationName("综合平台编程器"); + QApplication::setStyle(QStyleFactory::create("Fusion")); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/untitled/mainwindow.cpp b/untitled/mainwindow.cpp new file mode 100644 index 0000000..d37a4b6 --- /dev/null +++ b/untitled/mainwindow.cpp @@ -0,0 +1,215 @@ +#include "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) +{ + setWindowTitle("综合平台编程器"); + setGeometry(100, 100, 1000, 700); + + // 初始化标签页 + m_tabWidget = new QTabWidget(this); + m_tabWidget->setTabsClosable(true); + setCentralWidget(m_tabWidget); + connect(m_tabWidget, &QTabWidget::currentChanged, this, &MainWindow::onTabChanged); + + // 创建菜单和工具栏 + createMenus(); + createToolbars(); +} + +MainWindow::~MainWindow() +{ + // 标签页和工具栏由Qt自动销毁 +} + +// 创建菜单栏 +// 在createMenus()函数中添加以下代码,用于创建操作菜单及其功能 + +void MainWindow::createMenus() +{ + // 创建文件菜单(保持原有代码) + QMenu *fileMenu = menuBar()->addMenu("文件"); + QMenu *editMenu = menuBar()->addMenu("操作"); + QMenu *simulationMenu = menuBar()->addMenu("仿真"); + + // 新建HMI动作(保持原有代码) + QAction *newHmiAction = new QAction("新建HMI(&H)", this); + newHmiAction->setShortcut(tr("Ctrl+H")); + newHmiAction->setStatusTip("创建新的HMI文档"); + connect(newHmiAction, &QAction::triggered, this, &MainWindow::onNewHMI); + fileMenu->addAction(newHmiAction); + + // 新建PLC动作(保持原有代码) + QAction *newPlcAction = new QAction("新建PLC(&P)", this); + newPlcAction->setShortcut(tr("Ctrl+P")); + newPlcAction->setStatusTip("创建新的PLC文档"); + connect(newPlcAction, &QAction::triggered, this, &MainWindow::onNewPLC); + fileMenu->addAction(newPlcAction); + + // 操作菜单 - 添加复制、粘贴、删除功能 + QAction *copyAction = new QAction("复制(&C)", this); + copyAction->setShortcut(QKeySequence::Copy); // 标准复制快捷键 Ctrl+C + copyAction->setStatusTip("复制选中的项目"); + connect(copyAction, &QAction::triggered, this, [this]() { + // 获取当前活动的HMI文档 + if (auto hmiDoc = dynamic_cast(m_tabWidget->currentWidget())) { + hmiDoc->copySelectedItems(); + } + }); + + QAction *pasteAction = new QAction("粘贴(&V)", this); + pasteAction->setShortcut(QKeySequence::Paste); // 标准粘贴快捷键 Ctrl+V + pasteAction->setStatusTip("粘贴复制的项目"); + connect(pasteAction, &QAction::triggered, this, [this]() { + if (auto hmiDoc = dynamic_cast(m_tabWidget->currentWidget())) { + hmiDoc->pasteItems(); + } + }); + + QAction *deleteAction = new QAction("删除(&D)", this); + deleteAction->setShortcut(QKeySequence::Delete); // 删除键 + deleteAction->setStatusTip("删除选中的项目"); + connect(deleteAction, &QAction::triggered, this, [this]() { + if (auto hmiDoc = dynamic_cast(m_tabWidget->currentWidget())) { + hmiDoc->deleteSelectedItems(); + } + }); + + // 添加到操作菜单 + editMenu->addAction(copyAction); + editMenu->addAction(pasteAction); + editMenu->addAction(deleteAction); +} + + +// 创建左侧工具栏 +void MainWindow::createToolbars() +{ + m_leftToolBar = new QToolBar("绘图工具", this); + addToolBar(Qt::LeftToolBarArea, m_leftToolBar); + m_leftToolBar->setAllowedAreas(Qt::LeftToolBarArea); // 仅允许在左侧 +} + +// 更新工具栏(根据当前文档类型) +void MainWindow::updateToolBar(BaseDocument *doc) +{ + // 清空现有工具 + m_leftToolBar->clear(); + + if (!doc) return; + + // HMI文档显示绘图工具 + if (doc->type() == BaseDocument::HMI) + { + HMIDocument *hmiDoc = dynamic_cast(doc); + if (!hmiDoc) return; + + // 画指示灯按钮(支持拖拽) + QToolButton *ellipseBtn = new QToolButton; + ellipseBtn->setText("指示灯"); + ellipseBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + ellipseBtn->setIcon(QIcon("../two/untitled/images/灯泡.png")); // 可替换为实际图标 + ellipseBtn->setMinimumSize(120, 120); + connect(ellipseBtn, &QToolButton::clicked, [=]() { + hmiDoc->setDrawEllipse(true); + hmiDoc->setDrawRectangle(false); + }); + + // 为按钮安装事件过滤器处理拖拽 + ellipseBtn->installEventFilter(this); + ellipseBtn->setProperty("toolType", "指示灯"); + m_leftToolBar->addWidget(ellipseBtn); + + // 画按钮按钮(支持拖拽) + QToolButton *rectBtn = new QToolButton; + rectBtn->setText("按钮"); + rectBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + rectBtn->setIcon(QIcon("../two/untitled/images/按钮.png")); // 可替换为实际图标 + rectBtn->setMinimumSize(120, 120); + connect(rectBtn, &QToolButton::clicked, [=]() { + hmiDoc->setDrawRectangle(true); + hmiDoc->setDrawEllipse(false); + }); + + // 为按钮安装事件过滤器处理拖拽 + rectBtn->installEventFilter(this); + rectBtn->setProperty("toolType", "按钮"); + m_leftToolBar->addWidget(rectBtn); + } + + // PLC文档可添加自己的工具 + else if (doc->type() == BaseDocument::PLC) { + m_leftToolBar->addAction("常开触点"); + m_leftToolBar->addAction("常闭触点"); + } +} + +// 事件过滤器处理拖拽 +bool MainWindow::eventFilter(QObject *obj, QEvent *event) +{ + // 检查是否是工具栏按钮的鼠标按下事件 + QToolButton *toolBtn = qobject_cast(obj); + if (toolBtn && event->type() == QEvent::MouseButtonPress) + { + QMouseEvent *me = static_cast(event); + if (me->button() == Qt::LeftButton) + { + // 获取工具类型 + QString toolType = toolBtn->property("toolType").toString(); + if (!toolType.isEmpty()) + { + // 创建拖拽 + QMimeData *mime = new QMimeData; + mime->setText(toolType); + QDrag *drag = new QDrag(obj); + drag->setMimeData(mime); + drag->exec(Qt::CopyAction); + return true; + } + } + } + return QMainWindow::eventFilter(obj, event); +} + +// 新建HMI文档 +void MainWindow::onNewHMI() +{ + m_hmiCount++; + HMIDocument *doc = new HMIDocument; + doc->setTitle(QString("HMI文档 %1").arg(m_hmiCount)); + m_tabWidget->addTab(doc, doc->title()); + m_tabWidget->setCurrentWidget(doc); + updateToolBar(doc); // 更新工具栏为HMI工具 +} + +// 新建PLC文档 +void MainWindow::onNewPLC() +{ + m_plcCount++; + PLCDocument *doc = new PLCDocument; + m_tabWidget->addTab(doc, QString("PLC文档 %1").arg(m_plcCount)); + m_tabWidget->setCurrentWidget(doc); + updateToolBar(doc); // 更新工具栏为PLC工具 +} + +// 标签页切换时更新工具栏 +void MainWindow::onTabChanged(int idx) +{ + if (idx < 0) { + updateToolBar(nullptr); + return; + } + BaseDocument *doc = dynamic_cast(m_tabWidget->widget(idx)); + updateToolBar(doc); +} diff --git a/untitled/mainwindow.h b/untitled/mainwindow.h new file mode 100644 index 0000000..2ecc0d3 --- /dev/null +++ b/untitled/mainwindow.h @@ -0,0 +1,40 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include "document.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private slots: + void onNewHMI(); // 新建HMI文档 + void onNewPLC(); // 新建PLC文档 + void onTabChanged(int idx); // 标签页切换时更新工具栏 + +private: + void createMenus(); // 创建菜单栏 + void createToolbars(); // 创建工具栏(左侧) + void updateToolBar(BaseDocument *doc); // 根据文档类型更新工具栏 + + QTabWidget *m_tabWidget; // 多文档标签页 + QToolBar *m_leftToolBar; // 左侧工具栏 + int m_hmiCount = 0; // HMI文档计数器 + int m_plcCount = 0; // PLC文档计数器 +}; + +#endif // MAINWINDOW_H diff --git a/untitled/mainwindow.ui b/untitled/mainwindow.ui new file mode 100644 index 0000000..b232854 --- /dev/null +++ b/untitled/mainwindow.ui @@ -0,0 +1,22 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + diff --git a/untitled/plceditor.cpp b/untitled/plceditor.cpp new file mode 100644 index 0000000..466ade8 --- /dev/null +++ b/untitled/plceditor.cpp @@ -0,0 +1,72 @@ +#include "plceditor.h" +#include +#include +#include + +PLCElement::PLCElement(Type type, QGraphicsItem *parent) + : QGraphicsRectItem(parent), type(type) +{ + setRect(0, 0, 60, 30); + setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); +} + +void PLCElement::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { + Q_UNUSED(option); + Q_UNUSED(widget); + + // 绘制基本形状 + painter->setPen(QPen(Qt::black, 2)); + painter->setBrush(QBrush(Qt::white)); + painter->drawRect(rect()); + + // 绘制触点类型 + painter->setPen(QPen(Qt::red, 2)); + if (type == NormallyOpen) { + // 常开触点 + painter->drawLine(10, 15, 25, 15); + painter->drawLine(35, 15, 50, 15); + painter->drawLine(25, 10, 35, 20); + } else { + // 常闭触点 + painter->drawLine(10, 15, 25, 15); + painter->drawLine(35, 15, 50, 15); + painter->drawLine(25, 20, 35, 10); + painter->drawLine(25, 10, 35, 20); + } +} + +PLCEditor::PLCEditor(QWidget *parent) + : QWidget(parent), elementCount(0) +{ + // 创建布局 + QVBoxLayout* layout = new QVBoxLayout(this); + + // 创建图形场景和视图 + scene = new QGraphicsScene(this); + scene->setSceneRect(0, 0, 800, 600); + scene->setBackgroundBrush(Qt::lightGray); + + view = new QGraphicsView(scene, this); + view->setRenderHint(QPainter::Antialiasing); + layout->addWidget(view); +} + +void PLCEditor::onNormallyOpen() { + // 创建新的常开触点 + PLCElement* element = new PLCElement(PLCElement::NormallyOpen); + scene->addItem(element); + + // 放置在不同位置 + element->setPos(50 + (elementCount % 10) * 80, 50 + (elementCount / 10) * 50); + elementCount++; +} + +void PLCEditor::onNormallyClosed() { + // 创建新的常闭触点 + PLCElement* element = new PLCElement(PLCElement::NormallyClosed); + scene->addItem(element); + + // 放置在不同位置 + element->setPos(50 + (elementCount % 10) * 80, 50 + (elementCount / 10) * 50); + elementCount++; +} diff --git a/untitled/plceditor.h b/untitled/plceditor.h new file mode 100644 index 0000000..592e2f1 --- /dev/null +++ b/untitled/plceditor.h @@ -0,0 +1,40 @@ +#ifndef PLCEDITOR_H +#define PLCEDITOR_H + +#include +#include +#include +#include +#include +#include + +class PLCElement : public QGraphicsRectItem { +public: + enum Type { NormallyOpen, NormallyClosed }; + + PLCElement(Type type, QGraphicsItem *parent = nullptr); + Type getType() const { return type; } + +private: + Type type; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; +}; + +class PLCEditor : public QWidget +{ + Q_OBJECT + +public: + explicit PLCEditor(QWidget *parent = nullptr); + +public slots: + void onNormallyOpen(); + void onNormallyClosed(); + +private: + QGraphicsScene* scene; + QGraphicsView* view; + int elementCount; +}; + +#endif // PLCEDITOR_H diff --git a/untitled/untitled.pro b/untitled/untitled.pro new file mode 100644 index 0000000..af0ada3 --- /dev/null +++ b/untitled/untitled.pro @@ -0,0 +1,35 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + document.cpp \ + graphicsitems.cpp \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + document.h \ + graphicsitems.h \ + mainwindow.h + +FORMS += \ + mainwindow.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/untitled/untitled.pro.user b/untitled/untitled.pro.user new file mode 100644 index 0000000..563ea85 --- /dev/null +++ b/untitled/untitled.pro.user @@ -0,0 +1,563 @@ + + + + + + EnvironmentId + {a9bf9b5a-270d-4be3-80ad-6036193da221} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + -fno-delayed-template-parsing + + true + + + + ProjectExplorer.Project.Target.0 + + Desktop Qt 5.14.2 MinGW 32-bit + Desktop Qt 5.14.2 MinGW 32-bit + qt.qt5.5142.win32_mingw73_kit + 0 + 0 + 0 + + C:/Users/admin/Desktop/build-untitled-Desktop_Qt_5_14_2_MinGW_32_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + C:/Users/admin/Desktop/build-untitled-Desktop_Qt_5_14_2_MinGW_32_bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + false + false + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + + C:/Users/admin/Desktop/build-untitled-Desktop_Qt_5_14_2_MinGW_32_bit-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + false + true + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + untitled2 + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/admin/Desktop/two/untitled/untitled.pro + C:/Users/admin/Desktop/two/untitled/untitled.pro + + false + + false + true + true + false + false + true + + C:/Users/admin/Desktop/build-untitled-Desktop_Qt_5_14_2_MinGW_32_bit-Debug + + 1 + + + + ProjectExplorer.Project.Target.1 + + Desktop Qt 5.14.2 MinGW 64-bit + Desktop Qt 5.14.2 MinGW 64-bit + qt.qt5.5142.win64_mingw73_kit + 0 + 0 + 0 + + C:/Users/admin/Desktop/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + C:/Users/admin/Desktop/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + false + false + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + + C:/Users/admin/Desktop/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + false + true + true + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + + ProjectExplorer.CustomExecutableRunConfiguration + + + false + + false + true + false + false + true + + + + 1 + + + + ProjectExplorer.Project.TargetCount + 2 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + +