├── .gitignore ├── CMakeLists.txt ├── README.md ├── alluse.cpp ├── alluse.h ├── disuse ├── qinputnode.cpp ├── qinputnode.h ├── qoutputnode.cpp └── qoutputnode.h ├── enterlimiter.cpp ├── enterlimiter.h ├── imagelabel.cpp ├── imagelabel.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── math.cpp ├── math.h ├── opencv.cpp ├── opencv.h ├── qblueprint.cpp ├── qblueprint.h ├── qblueprint.ui ├── qblueprintconnection.cpp ├── qblueprintconnection.h ├── qblueprintnode.cpp ├── qblueprintnode.h ├── qblueprintport.cpp ├── qblueprintport.h ├── qinputnode.h ├── qnodefactory.cpp ├── qnodefactory.h ├── qoutputnode.h ├── qts.cpp ├── qts.h ├── testclass.cpp └── testclass.h /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | CMakeLists.txt.user* 41 | 42 | # xemacs temporary files 43 | *.flc 44 | 45 | # Vim temporary files 46 | .*.swp 47 | 48 | # Visual Studio generated files 49 | *.ib_pdb_index 50 | *.idb 51 | *.ilk 52 | *.pdb 53 | *.sln 54 | *.suo 55 | *.vcproj 56 | *vcproj.*.*.user 57 | *.ncb 58 | *.sdf 59 | *.opensdf 60 | *.vcxproj 61 | *vcxproj.* 62 | 63 | # MinGW generated files 64 | *.Debug 65 | *.Release 66 | 67 | # Python byte code 68 | *.pyc 69 | 70 | # Binaries 71 | # -------- 72 | *.dll 73 | *.exe 74 | 75 | # Ignore build directory 76 | /build/ 77 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(QBlueprint VERSION 0.1 LANGUAGES CXX) 4 | 5 | set(CMAKE_AUTOUIC ON) 6 | set(CMAKE_AUTOMOC ON) 7 | set(CMAKE_AUTORCC ON) 8 | 9 | set(CMAKE_CXX_STANDARD 11) 10 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 | set(OpenCV_DIR D:/opencv/opencv/new) 12 | 13 | find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) 14 | find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets) 15 | find_package(OpenCV QUIET) 16 | set(PROJECT_SOURCES 17 | main.cpp 18 | mainwindow.cpp 19 | mainwindow.h 20 | mainwindow.ui 21 | qblueprint.h qblueprint.cpp qblueprint.ui 22 | qblueprintnode.h qblueprintnode.cpp 23 | qblueprintport.h qblueprintport.cpp 24 | qblueprintconnection.h qblueprintconnection.cpp 25 | qnodefactory.h qnodefactory.cpp 26 | testclass.h testclass.cpp 27 | # qinputnode.h qinputnode.cpp 28 | # qoutputnode.h qoutputnode.cpp 29 | alluse.h alluse.cpp 30 | enterlimiter.h enterlimiter.cpp 31 | math.h math.cpp 32 | imagelabel.h imagelabel.cpp 33 | opencv.h opencv.cpp 34 | qts.h qts.cpp 35 | ) 36 | 37 | if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) 38 | qt_add_executable(QBlueprint 39 | MANUAL_FINALIZATION 40 | ${PROJECT_SOURCES} 41 | enterlimiter.h enterlimiter.cpp 42 | imagelabel.h imagelabel.cpp 43 | qts.h qts.cpp 44 | ) 45 | # Define target properties for Android with Qt 6 as: 46 | # set_property(TARGET QBlueprint APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR 47 | # ${CMAKE_CURRENT_SOURCE_DIR}/android) 48 | # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation 49 | else() 50 | if(ANDROID) 51 | add_library(QBlueprint SHARED 52 | ${PROJECT_SOURCES} 53 | ) 54 | # Define properties for Android with Qt 5 after find_package() calls as: 55 | # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") 56 | else() 57 | add_executable(QBlueprint 58 | ${PROJECT_SOURCES} 59 | ) 60 | endif() 61 | endif() 62 | 63 | target_link_libraries(QBlueprint PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) 64 | include_directories(${OpenCV_INCLUDE_DIRS}) 65 | 66 | if(OpenCV_FOUND) 67 | target_link_libraries(QBlueprint PRIVATE ${OpenCV_LIBS}) 68 | target_compile_definitions(QBlueprint PRIVATE OPENCV_FOUND) 69 | endif() 70 | set_target_properties(QBlueprint PROPERTIES 71 | MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com 72 | MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} 73 | MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} 74 | MACOSX_BUNDLE TRUE 75 | WIN32_EXECUTABLE TRUE 76 | ) 77 | 78 | install(TARGETS QBlueprint 79 | BUNDLE DESTINATION . 80 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 81 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 82 | ) 83 | 84 | if(QT_VERSION_MAJOR EQUAL 6) 85 | qt_finalize_executable(QBlueprint) 86 | endif() 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QBlueprint(Qt5 Qt6兼容) 2 | - [访问我的bilibili个人主页查看使用方法](https://space.bilibili.com/286896507?spm_id_from=333.1007.0.0) 3 | - [关注我的Github](https://github.com/IgnorAnsel) 4 | 5 | `QBlueprint` 是一个基于 Qt 实现的蓝图系统,类似于 Unreal Engine 中的蓝图系统。它允许用户通过节点连接的方式构建逻辑流,并支持通过函数创建节点。 6 | 7 | ## 功能介绍 8 | 9 | ### 1. **蓝图节点连接** 10 | - 支持将蓝图节点通过输入、输出端口进行连接,形成逻辑流。 11 | - 通过拖拽操作,用户可以直观地连接不同节点,构建节点网络。 12 | 13 | ### 2. **通过函数创建蓝图节点** 14 | - 支持根据函数自动生成蓝图节点。 15 | - 用户可以通过特定的函数自动生成对应的蓝图节点,并在蓝图系统中使用。 16 | 17 | ## 使用方法 18 | 19 | ### 1. 克隆项目 20 | 21 | ```bash 22 | git clone https://github.com/IgnorAnsel/QBlueprint.git 23 | cd QBlueprint 24 | ``` 25 | ### 2. 构建项目 26 | #### 使用 CMake 构建 27 | 确保你已经安装了 Qt 和 CMake。可以通过以下步骤来构建项目: 28 | ```bash 29 | mkdir build 30 | cd build 31 | cmake .. 32 | cmake --build . 33 | ``` 34 | ### 3. 运行项目 35 | 构建完成后,你可以在 build 目录下找到生成的可执行文件,直接运行它来启动 QBlueprint 应用程序: 36 | 37 | ```bash 38 | ./QBlueprint 39 | ``` 40 | ### 4. 创建节点并连接 41 | - 启动程序后,使用右键菜单创建蓝图节点。 42 | - 选择 int 类型的节点,拖拽以创建输入和输出端口。 43 | - 通过拖拽端口进行连接,实现节点间的逻辑流。 44 | 45 | ## 未来计划 46 | - 补全更多的数据类型。 47 | - 提供复杂的逻辑控制功能,如条件判断和循环控制节点。 48 | - 实现蓝图执行功能,支持运行时逻辑计算。 49 | 50 | ## 贡献指南 51 | - 我们欢迎社区贡献!以下是贡献的步骤: 52 | 53 | - Fork 此仓库 54 | - 创建一个新的分支 (git checkout -b feature/YourFeature) 55 | - 提交更改 (git commit -m 'Add some feature') 56 | - 推送到你自己的分支 (git push origin feature/YourFeature) 57 | - 打开一个 Pull Request 58 | 59 | ## 许可协议 60 | 本项目遵循 MIT 许可协议。 61 | -------------------------------------------------------------------------------- /alluse.cpp: -------------------------------------------------------------------------------- 1 | #include "alluse.h" 2 | 3 | AllUse::AllUse() 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /alluse.h: -------------------------------------------------------------------------------- 1 | #ifndef ALLUSE_H 2 | #define ALLUSE_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #define NUM_DATA_TYPES 22 22 | enum Type{FUNCTION,INPUT,OUTPUT,BRANCH,CONDITION,FORLOOP}; 23 | enum DataType { 24 | FOR_FUNCTION, // 为FUNCTION使用 25 | INT, // 整型 26 | FLOAT, // 单精度浮点型 27 | DOUBLE, // 双精度浮点型 28 | CHAR, // 字符型 29 | STRING, // 字符串型 30 | BOOL, // 布尔型 31 | LONG, // 长整型 32 | SHORT, // 短整型 33 | UNSIGNED_INT, // 无符号整型 34 | VARIANT, // QVariant 类型 (Qt通用类型) 35 | QSTRING, // QString 类型 (Qt字符串类型) 36 | QTIME, // QTime 类型 (Qt时间类型) 37 | QPOINT, // QPoint 类型 (Qt坐标点类型) 38 | QPOINTF, // QPointF 类型 (Qt浮点坐标点类型) 39 | QSIZE, // QSize 类型 (Qt尺寸类型) 40 | QSIZEF, // QSizeF 类型 (Qt浮点尺寸类型) 41 | QRECT, // QRect 类型 (Qt矩形类型) 42 | QRECTF, // QRectF 类型 (Qt浮点矩形类型) 43 | QCOLOR, // QColor 类型 (Qt颜色类型) 44 | QPIXMAP, // QPixmap 类型 (Qt图像类型) 45 | QIMAGE, // QImage 类型 (Qt图像类型) 46 | }; 47 | 48 | static QString DataTypeNames[] = { 49 | "FOR_FUNCTION", 50 | "INT", 51 | "FLOAT", 52 | "DOUBLE", 53 | "CHAR", 54 | "STRING", 55 | "BOOL", 56 | "LONG", 57 | "SHORT", 58 | "UNSIGNED_INT", 59 | "VARIANT", 60 | "QSTRING", 61 | "QTIME", 62 | "QPOINT", 63 | "QPOINTF", 64 | "QSIZE", 65 | "QSIZEF", 66 | "QRECT", 67 | "QRECTF", 68 | "QCOLOR", 69 | "QPIXMAP", 70 | "QIMAGE", 71 | }; 72 | static QString getEnumName(DataType dataType) { 73 | if (dataType >= 0 && dataType < NUM_DATA_TYPES) { 74 | return DataTypeNames[dataType]; 75 | } 76 | return "UNKNOWN"; 77 | } 78 | // 通过字符串获取对应的枚举值 79 | static DataType getEnumFromName(const QString& name) { 80 | for (int i = 0; i < NUM_DATA_TYPES; ++i) { 81 | if (DataTypeNames[i].compare(name, Qt::CaseInsensitive) == 0) { 82 | return static_cast(i); // 返回对应的枚举值 83 | } 84 | } 85 | return FOR_FUNCTION; // 如果未找到,返回无效的值 86 | } 87 | class AllUse 88 | { 89 | public: 90 | AllUse(); 91 | }; 92 | 93 | #endif // ALLUSE_H 94 | -------------------------------------------------------------------------------- /disuse/qinputnode.cpp: -------------------------------------------------------------------------------- 1 | #include "qinputnode.h" 2 | -------------------------------------------------------------------------------- /disuse/qinputnode.h: -------------------------------------------------------------------------------- 1 | #ifndef QINPUTNODE_H 2 | #define QINPUTNODE_H 3 | 4 | #include "qblueprintnode.h" 5 | #include 6 | #include 7 | 8 | class QInputNode : public QBlueprintNode // 输入节点,用于接收输入,显示数据 9 | { 10 | public: 11 | QInputNode(QGraphicsItem* parent = nullptr) 12 | : QBlueprintNode(parent) { 13 | setNodeTitle("Input Node"); 14 | addInputPort("Input"); 15 | setNodeType(Type::INPUT); 16 | qDebug() << "input" << nodeType; 17 | // 创建 QLabel 作为输出显示的 widget 18 | outputLabel = new QLabel("0"); 19 | outputLabel->setFixedSize(80, 20); 20 | 21 | // 将 QLabel 添加到 QGraphicsItem 22 | proxyWidget = new QGraphicsProxyWidget(this); 23 | proxyWidget->setWidget(outputLabel); 24 | proxyWidget->setPos(boundingRect().width() / 2 - outputLabel->width() / 2, 50); 25 | } 26 | 27 | // 设置输出值 28 | void setOutputValue(int value) { 29 | outputLabel->setText(QString::number(value)); 30 | } 31 | 32 | protected: 33 | //QRectF boundingRect() const override; 34 | 35 | void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { 36 | QBlueprintNode::paint(painter, option, widget); 37 | 38 | // 绘制输出字段区域(已经在 QLabel 中绘制) 39 | painter->setPen(Qt::black); 40 | painter->setBrush(Qt::black); 41 | } 42 | 43 | private: 44 | enum Type nodeType; 45 | QLabel* outputLabel; // 输出字段 46 | QGraphicsProxyWidget* proxyWidget; // QLabel 的代理 47 | }; 48 | 49 | #endif // QOUTPUTNODE_H 50 | 51 | -------------------------------------------------------------------------------- /disuse/qoutputnode.cpp: -------------------------------------------------------------------------------- 1 | #include "qoutputnode.h" 2 | 3 | //QOutputNode::QOutputNode() 4 | //{ 5 | 6 | //} 7 | -------------------------------------------------------------------------------- /disuse/qoutputnode.h: -------------------------------------------------------------------------------- 1 | #ifndef QOUTPUTNODE_H 2 | #define QOUTPUTNODE_H 3 | 4 | #include "qblueprintnode.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | class QOutputNode : public QBlueprintNode // 输出节点,用于自行输出数据 11 | { 12 | public: 13 | QOutputNode(QGraphicsItem* parent = nullptr) 14 | : QBlueprintNode(parent) { 15 | setNodeTitle("Output Node"); 16 | addOutputPort("Output"); 17 | setNodeType(Type::OUTPUT); 18 | // 创建 QLineEdit 作为用户输入的 widget 19 | inputField = new QLineEdit(); 20 | inputField->setValidator(new QIntValidator(0, 1000)); // 只允许输入整数 21 | inputField->setFixedSize(80, 20); 22 | 23 | // 将 QLineEdit 添加到 QGraphicsItem 24 | proxyWidget = new QGraphicsProxyWidget(this); 25 | proxyWidget->setWidget(inputField); 26 | proxyWidget->setPos(boundingRect().width() / 2 - inputField->width() / 2, 50); 27 | } 28 | 29 | // 设置输出值 30 | int getValue() const { 31 | return inputField->text().toInt(); 32 | } 33 | 34 | protected: 35 | 36 | void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { 37 | QBlueprintNode::paint(painter, option, widget); 38 | 39 | // 绘制输出字段区域(已经在 QLabel 中绘制) 40 | painter->setPen(Qt::black); 41 | painter->setBrush(Qt::white); 42 | } 43 | 44 | private: 45 | QLineEdit* inputField; // 输入字段 46 | QGraphicsProxyWidget* proxyWidget; // QLineEdit 的代理 47 | }; 48 | 49 | #endif // QOUTPUTNODE_H 50 | -------------------------------------------------------------------------------- /enterlimiter.cpp: -------------------------------------------------------------------------------- 1 | #include "enterlimiter.h" 2 | 3 | EnterLimiter::EnterLimiter() {} 4 | 5 | void setEnterLimiter(QLineEdit *lineEdit, QBlueprintPort *port) 6 | { 7 | QRegularExpression regExp; 8 | QRegularExpressionValidator* validator; 9 | 10 | switch (port->portDataType()) 11 | { 12 | case DataType::INT: 13 | case DataType::LONG: 14 | case DataType::SHORT: 15 | case DataType::UNSIGNED_INT: 16 | // 只允许输入整数 17 | regExp.setPattern("^-?\\d+$"); 18 | break; 19 | 20 | case DataType::FLOAT: 21 | case DataType::DOUBLE: 22 | // 允许输入浮点数(正负小数) 23 | regExp.setPattern("^-?\\d*\\.?\\d+$"); 24 | break; 25 | 26 | case DataType::CHAR: 27 | // 只允许输入一个字符 28 | regExp.setPattern("^.$"); 29 | break; 30 | 31 | case DataType::STRING: 32 | case DataType::QSTRING: 33 | // 允许输入任意字符串 34 | regExp.setPattern("^.*$"); 35 | break; 36 | 37 | case DataType::BOOL: 38 | // 只允许输入 true 或 false(忽略大小写) 39 | regExp.setPattern("^(true|false|True|False)$"); 40 | break; 41 | 42 | case DataType::QTIME: 43 | // 允许输入时间格式 (HH:MM:SS) 44 | regExp.setPattern("^\\d{2}:\\d{2}:\\d{2}$"); 45 | break; 46 | 47 | case DataType::QPOINT: 48 | case DataType::QPOINTF: 49 | case DataType::QSIZE: 50 | case DataType::QSIZEF: 51 | case DataType::QRECT: 52 | case DataType::QRECTF: 53 | // 允许输入格式 (x, y) 或 (x, y, width, height) 54 | regExp.setPattern("^\\(\\d+, \\d+(, \\d+, \\d+)?\\)$"); 55 | break; 56 | 57 | case DataType::QCOLOR: 58 | // 允许输入颜色十六进制 (#RRGGBB) 59 | regExp.setPattern("^#([A-Fa-f0-9]{6})$"); 60 | break; 61 | 62 | case DataType::QPIXMAP: 63 | case DataType::QIMAGE: 64 | // 不做任何限制,可以根据需要定制 65 | regExp.setPattern("^.*$"); 66 | break; 67 | 68 | default: 69 | // 默认不做限制 70 | regExp.setPattern("^.*$"); 71 | break; 72 | } 73 | 74 | validator = new QRegularExpressionValidator(regExp, lineEdit); 75 | lineEdit->setValidator(validator); 76 | } 77 | -------------------------------------------------------------------------------- /enterlimiter.h: -------------------------------------------------------------------------------- 1 | #ifndef ENTERLIMITER_H 2 | #define ENTERLIMITER_H 3 | #include "qblueprintport.h" 4 | #include 5 | #include 6 | #include 7 | void setEnterLimiter(QLineEdit* lineEdit, QBlueprintPort* port); 8 | 9 | class EnterLimiter 10 | { 11 | public: 12 | EnterLimiter(); 13 | 14 | }; 15 | 16 | #endif // ENTERLIMITER_H 17 | -------------------------------------------------------------------------------- /imagelabel.cpp: -------------------------------------------------------------------------------- 1 | #include "imagelabel.h" 2 | 3 | -------------------------------------------------------------------------------- /imagelabel.h: -------------------------------------------------------------------------------- 1 | // 修改后的 ImageLabel.h 2 | #ifndef IMAGELABEL_H 3 | #define IMAGELABEL_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class ImageLabel : public QLabel { 15 | Q_OBJECT 16 | public: 17 | explicit ImageLabel(QWidget* parent = nullptr) 18 | : QLabel(parent), imageDialog(nullptr) { 19 | setAlignment(Qt::AlignCenter); 20 | createButton(); 21 | } 22 | 23 | // 设置原始的 QImage,并更新显示 24 | void setImage(const QImage& img) { 25 | originalImage = img; // 保存原始图像 26 | QPixmap pixmap = QPixmap::fromImage(originalImage); // 将 QImage 转换为 QPixmap 27 | this->setPixmap(pixmap.scaled(this->size(), Qt::KeepAspectRatio)); // 显示缩放后的图像 28 | } 29 | 30 | protected: 31 | void mousePressEvent(QMouseEvent* event) override { 32 | // 保留默认的 QLabel 行为 33 | QLabel::mousePressEvent(event); 34 | } 35 | 36 | private slots: 37 | void toggleImageDialog() { 38 | if (imageDialog && imageDialog->isVisible()) { 39 | // 如果图像对话框已打开,则关闭 40 | imageDialog->close(); 41 | imageDialog->deleteLater(); 42 | imageDialog = nullptr; 43 | } else { 44 | // 创建并显示放大的图像 45 | imageDialog = new QDialog(); 46 | imageDialog->setWindowFlags(Qt::Popup); 47 | 48 | QLabel* enlargedLabel = new QLabel(imageDialog); 49 | 50 | // 检查 originalImage 是否为空,确保图像已正确传递 51 | if (!originalImage.isNull()) { 52 | enlargedLabel->setPixmap(QPixmap::fromImage(originalImage).scaled(400, 400, Qt::KeepAspectRatio)); 53 | } else { 54 | // 提示没有图像 55 | enlargedLabel->setText("No image available"); 56 | } 57 | 58 | QPushButton* closeButton = new QPushButton("X", imageDialog); 59 | closeButton->setFixedSize(30, 30); 60 | 61 | // 设置关闭按钮的点击事件 62 | connect(closeButton, &QPushButton::clicked, [this]() { 63 | if (imageDialog) { 64 | imageDialog->close(); 65 | imageDialog->deleteLater(); 66 | imageDialog = nullptr; 67 | } 68 | }); 69 | 70 | QVBoxLayout* layout = new QVBoxLayout(imageDialog); 71 | QHBoxLayout* topLayout = new QHBoxLayout(); 72 | topLayout->addWidget(closeButton); 73 | topLayout->addStretch(); 74 | 75 | layout->addLayout(topLayout); 76 | layout->addWidget(enlargedLabel); 77 | imageDialog->setLayout(layout); 78 | 79 | // 设置对话框的位置在主窗口旁边 80 | QPoint globalPos = this->mapToGlobal(this->rect().topLeft()); 81 | imageDialog->move(globalPos.x() + this->width(), globalPos.y()); 82 | 83 | imageDialog->show(); 84 | } 85 | } 86 | 87 | public: 88 | void setOpenButtonPos(QPointF buttonPos) 89 | { 90 | openButton->move(buttonPos.x(), buttonPos.y()); 91 | } 92 | private: 93 | QPushButton* openButton; // 控制打开/关闭的按钮 94 | QDialog* imageDialog; // 用于显示放大图像的对话框 95 | QImage originalImage; // 原始 QImage 96 | 97 | void createButton() { 98 | // 在 QLabel 的左上角创建一个按钮 99 | openButton = new QPushButton("O", this); 100 | openButton->setFixedSize(30, 30); 101 | openButton->move(0, 0); // 按钮位于左上角 102 | 103 | // 设置按钮的点击事件 104 | connect(openButton, &QPushButton::clicked, this, &ImageLabel::toggleImageDialog); 105 | } 106 | }; 107 | 108 | #endif // IMAGELABEL_H 109 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | MainWindow w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "./ui_mainwindow.h" 3 | 4 | MainWindow::MainWindow(QWidget *parent) 5 | : QMainWindow(parent) 6 | , ui(new Ui::MainWindow) 7 | , blueprint(new QBlueprint(this)) // 初始化QBlueprint对象 8 | { 9 | ui->setupUi(this); 10 | setCentralWidget(blueprint); 11 | } 12 | 13 | MainWindow::~MainWindow() 14 | { 15 | delete ui; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include "qblueprint.h" 6 | QT_BEGIN_NAMESPACE 7 | namespace Ui { class MainWindow; } 8 | QT_END_NAMESPACE 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | MainWindow(QWidget *parent = nullptr); 16 | ~MainWindow(); 17 | 18 | private: 19 | QBlueprint *blueprint; 20 | Ui::MainWindow *ui; 21 | }; 22 | #endif // MAINWINDOW_H 23 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 120 21 | 200 22 | 256 23 | 192 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 0 32 | 0 33 | 800 34 | 27 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /math.cpp: -------------------------------------------------------------------------------- 1 | #include "math.h" 2 | #include 3 | #include 4 | 5 | Math::Math() 6 | { 7 | 8 | } 9 | 10 | // 加法 11 | double Math::add(double a, double b) { 12 | return a + b; 13 | } 14 | 15 | // 减法 16 | double Math::subtract(double a, double b) { 17 | return a - b; 18 | } 19 | 20 | // 乘法 21 | double Math::multiply(double a, double b) { 22 | return a * b; 23 | } 24 | 25 | // 除法,注意处理除零情况 26 | double Math::divide(double a, double b) { 27 | if (b == 0) { 28 | return 0; 29 | } 30 | return a / b; 31 | } 32 | 33 | // 开方,注意处理负数情况 34 | double Math::sqrt(double a) { 35 | if (a <= 0) { 36 | return 0; 37 | } 38 | return std::sqrt(a); 39 | } 40 | 41 | double Math::pow(double base, double exponent) 42 | { 43 | return std::pow(base, exponent); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /math.h: -------------------------------------------------------------------------------- 1 | #ifndef MATH_H 2 | #define MATH_H 3 | 4 | 5 | class Math 6 | { 7 | public: 8 | Math(); 9 | static double add(double a, double b); 10 | static double subtract(double a, double b); 11 | static double multiply(double a, double b); 12 | static double divide(double a, double b); // 返回浮点数结果 13 | static double sqrt(double a); // 开平方 14 | static double pow(double base, double exponent); // 幂运算 15 | }; 16 | 17 | #endif // MATH_H 18 | -------------------------------------------------------------------------------- /opencv.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv.h" 2 | #ifdef OPENCV_FOUND 3 | 4 | opencv::opencv() 5 | { 6 | 7 | } 8 | 9 | QImage opencv::threshold(QImage inputImage, double threshValue, double maxValue, int thresholdType) 10 | { 11 | cv::Mat mat = QImageToCvMat(inputImage); 12 | 13 | if (mat.empty()) { 14 | qWarning("Input image is empty or not supported."); 15 | return QImage(); // 返回一个空的 QImage 16 | } 17 | 18 | cv::Mat grayMat; 19 | if (mat.channels() == 3 || mat.channels() == 4) { 20 | cv::cvtColor(mat, grayMat, cv::COLOR_BGR2GRAY); // 转换为灰度图 21 | } else { 22 | grayMat = mat; // 已经是灰度图 23 | } 24 | 25 | cv::Mat binaryMat; 26 | 27 | cv::threshold(grayMat, binaryMat, threshValue, maxValue, thresholdType); 28 | 29 | QImage binaryQImage = CvMatToQImage(binaryMat); 30 | 31 | return binaryQImage; 32 | } 33 | QStringList opencv::inputNames_threshold = { 34 | "inputImage (类型: QImage)\n\ 35 | 含义:输入的原始图像,类型为QImage,它是Qt中常用的图像格式之一。\n\ 36 | 用途:作为需要进行阈值处理的图像,它可以是彩色图像或灰度图像。输入图像将在函数内部转换为OpenCV的cv::Mat格式,以便进行进一步的图像处理。", 37 | "threshValue (类型: double)\n\ 38 | 含义:用于阈值处理的阈值(通常是灰度值),也就是判断一个像素值是否需要转化为最大值(maxValue)或0(根据thresholdType的不同)。\n\ 39 | 用途:该值用于确定阈值操作的标准。在灰度图像的情况下,像素值小于这个阈值的会被设置为0,像素值大于或等于此阈值的会被设置为maxValue。", 40 | "maxValue (类型: double)\n\ 41 | 含义:当一个像素的灰度值大于或等于阈值时,它将被赋值为maxValue。\n\ 42 | 用途:该参数决定了大于阈值的像素的值。通常它设置为255,表示白色,或者设置为其他值来获得不同的效果。", 43 | "thresholdType (类型: int)\n\ 44 | 含义:阈值处理的类型,决定了如何应用阈值。OpenCV提供了多种阈值类型,常见的有以下几种:\n\ 45 | cv::THRESH_BINARY:如果像素值大于等于threshValue,则设置为maxValue,否则设置为0(黑色)。这是最常用的二值化方式。\n\ 46 | cv::THRESH_BINARY_INV:与THRESH_BINARY相反,如果像素值大于等于threshValue,则设置为0,否则设置为maxValue。\n\ 47 | cv::THRESH_TRUNC:如果像素值大于等于threshValue,则将像素值截断为threshValue,否则不变。\n\ 48 | cv::THRESH_TOZERO:如果像素值大于等于threshValue,则保持原值,否则设置为0。\n\ 49 | cv::THRESH_TOZERO_INV:与THRESH_TOZERO相反,像素值大于等于threshValue时设置为0,否则保持原值。", 50 | }; 51 | QString opencv::outputName_threshold = "QImage"; 52 | 53 | ////////////////////////!!转灰度图!! 54 | QImage opencv::BGRtoGRAY(QImage inputImage) 55 | { 56 | cv::Mat mat = QImageToCvMat(inputImage); 57 | 58 | if (mat.empty()) { 59 | qWarning("Input image is empty or not supported."); 60 | return QImage(); // 返回一个空的 QImage 61 | } 62 | 63 | cv::Mat grayMat; 64 | if (mat.channels() == 3 || mat.channels() == 4) { 65 | // 如果是彩色图,转换为灰度图 66 | cv::cvtColor(mat, grayMat, cv::COLOR_BGR2GRAY); 67 | } else { 68 | // 如果已经是灰度图,直接复制 69 | grayMat = mat; 70 | } 71 | 72 | return CvMatToQImage(grayMat); 73 | } 74 | QStringList opencv::inputNames_BGRtoGRAY = {"QImage"}; 75 | QString opencv::outputName_BGRtoGRAY = "QImage"; 76 | 77 | ////////////////////////!!转HSV空间!! 78 | QImage opencv::BGRtoHSV(QImage inputImage) 79 | { 80 | cv::Mat mat = QImageToCvMat(inputImage); 81 | 82 | if (mat.empty()) { 83 | qWarning("Input image is empty or not supported."); 84 | return QImage(); // 返回一个空的 QImage 85 | } 86 | 87 | cv::cvtColor(mat,mat,cv::COLOR_BGR2HSV); 88 | 89 | return CvMatToQImage(mat); 90 | } 91 | QStringList opencv::inputNames_BGRtoHSV = {"QImage"}; 92 | QString opencv::outputName_BGRtoHSV = "QImage"; 93 | 94 | ////////////////////////!!腐蚀!! 95 | QImage opencv::erode(QImage inputImage,int Size_a = 1,int Size_b = 1) 96 | { 97 | cv::Mat mat = QImageToCvMat(inputImage); 98 | if (Size_a % 2 == 0) Size_a += 1; 99 | if (Size_b % 2 == 0) Size_b += 1; 100 | if (mat.empty()) { 101 | qWarning("Input image is empty or not supported."); 102 | return QImage(); // 返回一个空的 QImage 103 | } 104 | // kernel图像处理矩阵 105 | cv::Mat Kernel = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(Size_a,Size_b)); 106 | // 腐蚀 107 | cv::erode(mat,mat,Kernel); 108 | 109 | return CvMatToQImage(mat); 110 | } 111 | QStringList opencv::inputNames_erode = { 112 | "inputImage (类型: QImage)\n\ 113 | 含义:输入的原始图像,类型为QImage,是Qt中常用的图像格式之一。\n\ 114 | 用途:作为需要进行腐蚀处理的图像,输入图像将在函数内部转换为OpenCV的cv::Mat格式,以便进一步处理。", 115 | "Size_a (类型: int)\n\ 116 | 含义:腐蚀操作所使用的结构元素矩阵的宽度(即水平方向的尺寸)。\n\ 117 | 用途:用于确定腐蚀操作中结构元素的宽度,这将影响腐蚀的强度。结构元素矩阵的大小决定了腐蚀操作对图像的局部影响范围。", 118 | "Size_b (类型: int)\n\ 119 | 含义:腐蚀操作所使用的结构元素矩阵的高度(即垂直方向的尺寸)。\n\ 120 | 用途:用于确定腐蚀操作中结构元素的高度,同样影响腐蚀效果。结构元素的尺寸决定了在图像上进行腐蚀时,邻域内的像素如何变化。" 121 | }; 122 | QString opencv::outputName_erode = "QImage"; 123 | 124 | ////////////////////////!!膨胀!! 125 | QImage opencv::dilate(QImage inputImage,int Size_a = 1,int Size_b = 1) 126 | { 127 | cv::Mat mat = QImageToCvMat(inputImage); 128 | if (Size_a % 2 == 0) Size_a += 1; 129 | if (Size_b % 2 == 0) Size_b += 1; 130 | if (mat.empty()) { 131 | qWarning("Input image is empty or not supported."); 132 | return QImage(); // 返回一个空的 QImage 133 | } 134 | // kernel图像处理矩阵 135 | cv::Mat Kernel = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(Size_a,Size_b)); 136 | // 膨胀 137 | cv::dilate(mat,mat,Kernel); 138 | 139 | return CvMatToQImage(mat); 140 | } 141 | QStringList opencv::inputNames_dilate = { 142 | "inputImage (类型: QImage)\n\ 143 | 含义:输入的原始图像,类型为QImage,它是Qt中常用的图像格式。\n\ 144 | 用途:这是需要进行膨胀处理的输入图像,函数内部会将其转换为OpenCV的cv::Mat格式,以便进行图像处理。", 145 | "Size_a (类型: int, 默认值: 1)\n\ 146 | 含义:膨胀操作时所使用的结构元素矩阵的宽度(即水平方向的尺寸)。\n\ 147 | 用途:控制膨胀操作时结构元素的宽度。这个值越大,膨胀的效果越明显,图像中的亮区域将扩展得更大。", 148 | "Size_b (类型: int, 默认值: 1)\n\ 149 | 含义:膨胀操作时所使用的结构元素矩阵的高度(即垂直方向的尺寸)。\n\ 150 | 用途:控制膨胀操作时结构元素的高度。这个值越大,膨胀效果越强,图像中亮区域的扩展程度也越大。" 151 | }; 152 | QString opencv::outputName_dilate = "QImage"; 153 | 154 | ////////////////////////!!边缘检测!! 155 | QImage opencv::canny(QImage inputImage, int x = 1, int y = 1) 156 | { 157 | cv::Mat mat = QImageToCvMat(inputImage); 158 | 159 | if (mat.empty()) { 160 | qWarning("Input image is empty or not supported."); 161 | return QImage(); // 返回一个空的 QImage 162 | } 163 | 164 | cv::Mat edges; 165 | // 生成新的 Mat 来存储 Canny 输出 166 | cv::Canny(mat, edges, x, y); 167 | 168 | return CvMatToQImage(edges); // 返回处理后的图像 169 | } 170 | 171 | QStringList opencv::inputNames_canny = { 172 | "inputImage (类型: QImage)\n\ 173 | 含义:输入的原始图像,类型为QImage,它是Qt中常用的图像格式。\n\ 174 | 用途:这是需要进行Canny边缘检测的输入图像,函数内部会将其转换为OpenCV的cv::Mat格式,以便进行图像处理。", 175 | "x (类型: int, 默认值: 1)\n\ 176 | 含义:Canny算法的低阈值。\n\ 177 | 用途:用于设置边缘检测的敏感度,较低的值将允许检测到较多的边缘。", 178 | "y (类型: int, 默认值: 1)\n\ 179 | 含义:Canny算法的高阈值。\n\ 180 | 用途:用于设置边缘检测的可靠性,较高的值可以过滤掉不可靠的边缘。" 181 | }; 182 | 183 | QString opencv::outputName_canny = "QImage"; 184 | 185 | ////////////////////////!!高斯模糊!! 186 | QImage opencv::gaussianblur(QImage inputImage, int Size_a = 1, int Size_b = 1, int a = 0, int b = 0) 187 | { 188 | // 确保核大小是正的奇数 189 | if (Size_a <= 0 || Size_b <= 0 || Size_a % 2 == 0 || Size_b % 2 == 0) { 190 | qWarning("Kernel size must be positive odd numbers."); 191 | return QImage(); // 返回一个空的 QImage 192 | } 193 | 194 | cv::Mat mat = QImageToCvMat(inputImage); 195 | 196 | if (mat.empty()) { 197 | qWarning("Input image is empty or not supported."); 198 | return QImage(); // 返回一个空的 QImage 199 | } 200 | 201 | cv::Mat blurred; 202 | // 使用 GaussianBlur 进行模糊 203 | cv::GaussianBlur(mat, blurred, cv::Size(Size_a, Size_b), a, b); 204 | 205 | return CvMatToQImage(blurred); 206 | } 207 | 208 | QStringList opencv::inputNames_gaussianblur = { 209 | "inputImage (类型: QImage)\n\ 210 | 含义:输入的原始图像,类型为QImage,它是Qt中常用的图像格式。\n\ 211 | 用途:这是需要进行高斯模糊处理的输入图像,函数内部会将其转换为OpenCV的cv::Mat格式,以便进行图像处理。", 212 | 213 | "Size_a (类型: int, 默认值: 1)\n\ 214 | 含义:高斯模糊核的宽度(即水平方向的尺寸)。\n\ 215 | 用途:控制高斯模糊核的宽度。模糊核越大,模糊效果越强。必须是正奇数。", 216 | 217 | "Size_b (类型: int, 默认值: 1)\n\ 218 | 含义:高斯模糊核的高度(即垂直方向的尺寸)。\n\ 219 | 用途:控制高斯模糊核的高度。模糊核越大,模糊效果越强。必须是正奇数。", 220 | 221 | "a (类型: int, 默认值: 0)\n\ 222 | 含义:高斯模糊的标准差,在X方向的标准差。\n\ 223 | 用途:控制模糊的程度,标准差越大,模糊效果越强。设为0时,OpenCV会自动根据核的大小计算标准差。", 224 | 225 | "b (类型: int, 默认值: 0)\n\ 226 | 含义:高斯模糊的标准差,在Y方向的标准差。\n\ 227 | 用途:控制模糊的程度,标准差越大,模糊效果越强。设为0时,OpenCV会自动根据核的大小计算标准差。" 228 | }; 229 | QString opencv::outputName_gaussianblur = "QImage"; 230 | 231 | ////////////////////////!!旋转!! 232 | QImage opencv::rotateandincline(QImage inputImage,QPoint center,double angle = 0.0,double scale = 1.0) 233 | { 234 | cv::Mat mat = QImageToCvMat(inputImage); 235 | 236 | if (mat.empty()) { 237 | qWarning("Input image is empty or not supported."); 238 | return QImage(); // 返回一个空的 QImage 239 | } 240 | 241 | cv::Point2f cvcenter; 242 | cvcenter.x = center.x()/2.0f; 243 | cvcenter.y = center.y()/2.0f; 244 | // 计算矩阵,用来对图像进行变换 245 | cv::Mat rotation_matrix = cv::getRotationMatrix2D(cvcenter, angle, scale); 246 | 247 | // 计算新图像的大小,避免图像超出显示框边缘 248 | cv::Rect2f new_size = cv::RotatedRect(cv::Point2f(), mat.size(), angle).boundingRect2f(); 249 | 250 | // 调整矩阵,使旋转后的图像在显示框居中 251 | rotation_matrix.at(0, 2) += new_size.width / 2.0 - cvcenter.x; 252 | rotation_matrix.at(1, 2) += new_size.height / 2.0 - cvcenter.y; 253 | 254 | cv::Mat rotateMat; 255 | // 变换的函数 256 | cv::warpAffine(mat, rotateMat, rotation_matrix, new_size.size()); 257 | 258 | return CvMatToQImage(rotateMat); 259 | } 260 | QStringList opencv::inputNames_rotateandincline = { 261 | "inputImage (类型: QImage)\n\ 262 | 含义:输入的原始图像,类型为 QImage,表示需要进行旋转和倾斜处理的图像。\n\ 263 | 用途:这是函数的输入,函数内部会将其转换为 OpenCV 的 cv::Mat 格式,以便进行图像处理。", 264 | 265 | "center (类型: QPoint)\n\ 266 | 含义:旋转的中心点,类型为 QPoint(Qt 中的点类型)。\n\ 267 | 用途:指定旋转中心的位置,图像将围绕这个点进行旋转。", 268 | 269 | "angle (类型: double, 默认值: 0.0)\n\ 270 | 含义:旋转角度,单位为度(degrees)。\n\ 271 | 用途:控制旋转角度,默认值为 0.0,表示不进行旋转。正值表示顺时针旋转,负值表示逆时针旋转。", 272 | 273 | "scale (类型: double, 默认值: 1.0)\n\ 274 | 含义:缩放因子。\n\ 275 | 用途:控制图像的缩放,1.0 表示不缩放,小于 1.0 表示缩小,超过 1.0 表示放大。" 276 | }; 277 | QString opencv::outputName_rotateandincline = "QImage"; 278 | 279 | ////////////////////////!!透视变换!! 280 | QImage opencv::per_trans(QImage inputImage,QPoint tl,QPoint tr,QPoint dl,QPoint dr,QVector2D imgsize) 281 | { 282 | cv::Mat mat = QImageToCvMat(inputImage); 283 | 284 | if (mat.empty()) { 285 | qWarning("Input image is empty or not supported."); 286 | return QImage(); // 返回一个空的 QImage 287 | } 288 | 289 | cv::Point2f src[4] = {{(float)tl.x(),(float)tl.y()},{(float)tr.x(),(float)tr.y()}, 290 | {(float)dl.x(),(float)dl.y()},{(float)dr.x(),(float)dr.y()}}; 291 | cv::Point2f dst[4] = {{0,0},{imgsize[0],0},{0,imgsize[1]},{imgsize[0],imgsize[1]}}; 292 | // 变换矩阵 293 | cv::Mat martrix = cv::getPerspectiveTransform(src,dst); 294 | // 透视变换 295 | cv::Mat edges; 296 | cv::warpPerspective(mat,edges,martrix,cv::Point(imgsize[0],imgsize[1])); 297 | 298 | return CvMatToQImage(edges); 299 | } 300 | QStringList opencv::inputNames_per_trans = { 301 | "inputImage (类型: QImage)\n\ 302 | 含义:输入的原始图像,类型为 QImage。\n\ 303 | 用途:这是需要进行透视变换的输入图像,函数内部会将其转换为 OpenCV 的 cv::Mat 格式,以便进行图像处理。", 304 | 305 | "tl (类型: QPoint)\n\ 306 | 含义:图像变换前左上角的点,类型为 QPoint。\n\ 307 | 用途:指定透视变换前的图像左上角点坐标。", 308 | 309 | "tr (类型: QPoint)\n\ 310 | 含义:图像变换前右上角的点,类型为 QPoint。\n\ 311 | 用途:指定透视变换前的图像右上角点坐标。", 312 | 313 | "dl (类型: QPoint)\n\ 314 | 含义:图像变换前左下角的点,类型为 QPoint。\n\ 315 | 用途:指定透视变换前的图像左下角点坐标。", 316 | 317 | "dr (类型: QPoint)\n\ 318 | 含义:图像变换前右下角的点,类型为 QPoint。\n\ 319 | 用途:指定透视变换前的图像右下角点坐标。", 320 | 321 | "imgsize (类型: QVector2D)\n\ 322 | 含义:变换后图像的大小,类型为 QVector2D,包含宽度和高度。\n\ 323 | 用途:指定透视变换后的输出图像大小。" 324 | }; 325 | QString opencv::outputName_per_trans = "QImage"; 326 | 327 | ////////////////////////!!缩放大小!! 328 | QImage opencv::resize(QImage inputImage,double x_times = 1,double y_times = 1) 329 | { 330 | cv::Mat mat = QImageToCvMat(inputImage); 331 | // 确保缩放因子大于 0 332 | if (x_times <= 0 || y_times <= 0) { 333 | qWarning("Scale factors must be greater than 0."); 334 | return QImage(); // 返回一个空的 QImage 335 | } 336 | if (mat.empty()) { 337 | qWarning("Input image is empty or not supported."); 338 | return QImage(); // 返回一个空的 QImage 339 | } 340 | cv::Mat edges; 341 | cv::resize(mat,edges,cv::Size(),x_times,y_times); 342 | 343 | return CvMatToQImage(edges); 344 | } 345 | QStringList opencv::inputNames_resize = { 346 | "inputImage (类型: QImage)\n\ 347 | 含义:输入的原始图像,类型为 QImage。\n\ 348 | 用途:这是需要进行缩放操作的输入图像,函数内部会将其转换为 OpenCV 的 cv::Mat 格式,以便进行图像处理。", 349 | 350 | "x_times (类型: double, 默认值: 1)\n\ 351 | 含义:在水平方向上的缩放倍数。\n\ 352 | 用途:控制水平方向的缩放比例,值大于 1.0 时表示放大,值小于 1.0 时表示缩小。", 353 | 354 | "y_times (类型: double, 默认值: 1)\n\ 355 | 含义:在垂直方向上的缩放倍数。\n\ 356 | 用途:控制垂直方向的缩放比例,值大于 1.0 时表示放大,值小于 1.0 时表示缩小。" 357 | }; 358 | QString opencv::outputName_resize = "QImage"; 359 | 360 | ////////////////////////!!画直线!! 361 | QImage opencv::line(QImage inputImage,QPoint start,QPoint end,QColor color,int thickness = 1) 362 | { 363 | cv::Mat mat = QImageToCvMat(inputImage); 364 | if (thickness <= 0) { 365 | qWarning("Thickness must be greater than 0."); 366 | return QImage(); // 返回一个空的 QImage 367 | } 368 | if (mat.empty()) { 369 | qWarning("Input image is empty or not supported."); 370 | return QImage(); // 返回一个空的 QImage 371 | } 372 | 373 | // 数据类型转换 374 | cv::Point cvstart,cvend; 375 | cvstart.x = start.x(); cvstart.y = start.y(); 376 | cvend.x = end.x(); cvend.y = end.y(); 377 | 378 | cv::Scalar cvcolor(color.red(),color.green(),color.blue()); 379 | // 画线 380 | cv::line(mat,cvstart,cvend,cvcolor,thickness); 381 | 382 | return CvMatToQImage(mat); 383 | } 384 | QStringList opencv::inputNames_line = { 385 | "inputImage (类型: QImage)\n\ 386 | 含义:输入的原始图像,类型为 QImage。\n\ 387 | 用途:这是需要进行直线绘制的输入图像,函数内部会将其转换为 OpenCV 的 cv::Mat 格式,以便进行图像处理。", 388 | 389 | "start (类型: QPoint)\n\ 390 | 含义:直线的起点,类型为 QPoint。\n\ 391 | 用途:指定直线的起始点坐标。", 392 | 393 | "end (类型: QPoint)\n\ 394 | 含义:直线的终点,类型为 QPoint。\n\ 395 | 用途:指定直线的结束点坐标。", 396 | 397 | "color (类型: QVector3D)\n\ 398 | 含义:直线的颜色,类型为 QVector3D(三个分量代表 R、G、B)。\n\ 399 | 用途:控制直线的颜色。", 400 | 401 | "thickness (类型: int, 默认值: 1)\n\ 402 | 含义:直线的粗细,类型为 int。\n\ 403 | 用途:控制直线的粗细程度,默认为 1。" 404 | }; 405 | QString opencv::outputName_Bline = "QImage"; 406 | 407 | cv::Mat opencv::QImageToCvMat(const QImage &image) 408 | { 409 | switch (image.format()) { 410 | case QImage::Format_RGB32: { 411 | cv::Mat mat(image.height(), image.width(), CV_8UC4, const_cast(image.bits()), image.bytesPerLine()); 412 | return mat.clone(); // 返回一个深拷贝 413 | } 414 | case QImage::Format_ARGB32: { // 处理 QImage::Format_ARGB32 格式 415 | cv::Mat mat(image.height(), image.width(), CV_8UC4, const_cast(image.bits()), image.bytesPerLine()); 416 | return mat.clone(); // 深拷贝,避免原数据被修改 417 | } 418 | case QImage::Format_RGB888: { 419 | QImage swapped = image.rgbSwapped(); // Qt 使用 BGR 格式,OpenCV 使用 RGB 420 | cv::Mat mat(swapped.height(), swapped.width(), CV_8UC3, const_cast(swapped.bits()), swapped.bytesPerLine()); 421 | return mat.clone(); 422 | } 423 | case QImage::Format_Indexed8: { 424 | cv::Mat mat(image.height(), image.width(), CV_8UC1, const_cast(image.bits()), image.bytesPerLine()); 425 | return mat.clone(); 426 | } 427 | default: 428 | qWarning("QImage format not supported for conversion to cv::Mat."); 429 | return cv::Mat(); 430 | } 431 | } 432 | 433 | 434 | QImage opencv::CvMatToQImage(const cv::Mat &mat) 435 | { 436 | switch (mat.type()) { 437 | case CV_8UC4: { 438 | return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_RGB32).copy(); 439 | } 440 | case CV_8UC3: { 441 | return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_RGB888).rgbSwapped().copy(); // OpenCV 使用 BGR,Qt 使用 RGB 442 | } 443 | case CV_8UC1: { 444 | QVector colorTable; 445 | for (int i = 0; i < 256; i++) { 446 | colorTable.push_back(qRgb(i, i, i)); 447 | } 448 | QImage image(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Indexed8); 449 | image.setColorTable(colorTable); 450 | return image.copy(); 451 | } 452 | default: 453 | qWarning("cv::Mat format not supported for conversion to QImage."); 454 | return QImage(); 455 | } 456 | } 457 | #endif 458 | -------------------------------------------------------------------------------- /opencv.h: -------------------------------------------------------------------------------- 1 | #ifndef OPENCV_H 2 | #define OPENCV_H 3 | 4 | #include 5 | #ifdef OPENCV_FOUND 6 | #include 7 | #include 8 | #include 9 | //#include 10 | class opencv 11 | { 12 | public: 13 | opencv(); 14 | 15 | static QImage threshold(QImage inputImage, double threshValue, double maxValue, int thresholdType); 16 | static QStringList inputNames_threshold; 17 | static QString outputName_threshold; 18 | 19 | 20 | static QImage BGRtoGRAY(QImage inputImage); // 转换灰度图的函数 21 | static QStringList inputNames_BGRtoGRAY; 22 | static QString outputName_BGRtoGRAY; 23 | 24 | static QImage BGRtoHSV(QImage inputImage); // 转换HSv颜色空间的函数 25 | static QStringList inputNames_BGRtoHSV; 26 | static QString outputName_BGRtoHSV; 27 | 28 | static QImage erode(QImage inputImage,int Size_a,int Size_b); // 腐蚀 |(size指定图形处理矩阵kernel的大小,为奇数) 29 | static QStringList inputNames_erode; 30 | static QString outputName_erode; 31 | 32 | static QImage dilate(QImage inputImage,int Size_a,int Size_b); // 膨胀 | 33 | static QStringList inputNames_dilate; 34 | static QString outputName_dilate; 35 | 36 | static QImage canny(QImage inputImage,int x,int y); // 边缘检测(x,y越大线条越粗) 37 | static QStringList inputNames_canny; 38 | static QString outputName_canny; 39 | 40 | static QImage gaussianblur(QImage inputImage,int Size_a,int Size_b,int a,int b); // 高斯模糊(Size越大越模糊) 41 | static QStringList inputNames_gaussianblur; 42 | static QString outputName_gaussianblur; 43 | 44 | // 旋转/倾斜图像(center旋转中心点(做倾斜变换时一般为图像中心点),angle旋转/倾斜角度(度),scale缩放因子) 45 | static QImage rotateandincline(QImage inputImage,QPoint center,double angle,double scale); 46 | static QStringList inputNames_rotateandincline; 47 | static QString outputName_rotateandincline; 48 | 49 | // 透视变换(从图像选取四点tl左上、tr右上、dl左下、dr右下,变换到新图像上,imgsize新图像大小(width,hight)) 50 | static QImage per_trans(QImage inputImage,QPoint tl,QPoint tr,QPoint dl,QPoint dr,QVector2D imgsize); 51 | static QStringList inputNames_per_trans; 52 | static QString outputName_per_trans; 53 | 54 | static QImage resize(QImage inputImage,double x_times,double y_times); // 缩放图像大小(x_times水平方向缩放倍数,y_times竖直方向缩放倍数) 55 | static QStringList inputNames_resize; 56 | static QString outputName_resize; 57 | 58 | static QImage line(QImage inputImage,QPoint start,QPoint end,QColor color,int thickness); //在图像上画直线(从start画到end,color直线颜色,thick直线粗细) 59 | static QStringList inputNames_line; 60 | static QString outputName_Bline; 61 | 62 | private: 63 | // 将 QImage 转换为 cv::Mat 64 | static cv::Mat QImageToCvMat(const QImage &image); 65 | // 将 cv::Mat 转换为 QImage 66 | static QImage CvMatToQImage(const cv::Mat &mat); 67 | }; 68 | #endif 69 | #endif // OPENCV_H 70 | -------------------------------------------------------------------------------- /qblueprint.cpp: -------------------------------------------------------------------------------- 1 | #include "qblueprint.h" 2 | #include "ui_qblueprint.h" 3 | #include 4 | 5 | QBlueprint::QBlueprint(QWidget *parent) 6 | : QGraphicsView(parent), scene(new QGraphicsScene(this)) // 初始化场景 7 | { 8 | // 设置场景的范围,可以根据需要调整 9 | scene->setSceneRect(0, 0, 8000, 8000); 10 | #ifdef OPENCV_FOUND 11 | qDebug() << "OPENCV FOUND"; 12 | #endif 13 | // 将场景设置为QGraphicsView的场景 14 | setScene(scene); 15 | 16 | // 可选:设置视图的一些属性,比如抗锯齿等 17 | setRenderHint(QPainter::Antialiasing); 18 | setDragMode(QGraphicsView::ScrollHandDrag); // 设置拖拽模式 19 | // 初始视图缩放 20 | setViewportUpdateMode(QGraphicsView::FullViewportUpdate); 21 | setBackgroundBrush(QColor(30, 30, 30)); // 设置深色背景 22 | createBlueprintNodes(); 23 | } 24 | 25 | void QBlueprint::createBlueprintNodes() // 使用工厂方法基于函数生成节点 26 | { 27 | 28 | createOutputNode(); 29 | createInputNode(); 30 | createControlNode(); 31 | // 自动获取函数名不正常,直接填写你需要的名称 32 | 33 | // 创建 Math 相关的运算节点 34 | QBlueprintNode* math_add_node = QNodeFactory::createNodeFromFunction(this, &Math::add, "add", "Math"); 35 | QBlueprintNode* math_subtract_node = QNodeFactory::createNodeFromFunction(this, &Math::subtract, "subtract", "Math"); 36 | QBlueprintNode* math_multiply_node = QNodeFactory::createNodeFromFunction(this, &Math::multiply, "multiply", "Math"); 37 | QBlueprintNode* math_divide_node = QNodeFactory::createNodeFromFunction(this, &Math::divide, "divide", "Math"); 38 | QBlueprintNode* math_sqrt_node = QNodeFactory::createNodeFromFunction(this, &Math::sqrt, "sqrt", "Math"); 39 | QBlueprintNode* math_pow_node = QNodeFactory::createNodeFromFunction(this, &Math::pow, "pow", "Math"); 40 | // 创建 Qt 一些难以实现的(QPoint...) 41 | QBlueprintNode* qts_setpointf_node = QNodeFactory::createNodeFromFunction(this, &Qts::setQPointF,Qts::inputNames_setQPointF,Qts::outputName_setQPointF, "setQPointF", "Qts"); 42 | QBlueprintNode* qts_setpoint_node = QNodeFactory::createNodeFromFunction(this, &Qts::setQPoint, "setQPoint", "Qts"); 43 | QBlueprintNode* qts_getpointf_x_node = QNodeFactory::createNodeFromFunction(this, &Qts::getQPointF_X, "getQPointF_X", "Qts"); 44 | QBlueprintNode* qts_getpointf_y_node = QNodeFactory::createNodeFromFunction(this, &Qts::getQPointF_Y, "getQPointF_Y", "Qts"); 45 | QBlueprintNode* qts_getpoint_x_node = QNodeFactory::createNodeFromFunction(this, &Qts::getQPoint_X, "getQPoint_X", "Qts"); 46 | QBlueprintNode* qts_getpoint_y_node = QNodeFactory::createNodeFromFunction(this, &Qts::getQPoint_Y, "getQPoint_Y", "Qts"); 47 | 48 | #ifdef OPENCV_FOUND 49 | QBlueprintNode* opencv_threshold_node = QNodeFactory::createNodeFromFunction(this, &opencv::threshold, opencv::inputNames_threshold,opencv::outputName_threshold,"threshold", "opencv"); 50 | QBlueprintNode* opencv_BGRtoGRAY_node = QNodeFactory::createNodeFromFunction( 51 | this, 52 | &opencv::BGRtoGRAY, 53 | opencv::inputNames_BGRtoGRAY, 54 | opencv::outputName_BGRtoGRAY, 55 | "BGRtoGRAY", 56 | "opencv"); 57 | QBlueprintNode* opencv_BGRtoHSV_node = QNodeFactory::createNodeFromFunction( 58 | this, 59 | &opencv::BGRtoHSV, 60 | opencv::inputNames_BGRtoHSV, 61 | opencv::outputName_BGRtoHSV, 62 | "BGRtoHSV", 63 | "opencv"); 64 | QBlueprintNode* opencv_erode_node = QNodeFactory::createNodeFromFunction( 65 | this, 66 | &opencv::erode, 67 | opencv::inputNames_erode, 68 | opencv::outputName_erode, 69 | "erode", 70 | "opencv" 71 | ); 72 | QBlueprintNode* opencv_dilate_node = QNodeFactory::createNodeFromFunction( 73 | this, 74 | &opencv::dilate, 75 | opencv::inputNames_dilate, 76 | opencv::outputName_dilate, 77 | "dilate", 78 | "opencv" 79 | ); 80 | QBlueprintNode* opencv_canny_node = QNodeFactory::createNodeFromFunction( 81 | this, 82 | &opencv::canny, 83 | opencv::inputNames_canny, 84 | opencv::outputName_canny, 85 | "canny", 86 | "opencv" 87 | ); 88 | QBlueprintNode* opencv_gaussianblur_node = QNodeFactory::createNodeFromFunction( 89 | this, 90 | &opencv::gaussianblur, 91 | opencv::inputNames_gaussianblur, 92 | opencv::outputName_gaussianblur, 93 | "gaussianblur", 94 | "opencv" 95 | ); 96 | QBlueprintNode* opencv_rotateandincline_node = QNodeFactory::createNodeFromFunction( 97 | this, 98 | &opencv::rotateandincline, 99 | opencv::inputNames_rotateandincline, 100 | opencv::outputName_rotateandincline, 101 | "rotateandincline", 102 | "opencv" 103 | ); 104 | QBlueprintNode* opencv_per_trans_node = QNodeFactory::createNodeFromFunction( 105 | this, 106 | &opencv::per_trans, 107 | opencv::inputNames_per_trans, 108 | opencv::outputName_per_trans, 109 | "per_trans", 110 | "opencv" 111 | ); 112 | QBlueprintNode* opencv_resize_node = QNodeFactory::createNodeFromFunction( 113 | this, 114 | &opencv::resize, 115 | opencv::inputNames_resize, 116 | opencv::outputName_resize, 117 | "resize", 118 | "opencv" 119 | ); 120 | QBlueprintNode* opencv_line_node = QNodeFactory::createNodeFromFunction( 121 | this, 122 | &opencv::line, 123 | opencv::inputNames_line, 124 | opencv::outputName_Bline, 125 | "line", 126 | "opencv" 127 | ); 128 | 129 | //QBlueprintNode* opencv_convertToGray_node = QNodeFactory::createNodeFromFunction(this, &opencv::convertToGray, "convertToGray", "opencv"); 130 | #endif 131 | classifyNodes(); 132 | } 133 | 134 | void QBlueprint::classifyNodes() 135 | { 136 | std::unordered_map> classifiedNodes; 137 | for (QBlueprintNode* node : save_nodes) { 138 | QString className = node->getClassName(); 139 | QString functionName = node->getNodeTitle(); 140 | 141 | // 将函数名添加到对应类名的列表中 142 | classifiedNodes[className].push_back(functionName); 143 | } 144 | 145 | // 输出分类结果,或者进一步处理 146 | for (const auto& entry : classifiedNodes) { 147 | QString className = entry.first; 148 | const std::vector& functions = entry.second; 149 | 150 | qDebug() << "Class Name:" << className; 151 | for (const QString& func : functions) { 152 | qDebug() << " Function:" << func; 153 | } 154 | } 155 | } 156 | QBlueprint::~QBlueprint() 157 | { 158 | 159 | } 160 | void QBlueprint::contextMenuEvent(QContextMenuEvent* event) 161 | { 162 | // 创建右键菜单(主菜单,一级菜单) 163 | QMenu contextMenu; 164 | QPointF scenePos = mapToScene(event->pos()); 165 | 166 | // 设置 contextMenu(一级菜单)的样式表,保留圆角 167 | contextMenu.setStyleSheet( 168 | "QMenu { " 169 | " background-color: #353535; " // 一级菜单背景色 170 | " color: white; " // 一级菜单文字颜色 171 | " border: 2px solid #5A5A5A; " // 一级菜单边框 172 | " border-radius: 8px; " // 圆角边框 173 | "} " 174 | "QMenu::item:selected { " 175 | " background-color: #2A82DA; " // 鼠标悬停时的背景色(一级菜单) 176 | " color: white; " // 鼠标悬停时的文字颜色 177 | " border-radius: 4px; " // 圆角边框 178 | "}" 179 | ); 180 | 181 | // 使用一个 map 来临时存储类名和对应的节点列表 182 | QMap> classNodeMap; 183 | for (QBlueprintNode* node : save_nodes) { 184 | QString className = node->getClassName(); 185 | classNodeMap[className].append(node); 186 | } 187 | 188 | // 遍历每个类 189 | for (auto it = classNodeMap.begin(); it != classNodeMap.end(); ++it) { 190 | QString className = it.key(); 191 | QList nodes = it.value(); 192 | 193 | // 创建类名的一级菜单(作为 contextMenu 的子菜单) 194 | QMenu* classMenu = contextMenu.addMenu(className); 195 | 196 | // 根据类名设置不同的样式表,并保留圆角 197 | if (className == "Input") { 198 | // 设置 Input 类的样式:绿色(二级菜单),带圆角 199 | classMenu->setStyleSheet( 200 | "QMenu { " 201 | " background-color: #DFFFD6; " // 二级菜单的绿色背景 202 | " color: black; " // 二级菜单的文字颜色 203 | " border: 2px solid green; " // 二级菜单的绿色边框 204 | " border-radius: 8px; " // 圆角边框 205 | "} " 206 | "QMenu::item:selected { " 207 | " background-color: #8CD98C; " // 鼠标悬停时的绿色背景 208 | " color: black; " // 鼠标悬停时的文字颜色 209 | " border-radius: 4px; " // 圆角边框 210 | "}" 211 | ); 212 | } else if (className == "Output") { 213 | // 设置 Output 类的样式:红色(二级菜单),带圆角 214 | classMenu->setStyleSheet( 215 | "QMenu { " 216 | " background-color: #FFD6D6; " // 二级菜单的红色背景 217 | " color: black; " // 二级菜单的文字颜色 218 | " border: 2px solid red; " // 二级菜单的红色边框 219 | " border-radius: 8px; " // 圆角边框 220 | "} " 221 | "QMenu::item:selected { " 222 | " background-color: #FF7A7A; " // 鼠标悬停时的红色背景 223 | " color: black; " // 鼠标悬停时的文字颜色 224 | " border-radius: 4px; " // 圆角边框 225 | "}" 226 | ); 227 | } else { 228 | // 设置其他类的样式:蓝色(二级菜单),带圆角 229 | classMenu->setStyleSheet( 230 | "QMenu { " 231 | " background-color: #D6E6FF; " // 二级菜单的蓝色背景 232 | " color: black; " // 二级菜单的文字颜色 233 | " border: 2px solid blue; " // 二级菜单的蓝色边框 234 | " border-radius: 8px; " // 圆角边框 235 | "} " 236 | "QMenu::item:selected { " 237 | " background-color: #7A9EFF; " // 鼠标悬停时的蓝色背景 238 | " color: black; " // 鼠标悬停时的文字颜色 239 | " border-radius: 4px; " // 圆角边框 240 | "}" 241 | ); 242 | } 243 | 244 | // 添加该类的所有函数到二级菜单中 245 | for (QBlueprintNode* node : nodes) { 246 | QString functionName = node->getNodeTitle(); 247 | QAction* action = classMenu->addAction(functionName); 248 | 249 | // 使用 lambda 表达式捕获节点信息 250 | connect(action, &QAction::triggered, [this, node, scenePos]() { 251 | placeNodeInScene(node, scenePos); 252 | }); 253 | } 254 | } 255 | 256 | // 显示菜单 257 | contextMenu.exec(event->globalPos()); 258 | } 259 | 260 | 261 | void QBlueprint::placeNodeInScene(QBlueprintNode* originalNode, const QPointF& mousePos) 262 | { 263 | // 使用 clone 方法创建节点的副本 264 | QBlueprintNode* newNode = originalNode->clone(); 265 | // 设置位置 266 | newNode->setPos(mousePos); 267 | 268 | // 将节点添加到场景 269 | scene->addItem(newNode); 270 | 271 | // 将节点添加到 scene_nodes 向量中 272 | scene_nodes.push_back(newNode); 273 | } 274 | 275 | 276 | void QBlueprint::pushVectorQBlueprintNode(QBlueprintNode *node) 277 | { 278 | save_nodes.push_back(node); 279 | } 280 | 281 | // 重载绘制背景方法 282 | void QBlueprint::drawBackground(QPainter *painter, const QRectF &rect) 283 | { 284 | // 调用父类的绘制背景方法(以便保持默认行为) 285 | QGraphicsView::drawBackground(painter, rect); 286 | 287 | // 绘制网格背景 288 | const int gridSize = 20; // 网格大小 289 | QPen lightPen(QColor(60, 60, 60), 1); // 浅色网格线 290 | QPen darkPen(QColor(25, 25, 25), 2); // 深色网格线 291 | 292 | // 获取当前视图的矩形区域 293 | QRectF viewRect = rect; 294 | 295 | // 绘制小网格线 296 | painter->setPen(lightPen); 297 | for (qreal x = std::floor(viewRect.left() / gridSize) * gridSize; x < viewRect.right(); x += gridSize) 298 | { 299 | painter->drawLine(QLineF(x, viewRect.top(), x, viewRect.bottom())); 300 | } 301 | for (qreal y = std::floor(viewRect.top() / gridSize) * gridSize; y < viewRect.bottom(); y += gridSize) 302 | { 303 | painter->drawLine(QLineF(viewRect.left(), y, viewRect.right(), y)); 304 | } 305 | 306 | // 绘制大网格线(每隔5个小网格) 307 | painter->setPen(darkPen); 308 | for (qreal x = std::floor(viewRect.left() / (gridSize * 5)) * (gridSize * 5); x < viewRect.right(); x += gridSize * 5) 309 | { 310 | painter->drawLine(QLineF(x, viewRect.top(), x, viewRect.bottom())); 311 | } 312 | for (qreal y = std::floor(viewRect.top() / (gridSize * 5)) * (gridSize * 5); y < viewRect.bottom(); y += gridSize * 5) 313 | { 314 | painter->drawLine(QLineF(viewRect.left(), y, viewRect.right(), y)); 315 | } 316 | } 317 | 318 | void QBlueprint::wheelEvent(QWheelEvent *event) 319 | { 320 | const double scaleFactor = 1.15; // 缩放因子 321 | 322 | // 获取当前视图的变换矩阵 323 | QTransform currentTransform = transform(); 324 | 325 | // 当前的缩放比例,假设 x 和 y 的缩放比例相同 326 | double currentScale = currentTransform.m11(); // m11() 获取水平缩放比例 327 | 328 | // 根据滚轮滚动方向进行缩放,并限制缩放范围 329 | if (event->angleDelta().y() > 0) // 如果滚轮向上滚动,试图放大 330 | { 331 | if (currentScale < maxScaleFactor) // 限制最大缩放 332 | { 333 | scale(scaleFactor, scaleFactor); // 放大视图 334 | } 335 | } 336 | else // 如果滚轮向下滚动,试图缩小 337 | { 338 | if (currentScale > minScaleFactor) // 限制最小缩放 339 | { 340 | scale(1.0 / scaleFactor, 1.0 / scaleFactor); // 缩小视图 341 | } 342 | } 343 | 344 | // 获取鼠标在视图中的位置 345 | QPointF viewPos = event->position(); 346 | 347 | // 将视图坐标转换为场景坐标 348 | QPointF scenePos = mapToScene(viewPos.toPoint()); 349 | 350 | // 获取缩放后的鼠标位置在场景中的位置 351 | QPointF newScenePos = mapToScene(viewPos.toPoint()); 352 | 353 | // 计算视图需要移动的距离,以使得缩放后鼠标位置不变 354 | QPointF delta = newScenePos - scenePos; 355 | 356 | // 调整视图的中心,使得缩放后的鼠标位置保持不变 357 | setTransformationAnchor(QGraphicsView::NoAnchor); 358 | translate(delta.x(), delta.y()); 359 | // 在缩放后更新所有连接的位置 360 | updateAllConnections(); 361 | event->accept(); // 接受事件 362 | } 363 | void QBlueprint::mousePressEvent(QMouseEvent *event) 364 | { 365 | // 将视图坐标转换为场景坐标 366 | QPointF scenePos = mapToScene(event->pos()); 367 | 368 | // 查找鼠标点击的图元 369 | QGraphicsItem *item = scene->itemAt(scenePos, QTransform()); 370 | qDebug()<< "clicked item:" << item; 371 | // 首先检查是否点击在端口上 372 | QBlueprintPort *port = dynamic_cast(item); 373 | if (port) 374 | { 375 | qDebug() << "Clicked on port:" << port->name(); 376 | m_draggingPort = port; 377 | startConnectionDrag(scenePos); 378 | return; 379 | } 380 | 381 | // 然后检查是否点击在节点上 382 | QBlueprintNode *node = dynamic_cast(item); 383 | if (node) 384 | { 385 | // 使用访问器方法获取端口列表 386 | const auto& inputPorts = node->getInputPorts(); 387 | const auto& outputPorts = node->getOutputPorts(); 388 | 389 | // 遍历找到点击的端口 390 | for (auto *port : inputPorts) 391 | { 392 | if (port->contains(port->mapFromScene(scenePos))) 393 | { 394 | qDebug() << "Clicked on input port:" << port->name(); 395 | m_draggingPort = port; 396 | startConnectionDrag(scenePos); 397 | return; 398 | } 399 | } 400 | 401 | for (auto *port : outputPorts) 402 | { 403 | if (port->contains(port->mapFromScene(scenePos))) 404 | { 405 | qDebug() << "Clicked on output port:" << port->name(); 406 | m_draggingPort = port; 407 | startConnectionDrag(scenePos); 408 | return; 409 | } 410 | } 411 | } 412 | 413 | QGraphicsView::mousePressEvent(event); 414 | } 415 | 416 | void QBlueprint::mouseMoveEvent(QMouseEvent *event) 417 | { 418 | if (m_draggingPort && m_currentConnection) 419 | { 420 | // 将视图坐标转换为场景坐标 421 | QPointF scenePos = mapToScene(event->pos()); 422 | 423 | // 更新临时连线的位置 424 | m_currentConnection->updatePosition(m_draggingPort->centerPos(), scenePos); 425 | } 426 | 427 | QGraphicsView::mouseMoveEvent(event); 428 | } 429 | 430 | void QBlueprint::mouseReleaseEvent(QMouseEvent *event) 431 | { 432 | if (m_draggingPort && m_currentConnection) 433 | { 434 | // 将视图坐标转换为场景坐标 435 | QPointF scenePos = mapToScene(event->pos()); 436 | 437 | // 遍历场景中的所有项目,寻找匹配的端口 438 | QBlueprintPort *targetPort = nullptr; 439 | for (QGraphicsItem *item : scene->items(scenePos)) 440 | { 441 | targetPort = dynamic_cast(item); 442 | if (targetPort && targetPort != m_draggingPort && targetPort->portType() != m_draggingPort->portType()&&targetPort->parentItem() != m_draggingPort->parentItem()) 443 | { 444 | break; // 找到目标端口,退出循环 445 | } 446 | } 447 | if(targetPort) 448 | { 449 | if((targetPort->portType() == QBlueprintPort::EVENT_INPUT && m_currentConnection->startPort()->portType() == QBlueprintPort::EVENT_OUTPUT) 450 | ||(targetPort->portType() == QBlueprintPort::EVENT_OUTPUT && m_currentConnection->startPort()->portType() == QBlueprintPort::EVENT_INPUT) 451 | ||(targetPort->portType() == QBlueprintPort::EVENT_INPUT && m_currentConnection->startPort()->portType() == QBlueprintPort::EVENT_FALSE_RETURN) 452 | ||(targetPort->portType() == QBlueprintPort::EVENT_INPUT && m_currentConnection->startPort()->portType() == QBlueprintPort::EVENT_TRUE_RETURN)) 453 | { 454 | qDebug() << "事件端口连接"; 455 | m_currentConnection->setEndPort(targetPort); 456 | m_draggingPort->sendDataToConnectedPorts(); 457 | propagateDataFromInitialNode(m_currentConnection->startPort()); 458 | } 459 | else if (areTypesCompatible(m_currentConnection->startPort()->getVarTypeName(),targetPort->getVarTypeName()) 460 | && targetPort->portType()!=QBlueprintPort::EVENT_INPUT && targetPort->portType()!=QBlueprintPort::EVENT_OUTPUT) 461 | { 462 | qDebug() << "Found target port:" << targetPort->name(); 463 | // 连接两个端口 464 | m_currentConnection->setEndPort(targetPort); 465 | m_draggingPort->sendDataToConnectedPorts(); 466 | } 467 | else if(m_currentConnection->startPort()->getVarTypeName()==targetPort->getVarTypeName()) 468 | qDebug() << "真的吗"; 469 | else 470 | { 471 | qDebug() << m_currentConnection->startPort()->getVarTypeName() << " vs " << targetPort->getVarTypeName(); 472 | removeConnection(m_currentConnection); // 删除连接 473 | } 474 | } 475 | else 476 | removeConnection(m_currentConnection); 477 | 478 | 479 | m_currentConnection = nullptr; 480 | m_draggingPort = nullptr; 481 | } 482 | 483 | QGraphicsView::mouseReleaseEvent(event); 484 | } 485 | 486 | void QBlueprint::updateConnectionsForPort(QBlueprintPort *port) 487 | { 488 | // 遍历所有连接并更新与指定端口相关的连接 489 | for (QBlueprintConnection *connection : connections) 490 | { 491 | if (connection->startPort() == port) 492 | { 493 | connection->updatePosition(port->centerPos(), connection->endPort()->centerPos()); 494 | } 495 | else if (connection->endPort() == port) 496 | { 497 | connection->updatePosition(connection->startPort()->centerPos(), port->centerPos()); 498 | } 499 | } 500 | } 501 | 502 | void QBlueprint::updateAllConnections() 503 | { 504 | for (QBlueprintConnection* connection : connections) 505 | { 506 | if (connection->startPort() && connection->endPort()) 507 | { 508 | connection->updatePosition(connection->startPort()->centerPos(), connection->endPort()->centerPos()); 509 | } 510 | } 511 | } 512 | 513 | 514 | // 添加连接到列表中 515 | void QBlueprint::addConnection(QBlueprintConnection* connection) 516 | { 517 | connections.push_back(connection); 518 | scene->addItem(connection); // 将连接添加到场景中 519 | } 520 | 521 | // 从列表中移除连接 522 | void QBlueprint::removeConnection(QBlueprintConnection* connection) 523 | { 524 | connections.erase(std::remove(connections.begin(), connections.end(), connection), connections.end()); 525 | scene->removeItem(connection); // 从场景中移除连接 526 | delete connection; 527 | } 528 | 529 | // 在 startConnectionDrag 中使用 addConnection 530 | void QBlueprint::startConnectionDrag(const QPointF &startPos) 531 | { 532 | // 创建临时连线 533 | m_currentConnection = new QBlueprintConnection(m_draggingPort, nullptr); 534 | addConnection(m_currentConnection); // 将连接添加到管理列表中 535 | 536 | // 设置初始位置 537 | m_currentConnection->updatePosition(m_draggingPort->centerPos(), startPos); 538 | 539 | // 强制刷新场景 540 | scene->update(); 541 | } 542 | 543 | void QBlueprint::createOutputNode() 544 | { 545 | for (int i = 1; i < NUM_DATA_TYPES; ++i) { 546 | addOutputNode(static_cast(i)); 547 | } 548 | } 549 | 550 | void QBlueprint::createInputNode() 551 | { 552 | for (int i = 1; i < NUM_DATA_TYPES; ++i) { 553 | addInputNode(static_cast(i)); 554 | } 555 | } 556 | 557 | void QBlueprint::createControlNode() 558 | { 559 | QBlueprintNode* node_branch = new QBlueprintNode(Type::BRANCH); 560 | node_branch->setClassName("Control"); 561 | node_branch->setNodeTitle("Branch"); 562 | save_nodes.push_back(node_branch); 563 | 564 | QBlueprintNode* node_forloop = new QBlueprintNode(Type::FORLOOP); 565 | node_forloop->setClassName("Control"); 566 | node_forloop->setNodeTitle("ForLoop"); 567 | save_nodes.push_back(node_forloop); 568 | 569 | QBlueprintNode* node_condition = new QBlueprintNode(Type::CONDITION,DataType::VARIANT); 570 | node_condition->setClassName("Control"); 571 | node_condition->setNodeTitle("Condition"); 572 | save_nodes.push_back(node_condition); 573 | } 574 | void QBlueprint::addOutputNode(DataType dataType) 575 | { 576 | QBlueprintNode* node = new QBlueprintNode(Type::OUTPUT,dataType); 577 | node->setClassName("Output"); 578 | node->setNodeTitle(getEnumName(dataType)); 579 | save_nodes.push_back(node); 580 | } 581 | void QBlueprint::addInputNode(DataType dataType) 582 | { 583 | QBlueprintNode* node = new QBlueprintNode(Type::INPUT,dataType); 584 | node->setClassName("Input"); 585 | node->setNodeTitle(getEnumName(dataType)); 586 | save_nodes.push_back(node); 587 | } 588 | bool QBlueprint::isEventPortConnected(QBlueprintPort* outputPort, QBlueprintPort* inputPort) const { 589 | for (QBlueprintConnection* connection : connections) { 590 | qDebug() << "connection->startPort():" << connection->startPort()->portType() << "connection->endPort():" << connection->endPort()->portType(); 591 | if ((/*connection->startPort() == outputPort && connection->endPort() == inputPort &&*/ 592 | connection->startPort()->portType() == QBlueprintPort::EVENT_OUTPUT && 593 | connection->endPort()->portType() == QBlueprintPort::EVENT_INPUT) || 594 | (/*connection->startPort() == inputPort && connection->endPort() == outputPort &&*/ 595 | connection->startPort()->portType() == QBlueprintPort::EVENT_INPUT && 596 | connection->endPort()->portType() == QBlueprintPort::EVENT_OUTPUT)) { 597 | return true; 598 | } 599 | else if((connection->startPort()->data() == "true" && connection->startPort()->portType() == QBlueprintPort::EVENT_TRUE_RETURN 600 | && connection->endPort()->portType() == QBlueprintPort::EVENT_INPUT)|| 601 | (connection->startPort()->data() == "true" && connection->startPort()->portType() == QBlueprintPort::EVENT_INPUT 602 | && connection->endPort()->portType() == QBlueprintPort::EVENT_TRUE_RETURN)){ 603 | return true; 604 | qDebug() << "true"; 605 | } 606 | else if((connection->startPort()->data() == "false" && connection->startPort()->portType() == QBlueprintPort::EVENT_FALSE_RETURN 607 | && connection->endPort()->portType() == QBlueprintPort::EVENT_INPUT)|| 608 | (connection->startPort()->data() == "false" && connection->startPort()->portType() == QBlueprintPort::EVENT_INPUT 609 | && connection->endPort()->portType() == QBlueprintPort::EVENT_FALSE_RETURN)){ 610 | return true; 611 | qDebug() << "false"; 612 | } 613 | } 614 | return false; 615 | } 616 | void QBlueprint::propagateDataFromInitialNode(QBlueprintPort* initialPort) 617 | { 618 | if (!initialPort) return; 619 | 620 | // 获取初始端口的父节点 621 | QBlueprintNode* initialNode = dynamic_cast(initialPort->parentItem()); 622 | if (!initialNode) return; 623 | 624 | // 找到该节点的所有输出端口,并发送数据 625 | for (QBlueprintPort* outputPort : initialNode->getOutputPorts()) 626 | { 627 | outputPort->sendDataToConnectedPorts(); 628 | } 629 | } 630 | bool QBlueprint::isNumericType(const QString& type) 631 | { 632 | return (type == "int" || type == "float" || type == "double" || 633 | type == "short" || type == "long" || type == "unsigned int"); 634 | } 635 | // 类型兼容性检查函数 636 | bool QBlueprint::areTypesCompatible(const QString& type1, const QString& type2) 637 | { 638 | // 如果是相同类型,直接返回 true 639 | if (type1 == type2) 640 | return true; 641 | 642 | // 数值类型之间相互兼容 643 | if (isNumericType(type1) && isNumericType(type2)) 644 | return true; 645 | 646 | // QString 和 char* 之间相互兼容 647 | if ((type1 == "QString" && type2 == "char*") || (type1 == "char*" && type2 == "QString")) 648 | return true; 649 | 650 | // 默认情况下认为不兼容 651 | return false; 652 | } 653 | 654 | 655 | -------------------------------------------------------------------------------- /qblueprint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "qblueprintnode.h" 10 | #include "qblueprintconnection.h" 11 | #include "qnodefactory.h" 12 | 13 | /****************************************************/ 14 | // 在这里添加你的头文件 15 | #include "testclass.h" 16 | #include "math.h" 17 | #include "qts.h" 18 | #ifdef OPENCV_FOUND 19 | #include "opencv.h" 20 | #endif 21 | /****************************************************/ 22 | class QBlueprint : public QGraphicsView 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | explicit QBlueprint(QWidget *parent = nullptr); 28 | ~QBlueprint(); 29 | void removeConnection(QBlueprintConnection* connection); 30 | 31 | // 更新与指定端口相关的所有连接 32 | void updateConnectionsForPort(QBlueprintPort *port); 33 | // 存储所有连接的列表 34 | std::vector connections; 35 | //void addBlueprintNode(QString funcname); 36 | void createBlueprintNodes(); 37 | static int add(int a, int b, int c); 38 | static int deletea(int a); 39 | void placeNodeInScene(QBlueprintNode *originalNode, const QPointF& mousePos); 40 | void pushVectorQBlueprintNode(QBlueprintNode* node); 41 | bool isEventPortConnected(QBlueprintPort *outputPort, QBlueprintPort *inputPort) const; 42 | void propagateDataFromInitialNode(QBlueprintPort *initialPort); 43 | protected: 44 | void drawBackground(QPainter *painter, const QRectF &rect) override; 45 | void wheelEvent(QWheelEvent *event) override; 46 | void mousePressEvent(QMouseEvent *event) override; 47 | void mouseMoveEvent(QMouseEvent *event) override; 48 | void mouseReleaseEvent(QMouseEvent *event) override; 49 | 50 | void contextMenuEvent(QContextMenuEvent *event) override; 51 | private: 52 | const double minScaleFactor = 0.1; 53 | const double maxScaleFactor = 8.0; 54 | QGraphicsScene *scene; 55 | 56 | std::vector save_nodes; // 用于存储所有节点 57 | std::vector scene_nodes; // 用于存储场景中的节点 58 | 59 | 60 | // 添加和移除连接的方法 61 | void addConnection(QBlueprintConnection* connection); 62 | 63 | QBlueprintPort *m_draggingPort = nullptr; 64 | QBlueprintConnection *m_currentConnection = nullptr; 65 | void startConnectionDrag(const QPointF &startPos); 66 | void updateAllConnections(); 67 | void classifyNodes(); 68 | void createOutputNode(); // 输出节点,又连接输入,显示信息,同时也可再输出 69 | void createInputNode(); // 输入节点,用户输入,节点输出用户输入的内容 70 | void createControlNode(); // 控制节点,if-else,判断大小,for语句 71 | void addOutputNode(DataType dataType); 72 | void addInputNode(DataType dataType); 73 | bool isNumericType(const QString &type); 74 | bool areTypesCompatible(const QString &type1, const QString &type2); 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /qblueprint.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | QBlueprint 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /qblueprintconnection.cpp: -------------------------------------------------------------------------------- 1 | #include "qblueprintconnection.h" 2 | #include 3 | #include 4 | #include "qblueprint.h" 5 | QBlueprintConnection::QBlueprintConnection(QBlueprintPort *startPort, QBlueprintPort *endPort, QGraphicsItem *parent) 6 | : QGraphicsItem(parent), m_startPort(startPort), m_endPort(endPort), animationProgress(0.0) 7 | { 8 | // 初始化起点和终点坐标 9 | m_startPoint = startPort->centerPos(); 10 | if(startPort->portType() == QBlueprintPort::EVENT_INPUT || startPort->portType() == QBlueprintPort::EVENT_OUTPUT) 11 | m_startColor = Qt::white; 12 | else 13 | m_startColor = getColorFromType(startPort->getNodeType()); 14 | qDebug() << startPort->getNodeType(); 15 | if (endPort) 16 | { 17 | m_endPoint = endPort->centerPos(); 18 | m_endColor = getColorFromType(endPort->getNodeType()); 19 | } 20 | else 21 | { 22 | m_endPoint = m_startPoint; // 如果没有终点端口,临时设置终点为起点 23 | m_endColor = QColor(Qt::yellow); 24 | } 25 | setZValue(2); 26 | setFlag(QGraphicsItem::ItemIsSelectable, false); 27 | 28 | updatePosition(m_startPoint, m_endPoint); // 初始化位置 29 | 30 | // 初始化定时器 31 | animationTimer = new QTimer(this); 32 | connect(animationTimer, &QTimer::timeout, this, [this]() { 33 | animationProgress += 0.01; // 每次更新增加动画进度 34 | if (animationProgress > 1.0) // 超过1则重置为0 35 | animationProgress = 0.0; 36 | update(); // 触发重绘 37 | }); 38 | } 39 | 40 | void QBlueprintConnection::updatePosition(const QPointF &startPos, const QPointF &endPos) 41 | { 42 | // 更新起点和终点 43 | prepareGeometryChange(); // 通知 Qt 几何形状发生变化 44 | m_startPoint = startPos; 45 | m_endPoint = endPos; 46 | update(); // 刷新图形项 47 | } 48 | 49 | QRectF QBlueprintConnection::boundingRect() const 50 | { 51 | // 计算连接线的边界矩形 52 | return QRectF(m_startPoint, m_endPoint).normalized().adjusted(-5, -5, 5, 5); 53 | } 54 | 55 | void QBlueprintConnection::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 56 | { 57 | QPen pen; 58 | 59 | if (isSelected) 60 | { 61 | // 使用动态渐变效果 62 | QLinearGradient gradient(m_startPoint, m_endPoint); 63 | 64 | // 动态调整渐变的颜色位置, 使用多个渐变点模拟流动 65 | int numPoints = 20; // 渐变点的数量 66 | for (int i = 0; i <= numPoints; ++i) { 67 | // 计算每个渐变点的位置和颜色 68 | qreal position = fmod(animationProgress + static_cast(i) / numPoints, 1.0); 69 | QColor color = (i % 2 == 0) ? Qt::green : Qt::white; // 交替颜色 70 | gradient.setColorAt(position, color); 71 | } 72 | 73 | // 设置连接线的样式,将渐变应用到笔刷 74 | pen = QPen(QBrush(gradient), 2); // 使用渐变作为画笔 75 | } 76 | else 77 | { 78 | // 使用静态渐变效果 79 | QLinearGradient gradient(m_startPoint, m_endPoint); 80 | gradient.setColorAt(0, m_startColor); // 设置起始颜色 81 | gradient.setColorAt(1, m_endColor); // 设置结束颜色 82 | 83 | // 设置连接线的样式,将渐变应用到笔刷 84 | pen = QPen(QBrush(gradient), 2); // 使用渐变作为画笔 85 | } 86 | 87 | painter->setPen(pen); 88 | 89 | // 创建贝塞尔曲线 90 | QPainterPath path(m_startPoint); 91 | 92 | // 计算控制点 93 | QPointF controlPoint1; 94 | QPointF controlPoint2; 95 | 96 | // 获取起点和终点的位置差 97 | qreal dx = m_endPoint.x() - m_startPoint.x(); 98 | qreal dy = m_endPoint.y() - m_startPoint.y(); 99 | qreal offset = qAbs(dx) * 0.6; // 控制点偏移量 100 | 101 | if (m_startPort->portType() == QBlueprintPort::Output || m_startPort->portType() == QBlueprintPort::EVENT_OUTPUT 102 | || m_startPort->portType() == QBlueprintPort::EVENT_TRUE_RETURN || m_startPort->portType() == QBlueprintPort::EVENT_FALSE_RETURN) 103 | { 104 | if (dx > 0) 105 | { 106 | controlPoint1 = m_startPoint + QPointF(offset, 0); 107 | controlPoint2 = m_endPoint - QPointF(offset, 0); 108 | } 109 | else 110 | { 111 | controlPoint1 = m_startPoint + QPointF(offset, dy * 0.5); 112 | controlPoint2 = m_endPoint - QPointF(offset, -dy * 0.5); 113 | } 114 | } 115 | else if (m_startPort->portType() == QBlueprintPort::Input || m_startPort->portType() == QBlueprintPort::EVENT_INPUT) 116 | { 117 | if (dx > 0) 118 | { 119 | controlPoint1 = m_startPoint - QPointF(offset, 0); 120 | controlPoint2 = m_endPoint + QPointF(offset, 0); 121 | } 122 | else 123 | { 124 | controlPoint1 = m_startPoint - QPointF(offset, -dy * 0.5); 125 | controlPoint2 = m_endPoint + QPointF(offset, dy * 0.5); 126 | } 127 | } 128 | 129 | // 创建贝塞尔曲线 130 | path.cubicTo(controlPoint1, controlPoint2, m_endPoint); 131 | 132 | // 绘制曲线 133 | painter->drawPath(path); 134 | } 135 | 136 | 137 | void QBlueprintConnection::setEndPort(QBlueprintPort *endPort) 138 | { 139 | m_endPort = endPort; 140 | if (m_endPort) 141 | { 142 | // 如果存在终点端口,更新终点坐标为端口的中心 143 | updatePosition(m_startPort->centerPos(), m_endPort->centerPos()); 144 | m_startPort->sendDataToConnectedPorts(); 145 | } 146 | } 147 | QBlueprintPort* QBlueprintConnection::startPort() const 148 | { 149 | return m_startPort; 150 | } 151 | 152 | QBlueprintPort* QBlueprintConnection::endPort() const 153 | { 154 | return m_endPort; 155 | } 156 | QPainterPath QBlueprintConnection::shape() const 157 | { 158 | QPainterPath path(m_startPoint); 159 | 160 | // 计算控制点 161 | QPointF controlPoint1 = m_startPoint + QPointF((m_endPoint.x() - m_startPoint.x()) / 2, 0); 162 | QPointF controlPoint2 = m_endPoint + QPointF((m_startPoint.x() - m_endPoint.x()) / 2, 0); 163 | 164 | path.cubicTo(controlPoint1, controlPoint2, m_endPoint); 165 | 166 | // 使用路径描边器生成较细的形状 167 | QPainterPathStroker stroker; 168 | stroker.setWidth(6); // 线的宽度 169 | return stroker.createStroke(path); 170 | } 171 | 172 | void QBlueprintConnection::mousePressEvent(QGraphicsSceneMouseEvent *event) 173 | { 174 | clearSelection(); 175 | 176 | // 切换选中状态 177 | isSelected = true; 178 | animationTimer->start(50); // 启动动画,每50毫秒更新一次 179 | 180 | // 重绘线条 181 | update(); 182 | 183 | // 保持默认的事件处理 184 | QGraphicsItem::mousePressEvent(event); 185 | } 186 | // 静态方法:清除所有连接的选中状态 187 | void QBlueprintConnection::clearSelection() 188 | { 189 | QGraphicsScene *currentScene = this->scene(); 190 | if (!currentScene) return; 191 | 192 | QBlueprint *blueprintView = dynamic_cast(currentScene->views().first()); 193 | if (blueprintView) 194 | { 195 | for (QBlueprintConnection *connection : blueprintView->connections) 196 | { 197 | connection->isSelected = false; 198 | connection->animationTimer->stop(); // 停止动画 199 | connection->update(); 200 | } 201 | } 202 | } 203 | 204 | QColor QBlueprintConnection::getColorFromType(enum Type type) 205 | { 206 | if (type == Type::FUNCTION) 207 | return QColor(0, 128, 255); // 颜色为蓝色 208 | else if (type == Type::INPUT) 209 | return QColor(0, 255, 0); // 颜色为绿色 210 | else if (type == Type::OUTPUT) 211 | return QColor(Qt::red); // 颜色为红色 212 | else if (type == Type::CONDITION) 213 | return QColor(124, 255, 0); 214 | else if (type == Type::BRANCH) 215 | return QColor(0,125,125); 216 | else 217 | qDebug() <<"unkown type:" < 5 | #include 6 | #include 7 | #include "qblueprintport.h" 8 | #include 9 | #include 10 | #include 11 | class QBlueprintConnection : public QObject, public QGraphicsItem 12 | { 13 | Q_OBJECT 14 | public: 15 | QBlueprintConnection(QBlueprintPort *startPort, QBlueprintPort *endPort, QGraphicsItem *parent = nullptr); 16 | 17 | // 更新连接的起点和终点位置 18 | void updatePosition(const QPointF &startPos, const QPointF &endPos); 19 | 20 | QRectF boundingRect() const override; // 必须实现,用于定义图形项的边界矩形 21 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; // 必须实现,用于绘制项 22 | 23 | // 设置终点端口 24 | void setEndPort(QBlueprintPort *endPort); 25 | 26 | QBlueprintPort *endPort() const; 27 | QBlueprintPort *startPort() const; 28 | protected: 29 | QPainterPath shape() const override; 30 | void mousePressEvent(QGraphicsSceneMouseEvent *event) override; 31 | private: 32 | QBlueprintPort *m_startPort; // 起始端口 33 | QBlueprintPort *m_endPort; // 终点端口 34 | QColor m_startColor; 35 | QColor m_endColor; 36 | QPointF m_startPoint; // 连线的起点坐标 37 | QPointF m_endPoint; // 连线的终点坐标 38 | bool isSelected = false; // 标记线条是否被选中 39 | QTimer* animationTimer; // 定时器用于驱动动画 40 | qreal animationProgress; // 动画进度 [0, 1] 41 | void setupAnimation(); // 设置动画 42 | void clearSelection(); 43 | QColor getColorFromType(enum Type type); 44 | }; 45 | 46 | #endif // QBLUEPRINTCONNECTION_H 47 | -------------------------------------------------------------------------------- /qblueprintnode.cpp: -------------------------------------------------------------------------------- 1 | #include "qblueprintnode.h" 2 | #include "enterlimiter.h" 3 | #include 4 | #include "qblueprint.h" 5 | QBlueprintNode::QBlueprintNode(enum Type Type, DataType datatype, QGraphicsItem *parent) 6 | : QGraphicsItem(parent),dataType(datatype) 7 | { 8 | // 启用拖动(节点可以被鼠标拖动) 9 | setFlag(QGraphicsItem::ItemIsMovable); 10 | // 启用选择(节点可以被点击选中) 11 | setFlag(QGraphicsItem::ItemIsSelectable); 12 | setFlag(QGraphicsItem::ItemIsSelectable, true); 13 | setFlag(QGraphicsItem::ItemIsMovable, true); 14 | setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); 15 | setFlag(QGraphicsItem::ItemAcceptsInputMethod, true); 16 | setAcceptedMouseButtons(Qt::AllButtons); 17 | setFlag(QGraphicsItem::ItemIsFocusable, true); 18 | 19 | setZValue(1); 20 | dataType = datatype; 21 | setNodeType(Type); 22 | if(nodeType == Type::FUNCTION || nodeType == Type::BRANCH /*|| nodeType == Type::CONDITION*/ || nodeType == Type::FORLOOP) 23 | customNodePortSort(); 24 | else 25 | addButtonToTopLeft(); 26 | } 27 | QBlueprintNode::~QBlueprintNode() 28 | { 29 | // 删除节点时,移除所有连接 30 | for (QBlueprintPort *port : inputPorts) 31 | { 32 | port->removeConnections(); 33 | } 34 | for (QBlueprintPort *port : outputPorts) 35 | { 36 | port->removeConnections(); 37 | } 38 | } 39 | QBlueprintNode* QBlueprintNode::clone() const 40 | { 41 | // 创建一个新的 QBlueprintNode 实例 42 | QBlueprintNode* newNode = new QBlueprintNode(this->nodeType,this->dataType); 43 | newNode->setNodeTitle(this->m_name); 44 | newNode->setClassName(this->class_name); 45 | newNode->setNodeType(this->nodeType); 46 | qDebug() << "node type :" << nodeType; 47 | if(newNode->nodeType != Type::INPUT && newNode->nodeType != Type::BRANCH && newNode->nodeType != Type::CONDITION && newNode->nodeType != Type::FORLOOP) // 输入节点和控制节点是不需要添加事件端口的 48 | { 49 | qDebug() << "yes"; 50 | newNode->addInputPort(Type::FUNCTION); // 添加事件端口 51 | newNode->addOutputPort(Type::FUNCTION); 52 | } 53 | else if(newNode->nodeType == Type::BRANCH) 54 | { 55 | newNode->addInputPort(Type::BRANCH); 56 | newNode->addOutputPort(Type::BRANCH); 57 | } 58 | else if(newNode->nodeType == Type::CONDITION) 59 | { 60 | newNode->addButtonToTopLeft(Type::CONDITION); 61 | } 62 | else if(newNode->nodeType == Type::FORLOOP) 63 | { 64 | newNode->addInputPort(Type::FORLOOP); 65 | newNode->addOutputPort(Type::FORLOOP); 66 | } 67 | // 克隆输入端口 68 | for (QBlueprintPort* port : this->inputPorts) { 69 | QBlueprintPort* clonedPort = port->clone(); 70 | clonedPort->setParentItem(newNode); // 设置父项为新的 QBlueprintNode 71 | newNode->inputPorts.push_back(clonedPort); 72 | } 73 | 74 | // 克隆输出端口 75 | for (size_t i = 0; i < this->outputPorts.size(); ++i) { 76 | QBlueprintPort* port = this->outputPorts[i]; 77 | QBlueprintPort* clonedPort = port->clone(); // 假设 QBlueprintPort 有一个 clone 方法 78 | clonedPort->setParentItem(newNode); // 设置父项为新的 QBlueprintNode 79 | newNode->outputPorts.push_back(clonedPort); 80 | } 81 | // 设置克隆节点的初始位置 82 | newNode->setPos(this->pos()); 83 | 84 | return newNode; 85 | } 86 | 87 | 88 | 89 | QRectF QBlueprintNode::boundingRect() const 90 | { 91 | QFont font; 92 | QFontMetrics fontMetrics(font); 93 | 94 | // 计算输入端口名称的最大宽度 95 | int maxInputWidth = 0; 96 | for (const auto& port : inputPorts) 97 | { 98 | int textWidth = fontMetrics.horizontalAdvance(port->name()); 99 | if (textWidth > maxInputWidth) 100 | { 101 | maxInputWidth = textWidth; 102 | } 103 | } 104 | // 计算输出端口名称的最大宽度 105 | int maxOutputWidth = 0; 106 | for (const auto& port : outputPorts) 107 | { 108 | int textWidth = fontMetrics.horizontalAdvance(port->name()); 109 | if (textWidth > maxOutputWidth) 110 | { 111 | maxOutputWidth = textWidth; 112 | } 113 | } 114 | 115 | int padding = 80; 116 | if(nodeType == Type::CONDITION) 117 | maxInputWidth = 57; 118 | int nodeWidth = maxInputWidth + maxOutputWidth + padding; 119 | int count = 0; 120 | int nodeHeight = std::max(inputPorts.size(), outputPorts.size()) * 31 + 31; 121 | 122 | if (nodeType == Type::INPUT) 123 | { 124 | switch (dataType) { 125 | case DataType::INT: 126 | case DataType::FLOAT: 127 | case DataType::DOUBLE: 128 | case DataType::CHAR: 129 | case DataType::STRING: 130 | case DataType::BOOL: 131 | case DataType::LONG: 132 | case DataType::SHORT: 133 | case DataType::UNSIGNED_INT: 134 | case DataType::QSTRING: 135 | { 136 | int maxLineEditWidth = 0; 137 | for(const auto& lineEdit : lineEdits) 138 | { 139 | if(maxLineEditWidth < lineEdit->width()) 140 | maxLineEditWidth = lineEdit->width(); 141 | } 142 | nodeWidth += maxLineEditWidth; 143 | } 144 | break; 145 | case DataType::QIMAGE: 146 | nodeWidth += 110; 147 | nodeHeight += outputPorts.size() * 90; 148 | break; 149 | default: 150 | break; 151 | } 152 | } 153 | else if(nodeType == Type::OUTPUT) 154 | { 155 | switch (dataType) { 156 | case DataType::INT: 157 | case DataType::FLOAT: 158 | case DataType::DOUBLE: 159 | case DataType::CHAR: 160 | case DataType::STRING: 161 | case DataType::BOOL: 162 | case DataType::LONG: 163 | case DataType::SHORT: 164 | case DataType::UNSIGNED_INT: 165 | case DataType::QSTRING: 166 | { 167 | int maxLabelWidth = 0; 168 | for(const auto& label : outputlabel) 169 | { 170 | if(maxLabelWidth < label->width()) 171 | maxLabelWidth = label->width(); 172 | } 173 | nodeWidth += maxLabelWidth; 174 | break; 175 | } 176 | case DataType::QIMAGE: 177 | { 178 | int maxLabelWidth = 150; 179 | nodeWidth += maxLabelWidth; 180 | nodeHeight += (outputPorts.size()-1) * 65; 181 | break; 182 | } 183 | default: 184 | break; 185 | } 186 | } 187 | else if(nodeType == Type::CONDITION) 188 | { 189 | nodeWidth += 50; 190 | for (size_t i = 0; i < inputPorts.size(); ++i) { 191 | if(inputPorts[i]->name() == "E1" || inputPorts[i]->name() == "E2") 192 | count ++; 193 | else if(inputPorts[i]->name() == "Condition") 194 | count +=2; 195 | } 196 | if(count > 2) 197 | nodeHeight = std::max(inputPorts.size(), outputPorts.size()) * 31 + 31 + (count/2 - 1) * 20; 198 | } 199 | return QRectF(0, 0, nodeWidth, nodeHeight); 200 | } 201 | 202 | 203 | void QBlueprintNode::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 204 | { 205 | // 设置节点的背景为圆角矩形 206 | QRectF rect = boundingRect(); 207 | painter->setBrush(Qt::gray); 208 | painter->setPen(Qt::black); 209 | painter->drawRoundedRect(rect, 10, 10); // 绘制圆角矩形,10像素圆角 210 | 211 | // 绘制内边框 212 | QRectF innerRect = rect.adjusted(2, 2, -2, -2); // 调整rect以绘制一个稍小的矩形,形成内边框效果 213 | painter->setBrush(Qt::NoBrush); // 内边框不填充 214 | // 根据 nodeType 设置不同的内边框颜色 215 | if (nodeType == Type::FUNCTION) 216 | painter->setPen(QPen(QColor(0, 128, 255), 2)); // 设置内边框颜色为蓝色,宽度为2像素 217 | else if (nodeType == Type::INPUT) 218 | painter->setPen(QPen(QColor(0, 255, 0), 2)); // 设置内边框颜色为绿色,宽度为2像素 219 | else if (nodeType == Type::OUTPUT) 220 | painter->setPen(QPen(QColor(255, 0, 0), 2)); // 设置内边框颜色为红色,宽度为2像素 221 | painter->drawRoundedRect(innerRect, 8, 8); // 绘制内边框,圆角稍微小一点 222 | 223 | // 设置字体大小并绘制标题 224 | QFont font = painter->font(); 225 | font.setPointSize(10); // 设置字体大小 226 | painter->setFont(font); 227 | painter->setPen(Qt::black); 228 | 229 | // 设置标题区域 230 | QRectF titleRect = QRectF(rect.left(), rect.top(), rect.width(), 30); // 标题区域高度为30 231 | painter->drawText(titleRect, Qt::AlignCenter, m_name); // 居中绘制标题 232 | 233 | // 在标题和端口区域之间绘制一条分割线 234 | painter->setPen(QPen(Qt::black, 1)); // 设置线条颜色和宽度 235 | painter->drawLine(rect.left(), titleRect.bottom(), rect.right(), titleRect.bottom()); 236 | // 绘制端口区域,端口从下方开始排列 237 | if(dataType == DataType::QIMAGE) 238 | imageNodePortSort(); 239 | else 240 | customNodePortSort(); 241 | } 242 | void QBlueprintNode::addButtonToTopLeft(enum Type type) 243 | 244 | { 245 | // 创建 QPushButton 246 | QPushButton* button = new QPushButton("+"); 247 | // 创建 QGraphicsProxyWidget 并将按钮嵌入其中 248 | QGraphicsProxyWidget* buttonProxy = new QGraphicsProxyWidget(this); 249 | // 设置按钮的大小,按钮应该是一个正方形以便显示成圆形 250 | int buttonSize = 10; 251 | button->setFixedSize(buttonSize, buttonSize); 252 | // 使用样式表将按钮绘制成一个圆 253 | button->setStyleSheet( 254 | "QPushButton {" 255 | " background-color: #2196F3;" // 按钮背景颜色(蓝色) 256 | " color: white;" // 按钮文字颜色(白色) 257 | " border: none;" // 无边框 258 | " border-radius: 0px;" // 设置圆角半径,半径为按钮尺寸的一半 259 | "}" 260 | "QPushButton:pressed {" 261 | " background-color: #45a049;" // 点击时的背景颜色 262 | "}" 263 | ); 264 | buttonProxy->setWidget(button); 265 | buttonProxy->setPos(25, 10); 266 | 267 | // 设置按钮的点击事件 268 | connect(button, &QPushButton::clicked, [this](){ 269 | addInputPortCondition(Type::CONDITION); 270 | }); 271 | } 272 | void QBlueprintNode::addButtonToTopLeft() 273 | { 274 | // 创建 QPushButton 275 | QPushButton* button = new QPushButton("+"); 276 | // 创建 QGraphicsProxyWidget 并将按钮嵌入其中 277 | QGraphicsProxyWidget* buttonProxy = new QGraphicsProxyWidget(this); 278 | // 设置按钮的大小,按钮应该是一个正方形以便显示成圆形 279 | int buttonSize = 10; 280 | button->setFixedSize(buttonSize, buttonSize); 281 | // 使用样式表将按钮绘制成一个圆 282 | button->setStyleSheet( 283 | "QPushButton {" 284 | " background-color: #4CAF50;" // 按钮背景颜色(绿色) 285 | " color: white;" // 按钮文字颜色(白色) 286 | " border: none;" // 无边框 287 | " border-radius: 0px;" // 设置圆角半径,半径为按钮尺寸的一半 288 | "}" 289 | "QPushButton:pressed {" 290 | " background-color: #45a049;" // 点击时的背景颜色 291 | "}" 292 | ); 293 | buttonProxy->setWidget(button); 294 | buttonProxy->setPos(10, 10); 295 | 296 | // 设置按钮的点击事件 297 | connect(button, &QPushButton::clicked, [this]() { 298 | if(nodeType == Type::INPUT) 299 | { 300 | switch (dataType) { 301 | case DataType::INT: 302 | case DataType::FLOAT: 303 | case DataType::DOUBLE: 304 | case DataType::CHAR: 305 | case DataType::STRING: 306 | case DataType::BOOL: 307 | case DataType::LONG: 308 | case DataType::SHORT: 309 | case DataType::UNSIGNED_INT: 310 | case DataType::QSTRING:{ 311 | addLineEdit(addOutputPort(getEnumName(dataType))); 312 | break; 313 | } 314 | case DataType::QIMAGE:{ 315 | QBlueprintPort * port = addOutputPort(getEnumName(dataType)); 316 | imageNodePortSort(); 317 | addInputLabel(port); 318 | break; 319 | } 320 | default: 321 | break; 322 | } 323 | 324 | } 325 | else if(nodeType == Type::OUTPUT){ 326 | switch (dataType) { 327 | case DataType::INT: 328 | case DataType::FLOAT: 329 | case DataType::DOUBLE: 330 | case DataType::CHAR: 331 | case DataType::STRING: 332 | case DataType::BOOL: 333 | case DataType::LONG: 334 | case DataType::SHORT: 335 | case DataType::UNSIGNED_INT: 336 | case DataType::QSTRING: 337 | { 338 | QBlueprintPort * outputport = addOutputPort(getEnumName(dataType)); 339 | QBlueprintPort * inputport = addInputPort(getEnumName(dataType)); 340 | customNodePortSort(); 341 | addOutputLabel(outputport,inputport); 342 | break; 343 | } 344 | case DataType::QIMAGE:{ 345 | QBlueprintPort * outputport = addOutputPort(getEnumName(dataType)); 346 | QBlueprintPort * inputport = addInputPort(getEnumName(dataType)); 347 | imageNodePortSort(); 348 | addOutputLabel(outputport,inputport); 349 | break; 350 | } 351 | default: 352 | break; 353 | } 354 | } 355 | else if(nodeType == Type::CONDITION) // 添加条件 356 | { 357 | addInputPort(Type::CONDITION); 358 | addOutputPort(Type::CONDITION); 359 | } 360 | qDebug() << "Button clicked!"; 361 | }); 362 | } 363 | 364 | QBlueprintPort* QBlueprintNode::addInputPort() 365 | { 366 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::Input, "NULL", dataType, this, getEnumName(dataType)); 367 | port->setNodeType(nodeType); 368 | setQVariantType(port); 369 | inputPorts.push_back(port); 370 | return port; 371 | } 372 | 373 | QBlueprintPort* QBlueprintNode::addOutputPort() 374 | { 375 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::Output, "NULL", dataType, this, getEnumName(dataType)); 376 | port->setNodeType(nodeType); 377 | setQVariantType(port); 378 | outputPorts.push_back(port); 379 | return port; 380 | } 381 | 382 | QBlueprintPort* QBlueprintNode::addInputPort(const QString &name) 383 | { 384 | DataType datatype = dataType; 385 | if(dataType == DataType::FOR_FUNCTION) 386 | { 387 | datatype = getEnumFromName(name); 388 | } 389 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::Input, name, datatype, this, getEnumName(datatype)); 390 | port->setNodeType(nodeType); 391 | setQVariantType(port); 392 | inputPorts.push_back(port); 393 | return port; 394 | } 395 | 396 | QBlueprintPort* QBlueprintNode::addOutputPort(const QString &name) 397 | { 398 | DataType datatype = dataType; 399 | if(dataType == DataType::FOR_FUNCTION) 400 | { 401 | datatype = getEnumFromName(name); 402 | qDebug() << "eaesaeasease" << datatype; 403 | } 404 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::Output, name, datatype, this, getEnumName(datatype)); 405 | port->setNodeType(nodeType); 406 | setQVariantType(port); 407 | outputPorts.push_back(port); 408 | return port; 409 | } 410 | void QBlueprintNode::addInputPortCondition(enum Type Type) 411 | { 412 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::Input, "Condition", dataType, this, getEnumName(dataType)); 413 | port->setNodeType(nodeType); 414 | setQVariantType(port); 415 | inputPorts.push_back(port); 416 | addOutputPort(Type); 417 | customNodePortSort(); 418 | addOutputLabel(port, port); 419 | if(inputPorts.size()>1) 420 | { 421 | addRadioButtonOptions(port); 422 | } 423 | port->setVarType(QVariant::fromValue(bool())); 424 | } 425 | 426 | void QBlueprintNode::addInputPort(enum Type Type) 427 | { 428 | if(Type == Type::FUNCTION) 429 | { 430 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::EVENT_INPUT, "", dataType, this, getEnumName(dataType)); 431 | port->setNodeType(nodeType); 432 | setQVariantType(port); 433 | inputPorts.push_back(port); 434 | } 435 | else if(Type == Type::BRANCH) 436 | { 437 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::EVENT_INPUT, "", dataType, this, getEnumName(dataType)); 438 | QBlueprintPort *port_Condition = new QBlueprintPort(QBlueprintPort::Input, "Condition", DataType::BOOL, this, getEnumName(dataType)); 439 | port->setNodeType(nodeType); 440 | setQVariantType(port); 441 | port_Condition->setNodeType(nodeType); 442 | setQVariantType(port_Condition); 443 | inputPorts.push_back(port); 444 | inputPorts.push_back(port_Condition); 445 | customNodePortSort(); 446 | addOutputLabel(port, port); 447 | 448 | } 449 | else if(Type == Type::CONDITION) 450 | { 451 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::Input, "E1", dataType, this, getEnumName(dataType)); 452 | port->setNodeType(nodeType); 453 | setQVariantType(port); 454 | inputPorts.push_back(port); 455 | customNodePortSort(); 456 | addOutputLabel(port, port); 457 | QBlueprintPort *port2 = new QBlueprintPort(QBlueprintPort::Input, "E2", dataType, this, getEnumName(dataType)); 458 | port2->setNodeType(nodeType); 459 | setQVariantType(port2); 460 | inputPorts.push_back(port2); 461 | customNodePortSort(); 462 | addOutputLabel(port2, port2); 463 | addLineEdit(port, port2); 464 | if(inputPorts.size()>2) 465 | addRadioButtonOptions(port); 466 | port->setVarType(QVariant::fromValue(int())); 467 | port2->setVarType(QVariant::fromValue(int())); 468 | 469 | } 470 | else 471 | { 472 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::EVENT_INPUT, "", dataType, this, getEnumName(dataType)); 473 | port->setNodeType(nodeType); 474 | setQVariantType(port); 475 | inputPorts.push_back(port); 476 | } 477 | } 478 | void QBlueprintNode::addOutputPort(enum Type Type) 479 | { 480 | if(Type == Type::FUNCTION) 481 | { 482 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::EVENT_OUTPUT, "", dataType, this, getEnumName(dataType)); 483 | port->setNodeType(nodeType); 484 | setQVariantType(port); 485 | outputPorts.push_back(port); 486 | } 487 | else if(Type == Type::BRANCH) 488 | { 489 | QBlueprintPort *port_return_true = new QBlueprintPort(QBlueprintPort::EVENT_TRUE_RETURN, "True", dataType, this, getEnumName(dataType)); 490 | QBlueprintPort *port_return_false = new QBlueprintPort(QBlueprintPort::EVENT_FALSE_RETURN, "False", dataType, this, getEnumName(dataType)); 491 | port_return_true->setNodeType(nodeType); 492 | setQVariantType(port_return_true); 493 | port_return_false->setNodeType(nodeType); 494 | setQVariantType(port_return_false); 495 | outputPorts.push_back(port_return_true); 496 | outputPorts.push_back(port_return_false); 497 | customNodePortSort(); 498 | } 499 | else if(Type == Type::CONDITION) 500 | { 501 | if(outputPorts.size()==0) 502 | { 503 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::Output, "Condition", dataType, this, getEnumName(dataType)); 504 | port->setNodeType(nodeType); 505 | setQVariantType(port); 506 | port->setVarType(QVariant::fromValue(bool())); 507 | outputPorts.push_back(port); 508 | } 509 | } 510 | } 511 | 512 | QBlueprintPort *QBlueprintNode::addInputPort(const QString &name, const QString &brief) 513 | { 514 | DataType datatype = dataType; 515 | if(dataType == DataType::FOR_FUNCTION) 516 | { 517 | datatype = getEnumFromName(name); 518 | } 519 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::Input, name, datatype, this, brief); 520 | port->setPortBrief(brief); 521 | port->setNodeType(nodeType); 522 | setQVariantType(port); 523 | inputPorts.push_back(port); 524 | return port; 525 | } 526 | 527 | QBlueprintPort *QBlueprintNode::addOutputPort(const QString &name, const QString &brief) 528 | { 529 | DataType datatype = dataType; 530 | if(dataType == DataType::FOR_FUNCTION) 531 | { 532 | datatype = getEnumFromName(name); 533 | } 534 | QBlueprintPort *port = new QBlueprintPort(QBlueprintPort::Output, name, datatype, this, brief); 535 | port->setPortBrief(brief); 536 | port->setNodeType(nodeType); 537 | setQVariantType(port); 538 | outputPorts.push_back(port); 539 | return port; 540 | } 541 | 542 | void QBlueprintNode::setNodeTitle(QString name) 543 | { 544 | m_name = name; 545 | } 546 | 547 | void QBlueprintNode::setClassName(QString class_name) 548 | { 549 | QBlueprintNode::class_name = class_name; 550 | } 551 | 552 | QVariant QBlueprintNode::itemChange(GraphicsItemChange change, const QVariant &value) 553 | { 554 | if (change == QGraphicsItem::ItemPositionChange) 555 | { 556 | // 通知所有连接线进行更新 557 | for (QBlueprintPort *port : inputPorts) 558 | { 559 | port->updateConnections(); 560 | } 561 | for (QBlueprintPort *port : outputPorts) 562 | { 563 | port->updateConnections(); 564 | } 565 | } 566 | 567 | return QGraphicsItem::itemChange(change, value); 568 | } 569 | 570 | void QBlueprintNode::setQVariantType(QBlueprintPort* port) 571 | { 572 | switch (port->portDataType()) { 573 | case DataType::INT: 574 | port->setVarType(QVariant::fromValue(int())); // 设置为 int 类型 575 | break; 576 | case DataType::FLOAT: 577 | port->setVarType(QVariant::fromValue(float())); // 设置为 float 类型 578 | break; 579 | case DataType::DOUBLE: 580 | port->setVarType(QVariant::fromValue(double())); // 设置为 double 类型 581 | break; 582 | case DataType::CHAR: 583 | port->setVarType(QVariant::fromValue(char())); // 设置为 char 类型 584 | break; 585 | case DataType::STRING: 586 | port->setVarType(QVariant::fromValue(QString())); // 设置为 QString 类型 587 | break; 588 | case DataType::BOOL: 589 | port->setVarType(QVariant::fromValue(bool())); // 设置为 bool 类型 590 | break; 591 | case DataType::LONG: 592 | port->setVarType(QVariant::fromValue(qint64())); // 设置为 long 类型 (qint64) 593 | break; 594 | case DataType::SHORT: 595 | port->setVarType(QVariant::fromValue(short())); // 设置为 short 类型 596 | break; 597 | case DataType::UNSIGNED_INT: 598 | port->setVarType(QVariant::fromValue(uint())); // 设置为 unsigned int 类型 599 | break; 600 | case DataType::VARIANT: 601 | port->setVarType(QVariant()); // QVariant 自身 602 | break; 603 | case DataType::QSTRING: 604 | port->setVarType(QVariant::fromValue(QString())); // 设置为 QString 类型 605 | break; 606 | case DataType::QTIME: 607 | port->setVarType(QVariant::fromValue(QTime())); // 设置为 QTime 类型 608 | break; 609 | case DataType::QPOINT: 610 | port->setVarType(QVariant::fromValue(QPoint())); // 设置为 QPoint 类型 611 | break; 612 | case DataType::QPOINTF: 613 | port->setVarType(QVariant::fromValue(QPointF())); // 设置为 QPointF 类型 614 | break; 615 | case DataType::QSIZE: 616 | port->setVarType(QVariant::fromValue(QSize())); // 设置为 QSize 类型 617 | break; 618 | case DataType::QSIZEF: 619 | port->setVarType(QVariant::fromValue(QSizeF())); // 设置为 QSizeF 类型 620 | break; 621 | case DataType::QRECT: 622 | port->setVarType(QVariant::fromValue(QRect())); // 设置为 QRect 类型 623 | break; 624 | case DataType::QRECTF: 625 | port->setVarType(QVariant::fromValue(QRectF())); // 设置为 QRectF 类型 626 | break; 627 | case DataType::QCOLOR: 628 | port->setVarType(QVariant::fromValue(QColor())); // 设置为 QColor 类型 629 | break; 630 | case DataType::QPIXMAP: 631 | port->setVarType(QVariant::fromValue(QPixmap())); // 设置为 QPixmap 类型 632 | break; 633 | case DataType::QIMAGE: 634 | port->setVarType(QVariant::fromValue(QImage())); // 设置为 QImage 类型 635 | break; 636 | default: 637 | port->setVarType(QVariant()); // 默认设置为一个空的 QVariant 638 | break; 639 | } 640 | 641 | } 642 | 643 | void QBlueprintNode::customNodePortSort() { 644 | if(nodeType == Type::CONDITION) 645 | { 646 | // 排列输入端口 647 | int count = 0; 648 | for (size_t i = 0; i < inputPorts.size(); ++i) { 649 | QFontMetrics fontMetrics(inputPorts[i]->m_font); 650 | int inputTextWidth = fontMetrics.horizontalAdvance(inputPorts[i]->name()); 651 | inputPorts[i]->setPos(5, i * 30 + 40 + (count/2) * 20); // 左边距15,纵向位置 652 | if(inputPorts[i]->name() == "E1" || inputPorts[i]->name() == "E2") 653 | count ++; 654 | else if(inputPorts[i]->name() == "Condition") 655 | count +=2; 656 | } 657 | // 排列输出端口 658 | for (size_t i = 0; i < outputPorts.size(); ++i) { 659 | QFontMetrics fontMetrics(outputPorts[i]->m_font); 660 | int outputTextWidth = fontMetrics.horizontalAdvance(outputPorts[i]->name()); 661 | outputPorts[i]->setPos(boundingRect().width() - 15, i * 60 + 40); // 右边距15 662 | } 663 | } 664 | else { 665 | // 排列输入端口 666 | for (size_t i = 0; i < inputPorts.size(); ++i) { 667 | QFontMetrics fontMetrics(inputPorts[i]->m_font); 668 | int inputTextWidth = fontMetrics.horizontalAdvance(inputPorts[i]->name()); 669 | inputPorts[i]->setPos(5, i * 30 + 40); // 左边距15,纵向位置 670 | } 671 | // 排列输出端口 672 | for (size_t i = 0; i < outputPorts.size(); ++i) { 673 | QFontMetrics fontMetrics(outputPorts[i]->m_font); 674 | int outputTextWidth = fontMetrics.horizontalAdvance(outputPorts[i]->name()); 675 | if(nodeType == Type::CONDITION) // 条件节点的输出端口是需要间隔一个output的 676 | outputPorts[i]->setPos(boundingRect().width() - 15, i * 60 + 40); // 右边距15 677 | else 678 | outputPorts[i]->setPos(boundingRect().width() - 15, i * 30 + 40); // 右边距15 679 | } 680 | } 681 | } 682 | 683 | 684 | 685 | void QBlueprintNode::imageNodePortSort() { 686 | if(nodeType == Type::INPUT) 687 | { 688 | for (size_t i = 0; i < inputPorts.size(); ++i) { 689 | // 将输入端口放在左侧 690 | inputPorts[i]->setPos(5, i * 115 + 35); // 左边距 691 | } 692 | for (size_t i = 0; i < outputPorts.size(); ++i) { 693 | // 将输出端口放在右侧 694 | outputPorts[i]->setPos(boundingRect().width() - 15, i * 115 + 35); // 右边距 695 | } 696 | } 697 | else 698 | { 699 | int eventPortHeight = 30; // 事件端口所占高度 700 | int baseHeight = eventPortHeight; // 普通端口从这个高度开始排列 701 | // 首先,处理输入端口 702 | for (size_t i = 0; i < inputPorts.size(); ++i) { 703 | if (inputPorts[i]->portType() == QBlueprintPort::EVENT_INPUT) { 704 | // 事件端口固定在顶部位置 705 | inputPorts[i]->setPos(5, 40); // 事件输入端口固定位置 706 | } else { 707 | // 普通输入端口从事件端口之后开始排列 708 | inputPorts[i]->setPos(5, baseHeight + (i-1) * 95 + 35); // 左边距 709 | } 710 | } 711 | 712 | // 其次,处理输出端口 713 | for (size_t i = 0; i < outputPorts.size(); ++i) { 714 | if (outputPorts[i]->portType() == QBlueprintPort::EVENT_OUTPUT) { 715 | // 事件端口固定在顶部位置 716 | outputPorts[i]->setPos(boundingRect().width() - 15, 40); // 事件输出端口固定位置 717 | } else { 718 | // 普通输出端口从事件端口之后开始排列 719 | outputPorts[i]->setPos(boundingRect().width() - 15, baseHeight + (i-1) * 95 + 35); // 右边距 720 | } 721 | } 722 | } 723 | 724 | } 725 | void QBlueprintNode::addLineEdit(QBlueprintPort *port1, QBlueprintPort *port2) 726 | { 727 | // 创建 QLineEdit 728 | QLineEdit *lineEdit = new QLineEdit(); 729 | lineEdit->setPlaceholderText("条件"); // 设置提示文本 730 | lineEdit->setStyleSheet("QLineEdit { border: 1px solid black; padding: 2px; }"); // 设置样式 731 | lineEdit->resize(80, 20); 732 | 733 | // 创建 QGraphicsProxyWidget 并将 lineEdit 添加到代理 734 | QGraphicsProxyWidget *pMyProxy = new QGraphicsProxyWidget(this); 735 | pMyProxy->setWidget(lineEdit); 736 | 737 | // 设置 Z 值,确保控件显示在前景 738 | pMyProxy->setZValue(10); 739 | 740 | // 设置代理的位置 741 | QPointF outputPortPos = port2->pos(); 742 | pMyProxy->setPos(outputPortPos.x() - lineEdit->width() + 233, (outputPortPos.y()) - 3); 743 | 744 | // 将 lineEdit 添加到列表中 745 | lineEdits.push_back(lineEdit); 746 | relation.push_back(""); // 初始化为一个空的 QString,占据与该 lineEdit 对应的位置 747 | 748 | // 使用 QRegularExpressionValidator 限制输入条件符号 749 | QRegularExpression regex(R"(>|<|=|>=|<=|!=)"); // 允许的符号 750 | QValidator *validator = new QRegularExpressionValidator(regex, this); 751 | lineEdit->setValidator(validator); 752 | int currentIndex = lineEdits.size() - 1; // 当前 lineEdit 的索引 753 | connect(lineEdit, &QLineEdit::textChanged, this, [=](const QString &text) { 754 | QStringList allowedConditions = {">", "<", "=", ">=", "<=", "!="}; 755 | if (allowedConditions.contains(text.trimmed())) { 756 | relation[currentIndex] = text.trimmed(); // 将输入的条件符号存入 relation 的对应位置 757 | qDebug() << "输入条件符号:" << relation[currentIndex]; 758 | processData(nullptr, QVariant()); 759 | } else { 760 | qDebug() << "无效输入,当前仅支持条件符号:" << allowedConditions; 761 | } 762 | }); 763 | } 764 | 765 | 766 | void QBlueprintNode::addLineEdit(QBlueprintPort* port) 767 | { 768 | // 创建 QLineEdit 769 | QLineEdit* pLineEdit = new QLineEdit(""); 770 | pLineEdit->setStyleSheet("QLineEdit { border: 1px solid black; border-radius: 0px; padding: 2px; }"); 771 | 772 | // 创建 QGraphicsProxyWidget 并将 QLineEdit 添加到该代理 773 | QGraphicsProxyWidget* pMyProxy = new QGraphicsProxyWidget(this); // 代理作为 QGraphicsItem 的子项 774 | pMyProxy->setWidget(pLineEdit); // 将 QWidget 基类对象添加到代理中 775 | 776 | // 设置较高的 Z 值,确保控件显示在前景 777 | pMyProxy->setZValue(10); 778 | lineEdits.push_back(pLineEdit); 779 | 780 | connect(pLineEdit, &QLineEdit::textChanged, this, &QBlueprintNode::adjustLineEditWidth); 781 | 782 | QPointF outputPortPos = port->pos(); 783 | pMyProxy->setPos(outputPortPos.x() - pLineEdit->width() + 210, outputPortPos.y() + 35 + (outputPorts.size()-1) * 30); 784 | // connect(pLineEdit, &QLineEdit::textChanged, [port, this](const QString &text) { 785 | // port->sendDataToConnectedPorts(text); // 发送数据给所有连接的 input 端口 786 | // }); 787 | // 设置克隆的 QLineEdit 大小与原始的一致 788 | pMyProxy->resize(QSize(60, 10)); 789 | connect(pLineEdit, &QLineEdit::textChanged, [port, this](const QString &text) { 790 | // 将数据存储在 port 的变量中 791 | QVariant convertedValue; 792 | 793 | // 根据端口的数据类型转换输入的值 794 | switch (port->portDataType()) { 795 | case DataType::INT: 796 | convertedValue = QVariant::fromValue(text.toInt()); 797 | break; 798 | case DataType::FLOAT: 799 | convertedValue = QVariant::fromValue(text.toFloat()); 800 | break; 801 | case DataType::DOUBLE: 802 | convertedValue = QVariant::fromValue(text.toDouble()); 803 | break; 804 | case DataType::BOOL: 805 | convertedValue = QVariant::fromValue(text.toLower() == "true" || text == "1"); 806 | break; 807 | case DataType::QSTRING: 808 | convertedValue = QVariant::fromValue(text); 809 | break; 810 | case DataType::QPOINT: 811 | qDebug() << "neddthis ------"; 812 | break; 813 | // 添加其他类型的处理逻辑 814 | default: 815 | convertedValue = QVariant::fromValue(text); // 默认保存为字符串 816 | break; 817 | } 818 | 819 | // 将转换后的值存储到端口中 820 | port->setVarType(convertedValue); 821 | //port->setVarType(text); 822 | // 发送数据到连接的端口 823 | port->sendDataToConnectedPorts(); 824 | }); 825 | // 添加克隆的 QLineEdit 到新的节点的 lineEdits 列表 826 | setEnterLimiter(pLineEdit,port); 827 | lineEdits.push_back(pLineEdit); 828 | } 829 | void QBlueprintNode::adjustLineEditWidth(const QString &text) { 830 | QFontMetrics fontMetrics(font); 831 | 832 | // 计算文本宽度 833 | int textWidth = fontMetrics.horizontalAdvance(text); 834 | 835 | // 设置 QLineEdit 的宽度,添加一些额外的边距 836 | for (auto lineEdit : lineEdits) { 837 | lineEdit->setFixedWidth(textWidth + 20); // 添加20像素的边距 838 | } 839 | // 通知场景准备重新计算节点的边界 840 | prepareGeometryChange(); 841 | // 更新所有连接线的位置 842 | for (auto port : outputPorts) { 843 | port->updateConnections(); 844 | } 845 | for (auto port : inputPorts) { 846 | port->updateConnections(); 847 | } 848 | } 849 | void QBlueprintNode::adjustLabelWidth(const QString &text) { 850 | QFontMetrics fontMetrics(font); 851 | 852 | // 计算文本宽度 853 | int textWidth = fontMetrics.horizontalAdvance(text); 854 | 855 | // 设置 QLabel 的宽度,添加一些额外的边距 856 | for (auto label : outputlabel) { // 假设 outputlabel 是存储所有 QLabel 的列表 857 | label->setFixedWidth(textWidth + 20); // 添加20像素的边距 858 | } 859 | } 860 | 861 | 862 | void QBlueprintNode::addInputLabel(QBlueprintPort* port) 863 | { 864 | // 创建 QWidget 作为 QGraphicsProxyWidget 的容器 865 | QWidget* containerWidget = new QWidget(); 866 | containerWidget->setFixedSize(150, 110); // 设置容器大小,可以调整为合适的大小 867 | 868 | // 创建 QLabel 用于显示图片 869 | ImageLabel* pLabel = new ImageLabel(containerWidget); // 直接设置父对象为 containerWidget 870 | pLabel->setFixedSize(150, 90); // 设置 QLabel 大小 871 | pLabel->setStyleSheet("QLabel { border: 1px solid black; }"); 872 | pLabel->setAlignment(Qt::AlignCenter); // 居中显示图片 873 | pLabel->move(0, 0); // 设置 QLabel 在容器中的位置 874 | 875 | // 创建 QPushButton 用于打开文件选择对话框 876 | QPushButton* pButton = new QPushButton("选择图片", containerWidget); // 直接设置父对象为 containerWidget 877 | pButton->setFixedSize(150, 20); // 设置按钮大小 878 | pButton->move(0, 90); // 设置 QPushButton 在容器中的位置 879 | 880 | // 创建 QGraphicsProxyWidget 并将 containerWidget 添加到代理 881 | QGraphicsProxyWidget* pMyProxy = new QGraphicsProxyWidget(this); 882 | pMyProxy->setWidget(containerWidget); 883 | 884 | // 设置代理的位置 885 | QPointF inputPortPos = port->pos(); 886 | qDebug() << inputPortPos; 887 | pMyProxy->setPos(inputPortPos.x() - 215, inputPortPos.y()); // 设置代理在场景中的位置 888 | inputlabel.push_back(pLabel); 889 | // 设置代理大小 890 | pMyProxy->resize(containerWidget->size()); 891 | 892 | // 连接按钮点击事件,打开文件选择对话框 893 | connect(pButton, &QPushButton::clicked, [=]() { 894 | QString filePath = QFileDialog::getOpenFileName(nullptr, "选择图片", "", "Images (*.png *.jpg *.bmp)"); 895 | if (!filePath.isEmpty()) { 896 | QPixmap pixmap(filePath); 897 | pLabel->setPixmap(pixmap.scaled(pLabel->size(), Qt::KeepAspectRatio)); // 在 QLabel 中显示选中的图片 898 | QImage image(filePath); 899 | pLabel->setImage(image); 900 | // 更新输出端口的 QVariant 数据为 QImage 901 | port->setVarType(QVariant::fromValue(image)); 902 | 903 | // 将数据发送给所有连接的端口 904 | port->sendDataToConnectedPorts(); 905 | 906 | qDebug() << "Sending converted data from QImage to connected ports."; 907 | } 908 | }); 909 | 910 | // 确保场景更新 911 | //scene()->update(); 912 | } 913 | 914 | 915 | void QBlueprintNode::addOutputLabel(QBlueprintPort *outport, QBlueprintPort *inport) 916 | { 917 | QLabel* pLabel = nullptr; // 声明基类指针 918 | 919 | // 根据端口数据类型选择合适的 QLabel 类型 920 | if(outport->portDataType() == DataType::QIMAGE) { 921 | pLabel = new ImageLabel(); // 如果是 QIMAGE 类型,创建 ImageLabel 实例 922 | } else { 923 | pLabel = new QLabel(""); 924 | } 925 | pLabel->setStyleSheet("QLineEdit { border: 1px solid black; border-radius: 0px; padding: 2px; }"); 926 | 927 | // 创建 QGraphicsProxyWidget 并将 QLineEdit 添加到该代理 928 | QGraphicsProxyWidget* pMyProxy = new QGraphicsProxyWidget(this); // 代理作为 QGraphicsItem 的子项 929 | pMyProxy->setWidget(pLabel); // 将 QWidget 基类对象添加到代理中 930 | 931 | // 设置较高的 Z 值,确保控件显示在前景 932 | pMyProxy->setZValue(10); 933 | pLabel->setMinimumWidth(50); 934 | pLabel->setMinimumHeight(10); 935 | QPointF outputPortPos = inport->pos(); 936 | QFontMetrics fontMetrics(inport->m_font); 937 | int outputTextWidth = fontMetrics.horizontalAdvance(inport->name()); 938 | pMyProxy->setPos(outputPortPos.x() + outputTextWidth + 30, outputPortPos.y() - 3); 939 | if (outport->portDataType() == DataType::QIMAGE){ 940 | pMyProxy->resize(QSize(150, 90)); 941 | connect(outport, &QBlueprintPort::dataUpdated, [pLabel](const QVariant &data) { 942 | if (data.canConvert()) { 943 | QImage image = data.value(); 944 | if (!image.isNull()) { 945 | // 转换 QImage 为 QPixmap,并按比例缩放到 QLabel 的大小 946 | pLabel->setPixmap(QPixmap::fromImage(image).scaled(pLabel->size(), Qt::KeepAspectRatio)); 947 | } else { 948 | pLabel->setText("Invalid Image"); 949 | } 950 | } 951 | }); 952 | } 953 | // 设置克隆的 QLineEdit 大小与原始的一致 954 | else if(inport->name() == "Condition") 955 | pMyProxy->resize(QSize(50, 20)); 956 | else if(nodeType == Type::BRANCH) 957 | pMyProxy->resize(QSize(50, 20)); 958 | else 959 | pMyProxy->resize(QSize(100, 20)); 960 | 961 | // 添加克隆的 QLineEdit 到新的节点的 lineEdits 列表 962 | outputlabel.push_back(pLabel); 963 | } 964 | void QBlueprintNode::addRadioButtonOptions(QBlueprintPort *port) 965 | { 966 | // 创建 QWidget 作为 QGraphicsProxyWidget 的容器 967 | QWidget *containerWidget = new QWidget(); 968 | QHBoxLayout *layout = new QHBoxLayout(containerWidget); // 使用水平布局管理器 969 | 970 | // 设置布局的间距和边距 971 | layout->setSpacing(5); // 控件之间的间距 972 | layout->setContentsMargins(0, 0, 0, 0); // 去掉边距 973 | 974 | // 创建两个 QRadioButton 用于选择 || 和 && 975 | QRadioButton *orOption = new QRadioButton("||"); 976 | QRadioButton *andOption = new QRadioButton("&&"); 977 | 978 | // 设置单选按钮的大小 979 | orOption->setFixedSize(40, 20); 980 | andOption->setFixedSize(40, 20); 981 | 982 | // 默认选中 || 选项 983 | orOption->setChecked(true); 984 | 985 | // 初始化 radioButtonOptions 和 radioButtonValues 986 | radioButtonOptions.push_back({orOption, andOption}); 987 | radioButtonValues.push_back("||"); // 默认选中 "||" 988 | 989 | // 将单选按钮添加到布局 990 | layout->addWidget(orOption); 991 | layout->addWidget(andOption); 992 | 993 | // 创建 QGraphicsProxyWidget 并将 containerWidget 添加到代理 994 | QGraphicsProxyWidget *pMyProxy = new QGraphicsProxyWidget(this); 995 | pMyProxy->setWidget(containerWidget); 996 | 997 | // 设置代理的位置 998 | QPointF portPos = port->pos(); 999 | pMyProxy->setPos(portPos.x(), portPos.y() - 30); // 调整位置以适应布局 1000 | 1001 | // 确保布局适合代理的大小 1002 | containerWidget->setLayout(layout); 1003 | containerWidget->setFixedSize(235, 20); // 缩小容器的大小 1004 | 1005 | int currentIndex = radioButtonOptions.size() - 1; // 获取当前的索引 1006 | qDebug() << "radioButtonValues:" <name() == "E1") 1040 | { 1041 | bool E_bool = false; 1042 | qDebug() << "inputPorts[i]->name()" << inputPorts[i]->name(); 1043 | QString relationSymbol = relation[relationindex]; // 获取当前的关系符号 1044 | QVariant data1 = inputPorts[i]->data(); 1045 | QVariant data2 = inputPorts[i+1]->data(); 1046 | if (data1.canConvert() && data2.canConvert()) { 1047 | double value1 = data1.toDouble(); 1048 | double value2 = data2.toDouble(); 1049 | if (relationSymbol == ">") { 1050 | E_bool = value1 > value2; 1051 | } else if (relationSymbol == "<") { 1052 | E_bool = value1 < value2; 1053 | } else if (relationSymbol == "=") { 1054 | E_bool = value1 == value2; 1055 | } else if (relationSymbol == ">=") { 1056 | E_bool = value1 >= value2; 1057 | } else if (relationSymbol == "<=") { 1058 | E_bool = value1 <= value2; 1059 | } else if (relationSymbol == "!=") { 1060 | E_bool = value1 != value2; 1061 | } 1062 | else { 1063 | qDebug() << "Value1:" << value1 << "Value2:" << value2 << "Relation:" << relationSymbol << "E_bool:" << E_bool; 1064 | break; 1065 | } 1066 | } else { 1067 | qDebug() << "无法比较非数值类型数据"; 1068 | } 1069 | partbool.push_back(E_bool); 1070 | relationindex++; 1071 | } 1072 | else if(inputPorts[i]->name() == "Condition") 1073 | { 1074 | QVariant data = inputPorts[i]->data(); 1075 | data.canConvert(); 1076 | bool condition = data.toBool(); 1077 | partbool.push_back(condition); 1078 | } 1079 | else 1080 | continue; 1081 | } 1082 | qDebug() << "partbool.size()" << partbool.size(); 1083 | if(partbool.size() != 0) 1084 | result_bool = partbool[0]; 1085 | for(int i = 0;i< partbool.size()-1;i++) 1086 | { 1087 | if(partbool.size() == 0) 1088 | break; 1089 | if(radioButtonValues[radioButtonindex] == "||") 1090 | result_bool = result_bool || partbool[i+1]; 1091 | else if(radioButtonValues[radioButtonindex] == "&&") 1092 | result_bool = result_bool && partbool[i+1]; 1093 | radioButtonindex ++; 1094 | } 1095 | result = result_bool; 1096 | for (QBlueprintPort* outputPort : outputPorts) { 1097 | if (outputPort) { 1098 | outputPort->setVarType(result_bool); // 设置布尔值到输出端口 1099 | outputPort->sendDataToConnectedPorts(); // 发送数据给连接的下一个端口 1100 | } 1101 | } 1102 | 1103 | qDebug() << "Condition result:" << result_bool; 1104 | } 1105 | // 根据 inputPort 来判断需要处理的逻辑 1106 | //qDebug() << "节点" << m_name << "接收到数据:" << data << "从端口:" << inputPort->name(); 1107 | // 如果有对应的 QLabel 需要更新(例如在 inputPort 上有 QLabel) 1108 | auto it = std::find(inputPorts.begin(), inputPorts.end(), inputPort); 1109 | if (it != inputPorts.end()) { 1110 | int index = 0; 1111 | if(nodeType == Type::CONDITION) 1112 | index = std::distance(inputPorts.begin(), it); 1113 | else 1114 | index = std::distance(inputPorts.begin(), it) - 1; 1115 | if ((index < outputlabel.size()) /*&& isPortConnected(inputPort, outputPorts[index])*/) { 1116 | QLabel* label = outputlabel[index]; 1117 | if (inputPort->portDataType() == DataType::QIMAGE && data.canConvert()) { 1118 | // 将 QVariant 数据转换为 QImage 1119 | QImage image = data.value(); 1120 | ImageLabel* imageLabel = dynamic_cast(label); // 转化为ImageLabel 1121 | // 将 QImage 转换为 QPixmap 并设置到 QLabel 1122 | imageLabel->setImage(image); 1123 | imageLabel->setPixmap(QPixmap::fromImage(image).scaled(label->size(), Qt::KeepAspectRatio)); 1124 | } else { 1125 | // 更新 QLabel 显示的其他数据内容 1126 | label->setText(data.toString()); // 更新 QLabel 显示的文本内容 1127 | } 1128 | } 1129 | } 1130 | QVariant result; 1131 | if(class_name == "Math") 1132 | result = mathFunctions(); 1133 | #ifdef OPENCV_FOUND 1134 | else if(class_name == "opencv") 1135 | result = opencvFunctions(); 1136 | #endif 1137 | else if(class_name == "Qts") 1138 | result = qtsFunctions(); 1139 | if(inputPort != nullptr) 1140 | { 1141 | if(inputPort->getNodeType() == Type::FUNCTION) 1142 | for (QBlueprintPort* outputPort : outputPorts) { 1143 | if (outputPort) { 1144 | outputPort->setVarType(result); // 更新输出端口的 var 1145 | outputPort->sendDataToConnectedPorts(); // 发送数据到连接的下一个端口 1146 | } 1147 | } 1148 | // 处理数据的逻辑,将数据传递给 outputPort 1149 | else 1150 | for (QBlueprintPort* outputPort : outputPorts) { 1151 | if (outputPort && isPortConnected(inputPort, outputPort)) { 1152 | qDebug() << "the data:" << data; 1153 | outputPort->setVarType(data); // 更新 outputPort 的数据 1154 | outputPort->sendDataToConnectedPorts(); // 通过 outputPort 发送数据给下一个端口 1155 | } 1156 | } 1157 | } 1158 | 1159 | 1160 | } 1161 | bool QBlueprintNode::isPortConnected(QBlueprintPort* inputPort, QBlueprintPort* outputPort) { 1162 | if(inputPort->getNodeType()==Type::FUNCTION && outputPort->getNodeType()==Type::FUNCTION) 1163 | return true; 1164 | auto inputIt = std::find(inputPorts.begin(), inputPorts.end(), inputPort); 1165 | auto outputIt = std::find(outputPorts.begin(), outputPorts.end(), outputPort); 1166 | 1167 | if (inputIt == inputPorts.end() || outputIt == outputPorts.end()) { 1168 | return false; 1169 | } 1170 | 1171 | int inputIndex = std::distance(inputPorts.begin(), inputIt); 1172 | int outputIndex = std::distance(outputPorts.begin(), outputIt); 1173 | 1174 | if (inputIndex != outputIndex) { 1175 | return false; 1176 | } 1177 | 1178 | // 获取场景和 Blueprint 视图 1179 | QGraphicsScene* currentScene = scene(); 1180 | QBlueprint* blueprintView = dynamic_cast(currentScene->views().first()); 1181 | 1182 | if (blueprintView) { 1183 | qDebug() << "sadas" <isEventPortConnected(outputPort, inputPort); 1184 | return blueprintView->isEventPortConnected(outputPort, inputPort); 1185 | } 1186 | return false; 1187 | } 1188 | 1189 | QVariant QBlueprintNode::mathFunctions() 1190 | { 1191 | QVariant result; 1192 | if (m_name == "add" ) { 1193 | // 加法操作 1194 | result = Math::add(inputPorts[1]->data().toDouble(),inputPorts[2]->data().toDouble()); 1195 | } 1196 | else if (m_name == "subtract" ) { 1197 | // 减法操作 1198 | result = Math::subtract(inputPorts[1]->data().toDouble(),inputPorts[2]->data().toDouble()); 1199 | 1200 | } 1201 | else if (m_name == "multiply" ) { 1202 | // 乘法操作 1203 | result = Math::multiply(inputPorts[1]->data().toDouble(),inputPorts[2]->data().toDouble()); 1204 | 1205 | } 1206 | else if (m_name == "divide" ) { 1207 | // 除法操作,检查除数是否为零 1208 | result = Math::divide(inputPorts[1]->data().toDouble(),inputPorts[2]->data().toDouble()); 1209 | 1210 | } 1211 | else if (m_name == "sqrt" ) { 1212 | // 开方操作,只需要一个输入 1213 | result = Math::sqrt(inputPorts[1]->data().toDouble()); 1214 | 1215 | } 1216 | else if (m_name == "pow") { 1217 | result = Math::pow(inputPorts[1]->data().toDouble(),inputPorts[2]->data().toDouble()); 1218 | } 1219 | else if (m_name == "deletea") { 1220 | 1221 | } 1222 | return result; 1223 | } 1224 | 1225 | QVariant QBlueprintNode::qtsFunctions() 1226 | { 1227 | QVariant result; 1228 | 1229 | if (m_name == "setQPointF") { 1230 | result = Qts::setQPointF(inputPorts[1]->data().toDouble(), 1231 | inputPorts[2]->data().toDouble()); 1232 | } else if (m_name == "setQPoint") { 1233 | result = Qts::setQPoint(inputPorts[1]->data().toInt(), 1234 | inputPorts[2]->data().toInt()); 1235 | } else if (m_name == "getQPointF_X") { 1236 | QPointF point = inputPorts[1]->data().value(); 1237 | result = Qts::getQPointF_X(point); 1238 | } else if (m_name == "getQPointF_Y") { 1239 | QPointF point = inputPorts[1]->data().value(); 1240 | result = Qts::getQPointF_Y(point); 1241 | } else if (m_name == "getQPoint_X") { 1242 | QPoint point = inputPorts[1]->data().value(); 1243 | result = Qts::getQPoint_X(point); 1244 | } else if (m_name == "getQPoint_Y") { 1245 | QPoint point = inputPorts[1]->data().value(); 1246 | result = Qts::getQPoint_Y(point); 1247 | } 1248 | 1249 | return result; 1250 | } 1251 | void QBlueprintNode::addLabelToPort(QBlueprintPort* port, const QString &text) 1252 | { 1253 | QLabel* label = new QLabel(text); 1254 | label->setStyleSheet("QLabel { border: 1px solid black; padding: 2px; }"); 1255 | 1256 | QGraphicsProxyWidget* proxy = new QGraphicsProxyWidget(this); 1257 | proxy->setWidget(label); 1258 | 1259 | QPointF portPos = port->pos(); 1260 | proxy->setPos(portPos.x() - 70, portPos.y() - 5); 1261 | 1262 | inputlabel.push_back(label); 1263 | } 1264 | #ifdef OPENCV_FOUND 1265 | QVariant QBlueprintNode::opencvFunctions() 1266 | { 1267 | QVariant result; 1268 | if (m_name == "threshold") { 1269 | result = opencv::threshold(inputPorts[1]->data().value(), 1270 | inputPorts[2]->data().toDouble(), 1271 | inputPorts[3]->data().toDouble(), 1272 | inputPorts[4]->data().toInt()); 1273 | } 1274 | else if (m_name == "BGRtoHSV") { 1275 | result = opencv::BGRtoHSV(inputPorts[1]->data().value()); // BGR 转 HSV 1276 | } 1277 | // else if (m_name == "HSVtoBGR") { 1278 | // result = opencv::convertColor(inputPorts[1]->data().value()); // HSV 转 BGR 1279 | // } 1280 | else if (m_name == "BGRtoGRAY") { 1281 | result = opencv::BGRtoGRAY(inputPorts[1]->data().value()); // BGR 转 灰度 1282 | } 1283 | // else if (m_name == "RGBtoBGR") { 1284 | // result = opencv::convertColor(inputPorts[1]->data().value(), 1285 | // cv::COLOR_RGB2BGR); // RGB 转 BGR 1286 | // } 1287 | // else if (m_name == "RGBtoHSV") { 1288 | // result = opencv::convertColor(inputPorts[1]->data().value(), 1289 | // cv::COLOR_RGB2HSV); // RGB 转 HSV 1290 | // } 1291 | // else if (m_name == "HSVtoRGB") { 1292 | // result = opencv::convertColor(inputPorts[1]->data().value(), 1293 | // cv::COLOR_HSV2RGB); // HSV 转 RGB 1294 | // } 1295 | else if (m_name == "erode") { 1296 | result = opencv::erode(inputPorts[1]->data().value(), 1297 | inputPorts[2]->data().toInt(), 1298 | inputPorts[3]->data().toInt()); 1299 | } 1300 | else if (m_name == "dilate") { 1301 | result = opencv::dilate(inputPorts[1]->data().value(), 1302 | inputPorts[2]->data().toInt(), 1303 | inputPorts[3]->data().toInt()); 1304 | } 1305 | else if (m_name == "canny") { 1306 | result = opencv::canny(inputPorts[1]->data().value(), 1307 | inputPorts[2]->data().toInt(), 1308 | inputPorts[3]->data().toInt()); 1309 | } 1310 | else if (m_name == "gaussianblur") { 1311 | result = opencv::gaussianblur(inputPorts[1]->data().value(), 1312 | inputPorts[2]->data().toInt(), 1313 | inputPorts[3]->data().toInt(), 1314 | inputPorts[4]->data().toInt(), 1315 | inputPorts[5]->data().toInt()); 1316 | } 1317 | else if (m_name == "rotateandincline") { 1318 | result = opencv::rotateandincline(inputPorts[1]->data().value(), 1319 | inputPorts[2]->data().value(), 1320 | inputPorts[3]->data().toDouble(), 1321 | inputPorts[4]->data().toDouble()); 1322 | } 1323 | else if (m_name == "per_trans") { 1324 | result = opencv::per_trans(inputPorts[1]->data().value(), 1325 | inputPorts[2]->data().value(), 1326 | inputPorts[3]->data().value(), 1327 | inputPorts[4]->data().value(), 1328 | inputPorts[5]->data().value(), 1329 | inputPorts[6]->data().value()); 1330 | } 1331 | else if (m_name == "resize") { 1332 | result = opencv::resize(inputPorts[1]->data().value(), 1333 | inputPorts[2]->data().toDouble(), 1334 | inputPorts[3]->data().toDouble()); 1335 | } 1336 | else if (m_name == "line") { 1337 | result = opencv::line(inputPorts[1]->data().value(), 1338 | inputPorts[2]->data().value(), 1339 | inputPorts[3]->data().value(), 1340 | inputPorts[4]->data().value(), 1341 | inputPorts[5]->data().toInt()); 1342 | } 1343 | 1344 | return result; 1345 | } 1346 | #endif 1347 | 1348 | 1349 | 1350 | 1351 | 1352 | 1353 | 1354 | 1355 | -------------------------------------------------------------------------------- /qblueprintnode.h: -------------------------------------------------------------------------------- 1 | #ifndef QBLUEPRINTNODE_H 2 | #define QBLUEPRINTNODE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "imagelabel.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "alluse.h" 16 | #include "qblueprintport.h" 17 | #include "qblueprintconnection.h" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "testclass.h" 25 | class QBlueprint; 26 | class QBlueprintNode : public QObject, public QGraphicsItem 27 | { 28 | Q_OBJECT 29 | public: 30 | 31 | QBlueprintNode(enum Type Type,DataType datatype = DataType::FOR_FUNCTION, QGraphicsItem *parent = nullptr); 32 | ~QBlueprintNode(); 33 | // 重载boundingRect()方法,定义绘制区域 34 | QRectF boundingRect() const override; 35 | 36 | // 重载paint()方法,定义绘制节点的具体方式 37 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; 38 | void addInputPortCondition(enum Type Type); 39 | // 添加输入和输出端口 40 | QBlueprintPort* addInputPort(); 41 | QBlueprintPort* addOutputPort(); 42 | // 添加输入和输出端口(带名称) 43 | QBlueprintPort* addInputPort(const QString &name); 44 | QBlueprintPort* addOutputPort(const QString &name); 45 | // 添加输入和输出端口(为事件的) 46 | void addInputPort(enum Type Type); 47 | void addOutputPort(enum Type Type); 48 | // 有端口的信息简介时 49 | QBlueprintPort* addInputPort(const QString &name, const QString &brief); 50 | QBlueprintPort* addOutputPort(const QString &name, const QString &brief); 51 | const std::vector& getInputPorts() const { return inputPorts; } 52 | const std::vector& getOutputPorts() const { return outputPorts; } 53 | 54 | void setNodeTitle(QString name); 55 | void setClassName(QString class_name); 56 | // 获取类名 57 | QString getClassName() const { return class_name; } 58 | // 获取节点名称 59 | QString getNodeTitle() const { return m_name; } 60 | enum Type getNodeType() { return nodeType; } 61 | enum Type setNodeType(enum Type type){ nodeType = type; } 62 | QBlueprintNode *clone() const; 63 | 64 | // 数据类型管理方法 65 | void addDataType(DataType type) { dataTypes.push_back(type); } 66 | const std::vector& getDataTypes() const { return dataTypes; } 67 | void processData(QBlueprintPort *inputPort, const QVariant &data); 68 | bool isPortConnected(QBlueprintPort *inputPort, QBlueprintPort *outputPort); 69 | void addRadioButtonOptions(QBlueprintPort *port); 70 | protected: 71 | QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; 72 | 73 | private: 74 | void setQVariantType(QBlueprintPort* port); 75 | void customNodePortSort(); 76 | void imageNodePortSort(); 77 | QFont font; 78 | enum Type nodeType; 79 | DataType dataType; 80 | QString class_name; 81 | QString m_name; 82 | std::vector inputPorts; // 存储输入端口 83 | std::vector outputPorts; // 存储输出端口 84 | std::vector dataTypes; // 存储节点支持的数据类型 85 | 86 | //------------------------- 节点输入 --------------------------------// 87 | std::vector lineEdits; 88 | std::vector inputlabel; 89 | std::vector outputlabel; 90 | std::vector comboboxs; 91 | std::vector inputImagelabel; 92 | std::vector outputImagelabel; 93 | std::vector> radioButtonOptions; // 存放关系 || && 94 | std::vector radioButtonValues; 95 | std::vector relation; // 存放关系 ><=等 96 | std::vector partbool; // 每一个部分的bool值 97 | void addLineEdit(QBlueprintPort* port); 98 | void addLineEdit(QBlueprintPort *port1, QBlueprintPort *port2); 99 | void addInputLabel(QBlueprintPort* port); 100 | void addLabelToPort(QBlueprintPort *port, const QString &text); 101 | void addOutputLabel(QBlueprintPort* outport, QBlueprintPort* inport); 102 | void addButtonToTopLeft(enum Type type); 103 | void addButtonToTopLeft(); 104 | void adjustLineEditWidth(const QString &text); 105 | void adjustLabelWidth(const QString &text); 106 | 107 | 108 | // 109 | TestClass testClassInstance; 110 | 111 | QVariant mathFunctions(); 112 | QVariant opencvFunctions(); 113 | QVariant qtsFunctions(); 114 | 115 | }; 116 | 117 | #endif // QBLUEPRINTNODE_H 118 | -------------------------------------------------------------------------------- /qblueprintport.cpp: -------------------------------------------------------------------------------- 1 | #include "qblueprintport.h" 2 | #include "qblueprintconnection.h" 3 | #include "qblueprint.h" 4 | QBlueprintPort::QBlueprintPort(PortType type, const QString &name, DataType dataType, QGraphicsItem *parent, QString brief) 5 | : QGraphicsItem(parent), m_type(type), m_name(name),m_font(QFont("Arial", 10)),dataType(dataType),portBrief(brief) 6 | { 7 | setFlag(QGraphicsItem::ItemIsMovable, false); 8 | setFlag(QGraphicsItem::ItemIsSelectable, true); 9 | setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); // 允许发送几何变化 10 | qDebug() << "brief: " << brief << "portBrief:" << portBrief; 11 | setAcceptHoverEvents(true); 12 | setQVariantType(); 13 | setZValue(2); 14 | //initPortBrief(); 15 | } 16 | QBlueprintPort* QBlueprintPort::clone() const 17 | { 18 | // 创建一个新的 QBlueprintPort 实例并复制所需的属性 19 | QBlueprintPort* newPort = new QBlueprintPort(this->m_type, this->m_name, this->dataType, nullptr, this->portBrief); // 注意这里 parent 设为 nullptr 20 | newPort->setNodeType(this->parentnodeType); 21 | // 复制属性 22 | return newPort; 23 | } 24 | QRectF QBlueprintPort::boundingRect() const 25 | { 26 | return QRectF(0, 0, 10, 10); // 端口的大小为 10x10 27 | } 28 | 29 | void QBlueprintPort::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 30 | { 31 | painter->setFont(m_font); // 使用成员变量中的字体 32 | 33 | // 获取字体的度量信息,用来计算文本宽度 34 | QFontMetrics fontMetrics(m_font); 35 | int textWidth = fontMetrics.horizontalAdvance(m_name); 36 | int textHeight = fontMetrics.height(); 37 | 38 | // 绘制端口 39 | if (m_type == EVENT_INPUT || m_type == EVENT_OUTPUT || m_type == EVENT_TRUE_RETURN || m_type == EVENT_FALSE_RETURN) 40 | { 41 | // 绘制朝右的三角形 42 | QPolygonF triangle; 43 | qreal size = 5; // 三角形的大小 44 | QPointF center = boundingRect().center(); // 获取中心位置 45 | 46 | // 定义三角形的三个顶点 47 | triangle << QPointF(center.x() - size, center.y() - size); // 左上顶点 48 | triangle << QPointF(center.x() - size, center.y() + size); // 左下顶点 49 | triangle << QPointF(center.x() + size, center.y()); // 右侧顶点 50 | 51 | painter->setBrush(Qt::white); // 设置三角形为白色 52 | painter->drawPolygon(triangle); // 绘制三角形 53 | } 54 | else 55 | { 56 | // 绘制圆形端口 57 | painter->setBrush((m_type == Input) ? Qt::blue : Qt::green); // 输入端口为蓝色,输出端口为绿色 58 | painter->drawEllipse(boundingRect()); // 绘制圆形端口 59 | } 60 | 61 | // 绘制端口名称,放在端口旁边 62 | painter->setPen(Qt::white); 63 | if (m_type == Input || m_type == EVENT_INPUT) 64 | { 65 | // 输入端口:名称在端口的右边,左对齐 66 | QRectF textRect = boundingRect().translated(boundingRect().width() + 10, 0); // 向右移动文本区域,留出10像素的间距 67 | textRect.setWidth(textWidth); // 设置文本区域的宽度,确保名称的最后字符靠近端口 68 | textRect.setHeight(textHeight); 69 | painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, m_name); // 名称放在右侧,左对齐 70 | } 71 | else 72 | { 73 | // 输出端口:名称在端口的左边,右对齐 74 | QRectF textRect = boundingRect().translated(-textWidth - 10, 0); // 根据文本长度动态调整区域,留出10像素的间距 75 | textRect.setWidth(textWidth); // 设置文本区域的宽度,确保名称的最后字符靠近端口 76 | textRect.setHeight(textHeight); 77 | painter->drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, m_name); // 名称放在左侧,右对齐 78 | } 79 | } 80 | 81 | 82 | 83 | 84 | QPointF QBlueprintPort::centerPos() const 85 | { 86 | return mapToScene(boundingRect().center()); // 返回端口的中心点 87 | } 88 | 89 | void QBlueprintPort::updateConnections() 90 | { 91 | // 检查当前场景是否存在 92 | QGraphicsScene *currentScene = this->scene(); 93 | if (!currentScene) return; // 如果场景不存在,直接返回 94 | 95 | QBlueprint *blueprintView = dynamic_cast(currentScene->views().first()); 96 | 97 | if (blueprintView) 98 | { 99 | // 更新该端口相关的所有连接 100 | blueprintView->updateConnectionsForPort(this); 101 | } 102 | } 103 | 104 | // 移除并删除该端口相关的所有连接 105 | void QBlueprintPort::removeConnections() 106 | { 107 | QGraphicsScene *currentScene = this->scene(); 108 | QBlueprint *blueprintView = dynamic_cast(currentScene->views().first()); 109 | 110 | if (blueprintView) 111 | { 112 | std::vector toRemove; 113 | // 遍历所有连接,收集与此端口相关的连接 114 | for (QBlueprintConnection *connection : blueprintView->connections) 115 | { 116 | if (connection->startPort() == this || connection->endPort() == this) 117 | { 118 | toRemove.push_back(connection); 119 | } 120 | } 121 | 122 | // 删除所有相关连接 123 | for (QBlueprintConnection *connection : toRemove) 124 | { 125 | blueprintView->removeConnection(connection); 126 | } 127 | } 128 | } 129 | 130 | void QBlueprintPort::sendDataToConnectedPorts() { 131 | // 获取当前场景 132 | QGraphicsScene *currentScene = this->scene(); 133 | if (!currentScene) return; 134 | 135 | // 获取 QBlueprint 视图 136 | QBlueprint *blueprintView = dynamic_cast(currentScene->views().first()); 137 | 138 | if (blueprintView) { 139 | // 遍历所有连接,找到与当前端口相连的端口 140 | for (QBlueprintConnection *connection : blueprintView->connections) { 141 | if (connection->startPort() == this) { 142 | QBlueprintPort* targetPort = connection->endPort(); 143 | if (targetPort) { 144 | QVariant convertedVar; 145 | switch (targetPort->portDataType()) { 146 | case DataType::INT: 147 | convertedVar = QVariant::fromValue(var.toInt()); // 转换为 int 类型 148 | break; 149 | case DataType::FLOAT: 150 | convertedVar = QVariant::fromValue(var.toFloat()); // 转换为 float 类型 151 | break; 152 | case DataType::DOUBLE: 153 | convertedVar = QVariant::fromValue(var.toDouble()); // 转换为 double 类型 154 | break; 155 | case DataType::STRING: 156 | convertedVar = QVariant::fromValue(var.toString()); // 转换为 QString 类型 157 | break; 158 | case DataType::BOOL: 159 | convertedVar = QVariant::fromValue(var.toBool()); // 转换为 bool 类型 160 | break; 161 | case DataType::LONG: 162 | convertedVar = QVariant::fromValue(var.toLongLong()); // 转换为 long 类型 163 | break; 164 | case DataType::SHORT: 165 | convertedVar = QVariant::fromValue(static_cast(var.toInt())); // 转换为 short 类型 166 | break; 167 | case DataType::UNSIGNED_INT: 168 | convertedVar = QVariant::fromValue(var.toUInt()); // 转换为 unsigned int 类型 169 | break; 170 | case DataType::QIMAGE: 171 | if (var.canConvert()) { 172 | QImage image = var.value(); // 获取 QImage 对象 173 | convertedVar = QVariant::fromValue(image); // 将 QImage 转换为 QVariant 并存储 174 | } 175 | break; 176 | // 你可以根据需求添加其他类型转换逻辑... 177 | default: 178 | convertedVar = var; // 如果未匹配到类型,保持原样 179 | break; 180 | } 181 | // 发送转换后的数据给 targetPort 182 | qDebug() << "Sending converted data from" << this->name() << "to" << targetPort->name(); 183 | targetPort->receiveData(convertedVar); 184 | // 发送数据给 targetPort 185 | // qDebug() << "Sending data from" << this->name() << "to" << targetPort->name(); 186 | } 187 | } 188 | } 189 | } 190 | } 191 | 192 | void QBlueprintPort::receiveData(const QVariant &data) { 193 | if (data.canConvert()) { 194 | QImage image = data.value(); 195 | if (!image.isNull()) { 196 | qDebug() << "Received valid QImage data of size:" << image.size(); 197 | } else { 198 | qWarning() << "Received QImage but it is null."; 199 | } 200 | } else { 201 | qWarning() << "Invalid data received: " << data; 202 | } 203 | setData(data); 204 | // 通知父节点处理数据 205 | QBlueprintNode* parentNode = dynamic_cast(parentItem()); 206 | if (parentNode) { 207 | parentNode->processData(this, data); 208 | } 209 | } 210 | 211 | void QBlueprintPort::setVarType(const QVariant &value) 212 | { 213 | var = value; 214 | } 215 | 216 | void QBlueprintPort::setPortBrief(QString portBrief) 217 | { 218 | portBrief = "port:" + portBrief; 219 | } 220 | 221 | void QBlueprintPort::initPortBrief() 222 | { 223 | portBrief = "port:" + m_name; 224 | } 225 | 226 | QString QBlueprintPort::getVarTypeName() const 227 | { 228 | return var.typeName(); 229 | } 230 | 231 | void QBlueprintPort::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { 232 | QGraphicsItem::hoverEnterEvent(event); 233 | setToolTip(portBrief); // 显示端口名称 234 | } 235 | 236 | void QBlueprintPort::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { 237 | QGraphicsItem::hoverLeaveEvent(event); 238 | setToolTip(""); // 清空提示 239 | } 240 | 241 | void QBlueprintPort::setQVariantType() 242 | { 243 | switch (dataType) { 244 | case DataType::INT: 245 | setVarType(QVariant::fromValue(int())); // 设置为 int 类型 246 | break; 247 | case DataType::FLOAT: 248 | setVarType(QVariant::fromValue(float())); // 设置为 float 类型 249 | break; 250 | case DataType::DOUBLE: 251 | setVarType(QVariant::fromValue(double())); // 设置为 double 类型 252 | break; 253 | case DataType::CHAR: 254 | setVarType(QVariant::fromValue(char())); // 设置为 char 类型 255 | break; 256 | case DataType::STRING: 257 | setVarType(QVariant::fromValue(QString())); // 设置为 QString 类型 258 | break; 259 | case DataType::BOOL: 260 | setVarType(QVariant::fromValue(bool())); // 设置为 bool 类型 261 | break; 262 | case DataType::LONG: 263 | setVarType(QVariant::fromValue(qint64())); // 设置为 long 类型 (qint64) 264 | break; 265 | case DataType::SHORT: 266 | setVarType(QVariant::fromValue(short())); // 设置为 short 类型 267 | break; 268 | case DataType::UNSIGNED_INT: 269 | setVarType(QVariant::fromValue(uint())); // 设置为 unsigned int 类型 270 | break; 271 | case DataType::VARIANT: 272 | setVarType(QVariant()); // QVariant 自身 273 | break; 274 | case DataType::QSTRING: 275 | setVarType(QVariant::fromValue(QString())); // 设置为 QString 类型 276 | break; 277 | case DataType::QTIME: 278 | setVarType(QVariant::fromValue(QTime())); // 设置为 QTime 类型 279 | break; 280 | case DataType::QPOINT: 281 | setVarType(QVariant::fromValue(QPoint())); // 设置为 QPoint 类型 282 | break; 283 | case DataType::QPOINTF: 284 | setVarType(QVariant::fromValue(QPointF())); // 设置为 QPointF 类型 285 | break; 286 | case DataType::QSIZE: 287 | setVarType(QVariant::fromValue(QSize())); // 设置为 QSize 类型 288 | break; 289 | case DataType::QSIZEF: 290 | setVarType(QVariant::fromValue(QSizeF())); // 设置为 QSizeF 类型 291 | break; 292 | case DataType::QRECT: 293 | setVarType(QVariant::fromValue(QRect())); // 设置为 QRect 类型 294 | break; 295 | case DataType::QRECTF: 296 | setVarType(QVariant::fromValue(QRectF())); // 设置为 QRectF 类型 297 | break; 298 | case DataType::QCOLOR: 299 | setVarType(QVariant::fromValue(QColor())); // 设置为 QColor 类型 300 | break; 301 | case DataType::QPIXMAP: 302 | setVarType(QVariant::fromValue(QPixmap())); // 设置为 QPixmap 类型 303 | break; 304 | case DataType::QIMAGE: 305 | setVarType(QVariant::fromValue(QImage())); // 设置为 QImage 类型 306 | break; 307 | default: 308 | setVarType(QVariant()); // 默认设置为一个空的 QVariant 309 | break; 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /qblueprintport.h: -------------------------------------------------------------------------------- 1 | #ifndef QBLUEPRINTPORT_H 2 | #define QBLUEPRINTPORT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "alluse.h" 8 | class QBlueprintConnection; 9 | class QBlueprintPort : public QObject, public QGraphicsItem 10 | { 11 | Q_OBJECT 12 | public: 13 | enum PortType { Input, Output, EVENT_OUTPUT, EVENT_INPUT, EVENT_TRUE_RETURN, EVENT_FALSE_RETURN, EVENT_CONDITION}; // 定义端口类型:输入和输出 14 | QFont m_font; 15 | void setPortFont(const QFont &font) { 16 | m_font = font; 17 | } 18 | QBlueprintPort(PortType type, const QString &name, DataType dataType, QGraphicsItem *parent,QString brief); 19 | virtual QBlueprintPort* clone() const; 20 | 21 | // 设置端口的矩形区域 22 | QRectF boundingRect() const override; 23 | 24 | // 绘制端口 25 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; 26 | 27 | // 获取端口的中心点,用于连接线 28 | QPointF centerPos() const; 29 | 30 | PortType portType() const { return m_type; } 31 | DataType portDataType() const { return dataType; } 32 | // 设置和获取端口名称 33 | QString name() const { return m_name; } 34 | void setName(const QString &name) { m_name = name; } 35 | enum Type getNodeType() { return parentnodeType; } 36 | enum Type setNodeType(enum Type type){ parentnodeType = type; } 37 | void updateConnections(); 38 | void removeConnections(); 39 | void sendDataToConnectedPorts(); 40 | void receiveData(const QVariant &data); 41 | void setVarType(const QVariant &value); 42 | void setPortBrief(QString portBrief); 43 | void initPortBrief(); 44 | QString getVarTypeName() const; // 获取 var 类型名称的方法 45 | void setData(const QVariant& data) { 46 | var = data; 47 | emit dataUpdated(var); 48 | } 49 | 50 | QVariant data() const { 51 | return var; 52 | } 53 | signals: 54 | void dataUpdated(const QVariant &data); 55 | protected: 56 | void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; 57 | void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; 58 | 59 | //void mousePressEvent(QGraphicsSceneMouseEvent *event) override; 60 | private: 61 | QString portBrief; 62 | DataType dataType; 63 | QVariant var; 64 | enum Type parentnodeType; 65 | std::vector connections; // 存储连接的指针 66 | PortType m_type; 67 | QString m_name; // 端口名称 68 | void setQVariantType(); 69 | }; 70 | 71 | #endif // QBLUEPRINTPORT_H 72 | -------------------------------------------------------------------------------- /qinputnode.h: -------------------------------------------------------------------------------- 1 | #ifndef QINPUTNODE_H 2 | #define QINPUTNODE_H 3 | 4 | #include "qblueprintnode.h" 5 | #include 6 | #include 7 | 8 | class QInputNode : public QBlueprintNode // 输入节点,用于接收输入,显示数据 9 | { 10 | public: 11 | QInputNode(QGraphicsItem* parent = nullptr) 12 | : QBlueprintNode(parent) { 13 | setNodeTitle("Input Node"); 14 | addInputPort("Input"); 15 | setNodeType(Type::INPUT); 16 | qDebug() << "input" << nodeType; 17 | // 创建 QLabel 作为输出显示的 widget 18 | outputLabel = new QLabel("0"); 19 | outputLabel->setFixedSize(80, 20); 20 | 21 | // 将 QLabel 添加到 QGraphicsItem 22 | proxyWidget = new QGraphicsProxyWidget(this); 23 | proxyWidget->setWidget(outputLabel); 24 | proxyWidget->setPos(boundingRect().width() / 2 - outputLabel->width() / 2, 50); 25 | } 26 | 27 | // 设置输出值 28 | void setOutputValue(int value) { 29 | outputLabel->setText(QString::number(value)); 30 | } 31 | 32 | protected: 33 | //QRectF boundingRect() const override; 34 | 35 | void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { 36 | QBlueprintNode::paint(painter, option, widget); 37 | 38 | // 绘制输出字段区域(已经在 QLabel 中绘制) 39 | painter->setPen(Qt::black); 40 | painter->setBrush(Qt::black); 41 | } 42 | 43 | private: 44 | enum Type nodeType; 45 | QLabel* outputLabel; // 输出字段 46 | QGraphicsProxyWidget* proxyWidget; // QLabel 的代理 47 | }; 48 | 49 | #endif // QOUTPUTNODE_H 50 | 51 | -------------------------------------------------------------------------------- /qnodefactory.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IgnorAnsel/QBlueprint/108760e7b7d29e346a33ec23dea4fe4e86f122ff/qnodefactory.cpp -------------------------------------------------------------------------------- /qnodefactory.h: -------------------------------------------------------------------------------- 1 | #pragma once // 只允许包含一次 2 | 3 | #include "qblueprintnode.h" 4 | #include "qblueprint.h" 5 | #include 6 | #include 7 | #include 8 | class QBlueprint; 9 | class QBlueprintNode; 10 | // 一个用于将函数签名自动生成节点的工厂类 11 | class QNodeFactory 12 | { 13 | public: 14 | // 模板函数,传递一个函数并生成对应的节点 15 | /** 16 | * @brief 创建一个基于函数的 QBlueprintNode 节点。 17 | * 18 | * 该函数接受一个函数指针(或可调用对象),并生成一个对应的 `QBlueprintNode` 节点。此节点将函数的输入参数类型解析为输入端口,并将输出参数类型解析为输出端口。 19 | * 20 | * @tparam Func 类型化模板参数,表示传入的函数类型或可调用对象的类型。 21 | * @param blueprint 指向 `QBlueprint` 的指针,用于管理节点的父蓝图。 22 | * @param func 传入的函数指针或可调用对象,其输入输出参数将用于创建节点的输入输出端口。 23 | * @param nodeName (可选)节点的名称,如果未指定,则默认使用函数名。如果函数名无法自动解析,用户需要手动填写。 24 | * @param className (可选)节点所属的类名,默认为 "Other" 类别,可以用于分类节点。 25 | * @return QBlueprintNode* 返回一个新的 `QBlueprintNode` 节点,该节点封装了传入的函数,并可用于蓝图执行。 26 | * 27 | * @note 该函数通过 `__PRETTY_FUNCTION__` 提取函数名以便自动生成节点名称,但如果自动提取不正常,请手动传入 `nodeName` 参数。 28 | * 29 | * @details 函数的具体步骤: 30 | * 1. 创建一个新的 `QBlueprintNode`,并将节点类型设置为 `Type::FUNCTION`。 31 | * 2. 如果没有传入 `nodeName`,则尝试从函数签名中提取函数名并设置为节点名称。 32 | * 3. 使用 `parseInputPorts()` 函数解析传入函数的输入参数类型,创建对应的输入端口。 33 | * 4. 使用 `parseOutputPort()` 函数解析传入函数的输出类型,创建对应的输出端口。 34 | * 5. 将节点加入到蓝图管理的节点列表中,并返回该节点。 35 | */ 36 | template 37 | static QBlueprintNode* createNodeFromFunction(QBlueprint* blueprint, Func func, const QString& nodeName = QString(), const QString& className = "Other") // 自动获取函数名不正常,直接填写你需要的名称 38 | { 39 | QBlueprintNode* node = new QBlueprintNode(Type::FUNCTION); 40 | node->setClassName(className); 41 | // 如果没有提供节点名称,使用函数的类型名称 42 | if (nodeName.isEmpty()) { 43 | QString prettyFunction = __PRETTY_FUNCTION__; // 使用 __PRETTY_FUNCTION__ 44 | QString funcName = extractFunctionName(prettyFunction); 45 | 46 | node->setNodeTitle(funcName); 47 | } else { 48 | node->setNodeTitle(nodeName); 49 | } 50 | node->setNodeType(Type::FUNCTION); 51 | // 解析输入参数类型 52 | parseInputPorts(node, func); 53 | 54 | // 解析输出参数类型 55 | parseOutputPort(node, func); 56 | blueprint->pushVectorQBlueprintNode(node); 57 | return node; 58 | } 59 | /** 60 | * @brief 创建一个基于函数的 QBlueprintNode 节点。 61 | * 62 | * 该函数接受一个函数指针(或可调用对象),并生成一个对应的 `QBlueprintNode` 节点。此节点将函数的输入参数类型解析为输入端口,并将输出参数类型解析为输出端口。 63 | * 64 | * @tparam Func 类型化模板参数,表示传入的函数类型或可调用对象的类型。 65 | * @param blueprint 指向 `QBlueprint` 的指针,用于管理节点的父蓝图。 66 | * @param func 传入的函数指针或可调用对象,其输入输出参数将用于创建节点的输入输出端口。 67 | * @param inputNames 输入参数的名称列表,包含所有输入参数的名称。 68 | * @param outputName 输出参数的名称,指定返回值的名称。 69 | * @param nodeName (可选)节点的名称,如果未指定,则默认使用函数名。如果函数名无法自动解析,用户需要手动填写。 70 | * @param className (可选)节点所属的类名,默认为 "Other" 类别,可以用于分类节点。 71 | * @return QBlueprintNode* 返回一个新的 `QBlueprintNode` 节点,该节点封装了传入的函数,并可用于蓝图执行。 72 | * 73 | * @note 该函数通过 `__PRETTY_FUNCTION__` 提取函数名以便自动生成节点名称,但如果自动提取不正常,请手动传入 `nodeName` 参数。 74 | * 75 | * @details 函数的具体步骤: 76 | * 1. 创建一个新的 `QBlueprintNode`,并将节点类型设置为 `Type::FUNCTION`。 77 | * 2. 如果没有传入 `nodeName`,则尝试从函数签名中提取函数名并设置为节点名称。 78 | * 3. 使用 `parseInputPorts()` 函数解析传入函数的输入参数类型和名称,创建对应的输入端口。 79 | * 4. 使用 `parseOutputPort()` 函数解析传入函数的输出类型和名称,创建对应的输出端口。 80 | * 5. 将节点加入到蓝图管理的节点列表中,并返回该节点。 81 | */ 82 | template 83 | static QBlueprintNode* createNodeFromFunction(QBlueprint* blueprint, Func func, const QStringList& inputNames, const QString& outputName = QString(), const QString& nodeName = QString(), const QString& className = "Other") 84 | { 85 | QBlueprintNode* node = new QBlueprintNode(Type::FUNCTION); 86 | node->setClassName(className); 87 | 88 | if (nodeName.isEmpty()) { 89 | QString prettyFunction = __PRETTY_FUNCTION__; 90 | QString funcName = extractFunctionName(prettyFunction); 91 | node->setNodeTitle(funcName); 92 | } else { 93 | node->setNodeTitle(nodeName); 94 | } 95 | 96 | node->setNodeType(Type::FUNCTION); 97 | 98 | // 解析输入参数类型和名称 99 | parseInputPorts(node, func, inputNames); 100 | 101 | // 解析输出参数类型和名称 102 | parseOutputPort(node, func, outputName); 103 | 104 | blueprint->pushVectorQBlueprintNode(node); 105 | return node; 106 | } 107 | private: 108 | static QString extractFunctionName(const QString& prettyFunction) 109 | { 110 | // 使用正则表达式从输入字符串中匹配函数名称 111 | QRegularExpression regex(R"((\w+)::(\w+))"); // 匹配 "ClassName::FunctionName" 这样的格式 112 | QRegularExpressionMatch match = regex.match(prettyFunction); 113 | 114 | if (match.hasMatch()) { 115 | // 提取函数名称部分 116 | QString functionName = match.captured(2); 117 | return functionName; 118 | } 119 | 120 | // 如果没有匹配到合适的部分,返回原始输入 121 | return prettyFunction; 122 | } 123 | 124 | // 处理普通函数指针 125 | template 126 | static void parseInputPorts(QBlueprintNode* node, Ret(*)(Args...), const QStringList& inputNames) 127 | { 128 | size_t index = 0; 129 | (..., (node->addInputPort(getTypeName(), inputNames[index++]))); // 使用参数名称 130 | } 131 | 132 | // 处理成员函数指针 133 | template 134 | static void parseInputPorts(QBlueprintNode* node, Ret(ClassType::*)(Args...), const QStringList& inputNames) 135 | { 136 | size_t index = 0; 137 | (..., (node->addInputPort(getTypeName(), inputNames[index++]))); // 使用参数名称 138 | } 139 | 140 | // 解析输出类型 - 普通函数指针 141 | template 142 | static void parseOutputPort(QBlueprintNode* node, Ret(*)(Args...), const QString& outputName) 143 | { 144 | node->addOutputPort(getTypeName(), outputName); 145 | } 146 | 147 | // 解析输出类型 - 成员函数指针 148 | template 149 | static void parseOutputPort(QBlueprintNode* node, Ret(ClassType::*)(Args...), const QString& outputName) 150 | { 151 | node->addOutputPort(getTypeName(), outputName); 152 | } 153 | 154 | 155 | 156 | 157 | 158 | // 处理普通函数指针 159 | template 160 | static void parseInputPorts(QBlueprintNode* node, Ret(*)(Args...)) 161 | { 162 | (node->addInputPort(getTypeName()), ...); 163 | } 164 | 165 | // 处理成员函数指针 166 | template 167 | static void parseInputPorts(QBlueprintNode* node, Ret(ClassType::*)(Args...)) 168 | { 169 | qDebug() << "function"; 170 | (node->addInputPort(getTypeName()), ...); 171 | } 172 | 173 | // 解析输出类型 - 普通函数指针 174 | template 175 | static void parseOutputPort(QBlueprintNode* node, Ret(*)(Args...)) 176 | { 177 | node->addOutputPort(getTypeName()); 178 | } 179 | 180 | // 解析输出类型 - 成员函数指针 181 | template 182 | static void parseOutputPort(QBlueprintNode* node, Ret(ClassType::*)(Args...)) 183 | { 184 | node->addOutputPort(getTypeName()); 185 | } 186 | 187 | // 获取类型名称的辅助函数(简单示例,可以扩展以支持更多类型) 188 | template 189 | static QString getTypeName() 190 | { 191 | if (std::is_same::value) 192 | return "int"; 193 | else if (std::is_same::value) 194 | return "float"; 195 | else if (std::is_same::value) 196 | return "double"; 197 | else if (std::is_same::value) 198 | return "QString"; 199 | else if (std::is_same::value) // 添加对 QImage 的支持 200 | return "QImage"; 201 | else if (std::is_same::value) 202 | return "QPoint"; 203 | else if (std::is_same::value) 204 | return "QPointF"; 205 | else 206 | return "unknown"; 207 | } 208 | }; 209 | 210 | 211 | -------------------------------------------------------------------------------- /qoutputnode.h: -------------------------------------------------------------------------------- 1 | #ifndef QOUTPUTNODE_H 2 | #define QOUTPUTNODE_H 3 | 4 | #include "qblueprintnode.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | class QOutputNode : public QBlueprintNode // 输出节点,用于自行输出数据 11 | { 12 | public: 13 | QOutputNode(QGraphicsItem* parent = nullptr) 14 | : QBlueprintNode(parent) { 15 | setNodeTitle("Output Node"); 16 | addOutputPort("Output"); 17 | setNodeType(Type::OUTPUT); 18 | // 创建 QLineEdit 作为用户输入的 widget 19 | inputField = new QLineEdit(); 20 | inputField->setValidator(new QIntValidator(0, 1000)); // 只允许输入整数 21 | inputField->setFixedSize(80, 20); 22 | 23 | // 将 QLineEdit 添加到 QGraphicsItem 24 | proxyWidget = new QGraphicsProxyWidget(this); 25 | proxyWidget->setWidget(inputField); 26 | proxyWidget->setPos(boundingRect().width() / 2 - inputField->width() / 2, 50); 27 | } 28 | 29 | // 设置输出值 30 | int getValue() const { 31 | return inputField->text().toInt(); 32 | } 33 | 34 | protected: 35 | 36 | void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { 37 | QBlueprintNode::paint(painter, option, widget); 38 | 39 | // 绘制输出字段区域(已经在 QLabel 中绘制) 40 | painter->setPen(Qt::black); 41 | painter->setBrush(Qt::white); 42 | } 43 | 44 | private: 45 | QLineEdit* inputField; // 输入字段 46 | QGraphicsProxyWidget* proxyWidget; // QLineEdit 的代理 47 | }; 48 | 49 | #endif // QOUTPUTNODE_H 50 | -------------------------------------------------------------------------------- /qts.cpp: -------------------------------------------------------------------------------- 1 | #include "qts.h" 2 | 3 | Qts::Qts() {} 4 | 5 | QPointF Qts::setQPointF(double x_pos, double y_pos) 6 | { 7 | return QPointF(x_pos,y_pos); 8 | } 9 | 10 | double Qts::getQPointF_X(QPointF qpointf) 11 | { 12 | return qpointf.x(); 13 | } 14 | 15 | double Qts::getQPointF_Y(QPointF qpointf) 16 | { 17 | return qpointf.y(); 18 | } 19 | 20 | QPoint Qts::setQPoint(int x_pos, int y_pos) 21 | { 22 | return QPoint(x_pos,y_pos); 23 | } 24 | 25 | int Qts::getQPoint_X(QPoint qpoint) 26 | { 27 | return qpoint.x(); 28 | } 29 | 30 | int Qts::getQPoint_Y(QPoint qpoint) 31 | { 32 | return qpoint.y(); 33 | } 34 | QStringList Qts::inputNames_setQPointF = {"x_pos", "y_pos"}; 35 | QString Qts::outputName_setQPointF = "QPointF"; 36 | 37 | QStringList Qts::inputNames_getQPointF_X = {"qpointf"}; 38 | QString Qts::outputName_getQPointF_X = "x"; 39 | 40 | QStringList Qts::inputNames_getQPointF_Y = {"qpointf"}; 41 | QString Qts::outputName_getQPointF_Y = "y"; 42 | 43 | QStringList Qts::inputNames_setQPoint = {"x_pos", "y_pos"}; 44 | QString Qts::outputName_setQPoint = "QPoint"; 45 | 46 | QStringList Qts::inputNames_getQPoint_X = {"qpoint"}; 47 | QString Qts::outputName_getQPoint_X = "x"; 48 | 49 | QStringList Qts::inputNames_getQPoint_Y = {"qpoint"}; 50 | QString Qts::outputName_getQPoint_Y = "y"; 51 | -------------------------------------------------------------------------------- /qts.h: -------------------------------------------------------------------------------- 1 | #ifndef QTS_H 2 | #define QTS_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | class Qts 8 | { 9 | public: 10 | Qts(); 11 | static QPointF setQPointF(double x_pos, double y_pos); 12 | static double getQPointF_X(QPointF qpointf); 13 | static double getQPointF_Y(QPointF qpointf); 14 | 15 | static QPoint setQPoint(int x_pos, int y_pos); 16 | static int getQPoint_X(QPoint qpoint); 17 | static int getQPoint_Y(QPoint qpoint); 18 | static QStringList inputNames_setQPointF; 19 | static QString outputName_setQPointF; 20 | 21 | static QStringList inputNames_getQPointF_X; 22 | static QString outputName_getQPointF_X; 23 | 24 | static QStringList inputNames_getQPointF_Y; 25 | static QString outputName_getQPointF_Y; 26 | 27 | static QStringList inputNames_setQPoint; 28 | static QString outputName_setQPoint; 29 | 30 | static QStringList inputNames_getQPoint_X; 31 | static QString outputName_getQPoint_X; 32 | 33 | static QStringList inputNames_getQPoint_Y; 34 | static QString outputName_getQPoint_Y; 35 | }; 36 | 37 | #endif // QTS_H 38 | -------------------------------------------------------------------------------- /testclass.cpp: -------------------------------------------------------------------------------- 1 | #include "testclass.h" 2 | 3 | TestClass::TestClass() 4 | { 5 | 6 | } 7 | 8 | int TestClass::add(int a, int b) 9 | { 10 | return a+b; 11 | } 12 | 13 | float TestClass::test(float f) 14 | { 15 | return f; 16 | } 17 | -------------------------------------------------------------------------------- /testclass.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTCLASS_H 2 | #define TESTCLASS_H 3 | 4 | 5 | class TestClass 6 | { 7 | public: 8 | TestClass(); 9 | int add(int a, int b); 10 | float test(float f); 11 | }; 12 | 13 | #endif // TESTCLASS_H 14 | --------------------------------------------------------------------------------