Browse Source

综合平台编程器PLC与HMI模块搭建

master
kai__wei 15 hours ago
parent
commit
01967242ef
14 changed files with 1162 additions and 0 deletions
  1. +262
    -0
      ComprehensivePlatformProgrammer/HMI/hmicontrolitem.cpp
  2. +93
    -0
      ComprehensivePlatformProgrammer/HMI/hmicontrolitem.h
  3. +330
    -0
      ComprehensivePlatformProgrammer/HMI/hmieditorwidget.cpp
  4. +51
    -0
      ComprehensivePlatformProgrammer/HMI/hmieditorwidget.h
  5. +11
    -0
      ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.cpp
  6. +12
    -0
      ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.h
  7. +63
    -0
      ComprehensivePlatformProgrammer/PLC/plccontrolitem.cpp
  8. +36
    -0
      ComprehensivePlatformProgrammer/PLC/plccontrolitem.h
  9. +49
    -0
      ComprehensivePlatformProgrammer/PLC/plceditorwidget.cpp
  10. +27
    -0
      ComprehensivePlatformProgrammer/PLC/plceditorwidget.h
  11. +11
    -0
      ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.cpp
  12. +11
    -0
      ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.h
  13. +171
    -0
      ComprehensivePlatformProgrammer/mainwindow.cpp
  14. +35
    -0
      ComprehensivePlatformProgrammer/mainwindow.h

+ 262
- 0
ComprehensivePlatformProgrammer/HMI/hmicontrolitem.cpp View File

@@ -0,0 +1,262 @@
#include "hmicontrolitem.h"
#include <QPainter>
#include <QGraphicsScene>
#include <QCursor>
#include <QMenu>
#include <QAction>
#include <QGraphicsView>
#include <QColorDialog>
#include <QDebug>
#include <QInputDialog>
#include "hmibutton.h"
#include "hmiled.h"
#include <QMessageBox>
// 静态剪贴板数据,初始化为空
QVariantMap HmiControlItem::m_clipboardData;

// 构造函数:初始化控件基本属性
HmiControlItem::HmiControlItem(QGraphicsItem* parent)
: QObject(nullptr), QGraphicsItem(parent), m_registerAddress(-1)
{
// 设置控件属性:可移动、可选择、发送几何变化通知
setFlags(QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemSendsGeometryChanges);
// 启用鼠标悬停事件
setAcceptHoverEvents(true);
// 设置变换原点为控件左上角
setTransformOriginPoint(0, 0);
}

// 检查鼠标是否在右下角缩放区域
// 返回:true(在缩放区域内),false(不在)
bool HmiControlItem::isInScaleArea(const QPointF& pos) {
QRectF contentRect = boundingRect().adjusted(1, 1, -1, -1);
return (pos.x() >= contentRect.width() - m_scaleAreaSize &&
pos.x() <= contentRect.width()) &&
(pos.y() >= contentRect.height() - m_scaleAreaSize &&
pos.y() <= contentRect.height());
}

// 鼠标悬停事件处理:根据鼠标位置设置光标样式
void HmiControlItem::hoverMoveEvent(QGraphicsSceneHoverEvent* event) {
if (isSelected()) {
// 如果控件被选中
if (isInScaleArea(event->pos())) {
// 鼠标在右下角缩放区域,显示缩放光标
setCursor(Qt::SizeFDiagCursor);
} else {
// 鼠标在控件其他区域,显示移动光标
setCursor(Qt::SizeAllCursor);
}
} else {
// 控件未选中,显示默认光标
setCursor(Qt::ArrowCursor);
}
QGraphicsItem::hoverMoveEvent(event);
}

// 鼠标按下事件处理:开始拖动或缩放
void HmiControlItem::mousePressEvent(QGraphicsSceneMouseEvent* event) {
if (isSelected() && isInScaleArea(event->pos())) {
// 选中控件且点击缩放区域,进入缩放模式
m_isScaling = true;
m_originalRect = boundingRect().adjusted(1, 1, -1, -1);
m_scaleStartPos = event->pos();
event->accept();
return;
}
// 记录拖动开始位置
m_dragStartPos = event->pos();
QGraphicsItem::mousePressEvent(event);
}

// 鼠标移动事件处理:处理控件拖动或缩放
void HmiControlItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
if (m_isScaling) {
// 缩放模式:根据鼠标移动调整控件大小
qreal deltaX = event->pos().x() - m_scaleStartPos.x();
QRectF currentRect = transform().mapRect(m_originalRect);
qreal newWidth = currentRect.width() + deltaX;
newWidth = qMax(m_minWidth, qMin(newWidth, m_maxWidth));
qreal aspectRatio = getAspectRatio();
qreal newHeight = newWidth / aspectRatio;
newHeight = qMax(m_minHeight, qMin(newHeight, m_maxHeight));
newWidth = newHeight * aspectRatio;
if (qAbs(newWidth - currentRect.width()) > 1 ||
qAbs(newHeight - currentRect.height()) > 1) {
// 更新控件大小,应用缩放变换
prepareGeometryChange();
qreal scaleX = newWidth / m_originalRect.width();
qreal scaleY = newHeight / m_originalRect.height();
resetTransform();
setTransform(QTransform::fromScale(scaleX, scaleY), false);
update();
}
event->accept();
return;
}
if (isSelected()) {
// 拖动模式:移动控件位置
QPointF delta = event->pos() - m_dragStartPos;
QPointF newPos = pos() + delta;
QRectF itemRect = transform().mapRect(boundingRect());
QRectF sceneRect = scene()->sceneRect();
// 限制控件在场景边界内
newPos.setX(qMax(sceneRect.left(), qMin(newPos.x(), sceneRect.right() - itemRect.width())));
newPos.setY(qMax(sceneRect.top(), qMin(newPos.y(), sceneRect.bottom() - itemRect.height())));
if (newPos.x() + itemRect.width() > sceneRect.right()) {
newPos.setX(sceneRect.right() - itemRect.width());
}
if (newPos.y() + itemRect.height() > sceneRect.bottom()) {
newPos.setY(sceneRect.bottom() - itemRect.height());
}
if (newPos.x() < sceneRect.left()) {
newPos.setX(sceneRect.left());
}
if (newPos.y() < sceneRect.top()) {
newPos.setY(sceneRect.top());
}
setPos(newPos);
if (scene()) {
// 更新场景和视图
scene()->update();
if (scene()->views().size() > 0) {
scene()->views().first()->viewport()->update();
}
}
event->accept();
return;
}
QGraphicsItem::mouseMoveEvent(event);
}

// 鼠标释放事件处理:结束缩放或拖动
void HmiControlItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
m_isScaling = false;
QGraphicsItem::mouseReleaseEvent(event);
}

// 右键菜单事件处理:显示控件特定的上下文菜单
void HmiControlItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
QMenu menu;
createContextMenu(menu); // 子类实现具体菜单项
menu.exec(event->screenPos());
event->accept();
}

// 删除控件:从场景中移除并释放内存
void HmiControlItem::deleteItem() {
if (scene()) {
scene()->removeItem(this);
}
delete this;
}

// 更改控件颜色:弹出颜色对话框选择ON或OFF状态颜色
void HmiControlItem::changeAppearanceColor(bool isOnState, QColor& onColor, QColor& offColor) {
QColor newColor = QColorDialog::getColor(
Qt::white,
nullptr,
isOnState ? "选择ON状态颜色" : "选择OFF状态颜色"
);
if (newColor.isValid()) {
if (isOnState) {
onColor = newColor;
} else {
offColor = newColor;
}
update();
}
}

// 设置寄存器绑定
// 设置寄存器绑定
bool HmiControlItem::setRegisterBinding(int address) {
if (address >= 0 && address <= 4000) {
m_registerAddress = address;
return true;
} else {
qDebug() << "Invalid register binding: address=" << address;
return false;
}
}
// 存储数据到剪贴板
void HmiControlItem::copyToClipboard(const QVariantMap& data) {
m_clipboardData = data;
qDebug() << "Copied to clipboard:" << data;
}

// 获取剪贴板数据
QVariantMap HmiControlItem::getClipboardData() {
return m_clipboardData;
}

// 检查剪贴板是否有数据
bool HmiControlItem::hasClipboardData() {
return !m_clipboardData.isEmpty();
}

// 从剪贴板数据创建控件,指定粘贴位置
HmiControlItem* HmiControlItem::createFromClipboard(const QVariantMap& data, const QPointF& pos, QGraphicsItem* parent) {
QString type = data.value("type").toString();
HmiControlItem* item = nullptr;
if (type == "按钮") {
// 创建按钮控件并恢复属性
item = new HmiButton(parent);
HmiButton* button = static_cast<HmiButton*>(item);
button->setButtonState(data.value("isButtonOn", false).toBool());
button->setOnColor(QColor(data.value("onColor").toString()));
button->setOffColor(QColor(data.value("offColor").toString()));
} else if (type == "指示灯") {
// 创建指示灯控件并恢复属性
item = new HmiLed(parent);
HmiLed* led = static_cast<HmiLed*>(item);
led->setOnColor(QColor(data.value("onColor").toString()));
led->setOffColor(QColor(data.value("offColor").toString()));
}
if (item) {
// 设置粘贴位置
item->setPos(pos);
// 恢复缩放
qreal scaleX = data.value("scaleX", 1.0).toDouble();
qreal scaleY = data.value("scaleY", 1.0).toDouble();
item->setTransform(QTransform::fromScale(scaleX, scaleY));
// 恢复寄存器绑定
item->setRegisterBinding(data.value("registerAddress", -1).toInt());
}
return item;
}

// 创建右键菜单(默认实现,子类可扩展)
void HmiControlItem::createContextMenu(QMenu& menu) {
// 默认添加"设置寄存器"选项
QAction* setRegisterAction = new QAction("设置寄存器", &menu);
QObject::connect(setRegisterAction, &QAction::triggered, [this]() {
bool ok;
// 显示当前绑定的寄存器地址作为默认值
int currentAddress = m_registerAddress >= 0 ? m_registerAddress : 0;
int address = QInputDialog::getInt(nullptr,
"输入寄存器地址",
QString("地址 (0-4000):\n当前地址: %1").arg(currentAddress),
currentAddress, 0, 4000, 1, &ok);
if (ok) {
if (!setRegisterBinding(address)) {
QMessageBox::warning(nullptr, "错误", "无效的寄存器地址");
}
}
});
menu.addAction(setRegisterAction);
}
// 存储控件数据到剪贴板(默认实现)
QVariantMap HmiControlItem::saveToClipboard() const
{
QVariantMap data;
data["type"] = getItemType();
data["posX"] = pos().x();
data["posY"] = pos().y();
data["scaleX"] = transform().m11();
data["scaleY"] = transform().m22();
data["registerAddress"] = m_registerAddress;
return data;
}

+ 93
- 0
ComprehensivePlatformProgrammer/HMI/hmicontrolitem.h View File

@@ -0,0 +1,93 @@
#pragma once
#include <QGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QColor>
#include <QVariantMap>

// HMI控件抽象基类,定义所有控件的通用接口和行为
class HmiControlItem : public QObject, public QGraphicsItem {
Q_OBJECT
Q_INTERFACES(QGraphicsItem) // 添加此行以支持 QGraphicsItem 接口
public:
// 构造函数,初始化控件基本属性(如可移动、可选择、接受鼠标悬停等)
explicit HmiControlItem(QGraphicsItem* parent = nullptr);

// 返回控件的边界矩形(纯虚函数,子类必须实现以定义控件的绘制区域)
virtual QRectF boundingRect() const override = 0;

// 绘制控件(纯虚函数,子类实现具体的绘制逻辑,如按钮或指示灯的外观)
virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override = 0;

// 获取控件类型(用于剪贴板,子类返回特定类型,如“按钮”或“指示灯”)
virtual QString getItemType() const = 0;

// 存储控件数据到剪贴板(虚函数,子类实现,保存位置、缩放、状态等)
virtual QVariantMap saveToClipboard() const;

// 从剪贴板数据创建控件,允许指定粘贴位置
// 参数:data(剪贴板数据),pos(粘贴位置),parent(父项)
static HmiControlItem* createFromClipboard(const QVariantMap& data, const QPointF& pos, QGraphicsItem* parent = nullptr);

// 删除控件,从场景中移除并释放内存
void deleteItem();

// 静态剪贴板操作
// 存储控件数据到静态剪贴板
static void copyToClipboard(const QVariantMap& data);
// 获取当前剪贴板数据
static QVariantMap getClipboardData();
// 检查剪贴板是否有有效数据
static bool hasClipboardData();

// 设置寄存器绑定
bool setRegisterBinding(int address);

// 获取寄存器地址
int getRegisterAddress() const { return m_registerAddress; }

protected:
// 获取子类的宽高比(用于缩放时保持比例,默认为2.0)
virtual qreal getAspectRatio() const { return m_aspectRatio; }

// 鼠标事件处理
// 处理鼠标按下事件(如开始拖动或缩放)
void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
// 处理鼠标移动事件(如拖动控件或调整大小)
void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
// 处理鼠标释放事件(结束拖动或缩放)
void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
// 处理鼠标悬停事件(设置光标样式,如缩放或拖动光标)
void hoverMoveEvent(QGraphicsSceneHoverEvent* event) override;
// 处理右键菜单事件(显示控件特定的上下文菜单)
void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override;

// 检查鼠标是否在右下角缩放区域(用于判断是否进入缩放模式)
virtual bool isInScaleArea(const QPointF& pos);

// 创建右键菜单(虚函数,子类实现特定菜单项,如复制、删除等)
virtual void createContextMenu(QMenu& menu);

// 更改控件外观颜色(通过颜色对话框选择ON或OFF状态颜色)
void changeAppearanceColor(bool isOnState, QColor& onColor, QColor& offColor);

// 缩放相关成员
bool m_isScaling = false; // 是否处于缩放模式
QRectF m_originalRect; // 缩放开始时的原始边界矩形
QPointF m_scaleStartPos; // 缩放开始时的鼠标位置
QPointF m_dragStartPos; // 拖动开始时的鼠标位置
const qreal m_aspectRatio = 2.0; // 默认宽高比
const qreal m_scaleAreaSize = 15.0; // 右下角缩放区域大小
const qreal m_minWidth = 60.0; // 最小宽度
const qreal m_maxWidth = 320.0; // 最大宽度
const qreal m_minHeight = 30.0; // 最小高度
const qreal m_maxHeight = 160.0; // 最大高度

// 寄存器绑定成员
int m_registerAddress; // 寄存器地址(0-4000)

private:
// 静态剪贴板数据,存储单个控件的属性(共享于所有控件实例)
static QVariantMap m_clipboardData;
};

+ 330
- 0
ComprehensivePlatformProgrammer/HMI/hmieditorwidget.cpp View File

@@ -0,0 +1,330 @@
#include "hmieditorwidget.h"
#include "hmiwidgetfactory.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QDebug>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QMenu>
#include <QTimer>
#include <QCoreApplication>
#include <QMetaObject>
#include "hmicontrolitem.h"

HmiEditorWidget::HmiEditorWidget(QWidget* parent)
: QWidget(parent)
{
QHBoxLayout* mainLayout = new QHBoxLayout(this);
mainLayout->setContentsMargins(10, 10, 10, 10);
mainLayout->setSpacing(10);

QVBoxLayout* leftLayout = new QVBoxLayout();
leftLayout->setSpacing(5);

hmiToolbar = new QListWidget(this);
hmiToolbar->setViewMode(QListWidget::IconMode);
hmiToolbar->setIconSize(QSize(48, 48));
hmiToolbar->setFixedWidth(100);
hmiToolbar->setSpacing(10);
hmiToolbar->setDragEnabled(false);

QListWidgetItem* buttonItem = new QListWidgetItem();
buttonItem->setIcon(QIcon(":resource/image/button.png"));
buttonItem->setData(Qt::UserRole, QString("按钮"));
buttonItem->setToolTip("按钮");
hmiToolbar->addItem(buttonItem);

QListWidgetItem* ledItem = new QListWidgetItem();
ledItem->setIcon(QIcon(":resource/image/light.png"));
ledItem->setData(Qt::UserRole, QString("指示灯"));
ledItem->setToolTip("指示灯");
hmiToolbar->addItem(ledItem);

leftLayout->addWidget(hmiToolbar);

m_newPageButton = new QPushButton("新建页面", this);
m_deletePageButton = new QPushButton("删除页面", this);
m_prevPageButton = new QPushButton("上一页", this);
m_nextPageButton = new QPushButton("下一页", this);
m_pageLabel = new QLabel("无页面", this);

leftLayout->addWidget(m_newPageButton);
leftLayout->addWidget(m_deletePageButton);
leftLayout->addWidget(m_prevPageButton);
leftLayout->addWidget(m_nextPageButton);
leftLayout->addWidget(m_pageLabel);
leftLayout->addStretch();

mainLayout->addLayout(leftLayout);

hmiEditArea = new QGraphicsView(this);
hmiEditArea->setRenderHint(QPainter::Antialiasing);
hmiEditArea->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
hmiEditArea->setOptimizationFlags(QGraphicsView::DontSavePainterState | QGraphicsView::DontAdjustForAntialiasing);
hmiEditArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
hmiEditArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
mainLayout->addWidget(hmiEditArea, 1);

hmiEditArea->installEventFilter(this);

connect(hmiToolbar, &QListWidget::itemClicked, this, &HmiEditorWidget::onToolbarItemClicked);
connect(m_newPageButton, &QPushButton::clicked, this, &HmiEditorWidget::onNewPageClicked);
connect(m_deletePageButton, &QPushButton::clicked, this, &HmiEditorWidget::onDeletePageClicked);
connect(m_prevPageButton, &QPushButton::clicked, this, &HmiEditorWidget::onPreviousPageClicked);
connect(m_nextPageButton, &QPushButton::clicked, this, &HmiEditorWidget::onNextPageClicked);

ensureViewReady();
}

HmiEditorWidget::~HmiEditorWidget()
{
clearPages();
}

void HmiEditorWidget::ensureViewReady()
{
QCoreApplication::processEvents();
hmiEditArea->resetTransform();
hmiEditArea->viewport()->update();

if (hmiEditArea->viewport()->rect().isEmpty()) {
hmiEditArea->setFixedSize(600, 400);
QCoreApplication::processEvents();
}
}

void HmiEditorWidget::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
if (m_firstShow && m_pages.isEmpty()) {
m_firstShow = false;
createNewPage();
}
}

void HmiEditorWidget::createNewPage()
{
QGraphicsScene* newScene = new QGraphicsScene(this);
QRect viewRect = hmiEditArea->viewport()->rect();

if (viewRect.isEmpty()) {
viewRect = QRect(0, 0, 800, 600);
hmiEditArea->setSceneRect(viewRect);
}

newScene->setSceneRect(viewRect);
m_pages.append(newScene);
m_currentPageIndex = m_pages.size() - 1;

QMetaObject::invokeMethod(this, [this, newScene]() {
hmiEditArea->setScene(newScene);
initViewScale();
updatePageLabel();
hmiEditArea->viewport()->update();
}, Qt::QueuedConnection);
}

void HmiEditorWidget::refreshView()
{
if (m_currentPageIndex >= 0 && !m_pages.isEmpty()) {
hmiEditArea->setScene(nullptr);
hmiEditArea->setScene(m_pages[m_currentPageIndex]);
initViewScale();
hmiEditArea->viewport()->update();
}
}

void HmiEditorWidget::initViewScale()
{
if (!hmiEditArea || m_currentPageIndex < 0 || m_pages.isEmpty()) {
return;
}

QRect viewRect = hmiEditArea->viewport()->rect();
if (viewRect.isEmpty()) {
viewRect = QRect(0, 0, 800, 600);
}

QGraphicsScene* currentScene = m_pages[m_currentPageIndex];
currentScene->setSceneRect(viewRect);
hmiEditArea->fitInView(viewRect, Qt::KeepAspectRatio);
hmiEditArea->viewport()->update();
}

void HmiEditorWidget::updatePageLabel()
{
if (m_pages.isEmpty()) {
m_pageLabel->setText("无页面");
m_deletePageButton->setEnabled(false);
m_prevPageButton->setEnabled(false);
m_nextPageButton->setEnabled(false);
} else {
m_pageLabel->setText(QString("第 %1/%2 页").arg(m_currentPageIndex + 1).arg(m_pages.size()));
m_deletePageButton->setEnabled(m_pages.size() > 1);
m_prevPageButton->setEnabled(m_currentPageIndex > 0);
m_nextPageButton->setEnabled(m_currentPageIndex < m_pages.size() - 1);
}
}

void HmiEditorWidget::switchToPage(int index)
{
if (index >= 0 && index < m_pages.size()) {
m_currentPageIndex = index;
hmiEditArea->resetTransform();
hmiEditArea->setScene(m_pages[index]);

QRectF sceneRect = m_pages[index]->sceneRect();
if (!sceneRect.isEmpty()) {
hmiEditArea->fitInView(sceneRect, Qt::KeepAspectRatio);
}

updatePageLabel();
QMetaObject::invokeMethod(this, [this]() {
hmiEditArea->viewport()->update();
}, Qt::QueuedConnection);
}
}

bool HmiEditorWidget::eventFilter(QObject* obj, QEvent* event)
{
if (obj == hmiEditArea && event->type() == QEvent::ContextMenu) {
QContextMenuEvent* contextEvent = static_cast<QContextMenuEvent*>(event);
if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex]) {
QGraphicsItem* item = hmiEditArea->itemAt(contextEvent->pos());
if (!item) {
QMenu menu;
QAction* pasteAction = new QAction("粘贴", &menu);
pasteAction->setEnabled(HmiControlItem::hasClipboardData());
QObject::connect(pasteAction, &QAction::triggered, [this, contextEvent]() {
if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex] && HmiControlItem::hasClipboardData()) {
QPointF scenePos = hmiEditArea->mapToScene(contextEvent->pos());
HmiControlItem* newItem = HmiControlItem::createFromClipboard(HmiControlItem::getClipboardData(), scenePos);
if (newItem) {
m_pages[m_currentPageIndex]->addItem(newItem);
m_pages[m_currentPageIndex]->clearSelection();
newItem->setSelected(true);
hmiEditArea->viewport()->update();
}
}
});
menu.addAction(pasteAction);
menu.exec(contextEvent->globalPos());
}
}
return true;
}
return QWidget::eventFilter(obj, event);
}

void HmiEditorWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
if (m_currentPageIndex >= 0 && !m_pages.isEmpty()) {
initViewScale();
}
}

void HmiEditorWidget::keyPressEvent(QKeyEvent* event)
{
if (event->matches(QKeySequence::Copy)) {
if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex]) {
auto selectedItems = m_pages[m_currentPageIndex]->selectedItems();
if (!selectedItems.isEmpty()) {
HmiControlItem* item = dynamic_cast<HmiControlItem*>(selectedItems.first());
if (item) {
HmiControlItem::copyToClipboard(item->saveToClipboard());
}
}
}
}
else if (event->matches(QKeySequence::Paste)) {
if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex] && HmiControlItem::hasClipboardData()) {
QRectF sceneRect = m_pages[m_currentPageIndex]->sceneRect();
QPointF centerPos(sceneRect.width() / 2, sceneRect.height() / 2);
HmiControlItem* newItem = HmiControlItem::createFromClipboard(HmiControlItem::getClipboardData(), centerPos);
if (newItem) {
m_pages[m_currentPageIndex]->addItem(newItem);
m_pages[m_currentPageIndex]->clearSelection();
newItem->setSelected(true);
hmiEditArea->viewport()->update();
}
}
}
else if (event->matches(QKeySequence::Delete)) {
if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex]) {
auto selectedItems = m_pages[m_currentPageIndex]->selectedItems();
for (QGraphicsItem* item : selectedItems) {
HmiControlItem* controlItem = dynamic_cast<HmiControlItem*>(item);
if (controlItem) {
controlItem->deleteItem();
}
}
hmiEditArea->viewport()->update();
}
}
QWidget::keyPressEvent(event);
}

void HmiEditorWidget::onToolbarItemClicked(QListWidgetItem* item)
{
if (!item || m_currentPageIndex < 0 || !m_pages[m_currentPageIndex]) return;

QString type = item->data(Qt::UserRole).toString();
HmiControlItem* newItem = nullptr;
if (type == "按钮") {
newItem = new HmiButton();
} else if (type == "指示灯") {
newItem = new HmiLed();
}

if (newItem) {
QRectF sceneRect = m_pages[m_currentPageIndex]->sceneRect();
newItem->setPos(sceneRect.width() / 2, sceneRect.height() / 2);
m_pages[m_currentPageIndex]->addItem(newItem);
m_pages[m_currentPageIndex]->clearSelection();
newItem->setSelected(true);
hmiEditArea->viewport()->update();
}
}

void HmiEditorWidget::onNewPageClicked()
{
createNewPage();
}

void HmiEditorWidget::onDeletePageClicked()
{
if (m_currentPageIndex >= 0 && m_pages.size() > 1) {
QGraphicsScene* scene = m_pages[m_currentPageIndex];
m_pages.removeAt(m_currentPageIndex);
delete scene;
m_currentPageIndex = qMin(m_currentPageIndex, m_pages.size() - 1);
hmiEditArea->setScene(m_pages[m_currentPageIndex]);
initViewScale();
updatePageLabel();
hmiEditArea->viewport()->update();
}
}

void HmiEditorWidget::onPreviousPageClicked()
{
switchToPage(m_currentPageIndex - 1);
}

void HmiEditorWidget::onNextPageClicked()
{
switchToPage(m_currentPageIndex + 1);
}

void HmiEditorWidget::clearPages()
{
for (QGraphicsScene* scene : m_pages) {
delete scene;
}
m_pages.clear();
m_currentPageIndex = -1;
hmiEditArea->setScene(nullptr);
updatePageLabel();
}

+ 51
- 0
ComprehensivePlatformProgrammer/HMI/hmieditorwidget.h View File

@@ -0,0 +1,51 @@
#pragma once
#include <QWidget>
#include <QListWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QLabel>
#include <QPushButton>
#include <QList>

class HmiEditorWidget : public QWidget {
Q_OBJECT
public:
explicit HmiEditorWidget(QWidget* parent = nullptr);
~HmiEditorWidget();

Q_INVOKABLE QList<QGraphicsScene*> getPages() const { return m_pages; }
Q_INVOKABLE int getCurrentPageIndex() const { return m_currentPageIndex; }
Q_INVOKABLE void clearPages();
Q_INVOKABLE void createNewPage();
Q_INVOKABLE void refreshView();
void switchToPage(int index);

protected:
void keyPressEvent(QKeyEvent* event) override;
bool eventFilter(QObject* obj, QEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void showEvent(QShowEvent* event) override;

private slots:
void onToolbarItemClicked(QListWidgetItem* item);
void onNewPageClicked();
void onDeletePageClicked();
void onPreviousPageClicked();
void onNextPageClicked();

private:
void initViewScale();
void updatePageLabel();
void ensureViewReady();

QListWidget* hmiToolbar;
QGraphicsView* hmiEditArea;
QList<QGraphicsScene*> m_pages;
int m_currentPageIndex = -1;
QLabel* m_pageLabel;
QPushButton* m_newPageButton;
QPushButton* m_deletePageButton;
QPushButton* m_prevPageButton;
QPushButton* m_nextPageButton;
bool m_firstShow = true;
};

+ 11
- 0
ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.cpp View File

@@ -0,0 +1,11 @@
#include "hmiwidgetfactory.h"

// 根据名称创建HMI控件
HmiControlItem* HmiWidgetFactory::createItem(const QString& name) {
if (name == "按钮") {
return new HmiButton();
} else if (name == "指示灯") {
return new HmiLed();
}
return nullptr; // 未知类型返回空
}

+ 12
- 0
ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.h View File

@@ -0,0 +1,12 @@
#pragma once
#include "hmibutton.h"
#include "hmiled.h"
#include <QString>

// HMI控件工厂类(静态类,用于创建HMI控件)
class HmiWidgetFactory {
public:
// 静态方法:根据名称创建对应的HMI控件
static HmiControlItem* createItem(const QString& name);
};


+ 63
- 0
ComprehensivePlatformProgrammer/PLC/plccontrolitem.cpp View File

@@ -0,0 +1,63 @@
#include "plccontrolitem.h"
#include <QPainter>
#include<QGraphicsScene>
PlcControlItem::PlcControlItem(PlcControlType type, QGraphicsItem* parent)
: QGraphicsItem(parent), m_type(type)
{
// 设置控件属性:可移动、可选择、发送几何变化信号
setFlags(QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemSendsGeometryChanges);

// 设置变换原点为控件中心(优化旋转/缩放时的渲染)
setTransformOriginPoint(boundingRect().center());
}

// 关键修复1:确保边界矩形足够大,包含所有绘制内容
QRectF PlcControlItem::boundingRect() const {
// 扩大边界1像素,避免边缘被裁剪(解决抗锯齿导致的边缘缺失)
return QRectF(0, 0, 100, 40).adjusted(-1, -1, 1, 1);
}

// 关键修复2:优化绘制逻辑,确保完整重绘
void PlcControlItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
// 启用抗锯齿(使边缘平滑)
painter->setRenderHint(QPainter::Antialiasing);

// 绘制控件背景
painter->setPen(Qt::black); // 边框颜色
//painter->setBrush(isSelected() ? Qt::gray : Qt::white); // 选中时变灰
painter->drawRect(0, 0, 100, 40); // 绘制主体矩形(使用原始尺寸)

// 绘制控件文本
QString label;
switch (m_type) {
case PlcControlType::NormallyOpen: label = "常开"; break;
case PlcControlType::NormallyClosed: label = "常闭"; break;
case PlcControlType::Coil: label = "线圈"; break;
case PlcControlType::Compare: label = "比较"; break;
}
painter->drawText(QRectF(0, 0, 100, 40), Qt::AlignCenter, label); // 文本居中
}

// 鼠标按下:记录初始位置(控件局部坐标)
void PlcControlItem::mousePressEvent(QGraphicsSceneMouseEvent* event) {
m_dragStartPos = event->pos();
event->accept();
QGraphicsItem::mousePressEvent(event);
}

// 鼠标移动:更新位置并强制重绘
void PlcControlItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
if (isSelected()) {
// 计算位移(基于控件局部坐标)
QPointF delta = event->pos() - m_dragStartPos;
// 移动控件
moveBy(delta.x(), delta.y());

// 关键修复3:主动触发场景重绘,确保新旧位置都刷新
scene()->update();
}
event->accept();
QGraphicsItem::mouseMoveEvent(event);
}

+ 36
- 0
ComprehensivePlatformProgrammer/PLC/plccontrolitem.h View File

@@ -0,0 +1,36 @@
#pragma once // 防止头文件重复包含

#include <QGraphicsItem> // 基类:图形项
#include <QGraphicsSceneMouseEvent> // 鼠标事件类

// PLC控件类型枚举
enum class PlcControlType {
NormallyOpen, // 常开
NormallyClosed, // 常闭
Coil, // 线圈
Compare // 比较
};

// PLC控件图形项类
class PlcControlItem : public QGraphicsItem {
public:
// 构造函数:type为控件类型,parent为父图形项
PlcControlItem(PlcControlType type, QGraphicsItem* parent = nullptr);

// 重写:返回边界矩形
QRectF boundingRect() const override;
// 重写:绘制图形项
void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) override;

protected:
// 重写:鼠标按下事件
void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
// 重写:鼠标移动事件
void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;

private:
// 控件类型
PlcControlType m_type;
// 拖拽起始位置
QPointF m_dragStartPos;
};

+ 49
- 0
ComprehensivePlatformProgrammer/PLC/plceditorwidget.cpp View File

@@ -0,0 +1,49 @@
#include "plceditorwidget.h"
#include "plcwidgetfactory.h"
#include <QHBoxLayout>
#include <QGraphicsScene>
#include <QGraphicsView>

PlcEditorWidget::PlcEditorWidget(QWidget* parent)
: QWidget(parent)
{
QHBoxLayout* layout = new QHBoxLayout(this);

// 工具栏初始化
plcToolbar = new QListWidget(this);
plcToolbar->addItem("常开");
plcToolbar->addItem("常闭");
plcToolbar->addItem("线圈");
plcToolbar->addItem("比较");
plcToolbar->setFixedWidth(100);

// === 画布(场景+视图)修复配置 ===
plcScene = new QGraphicsScene(this);
// 1. 设置明确的场景边界
plcScene->setSceneRect(0, 0, 1000, 800);

plcEditArea = new QGraphicsView(plcScene, this);
// 2. 禁用视图自身的拖拽模式
plcEditArea->setDragMode(QGraphicsView::NoDrag);
// 3. 禁用滚动条(可选)
plcEditArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
plcEditArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// 4. 确保坐标1:1映射
plcEditArea->setTransform(QTransform());
plcEditArea->setResizeAnchor(QGraphicsView::NoAnchor);

layout->addWidget(plcToolbar);
layout->addWidget(plcEditArea);

connect(plcToolbar, &QListWidget::itemClicked,
this, &PlcEditorWidget::onToolbarClicked);
}

void PlcEditorWidget::onToolbarClicked(QListWidgetItem* item) {
QString type = item->text();
auto controlItem = PlcWidgetFactory::createItem(type);
if (controlItem) {
controlItem->setPos(50, 50); // 固定初始位置
plcScene->addItem(controlItem);
}
}

+ 27
- 0
ComprehensivePlatformProgrammer/PLC/plceditorwidget.h View File

@@ -0,0 +1,27 @@
#pragma once // 防止头文件重复包含

#include <QWidget> // 基类:Qt窗口部件类
#include <QListWidget> // 工具栏:列表控件
#include <QGraphicsView> // 编辑区域:图形视图
#include <QGraphicsScene> // 编辑区域:图形场景

// PLC编辑器类,继承自QWidget
class PlcEditorWidget : public QWidget {
Q_OBJECT // 启用Qt元对象系统

public:
// 构造函数:parent为父窗口,默认为nullptr
explicit PlcEditorWidget(QWidget* parent = nullptr);

private slots:
// 工具栏点击事件的槽函数(响应控件添加请求)
void onToolbarClicked(QListWidgetItem* item);

private:
// PLC工具栏(显示“常开”“常闭”等选项)
QListWidget* plcToolbar;
// PLC编辑区域的视图
QGraphicsView* plcEditArea;
// PLC编辑区域的场景
QGraphicsScene* plcScene;
};

+ 11
- 0
ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.cpp View File

@@ -0,0 +1,11 @@
// 包含PLC工厂头文件
#include "plcwidgetfactory.h"

// 根据名称创建PLC控件
PlcControlItem* PlcWidgetFactory::createItem(const QString& name) {
if (name == "常开") return new PlcControlItem(PlcControlType::NormallyOpen);
if (name == "常闭") return new PlcControlItem(PlcControlType::NormallyClosed);
if (name == "线圈") return new PlcControlItem(PlcControlType::Coil);
if (name == "比较") return new PlcControlItem(PlcControlType::Compare);
return nullptr; // 未知名称返回空
}

+ 11
- 0
ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.h View File

@@ -0,0 +1,11 @@
#pragma once // 防止头文件重复包含

#include "plccontrolitem.h" // 包含PLC控件类
#include <QString> // 字符串类

// PLC控件工厂类
class PlcWidgetFactory {
public:
// 静态方法:根据名称创建PLC控件
static PlcControlItem* createItem(const QString& name);
};

+ 171
- 0
ComprehensivePlatformProgrammer/mainwindow.cpp View File

@@ -0,0 +1,171 @@
#include "mainwindow.h"
#include "modules/HMI/hmieditorwidget.h"
#include "modules/PLC/plceditorwidget.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QInputDialog>
#include <QTabBar>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setWindowTitle("综合平台编程器");
setFixedSize(1000, 740);
tabWidget = new QTabWidget(this);
setCentralWidget(tabWidget);
tabWidget->setTabsClosable(true);
connect(tabWidget, &QTabWidget::tabCloseRequested, this, &MainWindow::onTabCloseRequested);
tabWidget->setStyleSheet("QTabWidget { margin: 10px; }");

fileManager = new FileManager(this);
createMenus();
}

MainWindow::~MainWindow() {}

void MainWindow::createMenus() {
fileMenu = menuBar()->addMenu("文件(&F)");
newAction = new QAction("新建(&N)", this);
newAction->setShortcut(QKeySequence("Ctrl+N"));
connect(newAction, &QAction::triggered, this, &MainWindow::onNewFile);
fileMenu->addAction(newAction);

openAction = new QAction("打开(&O)", this);
openAction->setShortcut(QKeySequence("Ctrl+O"));
connect(openAction, &QAction::triggered, this, &MainWindow::onOpenFile);
fileMenu->addAction(openAction);

saveAction = new QAction("保存(&S)", this);
saveAction->setShortcut(QKeySequence("Ctrl+S"));
connect(saveAction, &QAction::triggered, this, &MainWindow::onSaveFile);
fileMenu->addAction(saveAction);

closeAction = new QAction("关闭(&C)", this);
closeAction->setShortcut(QKeySequence("Ctrl+W"));
connect(closeAction, &QAction::triggered, this, &MainWindow::onCloseFile);
fileMenu->addAction(closeAction);

simulateMenu = menuBar()->addMenu("仿真(&M)");
simulateAction = new QAction("启动仿真(&R)", this);
simulateAction->setShortcut(QKeySequence("F5"));
connect(simulateAction, &QAction::triggered, this, &MainWindow::onSimulate);
simulateMenu->addAction(simulateAction);
}

void MainWindow::onTabCloseRequested(int index)
{
if (index < 0 || index >= tabWidget->count()) return;
QWidget* widget = tabWidget->widget(index);
if (widget) {
tabWidget->removeTab(index);
delete widget;
}
}

void MainWindow::onCloseFile()
{
int currentIndex = tabWidget->currentIndex();
if (currentIndex >= 0) {
onTabCloseRequested(currentIndex);
}
}

void MainWindow::onNewFile() {
QStringList types = {"HMI文件", "PLC文件"};
bool ok;
QString type = QInputDialog::getItem(this, "选择文件类型", "请选择要创建的文件类型:", types, 0, false, &ok);
if (ok && !type.isEmpty()) {
if (type == "HMI文件") {
HmiEditorWidget* newHmi = new HmiEditorWidget(this);
tabWidget->addTab(newHmi, "HMI编辑器");
tabWidget->setCurrentWidget(newHmi);
qDebug() << "MainWindow: 创建新HMI编辑器,ID:" << newHmi;
} else if (type == "PLC文件") {
PlcEditorWidget* newPlc = new PlcEditorWidget(this);
tabWidget->addTab(newPlc, "PLC编辑器");
tabWidget->setCurrentWidget(newPlc);
qDebug() << "MainWindow: 创建新PLC编辑器,ID:" << newPlc;
}
}
}

void MainWindow::onOpenFile() {
QString filePath = QFileDialog::getOpenFileName(this, "打开文件", "", "HMI文件 (*.hmi);;PLC文件 (*.plc);;所有文件 (*)");
if (!filePath.isEmpty()) {
QFileInfo info(filePath);
if (info.suffix() == "hmi") {
HmiEditorWidget* hmi = new HmiEditorWidget(this);
if (fileManager->loadHmiFile(hmi, filePath)) {
tabWidget->addTab(hmi, info.fileName());
tabWidget->setCurrentWidget(hmi);
QMessageBox::information(this, "加载成功", "HMI文件已加载:" + filePath);
qDebug() << "MainWindow: 打开HMI文件,编辑器ID:" << hmi << "路径:" << filePath;
} else {
delete hmi;
QMessageBox::warning(this, "加载失败", "无法加载HMI文件:" + filePath);
}
} else if (info.suffix() == "plc") {
PlcEditorWidget* plc = new PlcEditorWidget(this);
if (fileManager->loadPlcFile(plc, filePath)) {
tabWidget->addTab(plc, info.fileName());
tabWidget->setCurrentWidget(plc);
QMessageBox::information(this, "加载成功", "PLC文件已加载:" + filePath);
qDebug() << "MainWindow: 打开PLC文件,编辑器ID:" << plc << "路径:" << filePath;
} else {
delete plc;
QMessageBox::warning(this, "加载失败", "无法加载PLC文件:" + filePath);
}
}
}
}

void MainWindow::onSaveFile()
{
QWidget* currentWidget = tabWidget->currentWidget();
if (!currentWidget) {
QMessageBox::warning(this, "保存失败", "没有打开的文件");
return;
}

QString filter;
bool isHmi = qobject_cast<HmiEditorWidget*>(currentWidget) != nullptr;
bool isPlc = qobject_cast<PlcEditorWidget*>(currentWidget) != nullptr;

if (isHmi) {
filter = "HMI文件 (*.hmi)";
} else if (isPlc) {
filter = "PLC文件 (*.plc)";
} else {
QMessageBox::warning(this, "保存失败", "无法识别的文件类型");
return;
}

QString filePath = QFileDialog::getSaveFileName(this, "保存文件", "", filter);
if (!filePath.isEmpty()) {
bool success = false;
if (isHmi) {
HmiEditorWidget* hmi = qobject_cast<HmiEditorWidget*>(currentWidget);
success = fileManager->saveHmiFile(hmi, filePath);
qDebug() << "MainWindow: 保存HMI文件,编辑器ID:" << hmi << "路径:" << filePath << "结果:" << success;
} else if (isPlc) {
PlcEditorWidget* plc = qobject_cast<PlcEditorWidget*>(currentWidget);
success = fileManager->savePlcFile(plc, filePath);
qDebug() << "MainWindow: 保存PLC文件,编辑器ID:" << plc << "路径:" << filePath << "结果:" << success;
}

if (success) {
QMessageBox::information(this, "保存成功", "文件已保存到:" + filePath);
int currentIndex = tabWidget->currentIndex();
if (currentIndex >= 0) {
tabWidget->setTabText(currentIndex, QFileInfo(filePath).fileName());
}
} else {
QMessageBox::warning(this, "保存失败", "无法保存文件:" + filePath);
}
}
}

void MainWindow::onSimulate() {
QMessageBox::information(this, "仿真", "启动仿真功能\n(实际实现需要根据业务逻辑开发)");
}

+ 35
- 0
ComprehensivePlatformProgrammer/mainwindow.h View File

@@ -0,0 +1,35 @@
#pragma once
#include <QMainWindow>
#include <QTabWidget>
#include <QMenuBar>
#include <QMenu>
#include <QAction>
#include "filemanager.h"

class MainWindow : public QMainWindow {
Q_OBJECT

public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

private slots:
void onNewFile();
void onOpenFile();
void onSaveFile();
void onCloseFile();
void onTabCloseRequested(int index);
void onSimulate();

private:
void createMenus();
QTabWidget* tabWidget;
FileManager* fileManager;
QMenu* fileMenu;
QAction* newAction;
QAction* openAction;
QAction* saveAction;
QAction* closeAction;
QMenu* simulateMenu;
QAction* simulateAction;
};

Loading…
Cancel
Save