@@ -53,6 +53,16 @@ void HmiComponent::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti | |||
painter->setPen(QPen(Qt::darkRed, 2, Qt::DashLine)); | |||
painter->drawRect(boundingRect()); | |||
} | |||
// 如果被选中,绘制缩放控制点 | |||
if (isSelected()) { | |||
QRectF rect = boundingRect(); | |||
// 绘制右下角缩放控制点 | |||
painter->setBrush(Qt::white); | |||
painter->setPen(QPen(Qt::black, 1)); | |||
painter->drawRect(rect.right() - 8, rect.bottom() - 8, 8, 8); | |||
} | |||
} | |||
void HmiComponent::setAddress(int address) | |||
@@ -68,9 +78,31 @@ int HmiComponent::address() const | |||
return m_address; | |||
} | |||
void HmiComponent::setSize(double width, double height) | |||
{ | |||
prepareGeometryChange(); | |||
m_width = width; | |||
m_height = height; | |||
update(); | |||
} | |||
void HmiComponent::mousePressEvent(QGraphicsSceneMouseEvent *event) | |||
{ | |||
// 右键不触发选中信号,留给 contextMenuEvent 处理 | |||
// 检查是否点击了缩放控制点 | |||
if (event->button() == Qt::LeftButton && isSelected()) { | |||
QRectF rect = boundingRect(); | |||
QRectF handleRect(rect.right() - 8, rect.bottom() - 8, 8, 8); | |||
if (handleRect.contains(event->pos())) { | |||
m_resizing = true; | |||
m_resizeStartPos = event->scenePos(); | |||
m_resizeStartSize = QSizeF(rect.width(), rect.height()); | |||
event->accept(); | |||
return; | |||
} | |||
} | |||
// 原有的选中处理 | |||
if (event->button() == Qt::LeftButton) { | |||
emit selected(this); | |||
} | |||
@@ -229,6 +261,24 @@ void HmiComponent::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) | |||
void HmiComponent::mouseMoveEvent(QGraphicsSceneMouseEvent *event) | |||
{ | |||
if (m_resizing) { | |||
// 计算尺寸变化 | |||
QPointF delta = event->scenePos() - m_resizeStartPos; | |||
double newWidth = qMax(20.0, m_resizeStartSize.width() + delta.x()); | |||
double newHeight = qMax(20.0, m_resizeStartSize.height() + delta.y()); | |||
// 更新组件大小 | |||
prepareGeometryChange(); | |||
// 子类需要实现setSize函数 | |||
setSize(newWidth, newHeight); | |||
// 更新缩放控制点位置 | |||
update(); | |||
event->accept(); | |||
return; | |||
} | |||
if (!(flags() & QGraphicsItem::ItemIsMovable)) { | |||
// 如果项不可移动,直接调用基类处理 | |||
QGraphicsObject::mouseMoveEvent(event); | |||
@@ -263,8 +313,8 @@ void HmiComponent::mouseMoveEvent(QGraphicsSceneMouseEvent *event) | |||
QRectF newBounds = bounds.translated(newPos); | |||
// 限制组件边界必须完全在视图可见区域内 | |||
qreal limitedX = newPos.x(); | |||
qreal limitedY = newPos.y(); | |||
double limitedX = newPos.x(); | |||
double limitedY = newPos.y(); | |||
if (newBounds.left() < visibleSceneRect.left()) | |||
limitedX += visibleSceneRect.left() - newBounds.left(); | |||
@@ -279,6 +329,30 @@ void HmiComponent::mouseMoveEvent(QGraphicsSceneMouseEvent *event) | |||
event->accept(); | |||
} | |||
void HmiComponent::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) | |||
{ | |||
if (m_resizing && event->button() == Qt::LeftButton) { | |||
m_resizing = false; | |||
event->accept(); | |||
return; | |||
} | |||
QGraphicsObject::mouseReleaseEvent(event); | |||
} | |||
void HmiComponent::hoverMoveEvent(QGraphicsSceneHoverEvent *event) | |||
{ | |||
QRectF rect = boundingRect(); | |||
QRectF handleRect(rect.right() - 8, rect.bottom() - 8, 8, 8); | |||
if (isSelected() && handleRect.contains(event->pos())) { | |||
setCursor(Qt::SizeFDiagCursor); | |||
m_resizeHandle = BottomRight; | |||
} else { | |||
setCursor(Qt::ArrowCursor); | |||
m_resizeHandle = None; | |||
} | |||
QGraphicsObject::hoverMoveEvent(event); | |||
} | |||
void HmiComponent::setOnColor(const QColor& color) { | |||
m_onColor = color; | |||
@@ -300,6 +374,9 @@ QColor HmiComponent::offColor() const { | |||
HmiButton::HmiButton(QGraphicsItem *parent) : HmiComponent(parent), m_isOn(false) | |||
{ | |||
// 按钮保持矩形形状 | |||
m_width = 65.0; | |||
m_height = 30.0; | |||
m_color = Qt::gray; | |||
m_offColor = m_color; // OFF状态颜色设为默认颜色 | |||
m_onColor = Qt::green; // ON状态颜色为绿色 | |||
@@ -307,6 +384,11 @@ HmiButton::HmiButton(QGraphicsItem *parent) : HmiComponent(parent), m_isOn(false | |||
setColor(m_offColor); // 当前显示OFF颜色 | |||
} | |||
void HmiButton::setSize(double width, double height) | |||
{ | |||
HmiComponent::setSize(width, height); | |||
} | |||
bool HmiButton::isOn() const | |||
{ | |||
return m_isOn; | |||
@@ -326,7 +408,7 @@ void HmiButton::setOn(bool on) | |||
QRectF HmiButton::boundingRect() const | |||
{ | |||
return QRectF(0, 0, 65, 30); | |||
return QRectF(0, 0, m_width, m_height); | |||
} | |||
void HmiButton::paintShape(QPainter *painter) | |||
@@ -338,6 +420,9 @@ void HmiButton::paintShape(QPainter *painter) | |||
HmiIndicator::HmiIndicator(QGraphicsItem *parent) : HmiComponent(parent) | |||
{ | |||
// 指示灯初始为圆形 | |||
m_width = 30.0; | |||
m_height = 30.0; | |||
m_color = Qt::red; | |||
m_offColor = m_color; // OFF状态颜色设为默认颜色 | |||
m_onColor = Qt::green; // ON状态颜色为绿色 | |||
@@ -345,11 +430,17 @@ HmiIndicator::HmiIndicator(QGraphicsItem *parent) : HmiComponent(parent) | |||
setColor(m_offColor); // 当前显示OFF颜色 | |||
} | |||
void HmiIndicator::setSize(double width, double height) | |||
{ | |||
HmiComponent::setSize(width, height); | |||
} | |||
QRectF HmiIndicator::boundingRect() const | |||
{ | |||
return QRectF(0, 0, 30, 30); | |||
return QRectF(0, 0, m_width, m_height); | |||
} | |||
void HmiIndicator::paintShape(QPainter *painter) | |||
{ | |||
painter->setPen(Qt::NoPen); | |||
@@ -7,8 +7,10 @@ | |||
class QPainter; | |||
class QGraphicsSceneMouseEvent; | |||
class QGraphicsSceneContextMenuEvent; | |||
class QGraphicsSceneHoverEvent; | |||
enum class ComponentType { | |||
enum class ComponentType | |||
{ | |||
Button, | |||
Indicator | |||
}; | |||
@@ -51,11 +53,16 @@ public: | |||
void setAddress(int address); | |||
int address() const; | |||
void setSize(double width, double height); | |||
QSizeF size() const { return QSizeF(m_width, m_height); } | |||
protected: | |||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override; | |||
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; | |||
virtual void paintShape(QPainter* painter) = 0; | |||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; | |||
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; | |||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; | |||
protected: | |||
QColor m_color; // 当前显示颜色(编辑器中为 OFF) | |||
@@ -63,6 +70,16 @@ protected: | |||
QColor m_offColor; | |||
QString m_componentName; | |||
int m_address = 0; | |||
// 新增尺寸成员 | |||
double m_width = 65.0; | |||
double m_height = 30.0; | |||
enum ResizeHandle { None, BottomRight }; // 缩放控制点位置 | |||
ResizeHandle m_resizeHandle = None; | |||
bool m_resizing = false; | |||
QPointF m_resizeStartPos; | |||
QSizeF m_resizeStartSize; | |||
}; | |||
@@ -73,6 +90,7 @@ class HmiButton : public HmiComponent | |||
public: | |||
HmiButton(QGraphicsItem *parent = nullptr); | |||
QRectF boundingRect() const override; | |||
void setSize(double width, double height); | |||
// 新增状态标志,true = ON,false = OFF | |||
bool isOn() const; | |||
@@ -91,6 +109,7 @@ class HmiIndicator : public HmiComponent | |||
public: | |||
HmiIndicator(QGraphicsItem *parent = nullptr); | |||
QRectF boundingRect() const override; | |||
void setSize(double width, double height); | |||
protected: | |||
void paintShape(QPainter *painter) override; | |||
@@ -113,6 +113,10 @@ bool HMIModule::saveToFile(const QString& filePath) | |||
compObj["address"] = component->address(); | |||
// 添加按钮状态 | |||
compObj["isOn"] = false; // 默认false | |||
// 保存尺寸 | |||
compObj["width"] = component->boundingRect().width(); | |||
compObj["height"] = component->boundingRect().height(); | |||
if (auto btn = dynamic_cast<HmiButton*>(component)) { | |||
compObj["isOn"] = btn->isOn(); | |||
} | |||
@@ -255,7 +259,13 @@ bool HMIModule::openFromFile(const QString& filePath) | |||
continue; | |||
} | |||
// 加载尺寸 | |||
double width = compObj["width"].toDouble(65); // 默认值 | |||
double height = compObj["height"].toDouble(30); // 默认值 | |||
if (newItem) { | |||
// 设置尺寸 | |||
newItem->setSize(width, height); | |||
newItem->setPos(QPointF(x, y)); | |||
QColor color(colorName); | |||
if (color.isValid()) { | |||
@@ -452,11 +462,14 @@ void HMIModule::onCopyRequested() | |||
} | |||
m_copiedColor = itemToCopy->color(); | |||
// 新增复制以下属性 | |||
// 复制以下属性 | |||
m_copiedOnColor = itemToCopy->onColor(); | |||
m_copiedOffColor = itemToCopy->offColor(); | |||
m_copiedComponentName = itemToCopy->componentName(); | |||
m_copiedAddress = itemToCopy->address(); | |||
// 复制尺寸 | |||
m_copiedWidth = itemToCopy->size().width(); | |||
m_copiedHeight = itemToCopy->size().height(); | |||
m_hasCopiedItem = true; | |||
@@ -488,6 +501,8 @@ void HMIModule::onPasteRequested(const QPointF& scenePos) | |||
newItem->setOffColor(m_copiedOffColor); | |||
newItem->setComponentName(m_copiedComponentName); | |||
newItem->setAddress(m_copiedAddress); | |||
// 设置尺寸 | |||
newItem->setSize(m_copiedWidth, m_copiedHeight); | |||
newItem->setPos(scenePos); | |||
currentScene->addItem(newItem); // 使用当前页面的 scene | |||
setupNewComponent(newItem); | |||
@@ -57,6 +57,9 @@ private: | |||
QColor m_copiedOffColor; | |||
QString m_copiedComponentName; | |||
int m_copiedAddress = 0; | |||
// 新增复制尺寸变量 | |||
double m_copiedWidth = 0.0; | |||
double m_copiedHeight = 0.0; | |||
bool m_hasCopiedItem = false; // 指示当前是否已经有组件被复制 | |||
int m_pageCount = 1; // 初始化页面计数器 | |||
@@ -19,26 +19,10 @@ MainWindow::MainWindow(QWidget *parent) | |||
// 连接 HMIModule 的信号到 MainWindow 的槽 | |||
connect(hmi_, &HMIModule::logMessageGenerated, this, &MainWindow::appendLog); | |||
// 新建菜单 | |||
connect(ui_->action, &QAction::triggered, this, &MainWindow::onNewFile); | |||
connect(ui_->action_3, &QAction::triggered, this, [this]() { | |||
QString fileName = QFileDialog::getSaveFileName(this, "保存HMI文件", "", "JSON文件 (*.hmi)"); | |||
if (!fileName.isEmpty()) { | |||
if (!hmi_->saveToFile(fileName)) { | |||
QMessageBox::warning(this, "保存失败", "文件保存失败!"); | |||
} | |||
} | |||
}); | |||
connect(ui_->action_2, &QAction::triggered, this, [this]() { | |||
QString fileName = QFileDialog::getOpenFileName(this, "打开HMI文件", "", "JSON文件 (*.hmi)"); | |||
if (!fileName.isEmpty()) { | |||
if (!hmi_->openFromFile(fileName)) { | |||
QMessageBox::warning(this, "打开失败", "文件打开失败或格式不正确!"); | |||
} | |||
} | |||
}); | |||
// 新建/打开/保存文件 | |||
connect(ui_->actionNew, &QAction::triggered, this, &MainWindow::onNewFile); | |||
connect(ui_->actionOpen, &QAction::triggered, this, &MainWindow::onOpenFile); | |||
connect(ui_->actionSave, &QAction::triggered, this, &MainWindow::onSaveFile); | |||
} | |||
MainWindow::~MainWindow() | |||
@@ -64,31 +48,64 @@ void MainWindow::appendLog(const QString& message) | |||
void MainWindow::onNewFile() | |||
{ | |||
if (!hmi_) | |||
{ | |||
return; | |||
} | |||
if (hmi_->isModified()) { | |||
if (hmi_->isModified()) | |||
{ | |||
auto ret = QMessageBox::warning(this, "提示", | |||
"当前文件有未保存的更改,是否保存?", | |||
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, | |||
QMessageBox::Save); | |||
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); | |||
if (ret == QMessageBox::Save) { | |||
QString fileName = QFileDialog::getSaveFileName(this, "保存HMI设计", "", "JSON文件 (*.json)"); | |||
if (!fileName.isEmpty()) { | |||
if (!hmi_->saveToFile(fileName)) { | |||
if (ret == QMessageBox::Save) | |||
{ | |||
QString fileName = QFileDialog::getSaveFileName(this, "保存HMI文件", "", "HMI文件 (*.json)"); | |||
if (!fileName.isEmpty()) | |||
{ | |||
if (!hmi_->saveToFile(fileName)) | |||
{ | |||
QMessageBox::warning(this, "保存失败", "文件保存失败!"); | |||
return; // 不新建,保存失败 | |||
} | |||
} else { | |||
} | |||
else | |||
{ | |||
return; // 取消保存,取消新建 | |||
} | |||
} else if (ret == QMessageBox::Cancel) { | |||
} | |||
else if (ret == QMessageBox::Cancel) | |||
{ | |||
return; // 取消新建 | |||
} | |||
// 如果是Discard,直接继续新建,丢弃当前修改 | |||
} | |||
// 清空编辑区,新建页面 | |||
hmi_->resetPages(); | |||
} | |||
void MainWindow::onOpenFile() | |||
{ | |||
QString fileName = QFileDialog::getOpenFileName(this, "打开HMI文件", "", "HMI文件 (*.hmi)"); | |||
if (!fileName.isEmpty()) | |||
{ | |||
if (!hmi_->openFromFile(fileName)) | |||
{ | |||
QMessageBox::warning(this, "打开失败", "文件打开失败或格式不正确!"); | |||
} | |||
} | |||
} | |||
void MainWindow::onSaveFile() | |||
{ | |||
QString fileName = QFileDialog::getSaveFileName(this, "保存HMI文件", "", "HMI文件 (*.hmi)"); | |||
if (!fileName.isEmpty()) | |||
{ | |||
if (!hmi_->saveToFile(fileName)) | |||
{ | |||
QMessageBox::warning(this, "保存失败", "文件保存失败!"); | |||
} | |||
} | |||
} | |||
@@ -5,7 +5,8 @@ | |||
#include "hmimodule.h" | |||
QT_BEGIN_NAMESPACE | |||
namespace Ui { | |||
namespace Ui | |||
{ | |||
class MainWindow; | |||
} | |||
QT_END_NAMESPACE | |||
@@ -23,7 +24,9 @@ private: | |||
private slots: | |||
void appendLog(const QString& message); | |||
void onNewFile(); // 新增槽函数,处理“新建”菜单 | |||
void onNewFile(); | |||
void onOpenFile(); | |||
void onSaveFile(); | |||
private: | |||
Ui::MainWindow *ui_; | |||