diff --git a/ComprehensivePlatformProgrammer/HMI/hmibutton.cpp b/ComprehensivePlatformProgrammer/HMI/hmibutton.cpp new file mode 100644 index 0000000..3754d4f --- /dev/null +++ b/ComprehensivePlatformProgrammer/HMI/hmibutton.cpp @@ -0,0 +1,89 @@ +#include "hmibutton.h" +#include +#include +#include +#include + +// 构造函数:初始化按钮控件 +HmiButton::HmiButton(QGraphicsItem* parent) + : HmiControlItem(parent) { +} + +// 返回按钮的边界矩形 +QRectF HmiButton::boundingRect() const { + return QRectF(0, 0, 80, 40).adjusted(-1, -1, 1, 1); +} + +// 绘制按钮(根据状态设置不同颜色与文本) +void HmiButton::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::black); + painter->setBrush(m_isButtonOn ? m_buttonOnColor : m_buttonOffColor); + painter->drawRect(0, 0, 80, 40); + painter->drawText(QRectF(0, 0, 80, 40), Qt::AlignCenter, m_isButtonOn ? "ON" : "OFF"); + if (isSelected()) { + painter->setPen(QPen(Qt::black, 1, Qt::DashLine)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(boundingRect().adjusted(1, 1, -1, -1)); + } +} + +// 右键菜单 +void HmiButton::createContextMenu(QMenu& menu) { + HmiControlItem::createContextMenu(menu); + + QAction* deleteAction = new QAction("删除", &menu); + QObject::connect(deleteAction, &QAction::triggered, [this]() { deleteItem(); }); + + QAction* copyAction = new QAction("复制", &menu); + QObject::connect(copyAction, &QAction::triggered, [this]() { + copyToClipboard(saveToClipboard()); + }); + + QAction* setOnAction = new QAction("置ON", &menu); + QObject::connect(setOnAction, &QAction::triggered, [this]() { setButtonState(true); }); + + QAction* setOffAction = new QAction("置OFF", &menu); + QObject::connect(setOffAction, &QAction::triggered, [this]() { setButtonState(false); }); + + QMenu* appearanceMenu = menu.addMenu("更改外观"); + QAction* changeOnAppearance = new QAction("设置ON状态颜色", appearanceMenu); + QObject::connect(changeOnAppearance, &QAction::triggered, + [this]() { changeAppearanceColor(true, m_buttonOnColor, m_buttonOffColor); }); + + QAction* changeOffAppearance = new QAction("设置OFF状态颜色", appearanceMenu); + QObject::connect(changeOffAppearance, &QAction::triggered, + [this]() { changeAppearanceColor(false, m_buttonOnColor, m_buttonOffColor); }); + + menu.addAction(copyAction); + menu.addSeparator(); + menu.addAction(setOnAction); + menu.addAction(setOffAction); + menu.addMenu(appearanceMenu); + appearanceMenu->addAction(changeOnAppearance); + appearanceMenu->addAction(changeOffAppearance); + menu.addSeparator(); + menu.addAction(deleteAction); +} + +// 保存按钮数据 +QVariantMap HmiButton::saveToClipboard() const { + QVariantMap data = HmiControlItem::saveToClipboard(); + data["isButtonOn"] = m_isButtonOn; + data["onColor"] = m_buttonOnColor.name(); + data["offColor"] = m_buttonOffColor.name(); + return data; +} + +// 设置按钮状态并触发重绘(仅在变化时发出信号,避免读回写环路) +void HmiButton::setButtonState(bool on) { + if (m_isButtonOn == on) { update(); return; } + m_isButtonOn = on; + update(); + emit stateChanged(m_isButtonOn); +} + +\ diff --git a/ComprehensivePlatformProgrammer/HMI/hmibutton.h b/ComprehensivePlatformProgrammer/HMI/hmibutton.h new file mode 100644 index 0000000..56704cf --- /dev/null +++ b/ComprehensivePlatformProgrammer/HMI/hmibutton.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include "hmicontrolitem.h" +#include + +// HMI按钮控件类,继承自HmiControlItem,实现按钮特有的功能 +class HmiButton : public HmiControlItem { + Q_OBJECT +public: + // 构造函数,初始化按钮控件 + explicit HmiButton(QGraphicsItem* parent = nullptr); + + // 返回按钮的边界矩形(固定大小80x40,带调整边距) + QRectF boundingRect() const override; + + // 绘制按钮(根据ON/OFF状态绘制不同颜色和文本) + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + // 返回控件类型名称(用于创建/保存等) + QString getItemType() const override { return "按钮"; } + + // 保存按钮数据到剪贴板(包含状态与颜色) + QVariantMap saveToClipboard() const override; + + // 设置ON/OFF颜色(给现有创建/粘贴流程用) + void setOnColor(const QColor& color) { m_buttonOnColor = color; update(); } + void setOffColor(const QColor& color) { m_buttonOffColor = color; update(); } + + // 设置按钮ON/OFF;当状态发生变化时,会发出 stateChanged() 供 Modbus 写线圈 + void setButtonState(bool on); + +signals: + void stateChanged(bool on); // 新增:按钮状态变化信号 + +protected: + + // 右键菜单(保留已有:复制/删除/置ON/置OFF/改颜色/设置寄存器) + void createContextMenu(QMenu& menu) override; + +private: + bool m_isButtonOn = false; // 按钮状态(true为ON,false为OFF) + QColor m_buttonOnColor = Qt::blue; // ON状态颜色 + QColor m_buttonOffColor = Qt::lightGray; // OFF状态颜色 +}; diff --git a/ComprehensivePlatformProgrammer/HMI/hmicontrolitem.cpp b/ComprehensivePlatformProgrammer/HMI/hmicontrolitem.cpp index e2fc938..49bf5c8 100644 --- a/ComprehensivePlatformProgrammer/HMI/hmicontrolitem.cpp +++ b/ComprehensivePlatformProgrammer/HMI/hmicontrolitem.cpp @@ -16,7 +16,7 @@ QVariantMap HmiControlItem::m_clipboardData; // 构造函数:初始化控件基本属性 HmiControlItem::HmiControlItem(QGraphicsItem* parent) - : QObject(nullptr), QGraphicsItem(parent), m_registerAddress(-1) + : QObject(nullptr), QGraphicsItem(parent), m_registerAddress() { // 设置控件属性:可移动、可选择、发送几何变化通知 setFlags(QGraphicsItem::ItemIsMovable | @@ -97,38 +97,24 @@ void HmiControlItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { 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(); - } + // 使用scenePos计算偏移量,更准确 + QPointF delta = event->scenePos() - event->lastScenePos(); + QPointF newPos = pos() + delta; + + // 获取变换后的边界矩形 + QRectF itemRect = mapToScene(boundingRect()).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()))); + + setPos(newPos); + update(); + event->accept(); + return; } - event->accept(); - return; - } - QGraphicsItem::mouseMoveEvent(event); + QGraphicsItem::mouseMoveEvent(event); } // 鼠标释放事件处理:结束缩放或拖动 @@ -170,7 +156,6 @@ void HmiControlItem::changeAppearanceColor(bool isOnState, QColor& onColor, QCol } } -// 设置寄存器绑定 // 设置寄存器绑定 bool HmiControlItem::setRegisterBinding(int address) { if (address >= 0 && address <= 4000) { diff --git a/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.cpp b/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.cpp index 31ef799..b3b1089 100644 --- a/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.cpp +++ b/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.cpp @@ -1,54 +1,28 @@ #include "hmieditorwidget.h" -#include "hmiwidgetfactory.h" -#include +#include "hmibutton.h" +#include "hmiled.h" +#include "hmicontrolitem.h" + #include -#include -#include -#include -#include -#include +#include #include -#include -#include #include -#include "hmicontrolitem.h" +#include +#include +#include HmiEditorWidget::HmiEditorWidget(QWidget* parent) - : QWidget(parent) + : EditorWidget(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); + // 左侧:在父类 EditorWidget 的左侧工具栏区域追加页面按钮与标签 + QLayoutItem* item = layout()->itemAt(0)->widget()->layout()->itemAt(0); + auto* leftLayout = dynamic_cast(item->layout()); - m_newPageButton = new QPushButton("新建页面", this); + 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); + m_prevPageButton = new QPushButton("上一页", this); + m_nextPageButton = new QPushButton("下一页", this); + m_pageLabel = new QLabel("无页面", this); leftLayout->addWidget(m_newPageButton); leftLayout->addWidget(m_deletePageButton); @@ -57,100 +31,128 @@ HmiEditorWidget::HmiEditorWidget(QWidget* parent) 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_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); + connect(m_prevPageButton, &QPushButton::clicked, this, &HmiEditorWidget::onPreviousPageClicked); + connect(m_nextPageButton, &QPushButton::clicked, this, &HmiEditorWidget::onNextPageClicked); + + // 工具栏项 + initToolbar(); + // 确保视图已创建(父类提供) ensureViewReady(); + + // 捕获编辑区鼠标:既监听 QGraphicsView 自身,也监听其 viewport + if (editArea) { + editArea->installEventFilter(this); + if (editArea->viewport()) + editArea->viewport()->installEventFilter(this); + } } -HmiEditorWidget::~HmiEditorWidget() -{ +HmiEditorWidget::~HmiEditorWidget() { clearPages(); } -void HmiEditorWidget::ensureViewReady() +void HmiEditorWidget::refreshView() { - QCoreApplication::processEvents(); - hmiEditArea->resetTransform(); - hmiEditArea->viewport()->update(); - - if (hmiEditArea->viewport()->rect().isEmpty()) { - hmiEditArea->setFixedSize(600, 400); - QCoreApplication::processEvents(); + if (m_currentPageIndex >= 0 && !m_pages.isEmpty()) { + editArea->setScene(nullptr); + editArea->setScene(m_pages[m_currentPageIndex]); + initViewScale(); + editArea->viewport()->update(); } } -void HmiEditorWidget::showEvent(QShowEvent* event) -{ - QWidget::showEvent(event); - if (m_firstShow && m_pages.isEmpty()) { - m_firstShow = false; - createNewPage(); - } +void HmiEditorWidget::initToolbar() { + // “按钮” + auto* buttonItem = new QListWidgetItem(); + buttonItem->setIcon(QIcon(":resource/image/button.png")); + buttonItem->setData(Qt::UserRole, QString("按钮")); + buttonItem->setToolTip("按钮"); + toolbar->addItem(buttonItem); + + // “指示灯” + auto* ledItem = new QListWidgetItem(); + ledItem->setIcon(QIcon(":resource/image/light.png")); + ledItem->setData(Qt::UserRole, QString("指示灯")); + ledItem->setToolTip("指示灯"); + toolbar->addItem(ledItem); +} + +void HmiEditorWidget::onToolbarItemClicked(QListWidgetItem* item) { + if (!item) return; + + // —— 关键改动 ——: + // 不再“立即创建到场景中心”,而是进入“放置模式” + m_pendingType = item->data(Qt::UserRole).toString(); + setPlacingCursor(true); + + appendOutput(QString("已选择控件:%1。请在编辑区点击以放置。").arg(m_pendingType)); +} + +QGraphicsScene* HmiEditorWidget::getCurrentScene() const { + if (m_currentPageIndex >= 0 && m_currentPageIndex < m_pages.size()) + return m_pages[m_currentPageIndex]; + return nullptr; +} + +void HmiEditorWidget::clearPages() { + for (QGraphicsScene* scene : m_pages) delete scene; + m_pages.clear(); + m_currentPageIndex = -1; + if (editArea) editArea->setScene(nullptr); + updatePageLabel(); } void HmiEditorWidget::createNewPage() { - QGraphicsScene* newScene = new QGraphicsScene(this); - QRect viewRect = hmiEditArea->viewport()->rect(); - + auto* newScene = new QGraphicsScene(this); + QRect viewRect = editArea->viewport()->rect(); if (viewRect.isEmpty()) { - viewRect = QRect(0, 0, 800, 600); - hmiEditArea->setSceneRect(viewRect); + viewRect = QRect(0,0,800,600); + editArea->setSceneRect(viewRect); } newScene->setSceneRect(viewRect); m_pages.append(newScene); m_currentPageIndex = m_pages.size() - 1; - QMetaObject::invokeMethod(this, [this, newScene]() { - hmiEditArea->setScene(newScene); + QMetaObject::invokeMethod(this, [this, newScene](){ + editArea->setScene(newScene); initViewScale(); updatePageLabel(); - hmiEditArea->viewport()->update(); + editArea->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::switchToPage(int index) { + if (index >= 0 && index < m_pages.size()) { + m_currentPageIndex = index; + editArea->resetTransform(); + editArea->setScene(m_pages[index]); + updatePageLabel(); + QMetaObject::invokeMethod(this, [this](){ + initViewScale(); + editArea->viewport()->update(); + }, Qt::QueuedConnection); } } void HmiEditorWidget::initViewScale() { - if (!hmiEditArea || m_currentPageIndex < 0 || m_pages.isEmpty()) { - return; - } + if (!editArea || m_currentPageIndex < 0 || m_pages.isEmpty()) return; - QRect viewRect = hmiEditArea->viewport()->rect(); - if (viewRect.isEmpty()) { - viewRect = QRect(0, 0, 800, 600); - } + QRect viewRect = editArea->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(); + auto* currentScene = m_pages[m_currentPageIndex]; + QRectF currentRect = currentScene->sceneRect(); + const qreal newW = qMax(currentRect.width(), qreal(viewRect.width())); + const qreal newH = qMax(currentRect.height(), qreal(viewRect.height())); + currentScene->setSceneRect(0,0,newW,newH); + + editArea->viewport()->update(); } void HmiEditorWidget::updatePageLabel() @@ -161,51 +163,36 @@ void HmiEditorWidget::updatePageLabel() 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); + 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_currentPageIndextype() == QEvent::ContextMenu) { - QContextMenuEvent* contextEvent = static_cast(event); + // —— 右键空白处:弹出“粘贴”菜单(保留原逻辑) —— // + if (obj == editArea && event->type() == QEvent::ContextMenu) { + auto* contextEvent = static_cast(event); if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex]) { - QGraphicsItem* item = hmiEditArea->itemAt(contextEvent->pos()); - if (!item) { + QGraphicsItem* hit = editArea->itemAt(contextEvent->pos()); + if (!hit) { 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); + connect(pasteAction, &QAction::triggered, [this, contextEvent](){ + if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex] && + HmiControlItem::hasClipboardData()) { + + const QPointF scenePos = editArea->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(); + editArea->viewport()->update(); } } }); @@ -215,116 +202,167 @@ bool HmiEditorWidget::eventFilter(QObject* obj, QEvent* event) } return true; } - return QWidget::eventFilter(obj, event); -} -void HmiEditorWidget::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - if (m_currentPageIndex >= 0 && !m_pages.isEmpty()) { - initViewScale(); + // —— 关键改动:放置模式下,点击编辑区创建 —— // + // 捕获 QGraphicsView 的 viewport 左键按下 + if (obj == (editArea ? editArea->viewport() : nullptr) + && event->type() == QEvent::MouseButtonPress && !m_pendingType.isEmpty()) { + + auto* me = static_cast(event); + if (me->button() == Qt::LeftButton + && m_currentPageIndex >= 0 + && m_pages[m_currentPageIndex]) { + + const QPointF scenePos = editArea->mapToScene(me->pos()); + HmiControlItem* item = createItemByType(m_pendingType, scenePos); + if (item) { + m_pages[m_currentPageIndex]->addItem(item); + m_pages[m_currentPageIndex]->clearSelection(); + item->setSelected(true); + editArea->viewport()->update(); + } + // 一次放一个:退出放置模式(如需连续放置,注释掉两行) + m_pendingType.clear(); + setPlacingCursor(false); + + return true; // 已处理 + } } + + return EditorWidget::eventFilter(obj, event); +} + +void HmiEditorWidget::resizeEvent(QResizeEvent* e) { + EditorWidget::resizeEvent(e); + if (m_currentPageIndex >= 0 && !m_pages.isEmpty()) initViewScale(); +} + +void HmiEditorWidget::showEvent(QShowEvent* e) { + EditorWidget::showEvent(e); + if (m_pages.isEmpty()) createNewPage(); } void HmiEditorWidget::keyPressEvent(QKeyEvent* event) { + // Ctrl+C:复制(保留原逻辑——复制首个选中项至静态剪贴板) 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(selectedItems.first()); - if (item) { + if (auto* item = dynamic_cast(selectedItems.first())) { HmiControlItem::copyToClipboard(item->saveToClipboard()); } } } + return; } - 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); + + // Ctrl+V:粘贴(关键改动:若有选中源控件 → 旁边粘贴;否则 → 场景中心) + if (event->matches(QKeySequence::Paste)) { + if (m_currentPageIndex >= 0 && + m_pages[m_currentPageIndex] && + HmiControlItem::hasClipboardData()) { + + QPointF pastePos; + auto selectedItems = m_pages[m_currentPageIndex]->selectedItems(); + static int s_pasteIndex = 0; // 连续多次粘贴时,用来轻微错行 + + if (!selectedItems.isEmpty()) { + if (auto* src = dynamic_cast(selectedItems.first())) { + pastePos = calcPasteBesidePos(src, s_pasteIndex++); + } + } else { + // 无选中项:仍按原逻辑 → 场景中心 + QRectF rect = m_pages[m_currentPageIndex]->sceneRect(); + pastePos = QPointF(rect.width()/2, rect.height()/2); + s_pasteIndex = 0; + } + + HmiControlItem* newItem = + HmiControlItem::createFromClipboard(HmiControlItem::getClipboardData(), pastePos); if (newItem) { m_pages[m_currentPageIndex]->addItem(newItem); m_pages[m_currentPageIndex]->clearSelection(); newItem->setSelected(true); - hmiEditArea->viewport()->update(); + editArea->viewport()->update(); } } + return; } - else if (event->matches(QKeySequence::Delete)) { + + // Delete:删除(保留原逻辑) + 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(item); - if (controlItem) { - controlItem->deleteItem(); - } + for (QGraphicsItem* gi : selectedItems) { + if (auto* ci = dynamic_cast(gi)) ci->deleteItem(); } - hmiEditArea->viewport()->update(); + editArea->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(); + return; } - 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(); - } + EditorWidget::keyPressEvent(event); } -void HmiEditorWidget::onNewPageClicked() -{ - createNewPage(); -} +void HmiEditorWidget::onNewPageClicked() { createNewPage(); } -void HmiEditorWidget::onDeletePageClicked() -{ +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]); + + m_currentPageIndex = qMin(m_currentPageIndex, m_pages.size()-1); + editArea->setScene(m_pages[m_currentPageIndex]); initViewScale(); updatePageLabel(); - hmiEditArea->viewport()->update(); + editArea->viewport()->update(); + appendOutput(QString("删除页面,剩余页面数: %1").arg(m_pages.size())); } } -void HmiEditorWidget::onPreviousPageClicked() +void HmiEditorWidget::onPreviousPageClicked() { switchToPage(m_currentPageIndex - 1); } +void HmiEditorWidget::onNextPageClicked() { switchToPage(m_currentPageIndex + 1); } + +// —— 私有辅助 —— // + +HmiControlItem* HmiEditorWidget::createItemByType(const QString& type, const QPointF& scenePos) { - switchToPage(m_currentPageIndex - 1); + HmiControlItem* item = nullptr; + if (type == "按钮") { + item = new HmiButton(); + appendOutput("添加按钮控件"); + } else if (type == "指示灯") { + item = new HmiLed(); + appendOutput("添加指示灯控件"); + } + if (item) item->setPos(scenePos); + return item; } -void HmiEditorWidget::onNextPageClicked() +void HmiEditorWidget::setPlacingCursor(bool on) { - switchToPage(m_currentPageIndex + 1); + if (!editArea) return; + if (on) { + editArea->setCursor(Qt::CrossCursor); + if (editArea->viewport()) editArea->viewport()->setCursor(Qt::CrossCursor); + } else { + editArea->unsetCursor(); + if (editArea->viewport()) editArea->viewport()->unsetCursor(); + } } -void HmiEditorWidget::clearPages() +QPointF HmiEditorWidget::calcPasteBesidePos(HmiControlItem* src, int pasteIndex) const { - for (QGraphicsScene* scene : m_pages) { - delete scene; - } - m_pages.clear(); - m_currentPageIndex = -1; - hmiEditArea->setScene(nullptr); - updatePageLabel(); + if (!src) return QPointF(); + + // 使用“映射到场景后的边界框”来获取可视宽度,避免缩放带来的误差 + QRectF sceneRect = src->mapToScene(src->boundingRect()).boundingRect(); + const qreal w = qMax(sceneRect.width(), 24.0); // 保底宽度 + const qreal gap = 12.0; // 与原件的水平间隔 + const qreal stepY = 8.0; // 连续粘贴时的竖向微调 + + QPointF base = src->pos(); + return QPointF(base.x() + w + gap, base.y() + pasteIndex * stepY); } diff --git a/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.h b/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.h index 1cfa5de..94db205 100644 --- a/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.h +++ b/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.h @@ -1,51 +1,76 @@ #pragma once -#include -#include -#include -#include +#include "editorwidget.h" #include #include -#include +#include +#include +#include +#include + +class HmiControlItem; -class HmiEditorWidget : public QWidget { +/** + * @brief HMI (Human Machine Interface) 图形编辑器控件 + * + * 继承自 EditorWidget,扩展了多页面管理、点击放置与“旁边粘贴” + */ +class HmiEditorWidget : public EditorWidget { Q_OBJECT + public: explicit HmiEditorWidget(QWidget* parent = nullptr); ~HmiEditorWidget(); - Q_INVOKABLE QList 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(); + // 页面管理接口 + QGraphicsScene* getCurrentScene() const override; + QList getPages() const override { return m_pages; } + int getCurrentPageIndex() const override { return m_currentPageIndex; } + void clearPages() override; + void createNewPage(); void switchToPage(int index); + void refreshView(); protected: + // 覆盖 EditorWidget 接口 + void initToolbar() override; + void onToolbarItemClicked(QListWidgetItem* item) override; 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; + // 根据类型名创建控件并放到 scenePos 上;成功返回新控件指针 + HmiControlItem* createItemByType(const QString& type, const QPointF& scenePos); + + // 设置/清除“放置模式”的鼠标提示 + void setPlacingCursor(bool on); + + // 计算“在原控件旁边”的粘贴位置(第 n 次粘贴做轻微错行) + QPointF calcPasteBesidePos(HmiControlItem* src, int pasteIndex = 0) const; + +private: + // 页面 QList 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; + + // UI + QLabel* m_pageLabel = nullptr; + QPushButton* m_newPageButton = nullptr; + QPushButton* m_deletePageButton = nullptr; + QPushButton* m_prevPageButton = nullptr; + QPushButton* m_nextPageButton = nullptr; + + // 交互状态:待放置的控件类型(与工具栏 item 的 UserRole 文本一致:如 “按钮”“指示灯”) + QString m_pendingType; }; diff --git a/ComprehensivePlatformProgrammer/HMI/hmiled.cpp b/ComprehensivePlatformProgrammer/HMI/hmiled.cpp new file mode 100644 index 0000000..12775f0 --- /dev/null +++ b/ComprehensivePlatformProgrammer/HMI/hmiled.cpp @@ -0,0 +1,84 @@ +#include "hmiled.h" +#include +#include +#include +#include + +// 构造函数:初始化指示灯控件 +HmiLed::HmiLed(QGraphicsItem* parent) + : HmiControlItem(parent) { +} + +// 返回指示灯的边界矩形 +QRectF HmiLed::boundingRect() const { + return QRectF(0, 0, 60, 60).adjusted(-1, -1, 1, 1); +} + +// 绘制指示灯:根据状态绘制圆形,使用ON或OFF状态颜色 +void HmiLed::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option); // 消除未使用参数警告 + Q_UNUSED(widget); // 消除未使用参数警告 + painter->setRenderHint(QPainter::Antialiasing); // 启用抗锯齿 + painter->setPen(Qt::black); // 设置边框颜色 + painter->setBrush(m_isLedOn ? m_ledOnColor : m_ledOffColor); // 根据状态设置填充颜色 + qreal size = qMin(boundingRect().width(), boundingRect().height()); + painter->drawEllipse(0, 0, size - 2, size - 2); // 绘制圆形 + painter->drawText(QRectF(0, 0, size - 2, size - 2), Qt::AlignCenter, ""); // 空文本(占位) + if (isSelected()) { + // 选中时绘制虚线边框 + painter->setPen(QPen(Qt::black, 1, Qt::DashLine)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(boundingRect().adjusted(1, 1, -1, -1)); + } +} + +// 创建指示灯的右键菜单 +void HmiLed::createContextMenu(QMenu& menu) { + // 调用基类的右键菜单,添加寄存器绑定选项 + HmiControlItem::createContextMenu(menu); + + // 删除选项 + QAction* deleteAction = new QAction("删除", &menu); + QObject::connect(deleteAction, &QAction::triggered, [this]() { deleteItem(); }); + + // 复制选项 + QAction* copyAction = new QAction("复制", &menu); + QObject::connect(copyAction, &QAction::triggered, [this]() { + copyToClipboard(saveToClipboard()); + }); + + // 更改外观子菜单 + QMenu* appearanceMenu = menu.addMenu("更改外观"); + QAction* changeOnAppearance = new QAction("设置ON状态颜色", appearanceMenu); + QObject::connect(changeOnAppearance, &QAction::triggered, + [this]() { changeAppearanceColor(true, m_ledOnColor, m_ledOffColor); }); + + QAction* changeOffAppearance = new QAction("设置OFF状态颜色", appearanceMenu); + QObject::connect(changeOffAppearance, &QAction::triggered, + [this]() { changeAppearanceColor(false, m_ledOnColor, m_ledOffColor); }); + + // 添加菜单项 + menu.addAction(copyAction); + menu.addSeparator(); + menu.addMenu(appearanceMenu); + appearanceMenu->addAction(changeOnAppearance); + appearanceMenu->addAction(changeOffAppearance); + menu.addSeparator(); + menu.addAction(deleteAction); +} + +// 保存指示灯数据到剪贴板 +QVariantMap HmiLed::saveToClipboard() const { + QVariantMap data = HmiControlItem::saveToClipboard(); + data["isLedOn"] = m_isLedOn; + data["onColor"] = m_ledOnColor.name(); + data["offColor"] = m_ledOffColor.name(); + return data; +} + +// 设置指示灯状态并触发重绘 +void HmiLed::setLedState(bool on) { + m_isLedOn = on; + update(); +} diff --git a/ComprehensivePlatformProgrammer/HMI/hmiled.h b/ComprehensivePlatformProgrammer/HMI/hmiled.h new file mode 100644 index 0000000..b4cc9a8 --- /dev/null +++ b/ComprehensivePlatformProgrammer/HMI/hmiled.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include "hmicontrolitem.h" + +// HMI指示灯控件类,继承自HmiControlItem,实现指示灯特有的功能 +class HmiLed : public HmiControlItem { + Q_OBJECT +public: + // 构造函数,初始化指示灯控件 + explicit HmiLed(QGraphicsItem* parent = nullptr); + + // 返回指示灯的边界矩形(固定大小60x60,带调整边距) + QRectF boundingRect() const override; + + // 绘制指示灯(根据ON/OFF状态绘制不同颜色) + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + // 返回控件类型(固定为“指示灯”) + QString getItemType() const override { return "指示灯"; } + + // 保存指示灯数据到剪贴板(位置、缩放、状态、颜色等) + QVariantMap saveToClipboard() const override; + + // 设置ON状态颜色,触发重绘 + void setOnColor(const QColor& color) { m_ledOnColor = color; update(); } + + // 设置OFF状态颜色,触发重绘 + void setOffColor(const QColor& color) { m_ledOffColor = color; update(); } + + // 设置指示灯状态(ON/OFF),供外部调用(如寄存器状态变化),触发重绘 + void setLedState(bool on); + +protected: + // 返回宽高比(指示灯为正方形,固定为1.0) + qreal getAspectRatio() const override { return 1.0; } + + // 创建指示灯的右键菜单(包含复制、删除、颜色设置等,不包含置ON/OFF) + void createContextMenu(QMenu& menu) override; + +private: + bool m_isLedOn = false; // 指示灯状态(true为ON,false为OFF) + QColor m_ledOnColor = Qt::green; // ON状态颜色 + QColor m_ledOffColor = Qt::red; // OFF状态颜色 +}; diff --git a/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.cpp b/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.cpp deleted file mode 100644 index 4fdec9f..0000000 --- a/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "hmiwidgetfactory.h" - -// 根据名称创建HMI控件 -HmiControlItem* HmiWidgetFactory::createItem(const QString& name) { - if (name == "按钮") { - return new HmiButton(); - } else if (name == "指示灯") { - return new HmiLed(); - } - return nullptr; // 未知类型返回空 -} diff --git a/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.h b/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.h deleted file mode 100644 index b240216..0000000 --- a/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include "hmibutton.h" -#include "hmiled.h" -#include - -// HMI控件工厂类(静态类,用于创建HMI控件) -class HmiWidgetFactory { -public: - // 静态方法:根据名称创建对应的HMI控件 - static HmiControlItem* createItem(const QString& name); -}; -