马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
1. 资源介绍
功能:使用qt在画板上绘制各种外形,并保持绘制内容到xml文件中。
项目源码:https://github.com/egan2015/qdraw
软件界面:
1.1 支持shape
6种
1.2 支持的功能
6种,分别是对绘制的图形举行撤销undo,重做redo,裁剪,复制,粘贴,删除功能。
2. 总体类图关系
总体分割3个独立块。
2.1 绘制类
DrawTool 基类定义了根本的画图和交互举动,而各子类分别实现了不同的编辑和绘制功能。这种设计方式具有良好的扩展性,可以轻松添加新的工具类以支持更多范例的图形操作。但是具体的绘制操作是放在各自的GraphicsItem,背面会介绍。
很希奇,为何父类和子类都没有继续QObject,难道是不使用qt的信号槽机制?
注解:懂得可以不看,跳过。
这里以void PolygonTool::mouseReleaseEvent函数为例,其内部有一句
emit scene->itemAdded( item );
在 C++ 的 Qt 框架中,要使用信号和槽机制的确需要类继续自 QObject 并使用 Q_OBJECT 宏。然而,PolygonTool 类本身并没有使用 Q_OBJECT 或继续自 QObject,却调用了 emit 关键字发送信号。这可能导致一些困惑。
信号定义在其他类中: itemAdded 是在 scene 对象中定义的信号。PolygonTool 没有继续自 QObject,它仍然可以通过持有的 scene 对象(必须是 QObject 的子类)来发射信号。这是 Qt 框架设计的一部门,答应机动的信号和槽毗连,即使在复杂的类继续布局中。
2.2 GraphicsItem类
3. 绘制类成员介绍
3.1 DrawTool基类

其他四个类SelectTool, RotationTool, RectTool, PolygonTool都是继续此基类。 DrawTool 是一个基类,用于为不同的画图工具提供公共功能和接口。它的主要作用是定义和实现一些基础的画图操作,以及管理和查找不同的画图工具实例。
- //drawtool.h
- enum DrawShape
- {
- selection, // 选择模式:用于选择和操作现有的图形项。
- rotation, // 旋转模式:用于对图形项进行旋转操作。
- line, // 线段:用于绘制直线。
- rectangle, // 矩形:用于绘制矩形。
- roundrect, // 圆角矩形:用于绘制带有圆角的矩形。
- ellipse, // 椭圆:用于绘制椭圆形状。
- bezier, // 贝塞尔曲线:用于绘制贝塞尔曲线,用于平滑和复杂的曲线形状。
- polygon, // 多边形:用于绘制闭合的多边形。
- polyline, // 折线:用于绘制由多个线段组成的折线。
- };
- // 基类
- class DrawTool
- {
- public:
- DrawTool( DrawShape shape ); // 构造函数,初始化绘制工具,参数为绘制形状
- virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ; // 鼠标按下事件,虚函数
- virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
- virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
- virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event ,DrawScene *scene );
- DrawShape m_drawShape; // 当前绘制工具的形状
- bool m_hoverSizer; // 是否悬停在调整大小的手柄上
- static DrawTool * findTool( DrawShape drawShape ); // 根据形状查找相应的绘制工具
- static QList<DrawTool*> c_tools; // 静态成员,保存所有绘制工具的列表
- static QPointF c_down; // 静态成员,保存鼠标按下时的位置
- static quint32 c_nDownFlags; // 静态成员,保存按下时的标志
- static QPointF c_last; // 静态成员,保存最后一次鼠标事件的位置
- static DrawShape c_drawShape; // 静态成员,当前选中的绘制形状
- };
- // drawtool.cpp
- // 初始化全部的静态成员变量,为啥不在构造函数内部初始化:
- // 语法规则, 静态成员变量的初始化必须在类定义的外部进行
- // 因为是静态的,静态成员属于类而不是对象,避免重复初始化,静态成员变量在程序加载时就会被分配内存并初始化,而构造函数是在对象被创建时才会调用。
- QList<DrawTool*> DrawTool::c_tools; // 初始化静态成员,绘制工具列表
- QPointF DrawTool::c_down; // 初始化静态成员,鼠标按下位置
- QPointF DrawTool::c_last; // 初始化静态成员,最后一次鼠标位置
- quint32 DrawTool::c_nDownFlags; // 初始化静态成员,鼠标按下标志
- DrawShape DrawTool::c_drawShape = selection; // 初始化静态成员,当前绘制形状为 selection
- // DrawTool 构造函数,初始化绘制形状并将工具加入工具列表
- DrawTool::DrawTool(DrawShape shape)
- {
- m_drawShape = shape ; // 设置绘制形状
- m_hoverSizer = false; // 初始化悬停调整大小状态
- c_tools.push_back(this); // 将当前工具加入工具列表
- }
- // 鼠标按下事件处理
- void DrawTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- c_down = event->scenePos(); // 保存鼠标 按下时的位置
- c_last = event->scenePos(); // 初始化最后位置为 按下时位置
- }
- // 鼠标移动事件处理
- void DrawTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- c_last = event->scenePos(); // 更新最后一次鼠标事件的位置
- }
- // 鼠标释放事件处理
- void DrawTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- if (event->scenePos() == c_down ) // 如果鼠标释放位置和按下位置相同
- c_drawShape = selection; // 设置当前绘制形状为 selection
- setCursor(scene,Qt::ArrowCursor); // 重置光标为箭头样式
- }
- // 鼠标双击事件处理,当前为空实现
- void DrawTool::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- }
- // 根据形状查找相应的绘制工具
- DrawTool *DrawTool::findTool(DrawShape drawShape)
- {
- QList<DrawTool*>::const_iterator iter = c_tools.constBegin(); // 获取工具列表的常量迭代器
- for ( ; iter != c_tools.constEnd() ; ++iter ){
- if ((*iter)->m_drawShape == drawShape ) // 如果找到形状匹配的工具
- return (*iter); // 返回该工具
- }
- return 0; // 如果没有匹配的工具,返回 nullptr
- }
复制代码 3.2 SelectTool选中类

SelectTool 类专注于处置处罚画图场景中的选择操作,包括选择、移动、缩放和编辑图形项。它通过重写基类 DrawTool 的虚拟函数,提供了具体的选择操作实现。SelectTool 管理图形项的选择状态、虚线框的表现以及选择模式的切换,为用户提供了直观的操作反馈和功能。
- class SelectTool : public DrawTool
- {
- public:
- SelectTool();
- virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
- virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
- virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
- QPointF initialPositions; // 记录初始位置
- QPointF opposite_; // 对应点,用于大小调整操作
- QGraphicsPathItem * dashRect; // 虚线框,用于选择时的视觉反馈
- GraphicsItemGroup * selLayer; // 选择的图层
- };
- // 构造函数,初始化成员变量
- SelectTool::SelectTool()
- :DrawTool(selection) // 初始化基类 DrawTool,模式为 selection (选择),这样继承过来的变量得到了初始化。
- {
- dashRect = 0; // 初始化虚线框指针为空
- selLayer = 0; // 初始化选择层指针为空
- opposite_ = QPointF(); // 初始化对点为空,即与当前拖动的控制点相对的点。
- }
- // 选中时会有三种操作:size(调整大小),editor(编辑),move(移动)
- void SelectTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- DrawTool::mousePressEvent(event,scene); // 调用基类的鼠标按下事件处理
- if ( event->button() != Qt::LeftButton ) return; // 如果不是左键按下,则不处理
- if (!m_hoverSizer)
- scene->mouseEvent(event); // 如果没有悬停在调整器上,则将事件传递给场景
- nDragHandle = Handle_None; // 初始化拖拽句柄为无
- selectMode = none; // 初始化选择模式为无
- QList<QGraphicsItem *> items = scene->selectedItems(); // 获取当前选中的图形项,是通过鼠标点击、框选选中的。
- AbstractShape *item = 0; // 初始化选中的形状指针为空
- if ( items.count() == 1 ) // 如果只选中了一个图形项
- item = qgraphicsitem_cast<AbstractShape*>(items.first()); // 尝试将其转换为 AbstractShape 类型
- if ( item != 0 ){ // 如果选中的图形项不为空
- nDragHandle = item->collidesWithHandle(event->scenePos()); // 检查是否与句柄碰撞(鼠标在形状的边缘位置),从而判断当前模式。
- if ( nDragHandle != Handle_None && nDragHandle <= Left ) // 如果拖拽句柄<=Left,在左边就说明有碰撞
- selectMode = size; // 设置选择模式为大小调整,其中size是枚举SelectMode成员
- else if ( nDragHandle > Left ) // 如果拖拽句柄>Left, 设置选择模式为编辑
- selectMode = editor;
- else
- selectMode = move; // 否则光标就是没有碰撞,而是在形状内部,设置为移动模式
- if ( nDragHandle!= Handle_None && nDragHandle <= Left ){ // 如果是大小调整模式
- opposite_ = item->opposite(nDragHandle); // 获取相对点,即与当前拖动的控制点相对的点。
- if( opposite_.x() == 0 )
- opposite_.setX(1); // 防止出现零值,设置为 1
- if (opposite_.y() == 0 )
- opposite_.setY(1);
- }
- setCursor(scene,Qt::ClosedHandCursor); // 设置光标为闭合手形状
- }else if ( items.count() > 1 )
- selectMode = move; // 如果选中了多个图形项,则只能是移动模式
- if( selectMode == none ){ // 如果没有设置模式
- selectMode = netSelect; // 设置为网络选择模式
- if ( scene->view() ){
- QGraphicsView * view = scene->view();
- view->setDragMode(QGraphicsView::RubberBandDrag); // 设置视图为橡皮筋拖动模式
- }
- #if 0
- if ( selLayer ){ // 取消注释以清除选择图层
- scene->destroyGroup(selLayer);
- selLayer = 0;
- }
- #endif
- }
- if ( selectMode == move && items.count() == 1 ){ // 如果是移动模式且只有一个选中项
- if (dashRect ){
- scene->removeItem(dashRect); // 如果已有虚线框,移除
- delete dashRect; // 删除虚线框
- dashRect = 0; // 重置虚线框指针
- }
- dashRect = new QGraphicsPathItem(item->shape()); // 创建一个新的虚线框
- dashRect->setPen(Qt::DashLine); // 设置为虚线
- dashRect->setPos(item->pos()); // 设置位置
- dashRect->setTransformOriginPoint(item->transformOriginPoint()); // 设置变换原点
- dashRect->setTransform(item->transform()); // 应用图形项的变换
- dashRect->setRotation(item->rotation()); // 设置旋转
- dashRect->setScale(item->scale()); // 设置缩放
- dashRect->setZValue(item->zValue()); // 设置 Z 值
- scene->addItem(dashRect);// 将虚线框添加到场景
- initialPositions = item->pos(); // 记录初始位置
- }
- }
- // 处理鼠标移动事件
- void SelectTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- DrawTool::mouseMoveEvent(event,scene); // 调用基类的鼠标移动事件处理
- QList<QGraphicsItem *> items = scene->selectedItems(); // 获取当前选中的图形项
- AbstractShape * item = 0; // 初始化选中的形状指针为空
- if ( items.count() == 1 ){ // 如果只选中了一个图形项
- item = qgraphicsitem_cast<AbstractShape*>(items.first()); // 尝试将其转换为 AbstractShape 类型
- if ( item != 0 ){ // 如果选中的图形项不为空
- if ( nDragHandle != Handle_None && selectMode == size ){ // 如果是大小调整模式
- if (opposite_.isNull()){ // 如果对点为空
- opposite_ = item->opposite(nDragHandle); // 获取对点,即与当前拖动的控制点相对的点。
- if( opposite_.x() == 0 )
- opposite_.setX(1); // 防止零值,设置为 1
- if (opposite_.y() == 0 )
- opposite_.setY(1);
- }
- /*
- mapFromScene 的作用是将场景坐标(Scene Coordinates)转换为图形项自身的局部坐标系(Item Coordinates)。
- 将全局的场景坐标 c_last 转换为 item 自身的局部坐标系中的一个点。这是因为 c_last 是在场景中的一个点,
- 但我们在操作图形项时,通常需要知道该点在图形项自身坐标系中的位置。
- 按下c_down之后,移动c_last鼠标就是拉伸
- 按下的坐标是initial_delta,移动后的坐标是new_delta
-
- 在图形项的缩放过程中,我们通常是以某个固定点(对点 opposite_)为基准,
- 然后根据鼠标的位置(c_last 和 c_down)计算拉伸或缩放的比例。
- 这样就能准确地控制图形的大小变化,使得缩放操作相对直观且符合用户的期望。
- */
- QPointF new_delta = item->mapFromScene(c_last) - opposite_; // 计算新的相对距离: c_last鼠标移动的坐标 - 边界
- QPointF initial_delta = item->mapFromScene(c_down) - opposite_; // 计算初始相对距离: c_down鼠标按下的坐标 - 边界
- double sx = new_delta.x() / initial_delta.x(); // 计算 X 方向的缩放比例
- double sy = new_delta.y() / initial_delta.y(); // 计算 Y 方向的缩放比例
- /*
- nDragHandle: 一个图形项会有多个控制手柄(如顶点、边缘中点等),每个手柄对应不同的缩放或调整操作。
- sx sy: 表示 X,Y 方向上的缩放比例。
- opposite_: 表示固定不动的对点坐标(相对于拖动手柄)。在缩放或拉伸过程中,这个点保持不变,是所有变换操作的基准点。
- */
- item->stretch(nDragHandle, sx , sy , opposite_); // 执行图形项的拉伸操作
- emit scene->itemResize(item,nDragHandle,QPointF(sx,sy)); // 发送图形项大小调整的信号
- // qDebug()<<"scale:"<<nDragHandle<< item->mapToScene(opposite_)<< sx << " 锛? << sy
- // << new_delta << item->mapFromScene(c_last)
- // << initial_delta << item->mapFromScene(c_down) << item->boundingRect();
- } else if ( nDragHandle > Left && selectMode == editor ){ // 如果是编辑模式
- // 确定控制点:通过 nDragHandle 确定用户正在操作的控制点。
- // 更新位置:根据当前鼠标的位置 c_last,将控制点移动到新的位置。
- // 修改图形形状:调整图形项的形状以反映控制点的移动。这可能包括更新曲线的曲率、多边形的形状等。
- item->control(nDragHandle,c_last); // 更新图形项的控制点位置。控制点是用于定义图形形状的关键点
- emit scene->itemControl(item,nDragHandle,c_last,c_down); // 发送图形项控制的信号
- }
- else if(nDragHandle == Handle_None ){ // 如果没有拖拽句柄
- int handle = item->collidesWithHandle(event->scenePos()); // 检查是否与句柄碰撞
- if ( handle != Handle_None){
- setCursor(scene,Qt::OpenHandCursor); // 如果碰撞,设置光标为打开的手形状
- m_hoverSizer = true; // 设置悬停调整标志
- }else{
- setCursor(scene,Qt::ArrowCursor); // 否则设置光标为箭头形状
- m_hoverSizer = false; // 取消悬停调整标志
- }
- }
- }
- }
- if ( selectMode == move ){ // 如果是移动模式
- setCursor(scene,Qt::ClosedHandCursor); // 设置光标为闭合的手形状
- if ( dashRect ){
- dashRect->setPos(initialPositions + c_last - c_down); // 更新虚线框的位置
- }
- }
- if ( selectMode != size && items.count() > 1) // 如果不是大小调整模式且选中多个项
- {
- scene->mouseEvent(event); // 将事件传递给场景
- }
- }
- void SelectTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- DrawTool::mouseReleaseEvent(event,scene);
- if ( event->button() != Qt::LeftButton ) return;
- // 获取当前场景中所有被选中的图形项。
- QList<QGraphicsItem *> items = scene->selectedItems();
- // 如果只选中了一个图形项,进行以下处理。
- if ( items.count() == 1 ){
- AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
- // 如果转换成功且当前选择模式为移动模式,并且鼠标释放的位置与按下的位置不同。
- if ( item != 0 && selectMode == move && c_last != c_down ){
- // 将图形项的位置设置为初始位置加上鼠标移动的偏移量。
- item->setPos(initialPositions + c_last - c_down);
- // 触发场景的 itemMoved 信号,通知图形项已被移动,并传递移动的偏移量。
- emit scene->itemMoved(item , c_last - c_down );
- }
-
- // 如果图形项存在,并且选择模式为尺寸调整或编辑模式,并且鼠标释放的位置与按下的位置不同。
- else if ( item !=0 && (selectMode == size || selectMode ==editor) && c_last != c_down ){
- // 更新图形项的坐标,通常是为了确保调整后的形状参数被正确应用。
- item->updateCoordinate();
- }
- }
-
- // 如果选中了多个图形项,且选择模式为移动模式,并且鼠标释放的位置与按下的位置不同。
- else if ( items.count() > 1 && selectMode == move && c_last != c_down ){
- // 触发场景的 itemMoved 信号,传递 NULL 代表多个图形项的移动操作,并传递移动的偏移量。
- emit scene->itemMoved(NULL , c_last - c_down );
- }
- // 如果当前选择模式为网格选择模式(框选),进行以下处理。
- if (selectMode == netSelect ){
- // 检查场景是否有视图,如果有视图,则设置视图的拖拽模式为无拖拽。
- if ( scene->view() ){
- QGraphicsView * view = scene->view();
- view->setDragMode(QGraphicsView::NoDrag);
- }
- // 如果代码块开启,检查选中的图形项数量是否大于 1,并将其创建为一个组。
- #if 0
- if ( scene->selectedItems().count() > 1 ){
- selLayer = scene->createGroup(scene->selectedItems());
- selLayer->setSelected(true);
- }
- #endif
- }
-
- // 后面是清空成员变量。
- if (dashRect ){
- // 从场景中移除虚线矩形,并删除该对象释放内存。
- scene->removeItem(dashRect);
- delete dashRect;
- dashRect = 0;
- }
- // 重置选择模式为无,表示没有正在进行的选择或编辑操作。
- selectMode = none;
- // 重置拖拽句柄为无,表示没有正在进行的拖拽操作。
- nDragHandle = Handle_None;
- // 重置悬停尺寸标志为 false,表示当前鼠标没有悬停在可调整大小的区域。
- m_hoverSizer = false;
- // 重置对点坐标为 (0, 0),用于下一次的尺寸调整操作。
- opposite_ = QPointF();
- // 将鼠标事件传递给场景进行处理,确保场景接收到完整的鼠标事件流程。
- scene->mouseEvent(event);
- }
复制代码 3.3 RotationTool旋转类

RotationTool 类专注于处置处罚图形项的旋转操作。它通过重写基类 DrawTool 的虚拟函数,提供了具体的旋转实现。RotationTool 管理图形项的旋转状态、旋转角度的盘算以及旋转视觉反馈的表现,为用户提供了直观的旋转操作体验。通过管理 lastAngle 和 dashRect,RotationTool 可以大概准确地盘算和表现旋转效果,并在鼠标操作时给予用户实时反馈。
- class RotationTool : public DrawTool
- {
- public:
- RotationTool();
- virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
- virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
- virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
- qreal lastAngle; // 记录上一次计算的旋转角度
- QGraphicsPathItem * dashRect; // 用于显示虚线矩形的指针
- };
- // 构造函数,初始化旋转工具
- RotationTool::RotationTool()
- :DrawTool(rotation) // 实例化基类的成员变量
- {
- lastAngle = 0; // 初始化上一次角度为0
- dashRect = 0; // 初始化虚线矩形指针为空
- }
- void RotationTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- // 调用基类 DrawTool 的 mousePressEvent 方法
- DrawTool::mousePressEvent(event,scene);
- if ( event->button() != Qt::LeftButton ) return;
- // 如果没有在调整大小的状态,则将事件传递给场景
- if (!m_hoverSizer)
- scene->mouseEvent(event);
- // 获取当前场景中所有被选中的图形项
- QList<QGraphicsItem *> items = scene->selectedItems();
- // 如果选中了一个图形项
- if ( items.count() == 1 ){
- AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
- if ( item != 0 ){
- // 检测鼠标点击的位置是否在图形项的控制句柄上,即边界顶点等位置
- nDragHandle = item->collidesWithHandle(event->scenePos());
- // 如果点击的是控制句柄
- if ( nDragHandle !=Handle_None)
- {
- // 获取图形项的中心点相对于场景的坐标
- QPointF origin = item->mapToScene(item->boundingRect().center());
- // 计算鼠标当前位置相对于中心点的 Y 和 X 方向的长度
- qreal len_y = c_last.y() - origin.y();
- qreal len_x = c_last.x() - origin.x();
- // 使用反正切函数计算鼠标位置与中心点连线的角度,并将其转换为度数
- qreal angle = atan2(len_y,len_x)*180/PI;
- // 当前点和item中心点坐标(都是场景坐标)形成的角度
- lastAngle = angle; // 记录当前角度为 lastAngle
- selectMode = rotate; // 设置选择模式为旋转
- // 如果 dashRect 已存在,移除并删除它
- if (dashRect ){
- scene->removeItem(dashRect);
- delete dashRect;
- dashRect = 0;
- }
- // 创建一个新的虚线矩形项,以形状项的轮廓作为路径
- dashRect = new QGraphicsPathItem(item->shape());
- dashRect->setPen(Qt::DashLine); // 设置虚线笔刷
- dashRect->setPos(item->pos());
- dashRect->setTransformOriginPoint(item->transformOriginPoint()); // 设置变换的原点
- dashRect->setTransform(item->transform()); // 设置变换矩阵
- dashRect->setRotation(item->rotation()); // 设置旋转角度
- dashRect->setScale(item->scale()); // 设置缩放比例
- dashRect->setZValue(item->zValue()); // 设置 Z 轴位置
- scene->addItem(dashRect); // 将虚线矩形添加到场景中
- setCursor(scene,QCursor((QPixmap(":/icons/rotate.png")))); // 设置旋转光标
- }
- else{
- // 如果没有点击控制句柄,则切换到选择工具,并处理按下事件
- c_drawShape = selection;
- selectTool.mousePressEvent(event,scene);
- }
- }
- }
- }
- void RotationTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- // 调用基类 DrawTool 的 mouseMoveEvent 方法
- DrawTool::mouseMoveEvent(event,scene);
- // 获取当前场景中所有被选中的图形项
- QList<QGraphicsItem *> items = scene->selectedItems();
- // 如果选中了一个图形项
- if ( items.count() == 1 ){
- AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
- // 如果转换成功且在拖拽句柄且选择模式为旋转模式
- if ( item != 0 && nDragHandle !=Handle_None && selectMode == rotate ){
- // 获取图形项的中心点相对于场景的坐标
- QPointF origin = item->mapToScene(item->boundingRect().center());
- // 计算鼠标当前位置相对于中心点的 Y 和 X 方向的长度
- qreal len_y = c_last.y() - origin.y();
- qreal len_x = c_last.x() - origin.x();
- // 使用反正切函数计算鼠标位置与中心点连线的角度,并将其转换为度数
- qreal angle = atan2(len_y,len_x)*180/PI;
- // 计算新的旋转角度
- // angle - lastAngle: 计算自上次记录角度以来的角度变化量。这表示用户在旋转操作期间,鼠标移动所导致的角度变化。
- // item->rotation(): 获取图形项当前的旋转角度,自带的角度。
- angle = item->rotation() + int(angle - lastAngle) ;
- // 将角度限制在 -360 到 360 度之间
- if ( angle > 360 )
- angle -= 360;
- if ( angle < -360 )
- angle+=360;
- // 如果虚线矩形存在,更新其旋转角度
- if ( dashRect ){
- //dashRect->setTransform(QTransform::fromTranslate(15,15),true);
- //dashRect->setTransform(QTransform().rotate(angle));
- //dashRect->setTransform(QTransform::fromTranslate(-15,-15),true);
- dashRect->setRotation( angle );
- }
- // 设置旋转光标
- setCursor(scene,QCursor((QPixmap(":/icons/rotate.png"))));
- }
- else if ( item )
- {
- // 检查鼠标当前位置是否在控制句柄上
- int handle = item->collidesWithHandle(event->scenePos());
- // 如果在控制句柄上,设置旋转光标,并标记悬停状态
- if ( handle != Handle_None){
- setCursor(scene,QCursor((QPixmap(":/icons/rotate.png"))));
- m_hoverSizer = true;
- }else{
- // 如果不在控制句柄上,设置为普通箭头光标,并重置悬停状态
- setCursor(scene,Qt::ArrowCursor);
- m_hoverSizer = false;
- }
- }
- }
- // 将事件传递给场景
- scene->mouseEvent(event);
- }
- void RotationTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- // 调用基类 DrawTool 的 mouseReleaseEvent 方法
- DrawTool::mouseReleaseEvent(event,scene);
- // 如果释放的不是左键,直接返回
- if ( event->button() != Qt::LeftButton ) return;
- QList<QGraphicsItem *> items = scene->selectedItems();
- if ( items.count() == 1 ){
- AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
- // 如果转换成功且在拖拽句柄且选择模式为旋转模式
- if ( item != 0 && nDragHandle !=Handle_None && selectMode == rotate ){
- // 获取图形项的中心点相对于场景的坐标
- QPointF origin = item->mapToScene(item->boundingRect().center());
- // 计算鼠标当前位置相对于中心点的偏移量
- QPointF delta = c_last - origin ;
- // 计算鼠标当前位置相对于中心点的 Y 和 X 方向的长度
- qreal len_y = c_last.y() - origin.y();
- qreal len_x = c_last.x() - origin.x();
- // 使用反正切函数计算鼠标位置与中心点连线的角度,并将其转换为度数
- qreal angle = atan2(len_y,len_x)*180/PI,oldAngle = item->rotation();
- // 计算最终的旋转角度
- angle = item->rotation() + int(angle - lastAngle) ;
- // 将角度限制在 -360 到 360 度之间
- if ( angle > 360 )
- angle -= 360;
- if ( angle < -360 )
- angle+=360;
- // 设置图形项的旋转角度
- item->setRotation( angle );
- // 触发场景的 itemRotate 信号,通知图形项已被旋转,并传递旧的角度
- emit scene->itemRotate(item , oldAngle);
- qDebug()<<"rotate:"<<angle<<item->boundingRect();
- }
- }
- setCursor(scene,Qt::ArrowCursor);
- // 清空成员变量。
- selectMode = none;
- nDragHandle = Handle_None;
- lastAngle = 0;
- m_hoverSizer = false;
- if (dashRect ){
- scene->removeItem(dashRect);
- delete dashRect;
- dashRect = 0;
- }
- // 将事件传递给场景
- scene->mouseEvent(event);
- }
复制代码 3.4 RectTool
这里RectTool类支持绘制rectangle矩形、roundrect圆角矩形、ellipse椭圆,但是具体的绘制操作是由其成员变量item自己负责。
- class RectTool : public DrawTool
- {
- public:
- RectTool(DrawShape drawShape); // 构造函数,用于初始化工具的形状类型
- virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
- virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
- virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
- GraphicsItem * item; // 当前创建的图形项
- };
- RectTool::RectTool(DrawShape drawShape)
- :DrawTool(drawShape) // 调用基类构造函数初始化工具类型
- {
- item = 0; // 初始化图形项为nullptr
- }
- void RectTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- if ( event->button() != Qt::LeftButton ) return;
- // 清除场景中所有的选中项
- scene->clearSelection();
- // 调用基类的鼠标按下事件处理
- DrawTool::mousePressEvent(event,scene);
- // 根据工具类型创建不同的图形项
- switch ( c_drawShape ){
- case rectangle: // 矩形
- item = new GraphicsRectItem(QRect(1,1,1,1)); // 创建一个初始大小为1x1的矩形项
- break;
- case roundrect: // 圆角矩形
- item = new GraphicsRectItem(QRect(1,1,1,1),true); // 创建一个初始大小为1x1的圆角矩形项
- break;
- case ellipse: // 椭圆
- item = new GraphicsEllipseItem(QRect(1,1,1,1)); // 创建一个初始大小为1x1的椭圆项
- break;
- }
- if ( item == 0) return; // 如果图形项创建失败,则返回
- // 将点击起始点位置向右下偏移2像素:
- // 在图形项的初始创建时(例如,矩形或圆角矩形),其尺寸通常从一个很小的区域开始。偏移量确保图形项在可视区域内有足够的空间来展示
- c_down+=QPoint(2,2);
- item->setPos(event->scenePos()); // 设置图形项的位置为鼠标点击的位置
- scene->addItem(item); // 将图形项添加到场景中
- item->setSelected(true); // 选中图形项
- selectMode = size; // 设置选择模式为调整大小
- nDragHandle = RightBottom; // 设置拖拽手柄为右下角
- }
- void RectTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- setCursor(scene,Qt::CrossCursor); // 设置光标为十字形,以指示绘图模式
- selectTool.mouseMoveEvent(event,scene); // 调用选择工具的鼠标移动事件处理
- }
- void RectTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- selectTool.mouseReleaseEvent(event,scene); // 调用选择工具的鼠标释放事件处理
- // 如果鼠标释放位置与初始点击位置相同,则删除创建的图形项
- if ( event->scenePos() == (c_down-QPoint(2,2))){ // 前面c_down鼠标点击的位置已经偏移2像素,所以这里要补偿回来。
- if ( item != 0){
- item->setSelected(false); // 取消选中图形项
- scene->removeItem(item); // 从场景中移除图形项
- delete item ; // 删除图形项对象
- item = 0; // 将图形项指针设置为nullptr
- }
- qDebug()<<"RectTool removeItem:";
- }
-
- // 发射信号,表示图形项已添加到场景中
- else if( item ){
- emit scene->itemAdded( item );
- }
- // 将工具形状重置为选择模式
- c_drawShape = selection;
- }
复制代码
3.5 PolygonTool
PolygonTool 是一个用于绘制多边形、折线、线段或贝塞尔曲线的工具类,如下所示,继续自 DrawTool。它提供了在场景中通过鼠标交互来创建和编辑这些图形的功能。

- class PolygonTool : public DrawTool
- {
- public:
- PolygonTool(DrawShape shape );
- virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
- virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
- virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
- virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event ,DrawScene *scene );
- GraphicsPolygonItem * item; // 当前绘制的图形项,可能是多边形、贝塞尔曲线、多线段或直线
- int m_nPoints; // 当前图形的点的数量
- QPointF initialPositions; // 初始位置,用于记录第一个点的位置
- };
- PolygonTool::PolygonTool(DrawShape shape)
- :DrawTool(shape)
- {
- item = NULL; // 初始化 item 为 NULL
- m_nPoints = 0; // 初始化点的数量为 0
- }
- // 当首次按下时,会被添加两次,一次是item->addPoint(c_down); item->addPoint(c_down+QPoint(1,0));
- // 为了初始化一个起始线段或者提供初步的形状轮廓,使得在接下来的鼠标移动事件中能够立即看到一个图形的初步形态。
- void PolygonTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- // 调用基类的鼠标按下事件处理
- DrawTool::mousePressEvent(event,scene);
- if ( event->button() != Qt::LeftButton ) return;
- // 如果当前没有正在绘制的图形项,即刚开始绘制
- if ( item == NULL ){
- // 根据绘制的形状类型创建相应的图形项
- if ( c_drawShape == polygon ){
- item = new GraphicsPolygonItem(NULL); // 创建一个多边形项
- }else if (c_drawShape == bezier ){
- item = new GraphicsBezier(); // 创建一个贝塞尔曲线项
- }else if ( c_drawShape == polyline ){
- item = new GraphicsBezier(false); // 创建一个折线
- }else if ( c_drawShape == line ){
- item = new GraphicsLineItem(0); // 创建一个直线项
- }
- item->setPos(event->scenePos()); // 设置图形项的位置为鼠标按下的位置
- scene->addItem(item); // 将图形项添加到场景中
- initialPositions = c_down; // 记录起始位置
- item->addPoint(c_down); // 添加第一个点,因为是刚开始绘制
- item->setSelected(true); // 设置图形项为选中状态
- m_nPoints++;
- }else if ( c_down == c_last ){ // 如果按下的点与上一个点重合
- /*
- if ( item != NULL )
- {
- scene->removeItem(item); // 从场景中移除当前图形项
- delete item;
- item = NULL ;
- c_drawShape = selection;
- selectMode = none;
- return ;
- }
- */
- }
- item->addPoint(c_down+QPoint(1,0)); // 添加一个点,位置偏移一点以避免重合
- m_nPoints++; // 点的数量加 1
- selectMode = size ; // 设置选择模式为 size,用于调整大小
- nDragHandle = item->handleCount(); // 设置拖动手柄为当前图形项的手柄数量
- // 意味着将拖动手柄的索引设置为最后一个添加的手柄,即当前鼠标点击的位置。这有助于后续在鼠标移动事件中准确控制拖动的手柄。
- }
- void PolygonTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
-
- DrawTool::mouseMoveEvent(event,scene); // 调用基类的鼠标移动事件处理
- setCursor(scene,Qt::CrossCursor); // 设置光标为十字形
- // selectTool.mouseMoveEvent(event,scene); // 选择工具移动事件处理
- if ( item != 0 ){ // 如果图形项存在
- // 如果有有效的手柄并且选择模式是调整大小
- if ( nDragHandle != Handle_None && selectMode == size ){
- // 控制图形项的控制点位置
- item->control(nDragHandle,c_last);
- }
- }
- }
- // 直线是点击开始坐标,和最后单击一个点坐标即可。其他形状需要双击才结束,所以放到另一个函数处理。
- void PolygonTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- // 调用基类的鼠标释放事件处理
- DrawTool::mousePressEvent(event,scene);
- // 绘制的形状类型如果只直线,需要特殊处理下
- if ( c_drawShape == line ){
- item->endPoint(event->scenePos()); // 设置直线的结束点
- item->updateCoordinate(); // 更新图形项的坐标,确之前的变化得到应用
- emit scene->itemAdded( item ); // 触发图形项添加信号
- // 清空成员变量
- item = NULL; // 将 item 设置为 NULL
- selectMode = none; // 设置选择模式为 none
- c_drawShape = selection; // 设置绘制模式为选择模式
- m_nPoints = 0; // 重置点的数量
- }
- }
- void PolygonTool::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
- {
- // 调用基类的鼠标双击事件处理
- DrawTool::mouseDoubleClickEvent(event,scene);
- // 设置图形项的结束点
- item->endPoint(event->scenePos());
- // 更新图形项的坐标
- item->updateCoordinate();
- // 触发图形项添加信号
- emit scene->itemAdded( item );
- // 清空成员变量
- item = NULL;
- selectMode = none;
- c_drawShape = selection;
- m_nPoints = 0;
- }
复制代码
4. GraphicsItem类
AbstractShapeType是模板基类,提供根本的绘制属性和方法。 如果AbstractShapeType模板,拥有两个直接子类GraphicsItemGroup和GraphicsItem。
4.1 AbstractShapeType类
AbstractShapeType 是一个模板类,提供了一些画图的根本属性和方法。它通过继续自 BaseType(默认为 QGraphicsItem)来扩展图形项的功能,主要用于定义外形的通用举动和属性。
4.2 GraphicsItem类
可以看到GrapicsItems继续了AbstractShapeType<QGraphicsItem>,而AbstractShapeType是继续了GrapicsItems。这里设计有些题目,搞得复杂了,建议稍微看看就得了。
留意:这里似乎是相互继续的布局,c++不支持相互继续。举个简单例子:
- // 不支持直接相互继承
- class A : public B{}
- class B : public A> {}
- // 支持间接的相互继承,但不建议这么做
- template < typename BaseType = QGraphicsItem >
- class A : public BaseType {}
- class B : public A<B> {}
复制代码 相互继续:B继续了A<B>,而A<B>又继续了B,C++ 不支持真正的“相互继续”,即两个类互相直接继续彼此。模板类继续的情况虽然复杂,但并不即是相互继续。
建议在设计时避免这种复杂的继续布局,以减少潜在的题目和困惑。
- class GraphicsItem : public QObject,
- public AbstractShapeType<QGraphicsItem>
- {
- Q_OBJECT
- // Q_PROPERTY 自动生成对属性的访问函数,使得属性可以方便地通过 Qt 的信号槽机制、Qt Designer 或其他方式进行访问。
- Q_PROPERTY(QColor pen READ penColor WRITE setPen ) // 声明pen和setPen方法,是读还是写操作。
- Q_PROPERTY(QColor brush READ brushColor WRITE setBrushColor )
- Q_PROPERTY(qreal width READ width WRITE setWidth )
- Q_PROPERTY(qreal height READ height WRITE setHeight )
- Q_PROPERTY(QPointF position READ pos WRITE setPos )
- public:
- GraphicsItem(QGraphicsItem * parent );
- enum {Type = UserType+1}; // 定义一个唯一的类型标识符
- int type() const { return Type; } // 返回类型标识符
- virtual QPixmap image() ; // 声明一个虚拟函数用于获取图像
- signals:
- void selectedChange(QGraphicsItem *item); // 声明一个信号,当选择状态改变时发射
- protected:
- QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value); // 处理图形项的变化
- void updatehandles(); // 更新控制柄的位置
- void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); // 处理上下文菜单事件
- bool readBaseAttributes(QXmlStreamReader * xml ); // 从 XML 读取基本属性
- bool writeBaseAttributes( QXmlStreamWriter * xml ); // 将基本属性写入 XML
- };
- GraphicsItem::GraphicsItem(QGraphicsItem *parent)
- :AbstractShapeType<QGraphicsItem>(parent)
- {
- /* // 设置图形项的效果
- QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect;
- effect->setBlurRadius(4);
- setGraphicsEffect(effect);
- */
- // 初始化控制柄
- m_handles.reserve(Left); // 预留 m_handles 容器的空间,以提高性能并避免不必要的内存重新分配。
- for (int i = LeftTop; i <= Left; ++i) { // 遍历每个控制柄方向
- SizeHandleRect *shr = new SizeHandleRect(this,i); // 创建一个控制柄,都是矩形的边界框
- m_handles.push_back(shr);
- }
- // 设置图形项的标志
- setFlag(QGraphicsItem::ItemIsMovable, true); // 允许移动
- setFlag(QGraphicsItem::ItemIsSelectable, true); // 允许选择
- setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); // 当几何形状发生变化时发送通知
- this->setAcceptHoverEvents(true); // 接受悬停事件
- }
- QPixmap GraphicsItem::image() {
- QPixmap pixmap(64, 64); // 创建一个 64x64 的图像
- pixmap.fill(Qt::transparent); // 填充透明背景
- //设置绘制属性
- QPainter painter(&pixmap); // 创建绘图对象
- setPen(QPen(Qt::black)); // 设置画笔为黑色
- setBrush(Qt::white); // 设置画刷为白色
- QStyleOptionGraphicsItem *styleOption = new QStyleOptionGraphicsItem; // 创建绘图选项
- //painter.translate(m_localRect.center().x(),m_localRect.center().y()); // 移动绘图位置(被注释掉了)
- // 绘制图形: 实现不在代码中提供,因此图像的具体样式取决于 paint 函数如何绘制图形。
- paint(&painter,styleOption);
- delete styleOption; // 删除绘图选项
- return pixmap; // 返回图像
- }
- void GraphicsItem::updatehandles()
- {
- const QRectF &geom = this->boundingRect(); // 获取图形项的边界矩形
- // 获取控制柄列表的末尾
- const Handles::iterator hend = m_handles.end();
- for (Handles::iterator it = m_handles.begin(); it != hend; ++it) {
- SizeHandleRect *hndl = *it;
- // 根据控制柄的方向更新位置
- switch (hndl->dir()) { // hndl是哪个方向
- case LeftTop: // 左上角
- hndl->move(geom.x() , geom.y() ); // move 方法将手柄的左上角移动到 (x, y) 坐标。
- break;
- case Top: // 是上边
- hndl->move(geom.x() + geom.width() / 2 , geom.y() ); // 将手柄移动到图形项的上边中点位置。
- break;
- case RightTop:
- hndl->move(geom.x() + geom.width() , geom.y() );
- break;
- case Right:
- hndl->move(geom.x() + geom.width() , geom.y() + geom.height() / 2 );
- break;
- case RightBottom:
- hndl->move(geom.x() + geom.width() , geom.y() + geom.height() );
- break;
- case Bottom:
- hndl->move(geom.x() + geom.width() / 2 , geom.y() + geom.height() );
- break;
- case LeftBottom:
- hndl->move(geom.x(), geom.y() + geom.height());
- break;
- case Left:
- hndl->move(geom.x(), geom.y() + geom.height() / 2);
- break;
- default:
- break;
- }
- }
- }
- void GraphicsItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
- {
- Q_UNUSED(event); // 忽略事件
- }
- // 从xml读取属性值,赋值给对象实例
- bool GraphicsItem::readBaseAttributes(QXmlStreamReader *xml)
- {
- qreal x = xml->attributes().value(tr("x")).toDouble();
- qreal y = xml->attributes().value(tr("y")).toDouble();
- m_width = xml->attributes().value("width").toDouble();
- m_height = xml->attributes().value("height").toDouble();
- setZValue(xml->attributes().value("z").toDouble());
- setRotation(xml->attributes().value("rotate").toDouble());
- setPos(x,y);
- return true;
- }
- // 从对象实例中,写入属性值到xml文件中
- bool GraphicsItem::writeBaseAttributes(QXmlStreamWriter *xml)
- {
- xml->writeAttribute(tr("rotate"),QString("%1").arg(rotation())); // "rotate",表示 XML 属性的名称。%1 是一个占位符
- xml->writeAttribute(tr("x"),QString("%1").arg(pos().x()));
- xml->writeAttribute(tr("y"),QString("%1").arg(pos().y()));
- xml->writeAttribute(tr("z"),QString("%1").arg(zValue()));
- xml->writeAttribute(tr("width"),QString("%1").arg(m_width));
- xml->writeAttribute(tr("height"),QString("%1").arg(m_height));
- return true;
- }
- QVariant GraphicsItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
- {
- // 如果选择状态发生改变
- if ( change == QGraphicsItem::ItemSelectedHasChanged ) {
- // 如果不是一个组
- QGraphicsItemGroup *g = dynamic_cast<QGraphicsItemGroup*>(parentItem());
- if (!g)
- // 设置控制柄的状态:SelectionHandleOff, SelectionHandleInactive, SelectionHandleActive
- setState(value.toBool() ? SelectionHandleActive : SelectionHandleOff);
- // 如果是组,取消选择状态
- else{
- setSelected(false);
- return QVariant::fromValue<bool>(false);
- }
- }
- /*
- else if (change == ItemPositionChange && scene()) {
- // value is the new position.
- QPointF newPos = value.toPointF();
- QRectF rect = scene()->sceneRect();
- if (!rect.contains(newPos)) {
- // Keep the item inside the scene rect.
- newPos.setX(qMin(rect.right()-boundingRect().width()/2, qMax(newPos.x(), rect.left()+boundingRect().width()/2)));
- newPos.setY(qMin(rect.bottom()-boundingRect().height()/2, qMax(newPos.y(), rect.top()+boundingRect().height()/2)));
- return newPos;
- }
- }
- */
- return QGraphicsItem::itemChange(change, value); // 调用基类的 itemChange 方法
- }
复制代码 4.3 GraphicsRectItem类
GraphicsRectItem 类的作用主要是为图形场景中的矩形、圆角矩形元素提供一个基础的图形项,并为其提供各种功能和操作接口。
- class GraphicsRectItem : public GraphicsItem
- {
- public:
- // 构造函数,初始化矩形对象,rect为矩形区域,isRound表示是否是圆角矩形
- GraphicsRectItem(const QRect & rect, bool isRound = false, QGraphicsItem * parent = 0 );
- // 返回图形项的边界矩形
- QRectF boundingRect() const;
- // 返回图形项的形状,通常用于碰撞检测
- QPainterPath shape() const;
- // 控制柄操作,用于调整图形项
- void control(int dir, const QPointF & delta);
- // 拉伸操作,调整图形项的大小
- void stretch(int handle , double sx , double sy , const QPointF & origin);
- // 返回当前矩形区域
- QRectF rect() const { return m_localRect;}
- // 更新图形项的坐标
- void updateCoordinate();
- // 移动图形项
- void move( const QPointF & point );
- // 复制当前图形项
- QGraphicsItem *duplicate () const ;
- // 返回图形项的显示名称
- QString displayName() const { return tr("rectangle"); }
- // 从XML加载图形项
- virtual bool loadFromXml(QXmlStreamReader * xml );
- // 保存图形项到XML
- virtual bool saveToXml( QXmlStreamWriter * xml );
- protected:
- // 更新控制柄位置
- void updatehandles();
- // 绘制图形项
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
- bool m_isRound; // 是否为圆角矩形
- qreal m_fRatioY; // 控制圆角矩形的水平和垂直圆角半径比例
- qreal m_fRatioX; //
- QRectF m_initialRect; // m_initialRect、m_localRect: 初始矩形和当前矩形的位置和尺寸。
- QPointF opposite_; // 参考点,矩形的中心点
- QPointF m_originPoint; // 原点。m_originPoint、opposite_: 用于几何变换和定位的辅助点。
- };
- // 构造函数实现
- GraphicsRectItem::GraphicsRectItem(const QRect & rect , bool isRound , QGraphicsItem *parent)
- :GraphicsItem(parent) // 调用基类构造函数
- ,m_isRound(isRound) // 初始化是否圆角
- ,m_fRatioX(1/10.0) // 初始化宽度圆角比例
- ,m_fRatioY(1/3.0) // 初始化高度圆角比例
- {
- m_width = rect.width(); // 设置矩形宽度
- m_height = rect.height(); // 设置矩形高度
- m_initialRect = rect; // 初始化矩形区域
- m_localRect = rect; // 设置本地矩形区域
- m_originPoint = QPointF(0,0); // 设置原点
- // 如果是圆角矩形,添加控制柄用于调整圆角
- if( m_isRound ){
- SizeHandleRect *shr = new SizeHandleRect(this, 9 , true);
- m_handles.push_back(shr);
- shr = new SizeHandleRect(this, 10 , true);
- m_handles.push_back(shr);
- //shr = new SizeHandleRect(this, 11 , true);
- //m_handles.push_back(shr);
- }
- updatehandles(); // 更新控制柄位置
- }
- // 返回图形项的边界矩形
- QRectF GraphicsRectItem::boundingRect() const
- {
- return m_localRect; // 使用当前的本地矩形作为边界
- }
- // 返回图形项的形状路径
- QPainterPath GraphicsRectItem::shape() const
- {
- QPainterPath path; // 创建绘制路径对象
-
- double rx,ry; // 圆角的半径
- if(m_fRatioX<=0)
- rx=0; // 如果宽度比例小于等于0,则圆角半径为0
- else {
- rx = m_width * m_fRatioX + 0.5; // 否则计算圆角半径
- }
- if ( m_fRatioY <=0 )
- ry = 0;
- else
- ry = m_height * m_fRatioY + 0.5;
- // 如果是圆角矩形
- if ( m_isRound )
- path.addRoundedRect(rect(),rx,ry); // 添加圆角矩形到路径
- else
- path.addRect(rect()); // 否则添加普通矩形到路径
- return path;
- }
- // 控制矩形的形状,通过手柄调整圆角的半径。
- void GraphicsRectItem::control(int dir, const QPointF & delta)
- {
- // 从父项的坐标系转换到当前图形项(GraphicsRectItem)的本地坐标系中。
- // 该坐标是10个关键点之一。
- QPointF local_pt = mapFromParent(delta);
- // 根据不同的控制方向调整属性
- switch (dir) {
- // 如果是第9个关键点
- // 更新垂直方向的圆角半径比例。
- // 保证 y 不会超过矩形中心位置,也不会低于矩形顶部。
- case 9:
- {
- QRectF rect_roi = rect(); // 获取当前矩形区域
- int local_y = local_pt.y(); // 获取本地的y坐标
-
- // 控制 local_y 位置在合理范围内,从而有效限制圆角的最大和最小半径。
- if(local_y > rect_roi.center().y() ) local_y = rect_roi.center().y(); // 限制不超过矩形中心
- if(local_y < rect_roi.top()) local_y = rect_roi.top(); // 限制不超过顶部
- // 计算 m_fRatioY: 当前手柄位置 y 到矩形顶部的距离占矩形高度的比例。
- int H= rect_roi.height(); // 获取矩形高度
- if(H==0) H = 1; // 避免除零
- m_fRatioY = std::abs(((float)(rect_roi.top()- local_y)))/H; // 计算并设置高度比例
- }
- break;
- // 水平调整圆角
- // 保证 x 不会低于中心位置,也不会超过矩形右侧。
- case 10:
- {
- QRectF delta1 = rect();
- int x = local_pt.x();
- // 保证 x 不会低于中心位置,也不会超过矩形右侧。
- if(x < delta1.center().x() ) x = delta1.center().x();
- if(x>delta1.right()) x = delta1.right();
-
- int W= delta1.width();
- if(W==0) W=1;
- m_fRatioX = std::abs(((float)(delta1.right()-x)))/W;
- break;
- }
- // 用于调整原点位置
- case 11:
- {
- // setTransform(transform().translate(-local.x(),-local.y()));
- // setTransformOriginPoint(local.x(),local.y());
- // setTransform(transform().translate(local.x(),local.y()));
- m_originPoint = local_pt; // 设置原点为当前本地坐标
- }
- break;
- default:
- break;
- }
- prepareGeometryChange(); // 准备几何图形的变化,通知Qt重新绘制
- updatehandles(); // 更新控制柄位置
- }
- // 对矩形进行拉伸变换,根据给定的比例和参考点调整矩形的尺寸。
- void GraphicsRectItem::stretch(int handle , double sx, double sy, const QPointF & origin)
- {
- QTransform trans; // 创建一个变换对象
- switch (handle) { // 根据控制柄的不同类型调整拉伸比例
- case Right:
- case Left:
- sy = 1; // 如果是左右控制柄,则只拉伸宽度,不改变高度
- break;
- case Top:
- case Bottom: // 如果是上下控制柄,则只拉伸高度,不改变宽度
- sx = 1;
- break;
- default:
- break;
- }
- opposite_ = origin; // 设置参考点,这里是矩形的中心点
- // 这样做的好处是能够以特定点为中心进行变换(如缩放),而不是默认的图形左上角或中心。
- trans.translate(origin.x(), origin.y()); // 将坐标系原点移动到指定位置
- trans.scale(sx,sy); // 这样缩放是以新的原点(origin)为中心进行的。
- trans.translate(-origin.x(), -origin.y()); // 再将坐标系原点移回去
- prepareGeometryChange(); // 准备几何图形变化,通知Qt重新绘制
- m_localRect = trans.mapRect(m_initialRect); // 使用变换对象映射初始矩形到新的大小
- m_width = m_localRect.width(); // 更新宽度
- m_height = m_localRect.height(); // 更新高度
- updatehandles(); // 更新控制柄位置
- }
- // 更新图形项的坐标和变换,以适应其场景中的位置变化。
- void GraphicsRectItem::updateCoordinate()
- {
- // 计算原点与矩形中心的偏移量
- QPointF pt1,pt2,delta; // 声明坐标点和差值
- pt1 = mapToScene(transformOriginPoint()); // 获取变换原点的场景坐标
- pt2 = mapToScene(m_localRect.center()); // 获取矩形中心的场景坐标
- delta = pt1 - pt2; // 计算原点与中心点的差值
- // 如果没有父项,其坐标变换相对的是场景坐标系,而不是其他父项的坐标系。
- if (!parentItem() ){
- prepareGeometryChange(); // 准备几何图形变化
- // 移动m_localRect,将m_localRect的中心点移动到其原点位置。其中m_width是矩形的宽度
- // 通过将原点设为中心,任何缩放、旋转等变换都会以图形的中心为参考点
- m_localRect = QRectF(-m_width/2,-m_height/2,m_width,m_height);
- // 更新宽度 高度
- m_width = m_localRect.width();
- m_height = m_localRect.height();
- // 上面只是求变换参数,这里开始应用变换
- // 首先将图形移动到坐标系原点,以对齐新的坐标系中心。
- setTransform(transform().translate(delta.x(),delta.y()));
- // 设置变换原点为坐标系原点,也就是m_localRect中心点。
- setTransformOriginPoint(m_localRect.center());
- // 它会调整图形项的场景坐标位置,但不会影响项的局部坐标系,也不会影响项的变换矩阵
- // 操作的是图形项的场景坐标,属于一种外部移动。
- moveBy(-delta.x(),-delta.y());
- // 它会将变换矩阵中添加一个平移变换,改变图形项在其局部坐标系中的位置。
- // 改变的是图形项内部的变换矩阵,属于一种内部变换。
- setTransform(transform().translate(-delta.x(),-delta.y()));
- // 重置相对点
- opposite_ = QPointF(0,0);
- // 更新控制柄位置
- updatehandles();
- }
- m_initialRect = m_localRect; // 更新初始矩形为当前矩形
- }
- // 移动图形项到指定点。
- void GraphicsRectItem::move(const QPointF &point)
- {
- moveBy(point.x(),point.y());
- }
- // 创建一个当前图形项的副本,复制其位置、变换、状态等属性。
- QGraphicsItem *GraphicsRectItem::duplicate() const
- {
- // 创建新的矩形项,复制当前矩形和是否圆角属性
- GraphicsRectItem * item = new GraphicsRectItem( rect().toRect(),m_isRound);
-
- // 设置其属性
- item->m_width = width(); //
- item->m_height = height(); //
- item->setPos(pos().x(),pos().y()); // 复制位置
- item->setPen(pen()); // 复制画笔
- item->setBrush(brush()); // 复制画刷
- item->setTransform(transform()); // 复制变换
- item->setTransformOriginPoint(transformOriginPoint()); // 复制变换原点
- item->setRotation(rotation()); // 复制旋转角度
- item->setScale(scale()); // 复制缩放比例
- item->setZValue(zValue()+0.1); // 复制Z值并稍作提升
- item->m_fRatioY = m_fRatioY; //
- item->m_fRatioX = m_fRatioX; //
- item->updateCoordinate(); // 更新复制项的坐标
- return item;
- }
- // 从 XML 中加载图形项的状态。
- bool GraphicsRectItem::loadFromXml(QXmlStreamReader * xml )
- {
- m_isRound = (xml->name() == tr("roundrect")); // 判断是否为圆角矩形
- if ( m_isRound ){
- m_fRatioX = xml->attributes().value(tr("rx")).toDouble();
- m_fRatioY = xml->attributes().value(tr("ry")).toDouble();
- }
- readBaseAttributes(xml); // 读取基础属性
- updateCoordinate();
- xml->skipCurrentElement(); // 跳过当前元素
- return true;
- }
- // 将图形项的状态保存到 XML 中。
- bool GraphicsRectItem::saveToXml(QXmlStreamWriter * xml)
- {
- if ( m_isRound ){
- xml->writeStartElement(tr("roundrect")); // 写入开始标签
- xml->writeAttribute(tr("rx"),QString("%1").arg(m_fRatioX));
- xml->writeAttribute(tr("ry"),QString("%1").arg(m_fRatioY));
- }
- else
- xml->writeStartElement(tr("rect"));
- writeBaseAttributes(xml); // 写入基础属性
- xml->writeEndElement(); // 写入结束标签
- return true;
- }
- // 更新手柄的位置,用于控制矩形的圆角和变换。
- void GraphicsRectItem::updatehandles()
- {
- const QRectF &geom = this->boundingRect(); // 获取当前边界矩形
- GraphicsItem::updatehandles(); // 调用基类的更新控制柄方法
- // 如果是圆角矩形
- if ( m_isRound ){
- // 更新第8号控制柄位置,即控制圆角y方向半径比例
- m_handles[8]->move( geom.right() , geom.top() + geom.height() * m_fRatioY );
- // // 更新第9号控制柄位置,即控制圆角x方向半径比例
- m_handles[9]->move( geom.right() - geom.width() * m_fRatioX , geom.top());
- //m_handles[10]->move(m_originPoint.x(),m_originPoint.y());
- }
- }
- // 静态函数,重新计算点集的边界矩形
- static
- QRectF RecalcBounds(const QPolygonF& pts)
- {
- // 以第一个点为初始边界,后面不断地更新,最终找到真正的边界点。
- QRectF bounds(pts[0], QSize(0, 0));
- // 遍历所有点
- for (int i = 1; i < pts.count(); ++i)
- {
- if (pts[i].x() < bounds.left()) // 更新左边界
- bounds.setLeft(pts[i].x());
- if (pts[i].x() > bounds.right()) // 更新右边界
- bounds.setRight(pts[i].x());
- if (pts[i].y() < bounds.top()) // 更新上边界
- bounds.setTop(pts[i].y());
- if (pts[i].y() > bounds.bottom()) // 更新下边界
- bounds.setBottom (pts[i].y());
- }
- // 确保矩形的表示形式统一,即总是左上角的坐标最小,宽度和高度为正值。
- bounds = bounds.normalized(); // 将边界标准化
- return bounds;
- }
- // 绘制图形项
- void GraphicsRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
- {
- painter->setPen(pen()); // 设置画笔
- painter->setBrush(brush()); // 设置画刷
- // 圆角的半径
- double rx,ry;
- if(m_fRatioX<=0) rx=0; // 如果宽度比例小于等于0,则圆角半径为0
- else rx = m_width * m_fRatioX + 0.5; // 否则计算宽度圆角半径,半径+0.5,防止为0
- if ( m_fRatioY <=0 ) ry = 0;
- else ry = m_height * m_fRatioY + 0.5;
- // 绘制圆角矩形
- if ( m_isRound ) painter->drawRoundedRect(rect(),rx,ry);
- // 否则绘制普通矩形
- else painter->drawRect(rect().toRect());
- painter->setPen(Qt::blue);
- // 在矩形的中心点位置,绘制一个十字
- painter->drawLine(QLine(QPoint(opposite_.x()-6,opposite_.y()),QPoint(opposite_.x()+6,opposite_.y()))); // 绘制水平线
- painter->drawLine(QLine(QPoint(opposite_.x(),opposite_.y()-6),QPoint(opposite_.x(),opposite_.y()+6))); // 绘制垂直线
- // 如果图形项被选中
- if (option->state & QStyle::State_Selected)
- qt_graphicsItem_highlightSelected(this, painter, option); // 调用Qt函数高亮显示图形项
- }
复制代码 4.4 GraphicsEllipseItem类
GraphicsEllipseItem 类是一个用于管理和操作椭圆、扇形图形项的类,它继续自 GraphicsRectItem,扩展了矩形图形项的功能,以支持椭圆外形的绘制和交互。
- class GraphicsEllipseItem :public GraphicsRectItem
- {
- public:
- // 构造函数,初始化椭圆项
- GraphicsEllipseItem(const QRect & rect , QGraphicsItem * parent = 0);
- // 返回椭圆的绘制路径
- QPainterPath shape() const;
- // 根据控制方向和偏移量调整椭圆的属性
- void control(int dir, const QPointF & delta );
- // 返回椭圆的边界矩形
- QRectF boundingRect() const ;
- // 复制当前椭圆项并返回新的实例
- QGraphicsItem *duplicate() const;
- // 返回椭圆的显示名称
- QString displayName() const { return tr("ellipse"); }
- // 从 XML 文件加载椭圆的属性
- virtual bool loadFromXml(QXmlStreamReader * xml );
- // 保存椭圆的属性到 XML 文件
- virtual bool saveToXml( QXmlStreamWriter * xml );
- protected:
- // 更新控制柄的位置
- void updatehandles();
- // 绘制椭圆
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
- int m_startAngle; // 起始角度
- int m_spanAngle; // 跨度角度
- };
- GraphicsEllipseItem::GraphicsEllipseItem(const QRect & rect ,QGraphicsItem *parent)
- :GraphicsRectItem(rect,parent)
- {
- m_startAngle = 40; // 初始化起始角度
- m_spanAngle = 400; // 初始化跨度角度
- // 创建用于控制起始角度的控制柄 (编号9)
- SizeHandleRect *shr = new SizeHandleRect(this, 9 , true); // 9特指是圆心点
- m_handles.push_back(shr);
- // 创建用于控制终止角度的控制柄 (编号10)
- shr = new SizeHandleRect(this, 10 , true); // 10特指是圆上点
- m_handles.push_back(shr);
- // 更新所有控制柄的位置
- updatehandles();
- }
- // 返回椭圆的绘制路径
- QPainterPath GraphicsEllipseItem::shape() const
- {
- QPainterPath path;
- // 使用较大的作为起始角度。
- int startAngle = m_startAngle <= m_spanAngle ? m_startAngle : m_spanAngle;
- // 使用较小的作为结束角度。
- int endAngle = m_startAngle >= m_spanAngle ? m_startAngle : m_spanAngle;
-
- // 确保跨度不超过360度
- if(endAngle - startAngle > 360) endAngle = startAngle + 360;
- // 如果 m_localRect 为空矩形,返回空路径
- if (m_localRect.isNull()) return path;
- // 如果跨度不是完整的360度。说明不是完成的椭圆,是扇形结构
- if ((endAngle - startAngle) % 360 != 0 ) {
- path.moveTo(m_localRect.center()); // 移动到矩形中心
- path.arcTo(m_localRect, startAngle, endAngle - startAngle); // 绘制扇形
- }
- // 说明是完整的椭圆
- else {
- path.addEllipse(m_localRect);
- }
- path.closeSubpath(); // 关闭子路径
- return path;
- }
- // 根据控制方向和偏移量调整椭圆的属性
- void GraphicsEllipseItem::control(int dir, const QPointF & delta)
- {
- QPointF local = mapFromScene(delta); // 将控制点的场景坐标转换为本地坐标
- switch (dir) { // 根据不同的控制方向调整属性
- // 起始点角度:操作点和中心点连线的角度
- case 9:
- {
- qreal len_y = local.y() - m_localRect.center().y();
- qreal len_x = local.x() - m_localRect.center().x();
- m_startAngle = -atan2(len_y,len_x)*180/M_PI;
- }
- break;
- // 结束点:操作点和中心点连线的角度
- case 10:
- {
- qreal len_y = local.y() - m_localRect.center().y();
- qreal len_x = local.x() - m_localRect.center().x();
- m_spanAngle = -atan2(len_y,len_x)*180/M_PI;
- break;
- }
- default:
- break;
- }
- prepareGeometryChange(); // 准备几何形状变化
- // 确保起始角度和终止角度的关系正确
- if ( m_startAngle > m_spanAngle )
- m_startAngle-=360;
- if ( m_spanAngle < m_startAngle ){
- qreal tmp = m_spanAngle;
- m_spanAngle = m_startAngle;
- m_startAngle = tmp;
- }
- // 确保跨度不超过360度
- if ( qAbs(m_spanAngle-m_startAngle) > 360 ){
- m_startAngle = 40; // 重置为默认起始角度
- m_spanAngle = 400; // 重置为默认跨度角度
- }
- // 更新控制柄位置
- updatehandles();
- }
- // 返回椭圆的边界矩形
- QRectF GraphicsEllipseItem::boundingRect() const
- {
- return shape().controlPointRect(); // 使用形状的控制点矩形作为边界矩形
- }
- // 复制当前椭圆项并返回新的实例
- QGraphicsItem *GraphicsEllipseItem::duplicate() const
- {
- // 创建新的椭圆项
- GraphicsEllipseItem * item = new GraphicsEllipseItem( m_localRect.toRect() );
- // 复制属性
- item->m_width = width();
- item->m_height = height();
- item->m_startAngle = m_startAngle;
- item->m_spanAngle = m_spanAngle; // 复制跨度角度m_startAngle - m_endAngle
- // 复制其他属性
- item->setPos(pos().x(),pos().y());
- item->setPen(pen());
- item->setBrush(brush());
- item->setTransform(transform());
- item->setTransformOriginPoint(transformOriginPoint());
- item->setRotation(rotation());
- item->setScale(scale());
- item->setZValue(zValue()+0.1); // 提升 z 值,使之稍微位于上层
- item->updateCoordinate();
- return item;
- }
- // 从 XML 文件加载椭圆的属性
- bool GraphicsEllipseItem::loadFromXml(QXmlStreamReader *xml)
- {
- m_startAngle = xml->attributes().value("startAngle").toInt();
- m_spanAngle = xml->attributes().value("spanAngle").toInt();
- readBaseAttributes(xml);
- xml->skipCurrentElement();
- updateCoordinate();
- return true;
- }
- // 保存椭圆的属性到 XML 文件
- bool GraphicsEllipseItem::saveToXml(QXmlStreamWriter * xml)
- {
- xml->writeStartElement(tr("ellipse")); // 写入元素开始标签
- xml->writeAttribute("startAngle",QString("%1").arg(m_startAngle));
- xml->writeAttribute("spanAngle",QString("%1").arg(m_spanAngle));
- writeBaseAttributes(xml);
- xml->writeEndElement();
- return true;
- }
- // 更新控制柄的位置,在调用此函数前,m_localRect的中心点移动到其原点位置
- void GraphicsEllipseItem::updatehandles()
- {
- // 调用基类的更新控制柄方法
- GraphicsItem::updatehandles();
- // 计算本地矩形,将m_localRect的中心点移动到其原点位置
- QRectF local = QRectF(-m_width/2,-m_height/2,m_width,m_height);
- // 计算矩形中心点的偏移量
- QPointF delta = local.center() - m_localRect.center();
- //qDebug() << delta; // 一直是(0,0),可能是确认确保两者没有偏移。
- // 计算起始角度控制柄的位置
- qreal x = (m_width/2) * cos( -m_startAngle * M_PI / 180 );
- qreal y = (m_height/2) * sin( -m_startAngle * M_PI / 180);
- m_handles.at(8)->move(x-delta.x(),y-delta.y()); // 移动控制柄到新的位置
- // 计算终止角度控制柄的位置
- x = (m_width/2) * cos( -m_spanAngle * M_PI / 180);
- y = (m_height/2) * sin(-m_spanAngle * M_PI / 180);
- m_handles.at(9)->move(x-delta.x(),y-delta.y()); // 移动控制柄到新的位置
- }
- // 绘制椭圆
- void GraphicsEllipseItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
- {
- Q_UNUSED(option); // 忽略未使用的参数
- Q_UNUSED(widget); // 忽略未使用的参数
- QColor c = brushColor(); // 获取画刷颜色
- QRectF rc = m_localRect; // 获取本地矩形
- // 设置画笔
- painter->setPen(pen());
- QBrush b(c);
- painter->setBrush(b);
- // 使用较大的作为起始角度
- int startAngle = m_startAngle <= m_spanAngle ? m_startAngle : m_spanAngle;
- // 使用较小的作为结束角度
- int endAngle = m_startAngle >= m_spanAngle ? m_startAngle : m_spanAngle;
- // 确保跨度不超过360度
- if(endAngle - startAngle > 360) endAngle = startAngle + 360;
- // 如果是完整的椭圆
- if (qAbs(endAngle-startAngle) % (360) == 0) painter->drawEllipse(m_localRect);
- // 否则绘制扇形
- else painter->drawPie(m_localRect, startAngle * 16 , (endAngle-startAngle) * 16);
- // 如果项处于选中状态,绘制选中效果
- if (option->state & QStyle::State_Selected)
- qt_graphicsItem_highlightSelected(this, painter, option);
- }
复制代码 4.5 GraphicsPolygonItem类
GraphicsPolygonItem 类是一个自定义的 QGraphicsItem,用于在 Qt 图形视图框架中绘制和操作多边形图形。它继续自 GraphicsItem,提供了对多边形外形的机动控制和操作。
- class GraphicsPolygonItem : public GraphicsItem
- {
- public:
- // 构造函数,初始化多边形项
- GraphicsPolygonItem(QGraphicsItem * parent = 0);
- // 返回多边形的边界矩形
- QRectF boundingRect() const ;
- // 返回多边形的绘制路径
- QPainterPath shape() const;
- // 向多边形中添加一个点
- virtual void addPoint( const QPointF & point ) ;
- // 结束当前多边形点的添加
- virtual void endPoint(const QPointF & point );
- // 根据控制方向和偏移量调整多边形的属性
- void control(int dir, const QPointF & delta);
- // 根据控制柄和缩放比例拉伸多边形
- void stretch( int handle , double sx , double sy , const QPointF & origin );
- // 更新多边形的坐标
- void updateCoordinate ();
- // 从 XML 文件加载多边形的属性
- virtual bool loadFromXml(QXmlStreamReader * xml );
- // 保存多边形的属性到 XML 文件
- virtual bool saveToXml( QXmlStreamWriter * xml );
- // 返回多边形的显示名称
- QString displayName() const { return tr("polygon"); }
- // 复制当前多边形项并返回新的实例
- QGraphicsItem *duplicate() const;
- protected:
- // 更新控制柄的位置
- void updatehandles();
- // 绘制多边形
- void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
- QPolygonF m_points; // 存储多边形的点
- QPolygonF m_initialPoints; // 存储多边形的初始点
- };
- GraphicsPolygonItem::GraphicsPolygonItem(QGraphicsItem *parent)
- :GraphicsItem(parent)
- {
- // handles
- m_points.clear(); // 初始化点集合为空
- m_pen = QPen(Qt::black); // 设置画笔为黑色
- }
- // 返回多边形的边界矩形
- QRectF GraphicsPolygonItem::boundingRect() const
- {
- return shape().controlPointRect();
- }
- // 返回多边形的绘制路径
- QPainterPath GraphicsPolygonItem::shape() const
- {
- QPainterPath path;
- // 将多边形的点添加到绘制路径
- path.addPolygon(m_points);
-
- // 关闭子路径,使路径形成封闭的多边形
- path.closeSubpath();
- // 根据当前的画笔宽度调整路径,用于检测碰撞。
- // 这个函数会根据画笔的宽度来扩展或缩小路径,从而创建一个包含画笔影响的路径。
- return qt_graphicsItem_shapeFromPath(path,pen());
- }
- void GraphicsPolygonItem::addPoint(const QPointF &point)
- {
- // 将场景坐标的点转换为本地坐标并添加到点集合中
- m_points.append(mapFromScene(point));
- // 获取当前点的方向编号:点的个数就是编号,从1+8=9开始,m_points初始化有一个(0,0)坐标。
- int dir = m_points.count();
- // 创建一个新的控制柄用于调整点的位置
- SizeHandleRect *shr = new SizeHandleRect(this, dir+Left, true);
- // 设置控制柄为活动状态
- shr->setState(SelectionHandleActive);
- // 将控制柄添加到控制柄集合中
- m_handles.push_back(shr);
- }
- void GraphicsPolygonItem::control(int dir, const QPointF &delta)
- {
- // 将控制点的场景坐标转换为本地坐标
- QPointF pt = mapFromScene(delta);
- // 检查控制方向是否有效,小于等于Left,说明一个点都没有。
- if ( dir <= Left ) return ;
- // 更新指定控制点的位置
- m_points[dir - Left -1] = pt;
- // 准备更新几何图形
- prepareGeometryChange();
- // 更新多边形的边界矩形
- m_localRect = m_points.boundingRect();
- // 更新多边形的宽度和高度
- m_width = m_localRect.width();
- m_height = m_localRect.height();
- // 保存更新后的点集合
- m_initialPoints = m_points;
- // 更新控制柄的位置
- updatehandles();
- }
- void GraphicsPolygonItem::stretch(int handle, double sx, double sy, const QPointF &origin)
- {
- QTransform trans;
- // 根据控制柄方向调整缩放比例
- switch (handle) {
- case Right:
- case Left:
- sy = 1; // 水平方向的控制柄只影响水平缩放
- break;
- case Top:
- case Bottom:
- sx = 1; // 垂直方向的控制柄只影响垂直缩放
- break;
- default:
- break;
- }
- trans.translate(origin.x(),origin.y()); // 将原点平移到目标点
- trans.scale(sx,sy); // 执行缩放
- trans.translate(-origin.x(),-origin.y()); // 将原点平移到目标点
- // 准备更新几何图形
- prepareGeometryChange();
- // 将初始点集合按变换矩阵变换,更新多边形点集合
- m_points = trans.map(m_initialPoints);
- // 更新边界矩形
- m_localRect = m_points.boundingRect();
- m_width = m_localRect.width();
- m_height = m_localRect.height();
- // 更新控制柄的位置
- updatehandles();
- }
- void GraphicsPolygonItem::updateCoordinate()
- {
- // 将局部顶点坐标转换成场景坐标。
- QPolygonF pts = mapToScene(m_points);
- QPointF pt1, pt2, delta;
- if (parentItem()==NULL)
- {
- // 当前变换原点的场景坐标
- pt1 = mapToScene(transformOriginPoint());
- // 边界中心的场景坐标
- pt2 = mapToScene(boundingRect().center());
- delta = pt1 - pt2;
- //qDebug() << delta << pt1 << pt2; //存在偏移。
- // 更新所有点的坐标以考虑偏移量
- for (int i = 0; i < pts.count() ; ++i )
- pts[i]+=delta;
- prepareGeometryChange();
- // 更新多边形的本地点集合
- m_points = mapFromScene(pts);
- // 更新边界矩形
- m_localRect = m_points.boundingRect();
- m_width = m_localRect.width();
- m_height = m_localRect.height();
- // 更新图形的变换以适应新的原点
- setTransform(transform().translate(delta.x(),delta.y()));
- //setTransformOriginPoint(boundingRect().center());
- moveBy(-delta.x(),-delta.y());
- setTransform(transform().translate(-delta.x(),-delta.y()));
- // 更新控制柄的位置
- updatehandles();
- }
- // 保存更新后的点集合
- m_initialPoints = m_points;
- }
- bool GraphicsPolygonItem::loadFromXml(QXmlStreamReader *xml)
- {
- readBaseAttributes(xml);
- // 逐个读取多边形的点
- while(xml->readNextStartElement()){
- if (xml->name()=="point"){
- // 获取点的x和y坐标
- qreal x = xml->attributes().value("x").toDouble();
- qreal y = xml->attributes().value("y").toDouble();
- m_points.append(QPointF(x,y));
- // 创建控制柄
- int dir = m_points.count();
- SizeHandleRect *shr = new SizeHandleRect(this, dir+Left, true);
- m_handles.push_back(shr);
- // 跳过当前元素
- xml->skipCurrentElement();
- }else // 跳过非点元素
- xml->skipCurrentElement();
- }
- // 更新多边形的坐标
- updateCoordinate();
- return true;
- }
- bool GraphicsPolygonItem::saveToXml(QXmlStreamWriter *xml)
- {
- // 开始写入多边形元素
- xml->writeStartElement("polygon");
- writeBaseAttributes(xml);
- // 保存每个点的坐标
- for ( int i = 0 ; i < m_points.count();++i){
- xml->writeStartElement("point");
- xml->writeAttribute("x",QString("%1").arg(m_points[i].x()));
- xml->writeAttribute("y",QString("%1").arg(m_points[i].y()));
- xml->writeEndElement();
- }
- xml->writeEndElement();
- return true;
- }
- void GraphicsPolygonItem::endPoint(const QPointF & point)
- {
- Q_UNUSED(point);
- int nPoints = m_points.count();
- // 检查多边形最后两个点是否相同或水平对齐
- if( nPoints > 2 && (m_points[nPoints-1] == m_points[nPoints-2]/*相同*/ || /*水平对齐,只相差一个像素*/
- m_points[nPoints-1].x() - 1 == m_points[nPoints-2].x() &&
- m_points[nPoints-1].y() == m_points[nPoints-2].y())){
- // 删除重复的最后一个控制柄
- delete m_handles[Left + nPoints-1];
- // 移除重复点
- m_points.remove(nPoints-1);
- // 调整控制柄集合的大小
- m_handles.resize(Left + nPoints-1);
- }
- // 更新初始点集合
- m_initialPoints = m_points;
- }
- QGraphicsItem *GraphicsPolygonItem::duplicate() const
- {
- // 创建一个新的多边形项并复制当前项的属性
- GraphicsPolygonItem * item = new GraphicsPolygonItem( );
- item->m_width = width();
- item->m_height = height();
- item->m_points = m_points;
- // 为每个点创建控制柄
- for ( int i = 0 ; i < m_points.size() ; ++i ){
- item->m_handles.push_back(new SizeHandleRect(item,Left+i+1,true));
- }
- item->setPos(pos().x(),pos().y());
- item->setPen(pen());
- item->setBrush(brush());
- item->setTransform(transform());
- item->setTransformOriginPoint(transformOriginPoint());
- item->setRotation(rotation());
- item->setScale(scale());
- item->setZValue(zValue()+0.1); // 提升复制项的层级以避免重叠
- item->updateCoordinate();
- return item;
- }
- void GraphicsPolygonItem::updatehandles()
- {
- // 调用基类的方法更新控制柄
- GraphicsItem::updatehandles();
- // 根据多边形的点更新控制柄的位置
- for ( int i = 0 ; i < m_points.size(); ++i ){
- m_handles[Left+i]->move(m_points[i].x() ,m_points[i].y() );
- }
- }
- void GraphicsPolygonItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
- {
- Q_UNUSED(option);
- Q_UNUSED(widget);
- // 创建一个线性渐变的笔刷
- QColor c = brushColor();
- QLinearGradient result(boundingRect().topLeft(), boundingRect().topRight());
- result.setColorAt(0, c.dark(150));
- result.setColorAt(0.5, c.light(200));
- result.setColorAt(1, c.dark(150));
- painter->setBrush(result);
- // 设置画笔并绘制多边形
- painter->setPen(pen());
- painter->drawPolygon(m_points);
- // 如果项被选中,绘制选中高亮效果
- if (option->state & QStyle::State_Selected)
- qt_graphicsItem_highlightSelected(this, painter, option);
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |