@@ -0,0 +1,117 @@ | |||
#include "customgraphics.h" | |||
#include <QPainter> | |||
#include <QGraphicsSceneMouseEvent> | |||
#include <QMenu> | |||
#include <QGraphicsScene> | |||
// HmiComponent 基类实现 | |||
HmiComponent::HmiComponent(QGraphicsItem *parent) : QGraphicsObject(parent) | |||
{ | |||
setFlag(QGraphicsItem::ItemIsMovable, true); | |||
setFlag(QGraphicsItem::ItemIsSelectable, true); | |||
setAcceptHoverEvents(true); | |||
} | |||
void HmiComponent::setColor(const QColor& color) { | |||
if (m_color != color) { | |||
m_color = color; | |||
update(); | |||
} | |||
} | |||
QColor HmiComponent::color() const { | |||
return m_color; | |||
} | |||
void HmiComponent::setComponentName(const QString& name) { | |||
m_componentName = name; | |||
} | |||
QString HmiComponent::componentName() const { | |||
return m_componentName; | |||
} | |||
void HmiComponent::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) | |||
{ | |||
Q_UNUSED(option); | |||
Q_UNUSED(widget); | |||
painter->setRenderHint(QPainter::Antialiasing); | |||
paintShape(painter); | |||
if (isSelected()) { | |||
painter->setBrush(Qt::NoBrush); | |||
painter->setPen(QPen(Qt::darkRed, 2, Qt::DashLine)); | |||
painter->drawRect(boundingRect()); | |||
} | |||
} | |||
void HmiComponent::mousePressEvent(QGraphicsSceneMouseEvent *event) | |||
{ | |||
// 右键不触发选中信号,留给 contextMenuEvent 处理 | |||
if (event->button() == Qt::LeftButton) { | |||
emit selected(this); | |||
} | |||
QGraphicsObject::mousePressEvent(event); | |||
} | |||
// 实现 contextMenuEvent | |||
void HmiComponent::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) | |||
{ | |||
// 右键点击时,先清空场景中的其他选中项,然后选中当前项 | |||
scene()->clearSelection(); | |||
setSelected(true); | |||
emit selected(this); // 同样可以发出选中信号,以便更新属性面板等 | |||
QMenu menu; | |||
QAction *copyAction = menu.addAction("复制"); | |||
QAction *deleteAction = menu.addAction("删除"); | |||
// 使用 connect 将菜单动作的触发连接到信号的发射,“复制”和“删除” | |||
connect(copyAction, &QAction::triggered, this, [this]() { | |||
emit copyRequested(this); | |||
}); | |||
connect(deleteAction, &QAction::triggered, this, [this]() { | |||
emit deleteRequested(this); | |||
}); | |||
// 在鼠标光标位置显示菜单 | |||
menu.exec(event->screenPos()); | |||
} | |||
// HmiButton 类实现 | |||
HmiButton::HmiButton(QGraphicsItem *parent) : HmiComponent(parent) { | |||
m_color = Qt::gray; | |||
m_componentName = "Button"; | |||
} | |||
QRectF HmiButton::boundingRect() const { | |||
return QRectF(0, 0, 80, 30); | |||
} | |||
void HmiButton::paintShape(QPainter *painter) { | |||
painter->setPen(Qt::NoPen); | |||
painter->setBrush(m_color); | |||
painter->drawRect(boundingRect()); | |||
} | |||
// HmiIndicator 类实现 | |||
HmiIndicator::HmiIndicator(QGraphicsItem *parent) : HmiComponent(parent) { | |||
m_color = Qt::green; | |||
m_componentName = "Indicator"; | |||
} | |||
QRectF HmiIndicator::boundingRect() const { | |||
return QRectF(0, 0, 30, 30); | |||
} | |||
void HmiIndicator::paintShape(QPainter *painter) { | |||
painter->setPen(Qt::NoPen); | |||
painter->setBrush(m_color); | |||
painter->drawEllipse(boundingRect()); | |||
} |
@@ -0,0 +1,72 @@ | |||
#ifndef CUSTOMGRAPHICS_H | |||
#define CUSTOMGRAPHICS_H | |||
#include <QGraphicsObject> | |||
// 只需要前向声明,因为头文件中只用到了指针 | |||
class QPainter; | |||
class QGraphicsSceneMouseEvent; | |||
class QGraphicsSceneContextMenuEvent; | |||
enum class ComponentType { | |||
Button, | |||
Indicator | |||
}; | |||
// HMI组件基类 | |||
class HmiComponent : public QGraphicsObject | |||
{ | |||
Q_OBJECT | |||
Q_PROPERTY(QColor color READ color WRITE setColor) | |||
Q_PROPERTY(QString componentName READ componentName WRITE setComponentName) | |||
signals: | |||
void selected(HmiComponent* item); | |||
// 为复制和删除请求添加信号 | |||
void copyRequested(HmiComponent* item); | |||
void deleteRequested(HmiComponent* item); | |||
public: | |||
explicit HmiComponent(QGraphicsItem *parent = nullptr); | |||
void setColor(const QColor& color); | |||
QColor color() const; | |||
void setComponentName(const QString& name); | |||
QString componentName() const; | |||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; | |||
protected: | |||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override; | |||
// 重写上下文菜单事件 | |||
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; | |||
virtual void paintShape(QPainter* painter) = 0; | |||
protected: | |||
QColor m_color; | |||
QString m_componentName; | |||
}; | |||
// 按钮类 | |||
class HmiButton : public HmiComponent | |||
{ | |||
public: | |||
HmiButton(QGraphicsItem *parent = nullptr); | |||
QRectF boundingRect() const override; | |||
protected: | |||
void paintShape(QPainter *painter) override; | |||
}; | |||
// 指示灯类 | |||
class HmiIndicator : public HmiComponent | |||
{ | |||
public: | |||
HmiIndicator(QGraphicsItem *parent = nullptr); | |||
QRectF boundingRect() const override; | |||
protected: | |||
void paintShape(QPainter *painter) override; | |||
}; | |||
#endif // CUSTOMGRAPHICS_H |
@@ -0,0 +1,148 @@ | |||
#include "customgraphicsscene.h" | |||
#include <QGraphicsSceneMouseEvent> | |||
#include <QGraphicsView> | |||
#include <QKeyEvent> | |||
#include <QGuiApplication> | |||
#include <QMenu> | |||
// 实现新的构造函数 | |||
CustomGraphicsScene::CustomGraphicsScene(QGraphicsView* view, QObject *parent) | |||
: QGraphicsScene(parent), m_mode(Normal), m_view(view) | |||
{ | |||
// 断言确保传入的 view 是有效的 | |||
Q_ASSERT(m_view); | |||
} | |||
void CustomGraphicsScene::setMode(Mode mode) | |||
{ | |||
if (m_mode == mode) return; | |||
m_mode = mode; | |||
// 根据模式设置光标 | |||
if (m_mode == CreateItem) { | |||
m_view->setCursor(Qt::CrossCursor); | |||
} else { | |||
m_view->setCursor(Qt::ArrowCursor); // 恢复为标准箭头光标 | |||
} | |||
} | |||
// setComponentTypeToCreate 函数无需改动 | |||
void CustomGraphicsScene::setComponentTypeToCreate(ComponentType type) | |||
{ | |||
m_componentTypeToCreate = type; | |||
} | |||
// mousePressEvent 函数无需改动 | |||
void CustomGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) | |||
{ | |||
if (mouseEvent->button() != Qt::LeftButton) { | |||
QGraphicsScene::mousePressEvent(mouseEvent); | |||
return; | |||
} | |||
if (m_mode == CreateItem) { | |||
HmiComponent* item = nullptr; | |||
switch (m_componentTypeToCreate) { | |||
case ComponentType::Button: | |||
item = new HmiButton(); | |||
break; | |||
case ComponentType::Indicator: | |||
item = new HmiIndicator(); | |||
break; | |||
} | |||
if (item) | |||
{ | |||
item->setPos(mouseEvent->scenePos()); | |||
if (item->scene() == nullptr) | |||
{ | |||
addItem(item); | |||
emit componentCreated(item); | |||
} | |||
else | |||
{ | |||
// 如果 item 已经被添加到场景中,则删除它,避免内存泄漏 | |||
delete item; | |||
} | |||
} | |||
setMode(Normal); | |||
} else { | |||
QGraphicsScene::mousePressEvent(mouseEvent); | |||
} | |||
} | |||
// mouseMoveEvent 函数无需改动 | |||
void CustomGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) | |||
{ | |||
QGraphicsScene::mouseMoveEvent(mouseEvent); | |||
} | |||
// 修改或新增 keyPressEvent | |||
void CustomGraphicsScene::keyPressEvent(QKeyEvent *keyEvent) | |||
{ | |||
// 退出创建模式 | |||
if (keyEvent->key() == Qt::Key_Escape && m_mode == CreateItem) { | |||
setMode(Normal); | |||
keyEvent->accept(); | |||
return; | |||
} | |||
// 处理删除、复制、粘贴 | |||
if (selectedItems().isEmpty()) { | |||
QGraphicsScene::keyPressEvent(keyEvent); | |||
return; | |||
} | |||
switch (keyEvent->key()) { | |||
case Qt::Key_Delete: | |||
emit deleteRequestFromScene(); | |||
keyEvent->accept(); | |||
break; | |||
case Qt::Key_C: | |||
if (keyEvent->modifiers() & Qt::ControlModifier) { | |||
emit copyRequestFromScene(); | |||
keyEvent->accept(); | |||
} | |||
break; | |||
case Qt::Key_V: | |||
if (keyEvent->modifiers() & Qt::ControlModifier) { | |||
// 获取最后一个鼠标事件的位置来确定粘贴位置 | |||
emit pasteRequestFromScene(views().first()->mapToScene(views().first()->mapFromGlobal(QCursor::pos()))); | |||
keyEvent->accept(); | |||
} | |||
break; | |||
default: | |||
break; | |||
} | |||
QGraphicsScene::keyPressEvent(keyEvent); | |||
} | |||
void CustomGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *contextMenuEvent) | |||
{ | |||
// 检查鼠标下方是否有图形项 | |||
QGraphicsItem *item = itemAt(contextMenuEvent->scenePos(), views().first()->transform()); | |||
if (!item) { | |||
// 如果点击在空白处,则显示我们的粘贴菜单 | |||
contextMenuEvent->accept(); | |||
QMenu menu; | |||
QAction *pasteAction = menu.addAction("粘贴"); | |||
// 将菜单动作连接到场景的粘贴请求信号 | |||
connect(pasteAction, &QAction::triggered, this, [this, contextMenuEvent]() { | |||
emit pasteRequestFromScene(contextMenuEvent->scenePos()); | |||
}); | |||
menu.exec(contextMenuEvent->screenPos()); | |||
} else { | |||
// 如果点击在图形项上,则将事件传递给该图形项处理 | |||
// (这会调用 HmiComponent::contextMenuEvent) | |||
QGraphicsScene::contextMenuEvent(contextMenuEvent); | |||
} | |||
} |
@@ -0,0 +1,46 @@ | |||
#ifndef CUSTOMGRAPHICSSCENE_H | |||
#define CUSTOMGRAPHICSSCENE_H | |||
#include <QGraphicsScene> | |||
#include "customgraphics.h" | |||
class QGraphicsSceneMouseEvent; | |||
class QGraphicsSceneContextMenuEvent; | |||
class QGraphicsView; | |||
class QKeyEvent; | |||
class CustomGraphicsScene : public QGraphicsScene | |||
{ | |||
Q_OBJECT | |||
public: | |||
enum Mode { Normal, CreateItem }; | |||
explicit CustomGraphicsScene(QGraphicsView* view, QObject *parent = nullptr); | |||
void setMode(Mode mode); | |||
void setComponentTypeToCreate(ComponentType type); | |||
signals: | |||
void componentCreated(HmiComponent* item); | |||
// 转发键盘操作请求 | |||
void copyRequestFromScene(); | |||
void pasteRequestFromScene(const QPointF& scenePos); // 传递粘贴位置 | |||
void deleteRequestFromScene(); | |||
protected: | |||
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) override; | |||
// 在这里添加下面这行代码 | |||
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override; | |||
void keyPressEvent(QKeyEvent *keyEvent) override; | |||
void contextMenuEvent(QGraphicsSceneContextMenuEvent *contextMenuEvent) override; | |||
private: | |||
Mode m_mode; | |||
ComponentType m_componentTypeToCreate; | |||
QGraphicsView* m_view; | |||
}; | |||
#endif // CUSTOMGRAPHICSSCENE_H |
@@ -0,0 +1,154 @@ | |||
#include "hmimodule.h" | |||
#include <QGraphicsScene> | |||
#include <QGraphicsView> // --- 确保包含 QGraphicsView 头文件 --- | |||
#include <QDateTime> | |||
// HMIModule 构造函数无需改动 | |||
HMIModule::HMIModule(Ui::MainWindow* ui, QObject *parent) | |||
: QObject{parent}, ui_(ui), m_scene(nullptr) | |||
{} | |||
// 封装设置图标和大小的通用函数 | |||
void HMIModule::setButtonIcon(QAbstractButton *button, const QString &iconPath) | |||
{ | |||
button->setIcon(QIcon(iconPath)); | |||
button->setIconSize(QSize(30, 30)); | |||
} | |||
void HMIModule::init() { | |||
setButtonIcon(ui_->button, ":/resource/image/button.png"); | |||
setButtonIcon(ui_->lamp, ":/resource/image/lamp.png"); | |||
setButtonIcon(ui_->addPage, ":/resource/image/add.png"); | |||
setButtonIcon(ui_->deletePage, ":/resource/image/delete.png"); | |||
setButtonIcon(ui_->prePage, ":/resource/image/pre.png"); | |||
setButtonIcon(ui_->nextPage, ":/resource/image/next.png"); | |||
if (!ui_->hmiGraphicsView->scene()) { | |||
// 创建场景时,将 view 指针传入 | |||
m_scene = new CustomGraphicsScene(ui_->hmiGraphicsView, this); | |||
m_scene->setSceneRect(0, 0, 800, 600); | |||
ui_->hmiGraphicsView->setScene(m_scene); | |||
} else { | |||
m_scene = qobject_cast<CustomGraphicsScene*>(ui_->hmiGraphicsView->scene()); | |||
Q_ASSERT(m_scene); | |||
} | |||
connect(ui_->button, &QPushButton::clicked, this, &HMIModule::prepareToCreateButton); | |||
connect(ui_->lamp, &QPushButton::clicked, this, &HMIModule::prepareToCreateIndicator); | |||
connect(m_scene, &CustomGraphicsScene::componentCreated, this, &HMIModule::onComponentCreated); | |||
// 连接来自场景的键盘事件信号 | |||
connect(m_scene, &CustomGraphicsScene::copyRequestFromScene, this, &HMIModule::onCopyRequested); | |||
connect(m_scene, &CustomGraphicsScene::pasteRequestFromScene, this, &HMIModule::onPasteRequested); | |||
connect(m_scene, &CustomGraphicsScene::deleteRequestFromScene, this, &HMIModule::onDeleteRequested); | |||
} | |||
// 辅助函数,用于设置新创建的组件 | |||
void HMIModule::setupNewComponent(HmiComponent* item) | |||
{ | |||
if (!item) return; | |||
// 连接所有需要的信号 | |||
connect(item, &HmiComponent::selected, this, &HMIModule::onComponentSelected); | |||
connect(item, &HmiComponent::copyRequested, this, &HMIModule::onCopyRequested); | |||
connect(item, &HmiComponent::deleteRequested, this, &HMIModule::onDeleteRequested); | |||
// 生成日志 | |||
QString currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); | |||
QString log = QString("[%1] 创建 %2 组件") | |||
.arg(currentTime) | |||
.arg(item->componentName()); | |||
emit logMessageGenerated(log); | |||
} | |||
// 准备创建按钮的槽函数 | |||
void HMIModule::prepareToCreateButton() | |||
{ | |||
m_scene->setComponentTypeToCreate(ComponentType::Button); | |||
m_scene->setMode(CustomGraphicsScene::Mode::CreateItem); | |||
} | |||
// 准备创建指示灯的槽函数 | |||
void HMIModule::prepareToCreateIndicator() | |||
{ | |||
m_scene->setComponentTypeToCreate(ComponentType::Indicator); | |||
m_scene->setMode(CustomGraphicsScene::Mode::CreateItem); | |||
} | |||
// 处理新创建组件的槽函数 | |||
void HMIModule::onComponentCreated(HmiComponent* item) | |||
{ | |||
setupNewComponent(item); | |||
} | |||
void HMIModule::onCopyRequested() | |||
{ | |||
QList<QGraphicsItem*> selected = m_scene->selectedItems(); | |||
if (selected.isEmpty()) return; | |||
// 只复制第一个选中的项目 | |||
HmiComponent* itemToCopy = qgraphicsitem_cast<HmiComponent*>(selected.first()); | |||
if (!itemToCopy) return; | |||
// 存储组件信息到 "剪贴板" | |||
if (dynamic_cast<HmiButton*>(itemToCopy)) { | |||
m_copiedType = ComponentType::Button; | |||
} else if (dynamic_cast<HmiIndicator*>(itemToCopy)) { | |||
m_copiedType = ComponentType::Indicator; | |||
} | |||
m_copiedColor = itemToCopy->color(); | |||
m_hasCopiedItem = true; | |||
// 日志 | |||
emit logMessageGenerated(QString("[%1] 复制组件: %2") | |||
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")) | |||
.arg(itemToCopy->componentName())); | |||
} | |||
void HMIModule::onPasteRequested(const QPointF& scenePos) | |||
{ | |||
if (!m_hasCopiedItem) return; | |||
HmiComponent* newItem = nullptr; | |||
if (m_copiedType == ComponentType::Button) { | |||
newItem = new HmiButton(); | |||
} else { | |||
newItem = new HmiIndicator(); | |||
} | |||
if (newItem) { | |||
newItem->setColor(m_copiedColor); | |||
// 为了避免完全重叠,给一个小的偏移量 | |||
newItem->setPos(scenePos + QPointF(10, 10)); | |||
// 使用辅助函数完成添加和信号连接 | |||
// 注意:setupNewComponent内部会再次生成“创建”日志,这里是合理的 | |||
setupNewComponent(newItem); | |||
} | |||
} | |||
void HMIModule::onDeleteRequested() | |||
{ | |||
QList<QGraphicsItem*> selected = m_scene->selectedItems(); | |||
if (selected.isEmpty()) return; | |||
// 日志 | |||
emit logMessageGenerated(QString("[%1] 删除 %2 个组件") | |||
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")) | |||
.arg(selected.count())); | |||
// 从后往前删,避免迭代器失效问题 | |||
for (int i = selected.count() - 1; i >= 0; --i) { | |||
QGraphicsItem* item = selected.at(i); | |||
m_scene->removeItem(item); | |||
delete item; // 释放内存 | |||
} | |||
} | |||
// 统一处理选中事件的槽函数,无需修改 | |||
void HMIModule::onComponentSelected(HmiComponent* item) | |||
{ | |||
// if (item) { | |||
// qDebug() << "Component selected:" << item->componentName() | |||
// << "Color:" << item->color(); | |||
// // 未来可以在这里将item对象传递给属性编辑器 | |||
// } | |||
} |
@@ -0,0 +1,49 @@ | |||
#ifndef HMIMODULE_H | |||
#define HMIMODULE_H | |||
#include <QObject> | |||
#include "ui_MainWindow.h" | |||
// #include "customgraphics.h" // 已被 customgraphicsscene.h 包含 | |||
#include "customgraphicsscene.h" // 引入新的场景头文件 | |||
class HMIModule : public QObject | |||
{ | |||
Q_OBJECT | |||
public: | |||
explicit HMIModule(Ui::MainWindow* ui, QObject *parent = nullptr); | |||
void setButtonIcon(QAbstractButton* button, const QString& iconPath); | |||
void init(); | |||
private slots: | |||
// 准备创建组件,而不是直接创建 | |||
void prepareToCreateButton(); | |||
void prepareToCreateIndicator(); | |||
// 当场景创建了一个组件后,进行后续处理 | |||
void onComponentCreated(HmiComponent* item); | |||
void onComponentSelected(HmiComponent* item); | |||
// 处理复制、粘贴、删除的槽函数 | |||
void onCopyRequested(); | |||
void onPasteRequested(const QPointF& scenePos); | |||
void onDeleteRequested(); | |||
// 一个用于发送日志消息的信号 | |||
signals: | |||
void logMessageGenerated(const QString& message); | |||
private: | |||
void setupNewComponent(HmiComponent* item); | |||
private: | |||
Ui::MainWindow* ui_; | |||
CustomGraphicsScene* m_scene; | |||
// 用于实现剪贴板功能的成员变量 | |||
ComponentType m_copiedType; | |||
QColor m_copiedColor; | |||
bool m_hasCopiedItem = false; | |||
}; | |||
#endif // HMIMODULE_H |
@@ -0,0 +1,36 @@ | |||
#include "mainwindow.h" | |||
#include <QToolBar> | |||
#include <QToolButton> | |||
#include <QVBoxLayout> | |||
#include <QButtonGroup> | |||
MainWindow::MainWindow(QWidget *parent) | |||
: QMainWindow(parent) | |||
, ui_(new Ui::MainWindow) | |||
{ | |||
ui_->setupUi(this); | |||
hmi_ = new HMIModule(ui_, this); | |||
hmi_->init(); | |||
initMainWindow(); | |||
// 连接 HMIModule 的信号到 MainWindow 的槽 | |||
connect(hmi_, &HMIModule::logMessageGenerated, this, &MainWindow::appendLog); | |||
} | |||
MainWindow::~MainWindow() | |||
{ | |||
delete ui_; | |||
} | |||
void MainWindow::initMainWindow() | |||
{ | |||
setWindowTitle("综合平台编辑器"); | |||
setWindowIcon(QIcon(":/resource/image/editor.png")); | |||
} | |||
// 实现槽函数 | |||
void MainWindow::appendLog(const QString& message) | |||
{ | |||
ui_->textEdit->append(message); | |||
} |
@@ -0,0 +1,32 @@ | |||
#ifndef MAINWINDOW_H | |||
#define MAINWINDOW_H | |||
#include <QMainWindow> | |||
#include "hmimodule.h" | |||
QT_BEGIN_NAMESPACE | |||
namespace Ui { | |||
class MainWindow; | |||
} | |||
QT_END_NAMESPACE | |||
class MainWindow : public QMainWindow | |||
{ | |||
Q_OBJECT | |||
public: | |||
MainWindow(QWidget *parent = nullptr); | |||
~MainWindow(); | |||
private: | |||
void initMainWindow(); | |||
// 一个用于接收日志并更新UI的槽函数 | |||
private slots: | |||
void appendLog(const QString& message); | |||
private: | |||
Ui::MainWindow *ui_; | |||
HMIModule* hmi_; | |||
}; | |||
#endif // MAINWINDOW_H |