From 01967242efb54c1043a7714d7d1d4a3220cf143a Mon Sep 17 00:00:00 2001 From: kai__wei <2553229853@qq.com> Date: Sat, 9 Aug 2025 12:27:05 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=BC=E5=90=88=E5=B9=B3=E5=8F=B0=E7=BC=96?= =?UTF-8?q?=E7=A8=8B=E5=99=A8PLC=E4=B8=8EHMI=E6=A8=A1=E5=9D=97=E6=90=AD?= =?UTF-8?q?=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HMI/hmicontrolitem.cpp | 262 ++++++++++++++ .../HMI/hmicontrolitem.h | 93 +++++ .../HMI/hmieditorwidget.cpp | 330 ++++++++++++++++++ .../HMI/hmieditorwidget.h | 51 +++ .../HMI/hmiwidgetfactory.cpp | 11 + .../HMI/hmiwidgetfactory.h | 12 + .../PLC/plccontrolitem.cpp | 63 ++++ .../PLC/plccontrolitem.h | 36 ++ .../PLC/plceditorwidget.cpp | 49 +++ .../PLC/plceditorwidget.h | 27 ++ .../PLC/plcwidgetfactory.cpp | 11 + .../PLC/plcwidgetfactory.h | 11 + .../mainwindow.cpp | 171 +++++++++ ComprehensivePlatformProgrammer/mainwindow.h | 35 ++ 14 files changed, 1162 insertions(+) create mode 100644 ComprehensivePlatformProgrammer/HMI/hmicontrolitem.cpp create mode 100644 ComprehensivePlatformProgrammer/HMI/hmicontrolitem.h create mode 100644 ComprehensivePlatformProgrammer/HMI/hmieditorwidget.cpp create mode 100644 ComprehensivePlatformProgrammer/HMI/hmieditorwidget.h create mode 100644 ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.cpp create mode 100644 ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.h create mode 100644 ComprehensivePlatformProgrammer/PLC/plccontrolitem.cpp create mode 100644 ComprehensivePlatformProgrammer/PLC/plccontrolitem.h create mode 100644 ComprehensivePlatformProgrammer/PLC/plceditorwidget.cpp create mode 100644 ComprehensivePlatformProgrammer/PLC/plceditorwidget.h create mode 100644 ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.cpp create mode 100644 ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.h create mode 100644 ComprehensivePlatformProgrammer/mainwindow.cpp create mode 100644 ComprehensivePlatformProgrammer/mainwindow.h diff --git a/ComprehensivePlatformProgrammer/HMI/hmicontrolitem.cpp b/ComprehensivePlatformProgrammer/HMI/hmicontrolitem.cpp new file mode 100644 index 0000000..e2fc938 --- /dev/null +++ b/ComprehensivePlatformProgrammer/HMI/hmicontrolitem.cpp @@ -0,0 +1,262 @@ +#include "hmicontrolitem.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hmibutton.h" +#include "hmiled.h" +#include +// 静态剪贴板数据,初始化为空 +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(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(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; +} diff --git a/ComprehensivePlatformProgrammer/HMI/hmicontrolitem.h b/ComprehensivePlatformProgrammer/HMI/hmicontrolitem.h new file mode 100644 index 0000000..f5c2ea3 --- /dev/null +++ b/ComprehensivePlatformProgrammer/HMI/hmicontrolitem.h @@ -0,0 +1,93 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +// 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; +}; diff --git a/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.cpp b/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.cpp new file mode 100644 index 0000000..31ef799 --- /dev/null +++ b/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.cpp @@ -0,0 +1,330 @@ +#include "hmieditorwidget.h" +#include "hmiwidgetfactory.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(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(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(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(); +} diff --git a/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.h b/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.h new file mode 100644 index 0000000..1cfa5de --- /dev/null +++ b/ComprehensivePlatformProgrammer/HMI/hmieditorwidget.h @@ -0,0 +1,51 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class HmiEditorWidget : public QWidget { + 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(); + 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 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; +}; diff --git a/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.cpp b/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.cpp new file mode 100644 index 0000000..4fdec9f --- /dev/null +++ b/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.cpp @@ -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; // 未知类型返回空 +} diff --git a/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.h b/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.h new file mode 100644 index 0000000..b240216 --- /dev/null +++ b/ComprehensivePlatformProgrammer/HMI/hmiwidgetfactory.h @@ -0,0 +1,12 @@ +#pragma once +#include "hmibutton.h" +#include "hmiled.h" +#include + +// HMI控件工厂类(静态类,用于创建HMI控件) +class HmiWidgetFactory { +public: + // 静态方法:根据名称创建对应的HMI控件 + static HmiControlItem* createItem(const QString& name); +}; + diff --git a/ComprehensivePlatformProgrammer/PLC/plccontrolitem.cpp b/ComprehensivePlatformProgrammer/PLC/plccontrolitem.cpp new file mode 100644 index 0000000..7f48df3 --- /dev/null +++ b/ComprehensivePlatformProgrammer/PLC/plccontrolitem.cpp @@ -0,0 +1,63 @@ +#include "plccontrolitem.h" +#include +#include +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); +} diff --git a/ComprehensivePlatformProgrammer/PLC/plccontrolitem.h b/ComprehensivePlatformProgrammer/PLC/plccontrolitem.h new file mode 100644 index 0000000..770e235 --- /dev/null +++ b/ComprehensivePlatformProgrammer/PLC/plccontrolitem.h @@ -0,0 +1,36 @@ +#pragma once // 防止头文件重复包含 + +#include // 基类:图形项 +#include // 鼠标事件类 + +// 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; +}; diff --git a/ComprehensivePlatformProgrammer/PLC/plceditorwidget.cpp b/ComprehensivePlatformProgrammer/PLC/plceditorwidget.cpp new file mode 100644 index 0000000..1faed21 --- /dev/null +++ b/ComprehensivePlatformProgrammer/PLC/plceditorwidget.cpp @@ -0,0 +1,49 @@ +#include "plceditorwidget.h" +#include "plcwidgetfactory.h" +#include +#include +#include + +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); + } +} diff --git a/ComprehensivePlatformProgrammer/PLC/plceditorwidget.h b/ComprehensivePlatformProgrammer/PLC/plceditorwidget.h new file mode 100644 index 0000000..240a097 --- /dev/null +++ b/ComprehensivePlatformProgrammer/PLC/plceditorwidget.h @@ -0,0 +1,27 @@ +#pragma once // 防止头文件重复包含 + +#include // 基类:Qt窗口部件类 +#include // 工具栏:列表控件 +#include // 编辑区域:图形视图 +#include // 编辑区域:图形场景 + +// 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; +}; diff --git a/ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.cpp b/ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.cpp new file mode 100644 index 0000000..ac8053f --- /dev/null +++ b/ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.cpp @@ -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; // 未知名称返回空 +} diff --git a/ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.h b/ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.h new file mode 100644 index 0000000..6055cea --- /dev/null +++ b/ComprehensivePlatformProgrammer/PLC/plcwidgetfactory.h @@ -0,0 +1,11 @@ +#pragma once // 防止头文件重复包含 + +#include "plccontrolitem.h" // 包含PLC控件类 +#include // 字符串类 + +// PLC控件工厂类 +class PlcWidgetFactory { +public: + // 静态方法:根据名称创建PLC控件 + static PlcControlItem* createItem(const QString& name); +}; diff --git a/ComprehensivePlatformProgrammer/mainwindow.cpp b/ComprehensivePlatformProgrammer/mainwindow.cpp new file mode 100644 index 0000000..ac16a3e --- /dev/null +++ b/ComprehensivePlatformProgrammer/mainwindow.cpp @@ -0,0 +1,171 @@ +#include "mainwindow.h" +#include "modules/HMI/hmieditorwidget.h" +#include "modules/PLC/plceditorwidget.h" +#include +#include +#include +#include +#include + +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(currentWidget) != nullptr; + bool isPlc = qobject_cast(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(currentWidget); + success = fileManager->saveHmiFile(hmi, filePath); + qDebug() << "MainWindow: 保存HMI文件,编辑器ID:" << hmi << "路径:" << filePath << "结果:" << success; + } else if (isPlc) { + PlcEditorWidget* plc = qobject_cast(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(实际实现需要根据业务逻辑开发)"); +} diff --git a/ComprehensivePlatformProgrammer/mainwindow.h b/ComprehensivePlatformProgrammer/mainwindow.h new file mode 100644 index 0000000..47eaa6b --- /dev/null +++ b/ComprehensivePlatformProgrammer/mainwindow.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include +#include +#include +#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; +};