@@ -11,13 +11,15 @@ ResizableShape<BaseShape>::ResizableShape(qreal x, qreal y, qreal w, qreal h) | |||
this->setFlag(QGraphicsItem::ItemIsSelectable, true); | |||
this->setAcceptHoverEvents(true); | |||
} | |||
template <typename BaseShape> | |||
void ResizableShape<BaseShape>::hoverMoveEvent(QGraphicsSceneHoverEvent *event) | |||
{ | |||
if (isInResizeArea(event->pos())) { | |||
if (isInResizeArea(event->pos())) | |||
{ | |||
this->setCursor(Qt::SizeFDiagCursor); | |||
} else { | |||
} | |||
else | |||
{ | |||
this->setCursor(Qt::SizeAllCursor); | |||
} | |||
BaseShape::hoverMoveEvent(event); | |||
@@ -34,10 +36,13 @@ bool ResizableShape<BaseShape>::isInResizeArea(const QPointF &pos) const | |||
template <typename BaseShape> | |||
void ResizableShape<BaseShape>::mousePressEvent(QGraphicsSceneMouseEvent *event) | |||
{ | |||
if (isInResizeArea(event->pos())) { | |||
if (isInResizeArea(event->pos())) | |||
{ | |||
resizing = true; | |||
this->setCursor(Qt::SizeFDiagCursor); | |||
} else { | |||
} | |||
else | |||
{ | |||
resizing = false; | |||
this->setCursor(Qt::SizeAllCursor); | |||
BaseShape::mousePressEvent(event); | |||
@@ -47,11 +52,14 @@ void ResizableShape<BaseShape>::mousePressEvent(QGraphicsSceneMouseEvent *event) | |||
template <typename BaseShape> | |||
void ResizableShape<BaseShape>::mouseMoveEvent(QGraphicsSceneMouseEvent *event) | |||
{ | |||
if (resizing) { | |||
if (resizing) | |||
{ | |||
QRectF newRect = this->rect(); | |||
newRect.setBottomRight(event->pos()); | |||
this->setRect(newRect); | |||
} else { | |||
} | |||
else | |||
{ | |||
BaseShape::mouseMoveEvent(event); | |||
} | |||
} | |||
@@ -0,0 +1,24 @@ | |||
#include "basedocument.h" | |||
BaseDocument::BaseDocument(DocumentType type, QWidget *parent) | |||
: QWidget(parent), m_type(type) {} | |||
BaseDocument::DocumentType BaseDocument::type() const { | |||
return m_type; | |||
} | |||
QString BaseDocument::filePath() const { | |||
return m_filePath; | |||
} | |||
void BaseDocument::setFilePath(const QString &path) { | |||
m_filePath = path; | |||
} | |||
bool BaseDocument::isModified() const { | |||
return m_modified; | |||
} | |||
void BaseDocument::setModified(bool modified) { | |||
m_modified = modified; | |||
} |
@@ -0,0 +1,32 @@ | |||
#ifndef BASEDOCUMENT_H | |||
#define BASEDOCUMENT_H | |||
#include <QWidget> | |||
#include <QString> | |||
class BaseDocument : public QWidget | |||
{ | |||
Q_OBJECT | |||
public: | |||
enum DocumentType { HMI, PLC }; | |||
BaseDocument(DocumentType type, QWidget *parent = nullptr); | |||
virtual ~BaseDocument() = default; | |||
DocumentType type() const; | |||
virtual QString title() const = 0; | |||
virtual QString filePath() const; | |||
virtual void setFilePath(const QString &path); | |||
virtual bool isModified() const; | |||
virtual void setModified(bool modified); | |||
// 保存/加载接口 | |||
virtual bool saveToFile(const QString &filePath) = 0; | |||
virtual bool loadFromFile(const QString &filePath) = 0; | |||
protected: | |||
DocumentType m_type; | |||
QString m_filePath; | |||
bool m_modified = false; | |||
}; | |||
#endif // BASEDOCUMENT_H |
@@ -16,7 +16,7 @@ | |||
#include <QDragEnterEvent> | |||
#include <QDropEvent> | |||
#include <QDebug> | |||
#include<QFileInfo> | |||
HMIDocument::HMIDocument(QWidget *parent) | |||
: BaseDocument(HMI, parent), | |||
m_title("未命名HMI"), | |||
@@ -28,6 +28,24 @@ HMIDocument::HMIDocument(QWidget *parent) | |||
m_scene = new QGraphicsScene(this); | |||
m_scene->setSceneRect(0, 0, 800, 600); | |||
m_scene->setBackgroundBrush(QBrush(Qt::lightGray)); | |||
// 更粗的网格线示例 | |||
// QImage gridImage(40, 40, QImage::Format_ARGB32); | |||
// gridImage.fill(Qt::white); | |||
// QPainter painter(&gridImage); | |||
// painter.setPen(QPen(QColor(200, 200, 200), 1)); // 浅灰色 | |||
// // 绘制水平线 | |||
// painter.drawLine(0, 39, 39, 39); | |||
// // 绘制垂直线 | |||
// painter.drawLine(39, 0, 39, 39); | |||
// // 每隔4个小网格绘制一条稍粗的线 | |||
// painter.setPen(QPen(QColor(180, 180, 180), 1.5)); | |||
// painter.drawLine(0, 39, 39, 39); | |||
// painter.drawLine(39, 0, 39, 39); | |||
// m_scene->setBackgroundBrush(QBrush(gridImage)); | |||
// 创建视图 | |||
m_view = new QGraphicsView(m_scene, this); | |||
@@ -53,16 +71,14 @@ 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<QMouseEvent*>(event); | |||
setModified(true); | |||
if (mouseEvent->button() == Qt::RightButton) | |||
{ | |||
// 右键菜单 | |||
showContextMenu(mouseEvent->globalPos()); | |||
showContextMenu(mouseEvent->globalPos());// 右键菜单 | |||
return true; | |||
} | |||
if (mouseEvent->button() == Qt::LeftButton) | |||
@@ -72,42 +88,43 @@ bool HMIDocument::eventFilter(QObject *obj, QEvent *event) | |||
// 如果点击空白区域且有绘制标志,创建图形 | |||
if (!item) { | |||
if (m_canDrawEllipse) { | |||
createEllipse(scenePos); | |||
if (m_canDrawEllipse) | |||
{ | |||
createShape("ellipse", scenePos); | |||
resetDrawFlags(); | |||
} else if (m_canDrawRectangle) { | |||
createRectangle(scenePos); | |||
} | |||
else if (m_canDrawRectangle) | |||
{ | |||
createShape("rectangle", scenePos); | |||
resetDrawFlags(); | |||
} | |||
m_scene->clearSelection(); // 清除选择 | |||
return true; | |||
} | |||
} | |||
} | |||
// 拖拽事件(从工具栏拖拽绘制) | |||
if (event->type() == QEvent::DragEnter) { | |||
if (event->type() == QEvent::DragEnter) | |||
{ | |||
QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event); | |||
dragEvent->acceptProposedAction(); | |||
return true; | |||
} | |||
if (event->type() == QEvent::DragMove) { | |||
if (event->type() == QEvent::DragMove) | |||
{ | |||
static_cast<QDragMoveEvent*>(event)->acceptProposedAction(); | |||
return true; | |||
} | |||
if (event->type() == QEvent::Drop) { | |||
if (event->type() == QEvent::Drop) | |||
{ | |||
QDropEvent *dropEvent = static_cast<QDropEvent*>(event); | |||
const QMimeData *mimeData = dropEvent->mimeData(); // 显式获取mimeData | |||
const QMimeData *mimeData = dropEvent->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); | |||
} | |||
if (mimeData && mimeData->hasText()) | |||
{ | |||
createShape(mimeData->text(), scenePos); | |||
resetDrawFlags();//重置防止拖拽后再次点击生成 | |||
} | |||
dropEvent->acceptProposedAction(); | |||
return true; | |||
@@ -116,26 +133,24 @@ bool HMIDocument::eventFilter(QObject *obj, QEvent *event) | |||
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) | |||
void HMIDocument::createShape(const QString& type, 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); | |||
if (type == "指示灯" || type == "ellipse") { | |||
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); | |||
} | |||
else if (type == "按钮" || type == "rectangle") { | |||
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); | |||
} | |||
setModified(true); | |||
} | |||
// 重置绘制标志 | |||
void HMIDocument::resetDrawFlags() | |||
{ | |||
@@ -152,12 +167,15 @@ void HMIDocument::showContextMenu(QPoint globalPos) | |||
QMenu menu(this); | |||
if (!clickedItem) { | |||
if (!clickedItem) | |||
{ | |||
// 空白区域:仅显示粘贴 | |||
QAction *pasteAction = menu.addAction("粘贴"); | |||
pasteAction->setEnabled(!m_copiedItemsData.isEmpty()); | |||
connect(pasteAction, &QAction::triggered, this, &HMIDocument::pasteItems); | |||
} else { | |||
} | |||
else | |||
{ | |||
// 选中图形:显示完整菜单 | |||
QAction *propAction = menu.addAction("属性"); | |||
QAction *copyAction = menu.addAction("复制"); | |||
@@ -200,10 +218,12 @@ void HMIDocument::copySelectedItems() | |||
QList<QGraphicsItem*> selectedItems = m_scene->selectedItems(); | |||
if (selectedItems.isEmpty()) return; | |||
foreach (QGraphicsItem *item, selectedItems) { | |||
foreach (QGraphicsItem *item, selectedItems) | |||
{ | |||
m_copiedItemsData.append(serializeItem(item)); | |||
} | |||
m_lastPastePos = selectedItems.first()->pos(); | |||
setModified(true); | |||
} | |||
// 粘贴项 | |||
@@ -215,14 +235,17 @@ void HMIDocument::pasteItems() | |||
m_lastPastePos += offset; | |||
m_scene->clearSelection(); | |||
foreach (const QByteArray &itemData, m_copiedItemsData) { | |||
foreach (const QByteArray &itemData, m_copiedItemsData) | |||
{ | |||
QGraphicsItem *newItem = deserializeItem(itemData); | |||
if (newItem) { | |||
if (newItem) | |||
{ | |||
m_scene->addItem(newItem); | |||
newItem->setPos(m_lastPastePos); | |||
newItem->setSelected(true); | |||
} | |||
} | |||
setModified(true); | |||
} | |||
// 删除选中项 | |||
@@ -233,6 +256,7 @@ void HMIDocument::deleteSelectedItems() | |||
m_scene->removeItem(item); | |||
delete item; | |||
} | |||
setModified(true); | |||
} | |||
// 显示属性对话框 | |||
@@ -291,7 +315,7 @@ void HMIDocument::showItemProperties() | |||
}); | |||
// 布局组装 | |||
form->addRow("名称:", nameEdit); | |||
form->addRow("对象:", nameEdit); | |||
QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); | |||
form->addRow(btnBox); | |||
connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); | |||
@@ -299,6 +323,7 @@ void HMIDocument::showItemProperties() | |||
// 应用属性 | |||
if (dialog.exec() == QDialog::Accepted) { | |||
setModified(true); | |||
// 使用NamedItem接口设置名称 | |||
namedItem->setName(nameEdit->text()); | |||
@@ -325,27 +350,27 @@ QByteArray HMIDocument::serializeItem(QGraphicsItem *item) | |||
stream << item->flags(); | |||
stream << item->transform(); | |||
stream << item->isVisible(); | |||
// 形状信息 | |||
if (auto shape = dynamic_cast<QAbstractGraphicsShapeItem*>(item)) { | |||
if (auto shape = dynamic_cast<QAbstractGraphicsShapeItem*>(item)) | |||
{ | |||
stream << shape->pen(); | |||
stream << shape->brush(); | |||
stream << shape->boundingRect(); | |||
} | |||
// 具体图形信息 | |||
if (auto rect = dynamic_cast<ResizableRectangle*>(item)) { | |||
if (auto rect = dynamic_cast<ResizableRectangle*>(item)) | |||
{ | |||
stream << rect->rect(); | |||
stream << rect->pressedColor(); | |||
stream << rect->releasedColor(); | |||
stream << rect->name(); | |||
} else if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) { | |||
} else if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) | |||
{ | |||
stream << ellipse->rect(); | |||
stream << ellipse->onColor(); | |||
stream << ellipse->offColor(); | |||
stream << ellipse->name(); | |||
} | |||
return data; | |||
} | |||
@@ -406,3 +431,223 @@ QGraphicsItem *HMIDocument::deserializeItem(const QByteArray &data) | |||
return item; | |||
} | |||
void HMIDocument::startDrawingEllipse() | |||
{ | |||
m_canDrawEllipse = true; | |||
m_canDrawRectangle = false; | |||
} | |||
void HMIDocument::startDrawingRectangle() | |||
{ | |||
m_canDrawEllipse = false; | |||
m_canDrawRectangle = true; | |||
} | |||
bool HMIDocument::saveToFile(const QString &filePath) | |||
{ | |||
QFile file(filePath); | |||
if (!file.open(QIODevice::WriteOnly)) { | |||
qWarning() << "无法打开文件进行写入:" << filePath; | |||
return false; | |||
} | |||
// 创建JSON文档结构 | |||
QJsonObject docObject; | |||
docObject["title"] = m_title; | |||
docObject["sceneRect"] = QJsonArray({ | |||
m_scene->sceneRect().x(), | |||
m_scene->sceneRect().y(), | |||
m_scene->sceneRect().width(), | |||
m_scene->sceneRect().height() | |||
}); | |||
docObject["backgroundColor"] = m_scene->backgroundBrush().color().name(); | |||
// 序列化所有图形项 | |||
QJsonArray itemsArray; | |||
foreach (QGraphicsItem *item, m_scene->items()) { | |||
QJsonObject itemJson = itemToJson(item); | |||
if (!itemJson.isEmpty()) { | |||
itemsArray.append(itemJson); | |||
} | |||
} | |||
docObject["items"] = itemsArray; | |||
// 写入文件 | |||
QJsonDocument doc(docObject); | |||
file.write(doc.toJson()); | |||
file.close(); | |||
// 更新文档状态 | |||
setFilePath(filePath); | |||
setModified(false); | |||
QFileInfo fileInfo(filePath); | |||
setTitle(fileInfo.baseName()); | |||
return true; | |||
} | |||
// 新增:从文件加载文档 | |||
bool HMIDocument::loadFromFile(const QString &filePath) | |||
{ | |||
QFile file(filePath); | |||
if (!file.open(QIODevice::ReadOnly)) { | |||
qWarning() << "无法打开文件进行读取:" << filePath; | |||
return false; | |||
} | |||
// 读取JSON文档 | |||
QByteArray data = file.readAll(); | |||
QJsonDocument doc = QJsonDocument::fromJson(data); | |||
if (doc.isNull()) { | |||
qWarning() << "无效的JSON文档:" << filePath; | |||
return false; | |||
} | |||
// 解析文档 | |||
QJsonObject docObject = doc.object(); | |||
m_title = docObject["title"].toString(); | |||
// 设置场景属性 | |||
QJsonArray sceneRectArray = docObject["sceneRect"].toArray(); | |||
if (sceneRectArray.size() == 4) { | |||
QRectF sceneRect( | |||
sceneRectArray[0].toDouble(), | |||
sceneRectArray[1].toDouble(), | |||
sceneRectArray[2].toDouble(), | |||
sceneRectArray[3].toDouble() | |||
); | |||
m_scene->setSceneRect(sceneRect); | |||
} | |||
if (docObject.contains("backgroundColor")) { | |||
QColor bgColor(docObject["backgroundColor"].toString()); | |||
m_scene->setBackgroundBrush(QBrush(bgColor)); | |||
} | |||
// 清除现有内容 | |||
m_scene->clear(); | |||
// 加载所有图形项 | |||
QJsonArray itemsArray = docObject["items"].toArray(); | |||
foreach (QJsonValue itemValue, itemsArray) { | |||
QJsonObject itemJson = itemValue.toObject(); | |||
QGraphicsItem *item = jsonToItem(itemJson); | |||
if (item) { | |||
m_scene->addItem(item); | |||
} | |||
} | |||
// 更新文档状态 | |||
setFilePath(filePath); | |||
setModified(false); | |||
return true; | |||
} | |||
// 新增:将图形项转换为JSON对象 | |||
QJsonObject HMIDocument::itemToJson(QGraphicsItem *item) | |||
{ | |||
QJsonObject json; | |||
// 基础属性 | |||
json["type"] = item->type(); | |||
json["x"] = item->x(); | |||
json["y"] = item->y(); | |||
json["zValue"] = item->zValue(); | |||
json["visible"] = item->isVisible(); | |||
// 具体图形项属性 | |||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) { | |||
json["itemType"] = "ellipse"; | |||
json["rect"] = QJsonArray({ | |||
ellipse->rect().x(), | |||
ellipse->rect().y(), | |||
ellipse->rect().width(), | |||
ellipse->rect().height() | |||
}); | |||
json["onColor"] = ellipse->onColor().name(); | |||
json["offColor"] = ellipse->offColor().name(); | |||
json["currentColor"] = ellipse->brush().color().name(); | |||
json["penColor"] = ellipse->pen().color().name(); | |||
json["penWidth"] = ellipse->pen().width(); | |||
json["name"] = ellipse->name(); | |||
} | |||
else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) { | |||
json["itemType"] = "rectangle"; | |||
json["rect"] = QJsonArray({ | |||
rect->rect().x(), | |||
rect->rect().y(), | |||
rect->rect().width(), | |||
rect->rect().height() | |||
}); | |||
json["pressedColor"] = rect->pressedColor().name(); | |||
json["releasedColor"] = rect->releasedColor().name(); | |||
json["currentColor"] = rect->brush().color().name(); | |||
json["penColor"] = rect->pen().color().name(); | |||
json["penWidth"] = rect->pen().width(); | |||
json["name"] = rect->name(); | |||
} | |||
return json; | |||
} | |||
// 新增:从JSON对象创建图形项 | |||
QGraphicsItem *HMIDocument::jsonToItem(const QJsonObject &json) | |||
{ | |||
QString itemType = json["itemType"].toString(); | |||
QGraphicsItem *item = nullptr; | |||
if (itemType == "ellipse") { | |||
QJsonArray rectArray = json["rect"].toArray(); | |||
if (rectArray.size() != 4) return nullptr; | |||
ResizableEllipse *ellipse = new ResizableEllipse( | |||
rectArray[0].toDouble(), | |||
rectArray[1].toDouble(), | |||
rectArray[2].toDouble(), | |||
rectArray[3].toDouble() | |||
); | |||
ellipse->setOnColor(QColor(json["onColor"].toString())); | |||
ellipse->setOffColor(QColor(json["offColor"].toString())); | |||
ellipse->setBrush(QColor(json["currentColor"].toString())); | |||
ellipse->setPen(QPen( | |||
QColor(json["penColor"].toString()), | |||
json["penWidth"].toDouble() | |||
)); | |||
ellipse->setName(json["name"].toString()); | |||
item = ellipse; | |||
} | |||
else if (itemType == "rectangle") { | |||
QJsonArray rectArray = json["rect"].toArray(); | |||
if (rectArray.size() != 4) return nullptr; | |||
ResizableRectangle *rect = new ResizableRectangle( | |||
rectArray[0].toDouble(), | |||
rectArray[1].toDouble(), | |||
rectArray[2].toDouble(), | |||
rectArray[3].toDouble() | |||
); | |||
rect->setPressedColor(QColor(json["pressedColor"].toString())); | |||
rect->setReleasedColor(QColor(json["releasedColor"].toString())); | |||
rect->setBrush(QColor(json["currentColor"].toString())); | |||
rect->setPen(QPen( | |||
QColor(json["penColor"].toString()), | |||
json["penWidth"].toDouble() | |||
)); | |||
rect->setName(json["name"].toString()); | |||
item = rect; | |||
} | |||
// 设置基础属性 | |||
if (item) { | |||
item->setX(json["x"].toDouble()); | |||
item->setY(json["y"].toDouble()); | |||
item->setZValue(json["zValue"].toDouble()); | |||
item->setVisible(json["visible"].toBool()); | |||
} | |||
return item; | |||
} |
@@ -1,92 +0,0 @@ | |||
#ifndef DOCUMENT_H | |||
#define DOCUMENT_H | |||
#include <QWidget> | |||
#include <QGraphicsScene> | |||
#include <QGraphicsView> | |||
#include <QList> | |||
#include <QByteArray> | |||
#include <QPointF> | |||
// 前向声明 | |||
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<QByteArray> 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 |
@@ -1,26 +1,147 @@ | |||
#include "graphicsitems.h" | |||
#ifndef GRAPHICSITEMS_H | |||
#define GRAPHICSITEMS_H | |||
#include <QGraphicsRectItem> | |||
#include <QGraphicsEllipseItem> | |||
#include <QGraphicsSceneHoverEvent> | |||
#include <QGraphicsSceneMouseEvent> | |||
#include <QPainter> | |||
#include <QString> | |||
#include <QColor> | |||
#include <QCursor> | |||
#include <QGraphicsScene> | |||
// 命名项接口(用于属性设置) | |||
class NamedItem | |||
{ | |||
public: | |||
virtual QString name() const = 0; | |||
virtual void setName(const QString &name) = 0; | |||
virtual ~NamedItem() = default; | |||
}; | |||
// 可调整大小的图形基类(模板) | |||
template <typename BaseShape> | |||
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; | |||
this->setCursor(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; | |||
this->setCursor(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; | |||
this->setCursor(Qt::SizeFDiagCursor); | |||
BaseShape::mouseReleaseEvent(event); | |||
} | |||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override | |||
{ | |||
BaseShape::paint(painter, option, widget); | |||
} | |||
// 可调整大小的矩形(按钮)实现 | |||
ResizableRectangle::ResizableRectangle(qreal x, qreal y, qreal w, qreal h) | |||
: ResizableShape<QGraphicsRectItem>(x, y, w, h) | |||
// 命名项接口实现 | |||
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(50, 50), r.bottomRight()); | |||
return resizeArea.contains(pos); | |||
} | |||
}; | |||
// 可调整大小的矩形(按钮) | |||
class ResizableRectangle : public ResizableShape<QGraphicsRectItem> | |||
{ | |||
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<QGraphicsEllipseItem>(x, y, w, h) | |||
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<QGraphicsEllipseItem> | |||
{ | |||
m_name = "指示灯"; | |||
m_onColor = Qt::green; | |||
m_offColor = Qt::red; | |||
this->setBrush(m_onColor); | |||
} | |||
// 显式实例化模板类 | |||
template class ResizableShape<QGraphicsRectItem>; | |||
template class ResizableShape<QGraphicsEllipseItem>; | |||
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 |
@@ -27,24 +27,35 @@ class ResizableShape : public BaseShape, public NamedItem | |||
protected: | |||
bool m_resizing; | |||
QString m_name; | |||
Qt::CursorShape m_currentCursor; | |||
QPointF m_initialPos; // 用于存储调整开始时的位置 | |||
// 获取右下角调整区域 | |||
QRectF getResizeArea() const | |||
{ | |||
QRectF r = this->rect(); | |||
return QRectF(r.bottomRight() - QPointF(20, 20), r.bottomRight()); | |||
} | |||
// 检测是否在右下角调整区域 | |||
bool isInResizeArea(const QPointF &pos) const | |||
{ | |||
return getResizeArea().contains(pos); | |||
} | |||
public: | |||
ResizableShape(qreal x, qreal y, qreal w, qreal h) | |||
: BaseShape(x, y, w, h), m_resizing(false), m_currentCursor(Qt::SizeAllCursor) | |||
: BaseShape(x, y, w, h), m_resizing(false) | |||
{ | |||
this->setFlag(QGraphicsItem::ItemIsMovable, true); | |||
this->setFlag(QGraphicsItem::ItemIsSelectable, true); | |||
this->setAcceptHoverEvents(true); | |||
this->setCursor(Qt::SizeAllCursor); // 默认光标为移动光标 | |||
} | |||
// 事件处理(调整大小逻辑) | |||
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override | |||
{ | |||
if (isInResizeArea(event->pos())) { | |||
m_currentCursor = Qt::SizeFDiagCursor; | |||
this->setCursor(Qt::SizeFDiagCursor); // 右下角显示缩放光标 | |||
} else { | |||
m_currentCursor = Qt::SizeAllCursor; | |||
this->setCursor(Qt::SizeAllCursor); // 其他区域显示移动光标 | |||
} | |||
BaseShape::hoverMoveEvent(event); | |||
} | |||
@@ -53,10 +64,12 @@ public: | |||
{ | |||
if (isInResizeArea(event->pos())) { | |||
m_resizing = true; | |||
m_currentCursor = Qt::SizeFDiagCursor; | |||
} else { | |||
m_initialPos = event->pos(); // 存储初始位置 | |||
this->setCursor(Qt::SizeFDiagCursor); | |||
} | |||
else | |||
{ | |||
m_resizing = false; | |||
m_currentCursor = Qt::SizeAllCursor; | |||
BaseShape::mousePressEvent(event); | |||
} | |||
} | |||
@@ -64,9 +77,22 @@ public: | |||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override | |||
{ | |||
if (m_resizing) { | |||
// 计算尺寸变化量 | |||
QPointF delta = event->pos() - m_initialPos; | |||
QRectF newRect = this->rect(); | |||
newRect.setBottomRight(event->pos()); | |||
// 保持左上角不变,调整右下角 | |||
newRect.setWidth(newRect.width() + delta.x()); | |||
newRect.setHeight(newRect.height() + delta.y()); | |||
// 设置最小尺寸 | |||
if (newRect.width() < 20) newRect.setWidth(20); | |||
if (newRect.height() < 20) newRect.setHeight(20); | |||
this->setRect(newRect); | |||
this->update(); | |||
m_initialPos = event->pos(); // 更新初始位置 | |||
} else { | |||
BaseShape::mouseMoveEvent(event); | |||
} | |||
@@ -74,8 +100,16 @@ public: | |||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override | |||
{ | |||
m_resizing = false; | |||
m_currentCursor = Qt::SizeAllCursor; | |||
if (m_resizing) { | |||
m_resizing = false; | |||
// 根据位置恢复光标 | |||
if (isInResizeArea(event->pos())) { | |||
this->setCursor(Qt::SizeFDiagCursor); // 仍在调整区域 | |||
} else { | |||
this->setCursor(Qt::SizeAllCursor); // 离开调整区域 | |||
} | |||
} | |||
BaseShape::mouseReleaseEvent(event); | |||
} | |||
@@ -87,24 +121,19 @@ public: | |||
// 命名项接口实现 | |||
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<QGraphicsRectItem> | |||
{ | |||
public: | |||
ResizableRectangle(qreal x, qreal y, qreal w, qreal h); | |||
ResizableRectangle(qreal x, qreal y, qreal w, qreal h) | |||
: ResizableShape<QGraphicsRectItem>(x, y, w, h) | |||
{ | |||
m_pressedColor = Qt::darkYellow; | |||
m_releasedColor = Qt::yellow; | |||
this->setBrush(m_releasedColor); | |||
} | |||
QColor pressedColor() const { return m_pressedColor; } | |||
void setPressedColor(const QColor &color) { m_pressedColor = color; } | |||
@@ -124,7 +153,13 @@ private: | |||
class ResizableEllipse : public ResizableShape<QGraphicsEllipseItem> | |||
{ | |||
public: | |||
ResizableEllipse(qreal x, qreal y, qreal w, qreal h); | |||
ResizableEllipse(qreal x, qreal y, qreal w, qreal h) | |||
: ResizableShape<QGraphicsEllipseItem>(x, y, w, h) | |||
{ | |||
m_onColor = Qt::green; | |||
m_offColor = Qt::red; | |||
this->setBrush(m_offColor); | |||
} | |||
QColor onColor() const { return m_onColor; } | |||
void setOnColor(const QColor &color) { | |||
@@ -0,0 +1,651 @@ | |||
#include "hmidocument.h" | |||
#include "graphicsitems.h" | |||
#include <QGraphicsItem> | |||
#include <QGraphicsSceneHoverEvent> | |||
#include <QMouseEvent> | |||
#include <QMenu> | |||
#include <QAction> | |||
#include <QDataStream> | |||
#include <QColorDialog> | |||
#include <QDialog> | |||
#include <QFormLayout> | |||
#include <QLineEdit> | |||
#include <QPushButton> | |||
#include <QDialogButtonBox> | |||
#include <QMimeData> | |||
#include <QDragEnterEvent> | |||
#include <QDropEvent> | |||
#include <QDebug> | |||
#include <QFileInfo> | |||
#include <QVBoxLayout> | |||
#include <QJsonArray> | |||
#include <QJsonDocument> | |||
#include <QJsonObject> | |||
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<QMouseEvent*>(event); | |||
setModified(true); | |||
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) | |||
{ | |||
createShape("ellipse", scenePos); | |||
resetDrawFlags(); | |||
} | |||
else if (m_canDrawRectangle) | |||
{ | |||
createShape("rectangle", scenePos); | |||
resetDrawFlags(); | |||
} | |||
} | |||
} | |||
} | |||
// 拖拽事件(从工具栏拖拽绘制) | |||
if (event->type() == QEvent::DragEnter) | |||
{ | |||
QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>(event); | |||
dragEvent->acceptProposedAction(); | |||
return true; | |||
} | |||
if (event->type() == QEvent::DragMove) | |||
{ | |||
static_cast<QDragMoveEvent*>(event)->acceptProposedAction(); | |||
return true; | |||
} | |||
if (event->type() == QEvent::Drop) | |||
{ | |||
QDropEvent *dropEvent = static_cast<QDropEvent*>(event); | |||
const QMimeData *mimeData = dropEvent->mimeData(); | |||
QPointF scenePos = m_view->mapToScene(dropEvent->pos()); | |||
// 使用QMimeData | |||
if (mimeData && mimeData->hasText()) | |||
{ | |||
createShape(mimeData->text(), scenePos); | |||
resetDrawFlags();//重置防止拖拽后再次点击生成 | |||
} | |||
dropEvent->acceptProposedAction(); | |||
return true; | |||
} | |||
return QWidget::eventFilter(obj, event); | |||
} | |||
void HMIDocument::createShape(const QString& type, const QPointF &pos) | |||
{ | |||
if (type == "指示灯" || type == "ellipse") { | |||
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); | |||
} | |||
else if (type == "按钮" || type == "rectangle") { | |||
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); | |||
} | |||
setModified(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<ResizableEllipse*>(clickedItem)) { | |||
ellipse->setBrush(ellipse->onColor()); | |||
} else if (auto rect = dynamic_cast<ResizableRectangle*>(clickedItem)) { | |||
rect->setBrush(rect->pressedColor()); | |||
} | |||
}); | |||
connect(offAction, &QAction::triggered, [=]() { | |||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(clickedItem)) { | |||
ellipse->setBrush(ellipse->offColor()); | |||
} else if (auto rect = dynamic_cast<ResizableRectangle*>(clickedItem)) { | |||
rect->setBrush(rect->releasedColor()); | |||
} | |||
}); | |||
} | |||
menu.exec(globalPos + QPoint(10, 10)); | |||
} | |||
// 复制选中项 | |||
void HMIDocument::copySelectedItems() | |||
{ | |||
m_copiedItemsData.clear(); | |||
QList<QGraphicsItem*> selectedItems = m_scene->selectedItems(); | |||
if (selectedItems.isEmpty()) return; | |||
foreach (QGraphicsItem *item, selectedItems) | |||
{ | |||
m_copiedItemsData.append(serializeItem(item)); | |||
} | |||
m_lastPastePos = selectedItems.first()->pos(); | |||
setModified(true); | |||
} | |||
// 粘贴项 | |||
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); | |||
} | |||
} | |||
setModified(true); | |||
} | |||
// 删除选中项 | |||
void HMIDocument::deleteSelectedItems() | |||
{ | |||
QList<QGraphicsItem*> selectedItems = m_scene->selectedItems(); | |||
foreach (QGraphicsItem *item, selectedItems) { | |||
m_scene->removeItem(item); | |||
delete item; | |||
} | |||
setModified(true); | |||
} | |||
// 显示属性对话框 | |||
void HMIDocument::showItemProperties() | |||
{ | |||
QList<QGraphicsItem*> selectedItems = m_scene->selectedItems(); | |||
if (selectedItems.isEmpty()) return; | |||
QGraphicsItem *item = selectedItems.first(); | |||
// 将QGraphicsItem转换为NamedItem | |||
NamedItem *namedItem = dynamic_cast<NamedItem*>(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<ResizableEllipse*>(item)) { | |||
tempColor1 = ellipse->onColor(); | |||
tempColor2 = ellipse->offColor(); | |||
form->addRow("ON颜色:", colorBtn1); | |||
form->addRow("OFF颜色:", colorBtn2); | |||
} else if (auto rect = dynamic_cast<ResizableRectangle*>(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) { | |||
setModified(true); | |||
// 使用NamedItem接口设置名称 | |||
namedItem->setName(nameEdit->text()); | |||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) { | |||
ellipse->setOnColor(tempColor1); | |||
ellipse->setOffColor(tempColor2); | |||
} else if (auto rect = dynamic_cast<ResizableRectangle*>(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<QAbstractGraphicsShapeItem*>(item)) | |||
{ | |||
stream << shape->pen(); | |||
stream << shape->brush(); | |||
stream << shape->boundingRect(); | |||
} | |||
// 具体图形信息 | |||
if (auto rect = dynamic_cast<ResizableRectangle*>(item)) | |||
{ | |||
stream << rect->rect(); | |||
stream << rect->pressedColor(); | |||
stream << rect->releasedColor(); | |||
stream << rect->name(); | |||
} else if (auto ellipse = dynamic_cast<ResizableEllipse*>(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; | |||
} | |||
void HMIDocument::startDrawingEllipse() | |||
{ | |||
m_canDrawEllipse = true; | |||
m_canDrawRectangle = false; | |||
} | |||
void HMIDocument::startDrawingRectangle() | |||
{ | |||
m_canDrawEllipse = false; | |||
m_canDrawRectangle = true; | |||
} | |||
bool HMIDocument::saveToFile(const QString &filePath) | |||
{ | |||
QFile file(filePath); | |||
if (!file.open(QIODevice::WriteOnly)) { | |||
qWarning() << "无法打开文件进行写入:" << filePath; | |||
return false; | |||
} | |||
// 创建JSON文档结构 | |||
QJsonObject docObject; | |||
docObject["title"] = m_title; | |||
docObject["sceneRect"] = QJsonArray({ | |||
m_scene->sceneRect().x(), | |||
m_scene->sceneRect().y(), | |||
m_scene->sceneRect().width(), | |||
m_scene->sceneRect().height() | |||
}); | |||
docObject["backgroundColor"] = m_scene->backgroundBrush().color().name(); | |||
// 序列化所有图形项 | |||
QJsonArray itemsArray; | |||
foreach (QGraphicsItem *item, m_scene->items()) { | |||
QJsonObject itemJson = itemToJson(item); | |||
if (!itemJson.isEmpty()) { | |||
itemsArray.append(itemJson); | |||
} | |||
} | |||
docObject["items"] = itemsArray; | |||
// 写入文件 | |||
QJsonDocument doc(docObject); | |||
file.write(doc.toJson()); | |||
file.close(); | |||
// 更新文档状态 | |||
setFilePath(filePath); | |||
setModified(false); | |||
QFileInfo fileInfo(filePath); | |||
setTitle(fileInfo.baseName()); | |||
return true; | |||
} | |||
// 从文件加载文档 | |||
bool HMIDocument::loadFromFile(const QString &filePath) | |||
{ | |||
QFile file(filePath); | |||
if (!file.open(QIODevice::ReadOnly)) { | |||
qWarning() << "无法打开文件进行读取:" << filePath; | |||
return false; | |||
} | |||
// 读取JSON文档 | |||
QByteArray data = file.readAll(); | |||
QJsonDocument doc = QJsonDocument::fromJson(data); | |||
if (doc.isNull()) { | |||
qWarning() << "无效的JSON文档:" << filePath; | |||
return false; | |||
} | |||
// 解析文档 | |||
QJsonObject docObject = doc.object(); | |||
m_title = docObject["title"].toString(); | |||
// 设置场景属性 | |||
QJsonArray sceneRectArray = docObject["sceneRect"].toArray(); | |||
if (sceneRectArray.size() == 4) { | |||
QRectF sceneRect( | |||
sceneRectArray[0].toDouble(), | |||
sceneRectArray[1].toDouble(), | |||
sceneRectArray[2].toDouble(), | |||
sceneRectArray[3].toDouble() | |||
); | |||
m_scene->setSceneRect(sceneRect); | |||
} | |||
if (docObject.contains("backgroundColor")) { | |||
QColor bgColor(docObject["backgroundColor"].toString()); | |||
m_scene->setBackgroundBrush(QBrush(bgColor)); | |||
} | |||
// 清除现有内容 | |||
m_scene->clear(); | |||
// 加载所有图形项 | |||
QJsonArray itemsArray = docObject["items"].toArray(); | |||
foreach (QJsonValue itemValue, itemsArray) { | |||
QJsonObject itemJson = itemValue.toObject(); | |||
QGraphicsItem *item = jsonToItem(itemJson); | |||
if (item) { | |||
m_scene->addItem(item); | |||
} | |||
} | |||
// 更新文档状态 | |||
setFilePath(filePath); | |||
setModified(false); | |||
return true; | |||
} | |||
// 将图形项转换为JSON对象 | |||
QJsonObject HMIDocument::itemToJson(QGraphicsItem *item) | |||
{ | |||
QJsonObject json; | |||
// 基础属性 | |||
json["type"] = item->type(); | |||
json["x"] = item->x(); | |||
json["y"] = item->y(); | |||
json["zValue"] = item->zValue(); | |||
json["visible"] = item->isVisible(); | |||
// 具体图形项属性 | |||
if (auto ellipse = dynamic_cast<ResizableEllipse*>(item)) { | |||
json["itemType"] = "ellipse"; | |||
json["rect"] = QJsonArray({ | |||
ellipse->rect().x(), | |||
ellipse->rect().y(), | |||
ellipse->rect().width(), | |||
ellipse->rect().height() | |||
}); | |||
json["onColor"] = ellipse->onColor().name(); | |||
json["offColor"] = ellipse->offColor().name(); | |||
json["currentColor"] = ellipse->brush().color().name(); | |||
json["penColor"] = ellipse->pen().color().name(); | |||
json["penWidth"] = ellipse->pen().width(); | |||
json["name"] = ellipse->name(); | |||
} | |||
else if (auto rect = dynamic_cast<ResizableRectangle*>(item)) { | |||
json["itemType"] = "rectangle"; | |||
json["rect"] = QJsonArray({ | |||
rect->rect().x(), | |||
rect->rect().y(), | |||
rect->rect().width(), | |||
rect->rect().height() | |||
}); | |||
json["pressedColor"] = rect->pressedColor().name(); | |||
json["releasedColor"] = rect->releasedColor().name(); | |||
json["currentColor"] = rect->brush().color().name(); | |||
json["penColor"] = rect->pen().color().name(); | |||
json["penWidth"] = rect->pen().width(); | |||
json["name"] = rect->name(); | |||
} | |||
return json; | |||
} | |||
// 从JSON对象创建图形项 | |||
QGraphicsItem *HMIDocument::jsonToItem(const QJsonObject &json) | |||
{ | |||
QString itemType = json["itemType"].toString(); | |||
QGraphicsItem *item = nullptr; | |||
if (itemType == "ellipse") { | |||
QJsonArray rectArray = json["rect"].toArray(); | |||
if (rectArray.size() != 4) return nullptr; | |||
ResizableEllipse *ellipse = new ResizableEllipse( | |||
rectArray[0].toDouble(), | |||
rectArray[1].toDouble(), | |||
rectArray[2].toDouble(), | |||
rectArray[3].toDouble() | |||
); | |||
ellipse->setOnColor(QColor(json["onColor"].toString())); | |||
ellipse->setOffColor(QColor(json["offColor"].toString())); | |||
ellipse->setBrush(QColor(json["currentColor"].toString())); | |||
ellipse->setPen(QPen( | |||
QColor(json["penColor"].toString()), | |||
json["penWidth"].toDouble() | |||
)); | |||
ellipse->setName(json["name"].toString()); | |||
item = ellipse; | |||
} | |||
else if (itemType == "rectangle") { | |||
QJsonArray rectArray = json["rect"].toArray(); | |||
if (rectArray.size() != 4) return nullptr; | |||
ResizableRectangle *rect = new ResizableRectangle( | |||
rectArray[0].toDouble(), | |||
rectArray[1].toDouble(), | |||
rectArray[2].toDouble(), | |||
rectArray[3].toDouble() | |||
); | |||
rect->setPressedColor(QColor(json["pressedColor"].toString())); | |||
rect->setReleasedColor(QColor(json["releasedColor"].toString())); | |||
rect->setBrush(QColor(json["currentColor"].toString())); | |||
rect->setPen(QPen( | |||
QColor(json["penColor"].toString()), | |||
json["penWidth"].toDouble() | |||
)); | |||
rect->setName(json["name"].toString()); | |||
item = rect; | |||
} | |||
// 设置基础属性 | |||
if (item) { | |||
item->setX(json["x"].toDouble()); | |||
item->setY(json["y"].toDouble()); | |||
item->setZValue(json["zValue"].toDouble()); | |||
item->setVisible(json["visible"].toBool()); | |||
} | |||
return item; | |||
} | |||
QString HMIDocument::title() const { return m_title; } | |||
void HMIDocument::setTitle(const QString &title) { m_title = title; } | |||
QGraphicsView *HMIDocument::view() const { return m_view; } | |||
QGraphicsScene *HMIDocument::scene() const { return m_scene; } | |||
void HMIDocument::setDrawEllipse(bool enable) { m_canDrawEllipse = enable; } | |||
void HMIDocument::setDrawRectangle(bool enable) { m_canDrawRectangle = enable; } | |||
void HMIDocument::markModified() { setModified(true); } |
@@ -0,0 +1,74 @@ | |||
#ifndef HMIDOCUMENT_H | |||
#define HMIDOCUMENT_H | |||
#include "basedocument.h" | |||
#include <QGraphicsScene> | |||
#include <QGraphicsView> | |||
#include <QJsonObject> | |||
// 前向声明 | |||
class ResizableRectangle; | |||
class ResizableEllipse; | |||
class NamedItem; | |||
class HMIDocument : public BaseDocument | |||
{ | |||
Q_OBJECT | |||
public: | |||
explicit HMIDocument(QWidget *parent = nullptr); | |||
~HMIDocument() override; | |||
QString title() const override; | |||
void setTitle(const QString &title); | |||
QGraphicsView *view() const; | |||
QGraphicsScene *scene() const; | |||
// 绘图控制 | |||
void setDrawEllipse(bool enable); | |||
void setDrawRectangle(bool enable); | |||
// 编辑功能 | |||
void copySelectedItems(); | |||
void pasteItems(); | |||
void deleteSelectedItems(); | |||
void showItemProperties(); | |||
void startDrawingEllipse(); | |||
void startDrawingRectangle(); | |||
// 保存/加载 | |||
bool saveToFile(const QString &filePath) override; | |||
bool loadFromFile(const QString &filePath) override; | |||
void markModified(); | |||
signals: | |||
void titleChanged(const QString &title); | |||
protected: | |||
bool eventFilter(QObject *obj, QEvent *event) override; | |||
private: | |||
// 辅助方法 | |||
void createShape(const QString& type, const QPointF &pos); | |||
void resetDrawFlags(); | |||
void showContextMenu(QPoint globalPos); | |||
// 序列化相关 | |||
QByteArray serializeItem(QGraphicsItem *item); | |||
QGraphicsItem *deserializeItem(const QByteArray &data); | |||
QJsonObject itemToJson(QGraphicsItem *item); | |||
QGraphicsItem *jsonToItem(const QJsonObject &json); | |||
// 成员变量 | |||
QString m_title; | |||
QGraphicsScene *m_scene; | |||
QGraphicsView *m_view; | |||
bool m_canDrawEllipse; | |||
bool m_canDrawRectangle; | |||
// 复制粘贴缓冲区 | |||
QList<QByteArray> m_copiedItemsData; | |||
QPointF m_lastPastePos; | |||
}; | |||
#endif // HMIDOCUMENT_H |
@@ -1,4 +1,6 @@ | |||
#include "mainwindow.h" | |||
#include "hmidocument.h" | |||
#include "plcdocument.h" | |||
#include <QMenuBar> | |||
#include <QAction> | |||
#include <QToolButton> | |||
@@ -10,19 +12,24 @@ | |||
#include <QDragEnterEvent> | |||
#include <QDragMoveEvent> | |||
#include <QDropEvent> | |||
#include <QDebug> | |||
#include <QVBoxLayout> | |||
#include <QFileDialog> | |||
#include <QMessageBox> | |||
#include <QTextEdit> | |||
MainWindow::MainWindow(QWidget *parent) | |||
: QMainWindow(parent) | |||
{ | |||
setWindowTitle("综合平台编程器"); | |||
setGeometry(100, 100, 1000, 700); | |||
setGeometry(500, 200, 1000, 700); | |||
// 初始化标签页 | |||
m_tabWidget = new QTabWidget(this); | |||
m_tabWidget->setTabsClosable(true); | |||
setCentralWidget(m_tabWidget); | |||
connect(m_tabWidget, &QTabWidget::currentChanged, this, &MainWindow::onTabChanged); | |||
connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &MainWindow::onCloseTab); | |||
// 创建菜单和工具栏 | |||
createMenus(); | |||
createToolbars(); | |||
@@ -34,29 +41,45 @@ MainWindow::~MainWindow() | |||
} | |||
// 创建菜单栏 | |||
// 在createMenus()函数中添加以下代码,用于创建操作菜单及其功能 | |||
void MainWindow::createMenus() | |||
{ | |||
// 创建文件菜单(保持原有代码) | |||
// 创建文件菜单 | |||
QMenu *fileMenu = menuBar()->addMenu("文件"); | |||
QMenu *editMenu = menuBar()->addMenu("操作"); | |||
QMenu *simulationMenu = menuBar()->addMenu("仿真"); | |||
// 新建HMI动作(保持原有代码) | |||
// 新建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动作(保持原有代码) | |||
// 新建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); | |||
// 打开动作 | |||
m_openAction = new QAction("打开(&O)", this); | |||
m_openAction->setShortcut(QKeySequence::Open); | |||
m_openAction->setStatusTip("打开现有文档"); | |||
connect(m_openAction, &QAction::triggered, this, &MainWindow::onOpen); | |||
fileMenu->addAction(m_openAction); | |||
// 保存动作 | |||
m_saveAction = new QAction("保存(&S)", this); | |||
m_saveAction->setShortcut(QKeySequence::Save); | |||
m_saveAction->setStatusTip("保存当前文档"); | |||
connect(m_saveAction, &QAction::triggered, this, &MainWindow::onSave); | |||
fileMenu->addAction(m_saveAction); | |||
// 另存为动作 | |||
m_saveAsAction = new QAction("另存为(&A)", this); | |||
m_saveAsAction->setShortcut(QKeySequence::SaveAs); | |||
m_saveAsAction->setStatusTip("将文档另存为"); | |||
connect(m_saveAsAction, &QAction::triggered, this, &MainWindow::onSaveAs); | |||
fileMenu->addAction(m_saveAsAction); | |||
// 操作菜单 - 添加复制、粘贴、删除功能 | |||
QAction *copyAction = new QAction("复制(&C)", this); | |||
copyAction->setShortcut(QKeySequence::Copy); // 标准复制快捷键 Ctrl+C | |||
@@ -92,87 +115,278 @@ void MainWindow::createMenus() | |||
editMenu->addAction(deleteAction); | |||
} | |||
// 创建左侧工具栏 | |||
void MainWindow::createToolbars() | |||
{ | |||
m_leftToolBar = new QToolBar("绘图工具", this); | |||
m_leftToolBar = new QToolBar("绘图工具栏", this); | |||
addToolBar(Qt::LeftToolBarArea, m_leftToolBar); | |||
m_leftToolBar->setAllowedAreas(Qt::LeftToolBarArea); // 仅允许在左侧 | |||
m_leftToolBar->setFixedWidth(200); | |||
} | |||
// 更新工具栏(根据当前文档类型) | |||
void MainWindow::updateToolBar(BaseDocument *doc) | |||
{ | |||
// 清空现有工具 | |||
m_leftToolBar->clear(); | |||
m_leftToolBar->clear();// 清空现有工具 | |||
if (!doc) return; | |||
// HMI文档显示绘图工具 | |||
if (doc->type() == BaseDocument::HMI) | |||
if (doc->type() == BaseDocument::HMI)// HMI文档显示绘图工具 | |||
{ | |||
HMIDocument *hmiDoc = dynamic_cast<HMIDocument*>(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->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||
ellipseBtn->setIcon(QIcon("../two/untitled/images/灯泡.png"));//可替换为实际图标 | |||
ellipseBtn->installEventFilter(this);//为按钮安装事件过滤器处理拖拽 | |||
ellipseBtn->setProperty("toolType", "指示灯"); | |||
m_leftToolBar->addWidget(ellipseBtn); | |||
ellipseBtn->setStyleSheet(R"( | |||
QToolButton { | |||
margin: 2 auto; | |||
background-color: #f0f0f0; | |||
border-radius: 10px; | |||
border: 1px solid #ccc; | |||
padding: 10px 12px; | |||
font-size: 18px; | |||
font-weight: bold; | |||
color: #333; | |||
} | |||
QToolButton:hover { | |||
background-color: #e0e0e0; | |||
} | |||
)"); | |||
// 画按钮按钮(支持拖拽) | |||
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->setText("按 钮"); | |||
rectBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||
rectBtn->setIcon(QIcon("../two/untitled/images/按钮.png"));//可替换为实际图标 | |||
rectBtn->installEventFilter(this); | |||
rectBtn->setProperty("toolType", "按钮"); | |||
m_leftToolBar->addWidget(rectBtn); | |||
rectBtn->setStyleSheet(R"( | |||
QToolButton { | |||
margin: 2 auto; | |||
background-color: #f0f0f0; | |||
border-radius: 10px; | |||
border: 1px solid #ccc; | |||
padding: 10px 15px; | |||
font-size: 18px; | |||
font-weight: bold; | |||
color: #333; | |||
} | |||
QToolButton:hover { | |||
background-color: #e0e0e0; | |||
} | |||
)"); | |||
} | |||
// PLC按钮工具 | |||
else if (doc->type() == BaseDocument::PLC) | |||
{ | |||
// 常开触点按钮 | |||
QToolButton *normallyOpenBtn = new QToolButton; | |||
normallyOpenBtn->setText("常开触点"); | |||
normallyOpenBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||
normallyOpenBtn->setIcon(QIcon("../two/untitled/images/T-常开触点-01.png")); // 替换为实际图标 | |||
normallyOpenBtn->installEventFilter(this); | |||
normallyOpenBtn->setProperty("toolType", "常开触点"); | |||
m_leftToolBar->addWidget(normallyOpenBtn); | |||
normallyOpenBtn->setStyleSheet(R"( | |||
QToolButton { | |||
margin: 2 auto; | |||
background-color: #f0f0f0; | |||
border-radius: 10px; | |||
border: 1px solid #ccc; | |||
padding: 10px 1px; | |||
font-size: 18px; | |||
font-weight: bold; | |||
color: #333; | |||
} | |||
QToolButton:hover { | |||
background-color: #e0e0e0; | |||
} | |||
)"); | |||
// 常闭触点按钮 | |||
QToolButton *normallyClosedBtn = new QToolButton; | |||
normallyClosedBtn->setText("常闭触点"); | |||
normallyClosedBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||
normallyClosedBtn->setIcon(QIcon("../two/untitled/images/T-常闭触点-01-01.png")); // 替换为实际图标 | |||
normallyClosedBtn->installEventFilter(this); | |||
normallyClosedBtn->setProperty("toolType", "常闭触点"); | |||
m_leftToolBar->addWidget(normallyClosedBtn); | |||
normallyClosedBtn->setStyleSheet(R"( | |||
QToolButton { | |||
margin: 2 auto; | |||
background-color: #f0f0f0; | |||
border-radius: 10px; | |||
border: 1px solid #ccc; | |||
padding: 10px 1px; | |||
font-size: 18px; | |||
font-weight: bold; | |||
color: #333; | |||
} | |||
QToolButton:hover { | |||
background-color: #e0e0e0; | |||
} | |||
)"); | |||
// 大于按钮 | |||
QToolButton *greaterThanBtn = new QToolButton; | |||
greaterThanBtn->setText("大于"); | |||
greaterThanBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||
greaterThanBtn->setIcon(QIcon("../two/untitled/images/大于号.png")); // 替换为实际图标 | |||
greaterThanBtn->installEventFilter(this); | |||
greaterThanBtn->setProperty("toolType", "大于"); | |||
m_leftToolBar->addWidget(greaterThanBtn); | |||
greaterThanBtn->setStyleSheet(R"( | |||
QToolButton { | |||
margin: 2 auto; | |||
background-color: #f0f0f0; | |||
border-radius: 10px; | |||
border: 1px solid #ccc; | |||
padding: 10px 20px; | |||
font-size: 18px; | |||
font-weight: bold; | |||
color: #333; | |||
} | |||
QToolButton:hover { | |||
background-color: #e0e0e0; | |||
} | |||
)"); | |||
// 大于等于按钮 | |||
QToolButton *greaterThanEqualBtn = new QToolButton; | |||
greaterThanEqualBtn->setText("大于等于"); | |||
greaterThanEqualBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||
greaterThanEqualBtn->setIcon(QIcon("../two/untitled/images/大于等于.png")); // 替换为实际图标 | |||
greaterThanEqualBtn->installEventFilter(this); | |||
greaterThanEqualBtn->setProperty("toolType", "大于等于"); | |||
m_leftToolBar->addWidget(greaterThanEqualBtn); | |||
greaterThanEqualBtn->setStyleSheet(R"( | |||
QToolButton { | |||
margin: 2 auto; | |||
background-color: #f0f0f0; | |||
border-radius: 10px; | |||
border: 1px solid #ccc; | |||
padding: 10px 1px; | |||
font-size: 18px; | |||
font-weight: bold; | |||
color: #333; | |||
} | |||
QToolButton:hover { | |||
background-color: #e0e0e0; | |||
} | |||
)"); | |||
// 小于按钮 | |||
QToolButton *lessThanBtn = new QToolButton; | |||
lessThanBtn->setText("小于"); | |||
lessThanBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||
lessThanBtn->setIcon(QIcon("../two/untitled/images/小于号.png")); // 替换为实际图标 | |||
lessThanBtn->installEventFilter(this); | |||
lessThanBtn->setProperty("toolType", "小于"); | |||
m_leftToolBar->addWidget(lessThanBtn); | |||
lessThanBtn->setStyleSheet(R"( | |||
QToolButton { | |||
margin: 2 auto; | |||
background-color: #f0f0f0; | |||
border-radius: 10px; | |||
border: 1px solid #ccc; | |||
padding: 10px 20px; | |||
font-size: 18px; | |||
font-weight: bold; | |||
color: #333; | |||
} | |||
QToolButton:hover { | |||
background-color: #e0e0e0; | |||
} | |||
)"); | |||
// 小于等于按钮 | |||
QToolButton *lessThanEqualBtn = new QToolButton; | |||
lessThanEqualBtn->setText("小于等于"); | |||
lessThanEqualBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||
lessThanEqualBtn->setIcon(QIcon("../two/untitled/images/小于等于.png")); // 替换为实际图标 | |||
lessThanEqualBtn->installEventFilter(this); | |||
lessThanEqualBtn->setProperty("toolType", "小于等于"); | |||
m_leftToolBar->addWidget(lessThanEqualBtn); | |||
lessThanEqualBtn->setStyleSheet(R"( | |||
QToolButton { | |||
margin: 2 auto; | |||
background-color: #f0f0f0; | |||
border-radius: 10px; | |||
border: 1px solid #ccc; | |||
padding: 10px 1px; | |||
font-size: 18px; | |||
font-weight: bold; | |||
color: #333; | |||
} | |||
QToolButton:hover { | |||
background-color: #e0e0e0; | |||
} | |||
)"); | |||
// PLC文档可添加自己的工具 | |||
else if (doc->type() == BaseDocument::PLC) { | |||
m_leftToolBar->addAction("常开触点"); | |||
m_leftToolBar->addAction("常闭触点"); | |||
// 线圈按钮 | |||
QToolButton *coilBtn = new QToolButton; | |||
coilBtn->setText("线圈"); | |||
coilBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); | |||
coilBtn->setIcon(QIcon("../two/untitled/images/线-圈-圈.png")); // 替换为实际图标 | |||
coilBtn->installEventFilter(this); | |||
coilBtn->setProperty("toolType", "小于等于"); | |||
m_leftToolBar->addWidget(coilBtn); | |||
coilBtn->setStyleSheet(R"( | |||
QToolButton { | |||
margin: 2 auto; | |||
background-color: #f0f0f0; | |||
border-radius: 10px; | |||
border: 1px solid #ccc; | |||
padding: 10px 20px; | |||
font-size: 18px; | |||
font-weight: bold; | |||
color: #333; | |||
} | |||
QToolButton:hover { | |||
background-color: #e0e0e0; | |||
} | |||
)"); | |||
} | |||
} | |||
// 事件过滤器处理拖拽 | |||
bool MainWindow::eventFilter(QObject *obj, QEvent *event) | |||
{ | |||
// 检查是否是工具栏按钮的鼠标按下事件 | |||
QToolButton *toolBtn = qobject_cast<QToolButton*>(obj); | |||
if (toolBtn && event->type() == QEvent::MouseButtonPress) | |||
{ | |||
QMouseEvent *me = static_cast<QMouseEvent*>(event); | |||
if (me->button() == Qt::LeftButton) | |||
{ | |||
// 获取工具类型 | |||
QString toolType = toolBtn->property("toolType").toString(); | |||
if (!toolType.isEmpty()) | |||
{ | |||
// 创建拖拽 | |||
BaseDocument* currentDoc = dynamic_cast<BaseDocument*>(m_tabWidget->currentWidget()); | |||
if (currentDoc && currentDoc->type() == BaseDocument::HMI) { | |||
HMIDocument* hmiDoc = static_cast<HMIDocument*>(currentDoc); | |||
// 设置对应的绘制模式 | |||
if (toolType == "指示灯") | |||
{ | |||
hmiDoc->startDrawingEllipse(); | |||
} | |||
else if (toolType == "按钮") | |||
{ | |||
hmiDoc->startDrawingRectangle(); | |||
} | |||
} | |||
// 创建拖拽对象 | |||
QMimeData *mime = new QMimeData; | |||
mime->setText(toolType); | |||
QDrag *drag = new QDrag(obj); | |||
QDrag *drag = new QDrag(toolBtn); | |||
drag->setMimeData(mime); | |||
drag->exec(Qt::CopyAction); | |||
return true; | |||
@@ -187,7 +401,7 @@ void MainWindow::onNewHMI() | |||
{ | |||
m_hmiCount++; | |||
HMIDocument *doc = new HMIDocument; | |||
doc->setTitle(QString("HMI文档 %1").arg(m_hmiCount)); | |||
doc->setTitle("HMI文档 " + QString::number(m_hmiCount)); | |||
m_tabWidget->addTab(doc, doc->title()); | |||
m_tabWidget->setCurrentWidget(doc); | |||
updateToolBar(doc); // 更新工具栏为HMI工具 | |||
@@ -206,10 +420,125 @@ void MainWindow::onNewPLC() | |||
// 标签页切换时更新工具栏 | |||
void MainWindow::onTabChanged(int idx) | |||
{ | |||
if (idx < 0) { | |||
if (idx < 0) | |||
{ | |||
updateToolBar(nullptr); | |||
return; | |||
} | |||
BaseDocument *doc = dynamic_cast<BaseDocument*>(m_tabWidget->widget(idx)); | |||
updateToolBar(doc); | |||
} | |||
// 保存文档 | |||
void MainWindow::onSave() | |||
{ | |||
BaseDocument *doc = dynamic_cast<BaseDocument*>(m_tabWidget->currentWidget()); | |||
if (!doc) return; | |||
if (doc->filePath().isEmpty()) { | |||
saveDocumentAs(doc); | |||
} else { | |||
saveDocument(doc); | |||
} | |||
} | |||
// 另存为文档 | |||
void MainWindow::onSaveAs() | |||
{ | |||
BaseDocument *doc = dynamic_cast<BaseDocument*>(m_tabWidget->currentWidget()); | |||
if (doc) { | |||
saveDocumentAs(doc); | |||
} | |||
} | |||
// 打开文档 | |||
void MainWindow::onOpen() | |||
{ | |||
QString filePath = QFileDialog::getOpenFileName( | |||
this, | |||
"打开文档", | |||
"", | |||
"HMI文档 (*.hmi);;PLC文档 (*.plc)" | |||
); | |||
if (filePath.isEmpty()) return; | |||
QFileInfo fileInfo(filePath); | |||
BaseDocument *doc = nullptr; | |||
if (fileInfo.suffix().toLower() == "hmi") { | |||
doc = new HMIDocument; | |||
} else if (fileInfo.suffix().toLower() == "plc") { | |||
doc = new PLCDocument; | |||
} else { | |||
QMessageBox::warning(this, "打开文档", "不支持的文件格式"); | |||
return; | |||
} | |||
if (doc->loadFromFile(filePath)) { | |||
m_tabWidget->addTab(doc, doc->title()); | |||
m_tabWidget->setCurrentWidget(doc); | |||
updateToolBar(doc); | |||
} else { | |||
QMessageBox::critical(this, "打开文档", "无法加载文档"); | |||
delete doc; | |||
} | |||
} | |||
// 关闭标签页 | |||
void MainWindow::onCloseTab(int index) | |||
{ | |||
BaseDocument *doc = dynamic_cast<BaseDocument*>(m_tabWidget->widget(index)); | |||
if (!doc) return; | |||
if (doc->isModified()) | |||
{ | |||
QMessageBox::StandardButton reply = QMessageBox::question( | |||
this, | |||
"保存文档", | |||
QString("文档 '%1' 已修改,是否保存更改?").arg(doc->title()), | |||
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel | |||
); | |||
if (reply == QMessageBox::Save) { | |||
saveDocument(doc); | |||
} else if (reply == QMessageBox::Cancel) { | |||
return; | |||
} | |||
} | |||
m_tabWidget->removeTab(index); | |||
delete doc; | |||
} | |||
// 保存文档 | |||
void MainWindow::saveDocument(BaseDocument *doc) | |||
{ | |||
if (doc->saveToFile(doc->filePath())) | |||
{ | |||
doc->setModified(false); // 清除修改状态 | |||
QMessageBox::information(this, "保存文档", "文档保存成功"); | |||
} else { | |||
QMessageBox::critical(this, "保存文档", "无法保存文档"); | |||
} | |||
} | |||
// 另存为文档 | |||
void MainWindow::saveDocumentAs(BaseDocument *doc) | |||
{ | |||
QString filePath = QFileDialog::getSaveFileName( | |||
this, | |||
"另存为", | |||
doc->filePath().isEmpty() ? doc->title() : doc->filePath(), | |||
doc->type() == BaseDocument::HMI ?"HMI文档 (*.hmi)" :"PLC文档 (*.plc)" | |||
); | |||
if (filePath.isEmpty()) return; | |||
if (doc->saveToFile(filePath)) { | |||
doc->setModified(false); // 清除修改状态 | |||
QMessageBox::information(this, "保存文档", "文档保存成功"); | |||
} else { | |||
QMessageBox::critical(this, "保存文档", "无法保存文档"); | |||
} | |||
} |
@@ -4,7 +4,8 @@ | |||
#include <QMainWindow> | |||
#include <QTabWidget> | |||
#include <QToolBar> | |||
#include "document.h" | |||
#include <QAction> | |||
#include "basedocument.h" | |||
QT_BEGIN_NAMESPACE | |||
namespace Ui { class MainWindow; } | |||
@@ -25,16 +26,27 @@ private slots: | |||
void onNewHMI(); // 新建HMI文档 | |||
void onNewPLC(); // 新建PLC文档 | |||
void onTabChanged(int idx); // 标签页切换时更新工具栏 | |||
void onSave(); // 保存文档 | |||
void onSaveAs(); // 另存为文档 | |||
void onOpen(); // 打开文档 | |||
void onCloseTab(int index); // 关闭标签页 | |||
private: | |||
void createMenus(); // 创建菜单栏 | |||
void createToolbars(); // 创建工具栏(左侧) | |||
void updateToolBar(BaseDocument *doc); // 根据文档类型更新工具栏 | |||
void saveDocument(BaseDocument *doc); // 保存文档 | |||
void saveDocumentAs(BaseDocument *doc); // 另存为文档 | |||
QTabWidget *m_tabWidget; // 多文档标签页 | |||
QToolBar *m_leftToolBar; // 左侧工具栏 | |||
int m_hmiCount = 0; // HMI文档计数器 | |||
int m_plcCount = 0; // PLC文档计数器 | |||
// 菜单动作 | |||
QAction *m_saveAction; | |||
QAction *m_saveAsAction; | |||
QAction *m_openAction; | |||
}; | |||
#endif // MAINWINDOW_H |
@@ -0,0 +1,88 @@ | |||
#include "plcdocument.h" | |||
#include <QGraphicsScene> | |||
#include <QGraphicsView> | |||
#include <QPainter> | |||
#include <QBrush> | |||
#include <QPen> | |||
#include <QFile> | |||
#include <QJsonDocument> | |||
#include <QJsonObject> | |||
#include <QJsonArray> | |||
#include <QFileInfo> | |||
#include <QVBoxLayout> | |||
PLCDocument::PLCDocument(QWidget *parent) | |||
: BaseDocument(PLC, parent) | |||
{ | |||
// 创建绘图场景 | |||
m_scene = new QGraphicsScene(this); | |||
m_scene->setSceneRect(0, 0, 800, 600); | |||
// 创建网格背景 | |||
createGridBackground(); | |||
// 创建视图 | |||
m_view = new QGraphicsView(m_scene, this); | |||
m_view->setRenderHint(QPainter::Antialiasing); | |||
m_view->setDragMode(QGraphicsView::RubberBandDrag); | |||
// 布局(让视图占满文档区域) | |||
auto layout = new QVBoxLayout(this); | |||
layout->setContentsMargins(0, 0, 0, 0); | |||
layout->addWidget(m_view); | |||
setLayout(layout); | |||
} | |||
PLCDocument::~PLCDocument() | |||
{ | |||
// 场景和视图由Qt自动销毁 | |||
} | |||
QString PLCDocument::title() const { | |||
return "PLC文档"; | |||
} | |||
void PLCDocument::createGridBackground() | |||
{ | |||
// 创建网格图案 | |||
createGridPattern(); | |||
// 设置场景背景 | |||
QBrush gridBrush(m_gridPattern); | |||
m_scene->setBackgroundBrush(gridBrush); | |||
} | |||
void PLCDocument::createGridPattern() | |||
{ | |||
// 网格大小 | |||
const int size = m_gridSize; | |||
// 创建网格图案 | |||
m_gridPattern = QPixmap(size * 2, size * 2); | |||
m_gridPattern.fill(Qt::white); | |||
QPainter painter(&m_gridPattern); | |||
painter.setPen(QPen(QColor(220, 220, 220), 1)); | |||
// 绘制网格线 | |||
painter.drawLine(0, size, size * 2, size); // 水平线 | |||
painter.drawLine(size, 0, size, size * 2); // 垂直线 | |||
// 绘制网格交点 | |||
painter.setPen(QPen(QColor(180, 180, 180), 1)); | |||
painter.drawPoint(0, 0); | |||
painter.drawPoint(size, size); | |||
painter.drawPoint(0, size * 2); | |||
painter.drawPoint(size * 2, 0); | |||
painter.drawPoint(size * 2, size * 2); | |||
} | |||
bool PLCDocument::saveToFile(const QString &filePath) | |||
{ | |||
} | |||
bool PLCDocument::loadFromFile(const QString &filePath) | |||
{ | |||
} |
@@ -0,0 +1,32 @@ | |||
#ifndef PLCDOCUMENT_H | |||
#define PLCDOCUMENT_H | |||
#include "basedocument.h" | |||
#include <QGraphicsScene> | |||
#include <QGraphicsView> | |||
class PLCDocument : public BaseDocument | |||
{ | |||
Q_OBJECT | |||
public: | |||
explicit PLCDocument(QWidget *parent = nullptr); | |||
~PLCDocument() override; | |||
QString title() const override; | |||
QGraphicsView *view() const { return m_view; } | |||
QGraphicsScene *scene() const { return m_scene; } | |||
bool saveToFile(const QString &filePath) override; | |||
bool loadFromFile(const QString &filePath) override; | |||
private: | |||
void createGridBackground(); | |||
void createGridPattern(); | |||
QGraphicsScene *m_scene; | |||
QGraphicsView *m_view; | |||
QPixmap m_gridPattern; // 网格图案缓存 | |||
int m_gridSize = 20; // 网格大小(像素) | |||
}; | |||
#endif // PLCDOCUMENT_H |
@@ -16,15 +16,19 @@ DEFINES += QT_DEPRECATED_WARNINGS | |||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 | |||
SOURCES += \ | |||
document.cpp \ | |||
basedocument.cpp \ | |||
graphicsitems.cpp \ | |||
hmidocument.cpp \ | |||
main.cpp \ | |||
mainwindow.cpp | |||
mainwindow.cpp \ | |||
plcdocument.cpp | |||
HEADERS += \ | |||
document.h \ | |||
basedocument.h \ | |||
graphicsitems.h \ | |||
mainwindow.h | |||
hmidocument.h \ | |||
mainwindow.h \ | |||
plcdocument.h | |||
FORMS += \ | |||
mainwindow.ui | |||