qtdraw-使用qt画图之开源源码学习

打印 上一主题 下一主题

主题 1022|帖子 1022|积分 3066

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

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 是一个基类,用于为不同的画图工具提供公共功能和接口。它的主要作用是定义和实现一些基础的画图操作,以及管理和查找不同的画图工具实例。
  1. //drawtool.h
  2. enum DrawShape
  3. {
  4.     selection,   // 选择模式:用于选择和操作现有的图形项。
  5.     rotation,    // 旋转模式:用于对图形项进行旋转操作。
  6.     line,        // 线段:用于绘制直线。
  7.     rectangle,   // 矩形:用于绘制矩形。
  8.     roundrect,   // 圆角矩形:用于绘制带有圆角的矩形。
  9.     ellipse,     // 椭圆:用于绘制椭圆形状。
  10.     bezier,      // 贝塞尔曲线:用于绘制贝塞尔曲线,用于平滑和复杂的曲线形状。
  11.     polygon,     // 多边形:用于绘制闭合的多边形。
  12.     polyline,    // 折线:用于绘制由多个线段组成的折线。
  13. };
  14. // 基类
  15. class DrawTool
  16. {
  17. public:
  18.     DrawTool( DrawShape shape ); // 构造函数,初始化绘制工具,参数为绘制形状
  19.     virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;  // 鼠标按下事件,虚函数
  20.     virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
  21.     virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
  22.     virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event ,DrawScene *scene );
  23.     DrawShape m_drawShape;  // 当前绘制工具的形状
  24.     bool m_hoverSizer; // 是否悬停在调整大小的手柄上
  25.     static DrawTool * findTool( DrawShape drawShape ); // 根据形状查找相应的绘制工具
  26.     static QList<DrawTool*> c_tools; // 静态成员,保存所有绘制工具的列表
  27.     static QPointF c_down; // 静态成员,保存鼠标按下时的位置
  28.     static quint32 c_nDownFlags; // 静态成员,保存按下时的标志
  29.     static QPointF c_last;  // 静态成员,保存最后一次鼠标事件的位置
  30.     static DrawShape c_drawShape;  // 静态成员,当前选中的绘制形状
  31. };
  32. // drawtool.cpp
  33. // 初始化全部的静态成员变量,为啥不在构造函数内部初始化:
  34. // 语法规则, 静态成员变量的初始化必须在类定义的外部进行
  35. // 因为是静态的,静态成员属于类而不是对象,避免重复初始化,静态成员变量在程序加载时就会被分配内存并初始化,而构造函数是在对象被创建时才会调用。
  36. QList<DrawTool*> DrawTool::c_tools;  // 初始化静态成员,绘制工具列表
  37. QPointF DrawTool::c_down; // 初始化静态成员,鼠标按下位置
  38. QPointF DrawTool::c_last;  // 初始化静态成员,最后一次鼠标位置
  39. quint32 DrawTool::c_nDownFlags;   // 初始化静态成员,鼠标按下标志
  40. DrawShape DrawTool::c_drawShape = selection;  // 初始化静态成员,当前绘制形状为 selection
  41. // DrawTool 构造函数,初始化绘制形状并将工具加入工具列表
  42. DrawTool::DrawTool(DrawShape shape)
  43. {
  44.     m_drawShape = shape ;     // 设置绘制形状
  45.     m_hoverSizer = false;     // 初始化悬停调整大小状态
  46.     c_tools.push_back(this);  // 将当前工具加入工具列表
  47. }
  48. // 鼠标按下事件处理
  49. void DrawTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  50. {
  51.     c_down = event->scenePos();  // 保存鼠标 按下时的位置
  52.     c_last = event->scenePos();  // 初始化最后位置为 按下时位置
  53. }
  54. // 鼠标移动事件处理
  55. void DrawTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  56. {
  57.     c_last = event->scenePos();  // 更新最后一次鼠标事件的位置
  58. }
  59. // 鼠标释放事件处理
  60. void DrawTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  61. {
  62.     if (event->scenePos() == c_down )  // 如果鼠标释放位置和按下位置相同
  63.         c_drawShape = selection;  // 设置当前绘制形状为 selection
  64.     setCursor(scene,Qt::ArrowCursor);  // 重置光标为箭头样式
  65. }
  66. // 鼠标双击事件处理,当前为空实现
  67. void DrawTool::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  68. {
  69. }
  70. // 根据形状查找相应的绘制工具
  71. DrawTool *DrawTool::findTool(DrawShape drawShape)
  72. {
  73.     QList<DrawTool*>::const_iterator iter = c_tools.constBegin();  // 获取工具列表的常量迭代器
  74.     for ( ; iter != c_tools.constEnd() ; ++iter ){
  75.         if ((*iter)->m_drawShape == drawShape )  // 如果找到形状匹配的工具
  76.             return (*iter);  // 返回该工具
  77.     }
  78.     return 0;  // 如果没有匹配的工具,返回 nullptr
  79. }
复制代码
3.2 SelectTool选中类


SelectTool 类专注于处置处罚画图场景中的选择操作,包括选择、移动、缩放和编辑图形项。它通过重写基类 DrawTool 的虚拟函数,提供了具体的选择操作实现。SelectTool 管理图形项的选择状态、虚线框的表现以及选择模式的切换,为用户提供了直观的操作反馈和功能。
  1. class SelectTool : public DrawTool
  2. {
  3. public:
  4.     SelectTool();
  5.     virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
  6.     virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
  7.     virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
  8.     QPointF initialPositions;       // 记录初始位置
  9.     QPointF opposite_;              // 对应点,用于大小调整操作
  10.     QGraphicsPathItem * dashRect;   // 虚线框,用于选择时的视觉反馈
  11.     GraphicsItemGroup * selLayer;   // 选择的图层
  12. };
  13. // 构造函数,初始化成员变量
  14. SelectTool::SelectTool()
  15.     :DrawTool(selection)    // 初始化基类 DrawTool,模式为 selection (选择),这样继承过来的变量得到了初始化。
  16. {
  17.     dashRect = 0;           // 初始化虚线框指针为空
  18.     selLayer = 0;           // 初始化选择层指针为空
  19.     opposite_ = QPointF();  // 初始化对点为空,即与当前拖动的控制点相对的点。
  20. }
  21. // 选中时会有三种操作:size(调整大小),editor(编辑),move(移动)
  22. void SelectTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  23. {
  24.     DrawTool::mousePressEvent(event,scene);  // 调用基类的鼠标按下事件处理
  25.     if ( event->button() != Qt::LeftButton ) return;    // 如果不是左键按下,则不处理
  26.     if (!m_hoverSizer)
  27.        scene->mouseEvent(event);  // 如果没有悬停在调整器上,则将事件传递给场景
  28.     nDragHandle = Handle_None;    // 初始化拖拽句柄为无
  29.     selectMode = none;            // 初始化选择模式为无
  30.     QList<QGraphicsItem *> items = scene->selectedItems();  // 获取当前选中的图形项,是通过鼠标点击、框选选中的。
  31.     AbstractShape *item = 0;     // 初始化选中的形状指针为空
  32.     if ( items.count() == 1 )    // 如果只选中了一个图形项
  33.         item = qgraphicsitem_cast<AbstractShape*>(items.first());  // 尝试将其转换为 AbstractShape 类型
  34.     if ( item != 0 ){  // 如果选中的图形项不为空
  35.         nDragHandle = item->collidesWithHandle(event->scenePos());  // 检查是否与句柄碰撞(鼠标在形状的边缘位置),从而判断当前模式。
  36.         if ( nDragHandle != Handle_None && nDragHandle <= Left )    // 如果拖拽句柄<=Left,在左边就说明有碰撞
  37.              selectMode = size;         // 设置选择模式为大小调整,其中size是枚举SelectMode成员
  38.         else if ( nDragHandle > Left )  // 如果拖拽句柄>Left, 设置选择模式为编辑
  39.             selectMode = editor;        
  40.         else
  41.             selectMode =  move;         // 否则光标就是没有碰撞,而是在形状内部,设置为移动模式
  42.         if ( nDragHandle!= Handle_None && nDragHandle <= Left ){   // 如果是大小调整模式
  43.             opposite_ = item->opposite(nDragHandle);   // 获取相对点,即与当前拖动的控制点相对的点。
  44.             if( opposite_.x() == 0 )
  45.                 opposite_.setX(1);  // 防止出现零值,设置为 1
  46.             if (opposite_.y() == 0 )
  47.                 opposite_.setY(1);
  48.         }
  49.         setCursor(scene,Qt::ClosedHandCursor);  // 设置光标为闭合手形状
  50.     }else if ( items.count() > 1 )
  51.         selectMode =  move;   // 如果选中了多个图形项,则只能是移动模式
  52.     if( selectMode == none ){    // 如果没有设置模式
  53.         selectMode = netSelect;  // 设置为网络选择模式
  54.         if ( scene->view() ){
  55.             QGraphicsView * view = scene->view();
  56.             view->setDragMode(QGraphicsView::RubberBandDrag);  // 设置视图为橡皮筋拖动模式
  57.         }
  58. #if 0
  59.         if ( selLayer ){ // 取消注释以清除选择图层
  60.             scene->destroyGroup(selLayer);
  61.             selLayer = 0;
  62.         }
  63. #endif
  64.     }
  65.     if ( selectMode == move && items.count() == 1 ){  // 如果是移动模式且只有一个选中项
  66.         if (dashRect ){
  67.             scene->removeItem(dashRect);  // 如果已有虚线框,移除
  68.             delete dashRect;              // 删除虚线框
  69.             dashRect = 0;                 // 重置虚线框指针
  70.         }
  71.         dashRect = new QGraphicsPathItem(item->shape());   // 创建一个新的虚线框
  72.         dashRect->setPen(Qt::DashLine);   // 设置为虚线
  73.         dashRect->setPos(item->pos());    // 设置位置
  74.         dashRect->setTransformOriginPoint(item->transformOriginPoint());   // 设置变换原点
  75.         dashRect->setTransform(item->transform());         // 应用图形项的变换
  76.         dashRect->setRotation(item->rotation());           // 设置旋转
  77.         dashRect->setScale(item->scale());                 // 设置缩放
  78.         dashRect->setZValue(item->zValue());               // 设置 Z 值
  79.         scene->addItem(dashRect);// 将虚线框添加到场景
  80.         initialPositions = item->pos();  // 记录初始位置
  81.     }
  82. }
  83. // 处理鼠标移动事件
  84. void SelectTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  85. {
  86.     DrawTool::mouseMoveEvent(event,scene);  // 调用基类的鼠标移动事件处理
  87.     QList<QGraphicsItem *> items = scene->selectedItems();  // 获取当前选中的图形项
  88.     AbstractShape * item = 0;  // 初始化选中的形状指针为空
  89.     if ( items.count() == 1 ){  // 如果只选中了一个图形项
  90.         item = qgraphicsitem_cast<AbstractShape*>(items.first());  // 尝试将其转换为 AbstractShape 类型
  91.         if ( item != 0 ){  // 如果选中的图形项不为空
  92.             if ( nDragHandle != Handle_None && selectMode == size ){  // 如果是大小调整模式
  93.                 if (opposite_.isNull()){  // 如果对点为空
  94.                     opposite_ = item->opposite(nDragHandle);   // 获取对点,即与当前拖动的控制点相对的点。
  95.                     if( opposite_.x() == 0 )
  96.                         opposite_.setX(1);  // 防止零值,设置为 1
  97.                     if (opposite_.y() == 0 )
  98.                         opposite_.setY(1);
  99.                 }
  100.                 /*
  101.                 mapFromScene 的作用是将场景坐标(Scene Coordinates)转换为图形项自身的局部坐标系(Item Coordinates)。
  102.                 将全局的场景坐标 c_last 转换为 item 自身的局部坐标系中的一个点。这是因为 c_last 是在场景中的一个点,
  103.                 但我们在操作图形项时,通常需要知道该点在图形项自身坐标系中的位置。
  104.                 按下c_down之后,移动c_last鼠标就是拉伸
  105.                 按下的坐标是initial_delta,移动后的坐标是new_delta
  106.                
  107.                 在图形项的缩放过程中,我们通常是以某个固定点(对点 opposite_)为基准,
  108.                 然后根据鼠标的位置(c_last 和 c_down)计算拉伸或缩放的比例。
  109.                 这样就能准确地控制图形的大小变化,使得缩放操作相对直观且符合用户的期望。
  110.                 */
  111.                 QPointF new_delta = item->mapFromScene(c_last) - opposite_;  // 计算新的相对距离: c_last鼠标移动的坐标 - 边界
  112.                 QPointF initial_delta = item->mapFromScene(c_down) - opposite_;  // 计算初始相对距离: c_down鼠标按下的坐标 - 边界
  113.                 double sx = new_delta.x() / initial_delta.x();  // 计算 X 方向的缩放比例
  114.                 double sy = new_delta.y() / initial_delta.y();  // 计算 Y 方向的缩放比例
  115.                 /*
  116.                 nDragHandle: 一个图形项会有多个控制手柄(如顶点、边缘中点等),每个手柄对应不同的缩放或调整操作。
  117.                 sx sy: 表示 X,Y 方向上的缩放比例。
  118.                 opposite_: 表示固定不动的对点坐标(相对于拖动手柄)。在缩放或拉伸过程中,这个点保持不变,是所有变换操作的基准点。
  119.                 */
  120.                 item->stretch(nDragHandle, sx , sy , opposite_);  // 执行图形项的拉伸操作
  121.                 emit scene->itemResize(item,nDragHandle,QPointF(sx,sy)); // 发送图形项大小调整的信号
  122.               //  qDebug()<<"scale:"<<nDragHandle<< item->mapToScene(opposite_)<< sx << " 锛? << sy
  123.               //         << new_delta << item->mapFromScene(c_last)
  124.               //         << initial_delta << item->mapFromScene(c_down) << item->boundingRect();
  125.             } else if ( nDragHandle > Left  && selectMode == editor ){  // 如果是编辑模式
  126.                 // 确定控制点:通过 nDragHandle 确定用户正在操作的控制点。
  127.                 // 更新位置:根据当前鼠标的位置 c_last,将控制点移动到新的位置。
  128.                 // 修改图形形状:调整图形项的形状以反映控制点的移动。这可能包括更新曲线的曲率、多边形的形状等。
  129.                 item->control(nDragHandle,c_last);   // 更新图形项的控制点位置。控制点是用于定义图形形状的关键点
  130.                 emit scene->itemControl(item,nDragHandle,c_last,c_down);  // 发送图形项控制的信号
  131.             }
  132.             else if(nDragHandle == Handle_None ){  // 如果没有拖拽句柄
  133.                  int handle = item->collidesWithHandle(event->scenePos());  // 检查是否与句柄碰撞
  134.                  if ( handle != Handle_None){
  135.                      setCursor(scene,Qt::OpenHandCursor);  // 如果碰撞,设置光标为打开的手形状
  136.                      m_hoverSizer = true;  // 设置悬停调整标志
  137.                  }else{
  138.                      setCursor(scene,Qt::ArrowCursor);  // 否则设置光标为箭头形状
  139.                      m_hoverSizer = false;   // 取消悬停调整标志
  140.                  }
  141.              }
  142.         }
  143.     }
  144.     if ( selectMode == move ){  // 如果是移动模式
  145.         setCursor(scene,Qt::ClosedHandCursor);  // 设置光标为闭合的手形状
  146.         if ( dashRect ){
  147.             dashRect->setPos(initialPositions + c_last - c_down);  // 更新虚线框的位置
  148.         }
  149.     }
  150.     if ( selectMode != size  && items.count() > 1)  // 如果不是大小调整模式且选中多个项
  151.     {
  152.         scene->mouseEvent(event);   // 将事件传递给场景
  153.     }
  154. }
  155. void SelectTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  156. {
  157.     DrawTool::mouseReleaseEvent(event,scene);
  158.     if ( event->button() != Qt::LeftButton ) return;
  159.     // 获取当前场景中所有被选中的图形项。
  160.     QList<QGraphicsItem *> items = scene->selectedItems();
  161.     // 如果只选中了一个图形项,进行以下处理。
  162.     if ( items.count() == 1 ){
  163.         AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
  164.         // 如果转换成功且当前选择模式为移动模式,并且鼠标释放的位置与按下的位置不同。
  165.         if ( item != 0  && selectMode == move && c_last != c_down ){
  166.             // 将图形项的位置设置为初始位置加上鼠标移动的偏移量。
  167.             item->setPos(initialPositions + c_last - c_down);
  168.             // 触发场景的 itemMoved 信号,通知图形项已被移动,并传递移动的偏移量。
  169.             emit scene->itemMoved(item , c_last - c_down );
  170.          }
  171.         
  172.         // 如果图形项存在,并且选择模式为尺寸调整或编辑模式,并且鼠标释放的位置与按下的位置不同。
  173.         else if ( item !=0 && (selectMode == size || selectMode ==editor) && c_last != c_down ){
  174.             // 更新图形项的坐标,通常是为了确保调整后的形状参数被正确应用。
  175.             item->updateCoordinate();
  176.         }
  177.     }
  178.    
  179.     // 如果选中了多个图形项,且选择模式为移动模式,并且鼠标释放的位置与按下的位置不同。
  180.     else if ( items.count() > 1 && selectMode == move && c_last != c_down ){
  181.         // 触发场景的 itemMoved 信号,传递 NULL 代表多个图形项的移动操作,并传递移动的偏移量。
  182.           emit scene->itemMoved(NULL , c_last - c_down );
  183.     }
  184.     // 如果当前选择模式为网格选择模式(框选),进行以下处理。
  185.     if (selectMode == netSelect ){
  186.         // 检查场景是否有视图,如果有视图,则设置视图的拖拽模式为无拖拽。
  187.         if ( scene->view() ){
  188.             QGraphicsView * view = scene->view();
  189.             view->setDragMode(QGraphicsView::NoDrag);
  190.         }
  191.         // 如果代码块开启,检查选中的图形项数量是否大于 1,并将其创建为一个组。
  192. #if 0
  193.         if ( scene->selectedItems().count() > 1 ){
  194.             selLayer = scene->createGroup(scene->selectedItems());
  195.             selLayer->setSelected(true);
  196.         }
  197. #endif
  198.     }
  199.    
  200.     // 后面是清空成员变量。
  201.     if (dashRect ){
  202.         // 从场景中移除虚线矩形,并删除该对象释放内存。
  203.         scene->removeItem(dashRect);
  204.         delete dashRect;
  205.         dashRect = 0;
  206.     }
  207.     // 重置选择模式为无,表示没有正在进行的选择或编辑操作。
  208.     selectMode = none;
  209.     // 重置拖拽句柄为无,表示没有正在进行的拖拽操作。
  210.     nDragHandle = Handle_None;
  211.     // 重置悬停尺寸标志为 false,表示当前鼠标没有悬停在可调整大小的区域。
  212.     m_hoverSizer = false;
  213.     // 重置对点坐标为 (0, 0),用于下一次的尺寸调整操作。
  214.     opposite_ = QPointF();
  215.     // 将鼠标事件传递给场景进行处理,确保场景接收到完整的鼠标事件流程。
  216.     scene->mouseEvent(event);
  217. }
复制代码
3.3 RotationTool旋转类


RotationTool 类专注于处置处罚图形项的旋转操作。它通过重写基类 DrawTool 的虚拟函数,提供了具体的旋转实现。RotationTool 管理图形项的旋转状态、旋转角度的盘算以及旋转视觉反馈的表现,为用户提供了直观的旋转操作体验。通过管理 lastAngle 和 dashRect,RotationTool 可以大概准确地盘算和表现旋转效果,并在鼠标操作时给予用户实时反馈。
  1. class  RotationTool : public DrawTool
  2. {
  3. public:
  4.     RotationTool();
  5.     virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
  6.     virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
  7.     virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
  8.     qreal lastAngle;  // 记录上一次计算的旋转角度
  9.     QGraphicsPathItem * dashRect;  // 用于显示虚线矩形的指针
  10. };
  11. // 构造函数,初始化旋转工具
  12. RotationTool::RotationTool()
  13.     :DrawTool(rotation) // 实例化基类的成员变量
  14. {
  15.     lastAngle = 0;    // 初始化上一次角度为0
  16.     dashRect = 0;     // 初始化虚线矩形指针为空
  17. }
  18. void RotationTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  19. {
  20.     // 调用基类 DrawTool 的 mousePressEvent 方法
  21.     DrawTool::mousePressEvent(event,scene);
  22.     if ( event->button() != Qt::LeftButton ) return;
  23.     // 如果没有在调整大小的状态,则将事件传递给场景
  24.     if (!m_hoverSizer)
  25.       scene->mouseEvent(event);
  26.     // 获取当前场景中所有被选中的图形项
  27.     QList<QGraphicsItem *> items = scene->selectedItems();
  28.     // 如果选中了一个图形项
  29.     if ( items.count() == 1 ){
  30.         AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
  31.         if ( item != 0 ){
  32.             // 检测鼠标点击的位置是否在图形项的控制句柄上,即边界顶点等位置
  33.             nDragHandle = item->collidesWithHandle(event->scenePos());
  34.             // 如果点击的是控制句柄
  35.             if ( nDragHandle !=Handle_None)
  36.             {
  37.                 // 获取图形项的中心点相对于场景的坐标
  38.                 QPointF origin = item->mapToScene(item->boundingRect().center());
  39.                 // 计算鼠标当前位置相对于中心点的 Y 和 X 方向的长度
  40.                 qreal len_y = c_last.y() - origin.y();
  41.                 qreal len_x = c_last.x() - origin.x();
  42.                 // 使用反正切函数计算鼠标位置与中心点连线的角度,并将其转换为度数
  43.                 qreal angle = atan2(len_y,len_x)*180/PI;
  44.                 // 当前点和item中心点坐标(都是场景坐标)形成的角度
  45.                 lastAngle = angle;  // 记录当前角度为 lastAngle
  46.                 selectMode = rotate;  // 设置选择模式为旋转
  47.                 // 如果 dashRect 已存在,移除并删除它
  48.                 if (dashRect ){
  49.                     scene->removeItem(dashRect);
  50.                     delete dashRect;
  51.                     dashRect = 0;
  52.                 }
  53.                 // 创建一个新的虚线矩形项,以形状项的轮廓作为路径
  54.                 dashRect = new QGraphicsPathItem(item->shape());
  55.                 dashRect->setPen(Qt::DashLine);  // 设置虚线笔刷
  56.                 dashRect->setPos(item->pos());
  57.                 dashRect->setTransformOriginPoint(item->transformOriginPoint());  // 设置变换的原点
  58.                 dashRect->setTransform(item->transform());  // 设置变换矩阵
  59.                 dashRect->setRotation(item->rotation());  // 设置旋转角度
  60.                 dashRect->setScale(item->scale());  // 设置缩放比例
  61.                 dashRect->setZValue(item->zValue());  // 设置 Z 轴位置
  62.                 scene->addItem(dashRect);  // 将虚线矩形添加到场景中
  63.                 setCursor(scene,QCursor((QPixmap(":/icons/rotate.png"))));  // 设置旋转光标
  64.             }
  65.             else{
  66.                 // 如果没有点击控制句柄,则切换到选择工具,并处理按下事件
  67.                     c_drawShape = selection;
  68.                     selectTool.mousePressEvent(event,scene);
  69.                 }
  70.         }
  71.     }
  72. }
  73. void RotationTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  74. {
  75.     // 调用基类 DrawTool 的 mouseMoveEvent 方法
  76.     DrawTool::mouseMoveEvent(event,scene);
  77.     // 获取当前场景中所有被选中的图形项
  78.     QList<QGraphicsItem *> items = scene->selectedItems();
  79.     // 如果选中了一个图形项
  80.     if ( items.count() == 1 ){
  81.         AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
  82.         // 如果转换成功且在拖拽句柄且选择模式为旋转模式
  83.         if ( item != 0  && nDragHandle !=Handle_None && selectMode == rotate ){
  84.             // 获取图形项的中心点相对于场景的坐标
  85.              QPointF origin = item->mapToScene(item->boundingRect().center());
  86.              // 计算鼠标当前位置相对于中心点的 Y 和 X 方向的长度
  87.              qreal len_y = c_last.y() - origin.y();
  88.              qreal len_x = c_last.x() - origin.x();
  89.              // 使用反正切函数计算鼠标位置与中心点连线的角度,并将其转换为度数
  90.              qreal angle = atan2(len_y,len_x)*180/PI;
  91.              // 计算新的旋转角度
  92.              // angle - lastAngle: 计算自上次记录角度以来的角度变化量。这表示用户在旋转操作期间,鼠标移动所导致的角度变化。
  93.              // item->rotation(): 获取图形项当前的旋转角度,自带的角度。
  94.              angle = item->rotation() + int(angle - lastAngle) ;  
  95.              // 将角度限制在 -360 到 360 度之间
  96.              if ( angle > 360 )
  97.                  angle -= 360;
  98.              if ( angle < -360 )
  99.                  angle+=360;
  100.              // 如果虚线矩形存在,更新其旋转角度
  101.              if ( dashRect ){
  102.                  //dashRect->setTransform(QTransform::fromTranslate(15,15),true);
  103.                  //dashRect->setTransform(QTransform().rotate(angle));
  104.                  //dashRect->setTransform(QTransform::fromTranslate(-15,-15),true);
  105.                  dashRect->setRotation( angle );
  106.              }
  107.              // 设置旋转光标
  108.              setCursor(scene,QCursor((QPixmap(":/icons/rotate.png"))));
  109.         }
  110.         else if ( item )
  111.         {
  112.             // 检查鼠标当前位置是否在控制句柄上
  113.             int handle = item->collidesWithHandle(event->scenePos());
  114.             // 如果在控制句柄上,设置旋转光标,并标记悬停状态
  115.             if ( handle != Handle_None){
  116.                 setCursor(scene,QCursor((QPixmap(":/icons/rotate.png"))));
  117.                 m_hoverSizer = true;
  118.             }else{
  119.                 // 如果不在控制句柄上,设置为普通箭头光标,并重置悬停状态
  120.                 setCursor(scene,Qt::ArrowCursor);
  121.                 m_hoverSizer = false;
  122.             }
  123.         }
  124.     }
  125.     // 将事件传递给场景
  126.     scene->mouseEvent(event);
  127. }
  128. void RotationTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  129. {
  130.     // 调用基类 DrawTool 的 mouseReleaseEvent 方法
  131.     DrawTool::mouseReleaseEvent(event,scene);
  132.     // 如果释放的不是左键,直接返回
  133.     if ( event->button() != Qt::LeftButton ) return;
  134.     QList<QGraphicsItem *> items = scene->selectedItems();
  135.     if ( items.count() == 1 ){
  136.         AbstractShape * item = qgraphicsitem_cast<AbstractShape*>(items.first());
  137.         // 如果转换成功且在拖拽句柄且选择模式为旋转模式
  138.         if ( item != 0  && nDragHandle !=Handle_None && selectMode == rotate ){
  139.             // 获取图形项的中心点相对于场景的坐标
  140.              QPointF origin = item->mapToScene(item->boundingRect().center());
  141.              // 计算鼠标当前位置相对于中心点的偏移量
  142.              QPointF delta = c_last - origin ;
  143.              // 计算鼠标当前位置相对于中心点的 Y 和 X 方向的长度
  144.              qreal len_y = c_last.y() - origin.y();
  145.              qreal len_x = c_last.x() - origin.x();
  146.              // 使用反正切函数计算鼠标位置与中心点连线的角度,并将其转换为度数
  147.              qreal angle = atan2(len_y,len_x)*180/PI,oldAngle = item->rotation();
  148.              // 计算最终的旋转角度
  149.              angle = item->rotation() + int(angle - lastAngle) ;
  150.              // 将角度限制在 -360 到 360 度之间
  151.              if ( angle > 360 )
  152.                  angle -= 360;
  153.              if ( angle < -360 )
  154.                  angle+=360;
  155.              // 设置图形项的旋转角度
  156.              item->setRotation( angle );
  157.              // 触发场景的 itemRotate 信号,通知图形项已被旋转,并传递旧的角度
  158.              emit scene->itemRotate(item , oldAngle);
  159.              qDebug()<<"rotate:"<<angle<<item->boundingRect();
  160.         }
  161.     }
  162.     setCursor(scene,Qt::ArrowCursor);
  163.     // 清空成员变量。
  164.     selectMode = none;
  165.     nDragHandle = Handle_None;
  166.     lastAngle = 0;
  167.     m_hoverSizer = false;
  168.     if (dashRect ){
  169.         scene->removeItem(dashRect);
  170.         delete dashRect;
  171.         dashRect = 0;
  172.     }
  173.     // 将事件传递给场景
  174.     scene->mouseEvent(event);
  175. }
复制代码
3.4 RectTool


这里RectTool类支持绘制rectangle矩形、roundrect圆角矩形、ellipse椭圆,但是具体的绘制操作是由其成员变量item自己负责。
  1. class RectTool : public DrawTool
  2. {
  3. public:
  4.     RectTool(DrawShape drawShape);  // 构造函数,用于初始化工具的形状类型
  5.     virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
  6.     virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
  7.     virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
  8.     GraphicsItem * item;   // 当前创建的图形项
  9. };
  10. RectTool::RectTool(DrawShape drawShape)
  11.     :DrawTool(drawShape)  // 调用基类构造函数初始化工具类型
  12. {
  13.     item = 0; // 初始化图形项为nullptr
  14. }
  15. void RectTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  16. {
  17.     if ( event->button() != Qt::LeftButton ) return;
  18.     // 清除场景中所有的选中项
  19.     scene->clearSelection();
  20.     // 调用基类的鼠标按下事件处理
  21.     DrawTool::mousePressEvent(event,scene);
  22.     // 根据工具类型创建不同的图形项
  23.     switch ( c_drawShape ){
  24.     case rectangle: // 矩形
  25.         item = new GraphicsRectItem(QRect(1,1,1,1));        // 创建一个初始大小为1x1的矩形项
  26.         break;
  27.     case roundrect: // 圆角矩形
  28.         item = new GraphicsRectItem(QRect(1,1,1,1),true);   // 创建一个初始大小为1x1的圆角矩形项
  29.         break;
  30.     case ellipse:   // 椭圆
  31.         item = new GraphicsEllipseItem(QRect(1,1,1,1));     // 创建一个初始大小为1x1的椭圆项
  32.         break;
  33.     }
  34.     if ( item == 0) return;  // 如果图形项创建失败,则返回
  35.     // 将点击起始点位置向右下偏移2像素:
  36.     // 在图形项的初始创建时(例如,矩形或圆角矩形),其尺寸通常从一个很小的区域开始。偏移量确保图形项在可视区域内有足够的空间来展示
  37.     c_down+=QPoint(2,2);               
  38.     item->setPos(event->scenePos());   // 设置图形项的位置为鼠标点击的位置
  39.     scene->addItem(item);              // 将图形项添加到场景中
  40.     item->setSelected(true);           // 选中图形项
  41.     selectMode = size;                 // 设置选择模式为调整大小
  42.     nDragHandle = RightBottom;         // 设置拖拽手柄为右下角
  43. }
  44. void RectTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  45. {
  46.     setCursor(scene,Qt::CrossCursor);       // 设置光标为十字形,以指示绘图模式
  47.     selectTool.mouseMoveEvent(event,scene); // 调用选择工具的鼠标移动事件处理
  48. }
  49. void RectTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  50. {
  51.     selectTool.mouseReleaseEvent(event,scene);    // 调用选择工具的鼠标释放事件处理
  52.     // 如果鼠标释放位置与初始点击位置相同,则删除创建的图形项
  53.     if ( event->scenePos() == (c_down-QPoint(2,2))){  // 前面c_down鼠标点击的位置已经偏移2像素,所以这里要补偿回来。
  54.        if ( item != 0){
  55.          item->setSelected(false);  // 取消选中图形项
  56.          scene->removeItem(item);   // 从场景中移除图形项
  57.          delete item ;              // 删除图形项对象
  58.          item = 0;                  // 将图形项指针设置为nullptr
  59.        }
  60.        qDebug()<<"RectTool removeItem:";
  61.     }
  62.    
  63.     // 发射信号,表示图形项已添加到场景中
  64.     else if( item ){
  65.         emit scene->itemAdded( item );
  66.     }
  67.     // 将工具形状重置为选择模式
  68.     c_drawShape = selection;
  69. }
复制代码

3.5 PolygonTool


 PolygonTool 是一个用于绘制多边形折线线段贝塞尔曲线的工具类,如下所示,继续自 DrawTool。它提供了在场景中通过鼠标交互来创建和编辑这些图形的功能。

  1. class PolygonTool : public DrawTool
  2. {
  3. public:
  4.     PolygonTool(DrawShape shape );
  5.     virtual void mousePressEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
  6.     virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene ) ;
  7.     virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event , DrawScene * scene );
  8.     virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event ,DrawScene *scene );
  9.     GraphicsPolygonItem * item;  // 当前绘制的图形项,可能是多边形、贝塞尔曲线、多线段或直线
  10.     int m_nPoints;               // 当前图形的点的数量
  11.     QPointF initialPositions;    // 初始位置,用于记录第一个点的位置
  12. };
  13. PolygonTool::PolygonTool(DrawShape shape)
  14.     :DrawTool(shape)
  15. {
  16.     item = NULL;      // 初始化 item 为 NULL
  17.     m_nPoints = 0;    // 初始化点的数量为 0
  18. }
  19. // 当首次按下时,会被添加两次,一次是item->addPoint(c_down);  item->addPoint(c_down+QPoint(1,0));
  20. // 为了初始化一个起始线段或者提供初步的形状轮廓,使得在接下来的鼠标移动事件中能够立即看到一个图形的初步形态。
  21. void PolygonTool::mousePressEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  22. {
  23.     // 调用基类的鼠标按下事件处理
  24.     DrawTool::mousePressEvent(event,scene);
  25.     if ( event->button() != Qt::LeftButton ) return;
  26.     // 如果当前没有正在绘制的图形项,即刚开始绘制
  27.     if ( item == NULL ){
  28.         // 根据绘制的形状类型创建相应的图形项
  29.         if ( c_drawShape == polygon ){
  30.             item = new GraphicsPolygonItem(NULL);      // 创建一个多边形项
  31.         }else if (c_drawShape == bezier ){            
  32.             item = new GraphicsBezier();               // 创建一个贝塞尔曲线项
  33.         }else if ( c_drawShape == polyline ){         
  34.             item = new GraphicsBezier(false);          // 创建一个折线
  35.         }else if ( c_drawShape == line ){              
  36.             item = new GraphicsLineItem(0);            // 创建一个直线项
  37.         }
  38.         item->setPos(event->scenePos());  // 设置图形项的位置为鼠标按下的位置              
  39.         scene->addItem(item);             // 将图形项添加到场景中
  40.         initialPositions = c_down;        // 记录起始位置
  41.         item->addPoint(c_down);           // 添加第一个点,因为是刚开始绘制
  42.         item->setSelected(true);          // 设置图形项为选中状态
  43.         m_nPoints++;
  44.     }else if ( c_down == c_last ){        // 如果按下的点与上一个点重合
  45.         /*
  46.         if ( item != NULL )
  47.         {
  48.             scene->removeItem(item);      // 从场景中移除当前图形项
  49.             delete item;
  50.             item = NULL ;
  51.             c_drawShape = selection;
  52.             selectMode = none;
  53.             return ;
  54.         }
  55.         */
  56.     }
  57.     item->addPoint(c_down+QPoint(1,0));        // 添加一个点,位置偏移一点以避免重合
  58.     m_nPoints++;                               // 点的数量加 1
  59.     selectMode = size ;                        // 设置选择模式为 size,用于调整大小
  60.     nDragHandle = item->handleCount();         // 设置拖动手柄为当前图形项的手柄数量
  61.     // 意味着将拖动手柄的索引设置为最后一个添加的手柄,即当前鼠标点击的位置。这有助于后续在鼠标移动事件中准确控制拖动的手柄。
  62. }
  63. void PolygonTool::mouseMoveEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  64. {
  65.    
  66.     DrawTool::mouseMoveEvent(event,scene);  // 调用基类的鼠标移动事件处理
  67.     setCursor(scene,Qt::CrossCursor);       // 设置光标为十字形
  68. //    selectTool.mouseMoveEvent(event,scene);  // 选择工具移动事件处理
  69.     if ( item != 0 ){  // 如果图形项存在
  70.          // 如果有有效的手柄并且选择模式是调整大小
  71.         if ( nDragHandle != Handle_None && selectMode == size ){
  72.             // 控制图形项的控制点位置
  73.             item->control(nDragHandle,c_last);
  74.         }
  75.     }
  76. }
  77. // 直线是点击开始坐标,和最后单击一个点坐标即可。其他形状需要双击才结束,所以放到另一个函数处理。
  78. void PolygonTool::mouseReleaseEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  79. {
  80.     // 调用基类的鼠标释放事件处理
  81.     DrawTool::mousePressEvent(event,scene);
  82.     // 绘制的形状类型如果只直线,需要特殊处理下
  83.     if ( c_drawShape == line ){                     
  84.         item->endPoint(event->scenePos());           // 设置直线的结束点
  85.         item->updateCoordinate();                    // 更新图形项的坐标,确之前的变化得到应用
  86.         emit scene->itemAdded( item );               // 触发图形项添加信号
  87.         // 清空成员变量
  88.         item = NULL;                                 // 将 item 设置为 NULL
  89.         selectMode = none;                           // 设置选择模式为 none
  90.         c_drawShape = selection;                     // 设置绘制模式为选择模式
  91.         m_nPoints = 0;                               // 重置点的数量
  92.     }
  93. }
  94. void PolygonTool::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event, DrawScene *scene)
  95. {
  96.     // 调用基类的鼠标双击事件处理
  97.     DrawTool::mouseDoubleClickEvent(event,scene);
  98.     // 设置图形项的结束点
  99.     item->endPoint(event->scenePos());
  100.     // 更新图形项的坐标
  101.     item->updateCoordinate();
  102.     // 触发图形项添加信号
  103.     emit scene->itemAdded( item );
  104.     // 清空成员变量
  105.     item = NULL;
  106.     selectMode = none;
  107.     c_drawShape = selection;
  108.     m_nPoints = 0;
  109. }
复制代码

4. GraphicsItem类


AbstractShapeType是模板基类,提供根本的绘制属性和方法。 如果AbstractShapeType模板,拥有两个直接子类GraphicsItemGroup和GraphicsItem。


4.1 AbstractShapeType类


AbstractShapeType 是一个模板类,提供了一些画图的根本属性和方法。它通过继续自 BaseType(默认为 QGraphicsItem)来扩展图形项的功能,主要用于定义外形的通用举动和属性。
  1. // AbstractShapeType模板类:定义了一些绘图形状的基本属性和方法。
  2. template < typename BaseType = QGraphicsItem >
  3. class AbstractShapeType : public BaseType  // 这么写,继承也是一个动态的,通过传参指定。
  4. {
  5. public:
  6.     // 构造函数,初始化基类,并设置笔刷和笔的初始状态
  7.    explicit AbstractShapeType(QGraphicsItem * parent = 0 )
  8.         :BaseType(parent)
  9.     {
  10.        // 设置画笔为无笔触(透明)
  11.         m_pen=QPen(Qt::NoPen);
  12.         // 设置画刷为随机颜色的画刷
  13.         m_brush= QBrush(QColor(rand() % 32 * 8, rand() % 32 * 8, rand() % 32 * 8));
  14.         // 初始化宽度和高度为0
  15.         m_width = m_height = 0;
  16.     }
  17.     virtual ~AbstractShapeType(){}
  18.     // 返回图形的显示名称
  19.     virtual QString displayName () const { return QString("AbstractType");}
  20.     // 处理控制点的操作,默认实现为空
  21.     virtual void control(int dir, const QPointF & delta ){ Q_UNUSED(dir);Q_UNUSED(delta);}
  22.     // 处理图形的拉伸操作,默认实现为空
  23.     virtual void stretch( int  , double  , double  , const QPointF & ) {}
  24.     // 返回图形的边界矩形
  25.     virtual QRectF  rect() const { return m_localRect; }
  26.     // 更新图形的坐标系,默认实现为空
  27.     virtual void updateCoordinate () {}
  28.     // 移动图形,默认实现为空
  29.     virtual void move( const QPointF & point ){Q_UNUSED(point);}
  30.     // 复制图形项,默认返回NULL
  31.     virtual QGraphicsItem * duplicate() const { return NULL;}
  32.     // 返回控制点的数量
  33.     virtual int handleCount() const { return m_handles.size();}
  34.     // 从XML加载图形,必须由派生类实现
  35.     virtual bool loadFromXml(QXmlStreamReader * xml ) = 0;
  36.     // 保存图形到XML,必须由派生类实现
  37.     virtual bool saveToXml( QXmlStreamWriter * xml ) = 0 ;
  38.     // 检查指定点是否与控制点碰撞,返回碰撞的控制点方向
  39.     int collidesWithHandle( const QPointF & point ) const
  40.     {
  41.         // 获取处理手柄的逆向迭代器的结束位置。m_handles:是一个存储图形项控制手柄的容器
  42.         const Handles::const_reverse_iterator hend =  m_handles.rend();
  43.         // 从末尾开始遍历所有处理手柄
  44.         for (Handles::const_reverse_iterator it = m_handles.rbegin(); it != hend; ++it)
  45.         {
  46.             // 将点从场景坐标系转换为处理手柄的局部坐标系
  47.             QPointF pt = (*it)->mapFromScene(point);
  48.             // 检查点是否在处理手柄的区域内
  49.             if ((*it)->contains(pt) ){
  50.                 return (*it)->dir();  // 返回处理手柄的方向
  51.             }
  52.         }
  53.         return Handle_None;  // 未碰撞,返回无控制点
  54.     }
  55.     // 返回指定控制点的位置
  56.     virtual QPointF handlePos( int handle ) const
  57.     {
  58.         // 从末尾开始遍历所有处理手柄
  59.         const Handles::const_reverse_iterator hend =  m_handles.rend();
  60.         for (Handles::const_reverse_iterator it = m_handles.rbegin(); it != hend; ++it)
  61.         {
  62.             if ((*it)->dir() == handle ){
  63.                 return (*it)->pos();
  64.             }
  65.         }
  66.         // 如果找不到处理手柄,返回默认的 QPointF()
  67.         return QPointF();
  68.     }
  69.     // 根据缩放方向交换控制点
  70.     int swapHandle(int handle, const QPointF& scale ) const
  71.     {
  72.         int dir = Handle_None;
  73.         if ( scale.x() < 0 || scale.y() < 0 ){
  74.             // 根据当前处理手柄和比例因子的方向,计算交换后的处理手柄方向
  75.             switch (handle) {
  76.             case RightTop:
  77.                 if ( scale.x() < 0 && scale.y() < 0 )
  78.                     dir = LeftBottom;
  79.                 else if ( scale.x() > 0 && scale.y() < 0 )
  80.                     dir = RightBottom;
  81.                 else
  82.                     dir = LeftTop;
  83.                 break;
  84.             case RightBottom:
  85.                 if ( scale.x() < 0 && scale.y() < 0 )
  86.                     dir = LeftTop;
  87.                 else if ( scale.x() > 0 && scale.y() < 0 )
  88.                     dir = RightTop;
  89.                 else
  90.                     dir = LeftBottom;
  91.                 break;
  92.             case LeftBottom:
  93.                 if ( scale.x() < 0 && scale.y() < 0 )
  94.                     dir = RightTop;
  95.                 else if ( scale.x() > 0 && scale.y() < 0 )
  96.                     dir = LeftTop;
  97.                 else
  98.                     dir = RightBottom;
  99.                 break;
  100.             case LeftTop:
  101.                 if ( scale.x() < 0 && scale.y() < 0 )
  102.                     dir = RightBottom;
  103.                 else if ( scale.x() > 0 && scale.y() < 0 )
  104.                     dir = LeftBottom;
  105.                 else
  106.                     dir = RightTop;
  107.                 break;
  108.             case Right:
  109.                 if (scale.x() < 0 )
  110.                     dir = Left;
  111.                 break;
  112.             case Left:
  113.                 if (scale.x() < 0 )
  114.                     dir = Right;
  115.                 break;
  116.             case Top:
  117.                 if (scale.y()<0)
  118.                     dir = Bottom;
  119.                 break;
  120.             case Bottom:
  121.                 if (scale.y()<0)
  122.                    dir = Top;
  123.                 break;
  124.             }
  125.         }
  126.         return dir;
  127.     }
  128.     // 返回指定控制点的相对位置
  129.     virtual QPointF opposite( int handle ) {
  130.         QPointF pt;
  131.         switch (handle) {
  132.         case Right:
  133.             pt = m_handles.at(Left-1)->pos();
  134.             break;
  135.         case RightTop:
  136.             pt = m_handles[LeftBottom-1]->pos();
  137.             break;
  138.         case RightBottom:
  139.             pt = m_handles[LeftTop-1]->pos();
  140.             break;
  141.         case LeftBottom:
  142.             pt = m_handles[RightTop-1]->pos();
  143.             break;
  144.         case Bottom:
  145.             pt = m_handles[Top-1]->pos();
  146.             break;
  147.         case LeftTop:
  148.             pt = m_handles[RightBottom-1]->pos();
  149.             break;
  150.         case Left:
  151.             pt = m_handles[Right-1]->pos();
  152.             break;
  153.         case Top:
  154.             pt = m_handles[Bottom-1]->pos();
  155.             break;
  156.          }
  157.         return pt;
  158.     }
  159.     // 返回笔刷颜色
  160.     QColor brushColor() const {return m_brush.color();}
  161.     // 获取画刷
  162.     QBrush brush() const {return m_brush;}
  163.     // 获取画笔
  164.     QPen   pen() const {return m_pen;}
  165.     // 获取画笔的颜色
  166.     QColor penColor() const {return m_pen.color();}
  167.     // 设置画笔
  168.     void setPen(const QPen & pen ) { m_pen = pen;}
  169.     // 设置画刷
  170.     void setBrush( const QBrush & brush ) { m_brush = brush ; }
  171.     // 设置画刷的颜色
  172.     void setBrushColor( const QColor & color ) { m_brush.setColor(color);}
  173.     // 获取宽度
  174.     qreal width() const { return m_width ; }
  175.     // 设置宽度,并更新坐标
  176.     void   setWidth( qreal width )
  177.     {
  178.         m_width = width ;
  179.         updateCoordinate();
  180.     }
  181.     // 获取高度
  182.     qreal  height() const {return m_height;}
  183.     // 设置高度,并更新坐标
  184.     void   setHeight ( qreal height )
  185.     {
  186.         m_height = height ;
  187.         updateCoordinate();
  188.     }
  189. protected:
  190.     // 更新处理手柄(虚函数,子类可以重写)
  191.     virtual void updatehandles(){}            
  192.     // 设置处理手柄的状态
  193.     void setState(SelectionHandleState st)
  194.     {
  195.         // 遍历所有处理手柄并设置其状态
  196.         const Handles::iterator hend =  m_handles.end();
  197.         for (Handles::iterator it = m_handles.begin(); it != hend; ++it)
  198.             (*it)->setState(st);
  199.     }
  200.     // 画刷(用于填充形状)
  201.     QBrush m_brush;
  202.     // 画笔(用于边框)
  203.     QPen   m_pen ;
  204.     // 处理手柄的容器
  205.     typedef std::vector<SizeHandleRect*> Handles;
  206.     Handles m_handles;
  207.     // 本地矩形(形状的边界)
  208.     QRectF m_localRect;
  209.     // 形状的宽度和高度
  210.     qreal m_width;
  211.     qreal m_height;
  212. };
  213. typedef  AbstractShapeType< QGraphicsItem > AbstractShape;
复制代码
 4.2 GraphicsItem类


可以看到GrapicsItems继续了AbstractShapeType<QGraphicsItem>,而AbstractShapeType是继续了GrapicsItems。这里设计有些题目,搞得复杂了,建议稍微看看就得了。
留意:这里似乎是相互继续的布局,c++不支持相互继续。举个简单例子:
  1. // 不支持直接相互继承
  2. class A : public B{}
  3. class B :  public A> {}
  4. // 支持间接的相互继承,但不建议这么做
  5. template < typename BaseType = QGraphicsItem >
  6. class A : public BaseType {}
  7. class B :  public A<B> {}
复制代码
相互继续:B继续了A<B>,而A<B>又继续了B,C++ 不支持真正的“相互继续”,即两个类互相直接继续彼此。模板类继续的情况虽然复杂,但并不即是相互继续。
建议在设计时避免这种复杂的继续布局,以减少潜在的题目和困惑。
  1. class GraphicsItem : public QObject,
  2.         public AbstractShapeType<QGraphicsItem>
  3. {
  4.     Q_OBJECT
  5.     // Q_PROPERTY 自动生成对属性的访问函数,使得属性可以方便地通过 Qt 的信号槽机制、Qt Designer 或其他方式进行访问。
  6.     Q_PROPERTY(QColor pen READ penColor WRITE setPen )        // 声明pen和setPen方法,是读还是写操作。
  7.     Q_PROPERTY(QColor brush READ brushColor WRITE setBrushColor )
  8.     Q_PROPERTY(qreal  width READ width WRITE setWidth )
  9.     Q_PROPERTY(qreal  height READ height WRITE setHeight )
  10.     Q_PROPERTY(QPointF  position READ pos WRITE setPos )
  11. public:
  12.     GraphicsItem(QGraphicsItem * parent );
  13.     enum {Type = UserType+1};   // 定义一个唯一的类型标识符        
  14.     int  type() const { return Type; } // 返回类型标识符
  15.     virtual QPixmap image() ;  // 声明一个虚拟函数用于获取图像
  16. signals:
  17.     void selectedChange(QGraphicsItem *item);  // 声明一个信号,当选择状态改变时发射
  18. protected:
  19.     QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value); // 处理图形项的变化
  20.     void updatehandles(); // 更新控制柄的位置
  21.     void contextMenuEvent(QGraphicsSceneContextMenuEvent *event);  // 处理上下文菜单事件
  22.     bool readBaseAttributes(QXmlStreamReader * xml );     // 从 XML 读取基本属性
  23.     bool writeBaseAttributes( QXmlStreamWriter * xml );   // 将基本属性写入 XML
  24. };
  25. GraphicsItem::GraphicsItem(QGraphicsItem *parent)
  26.     :AbstractShapeType<QGraphicsItem>(parent)
  27. {
  28.     /*  // 设置图形项的效果
  29.     QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect;
  30.     effect->setBlurRadius(4);
  31.     setGraphicsEffect(effect);
  32.    */
  33.    // 初始化控制柄
  34.     m_handles.reserve(Left);  // 预留 m_handles 容器的空间,以提高性能并避免不必要的内存重新分配。
  35.     for (int i = LeftTop; i <= Left; ++i) {  // 遍历每个控制柄方向
  36.         SizeHandleRect *shr = new SizeHandleRect(this,i);  // 创建一个控制柄,都是矩形的边界框
  37.         m_handles.push_back(shr);
  38.     }
  39.     // 设置图形项的标志
  40.     setFlag(QGraphicsItem::ItemIsMovable, true);  // 允许移动
  41.     setFlag(QGraphicsItem::ItemIsSelectable, true); // 允许选择
  42.     setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);  // 当几何形状发生变化时发送通知
  43.     this->setAcceptHoverEvents(true);  // 接受悬停事件
  44. }
  45. QPixmap GraphicsItem::image() {
  46.     QPixmap pixmap(64, 64);   // 创建一个 64x64 的图像
  47.     pixmap.fill(Qt::transparent);  // 填充透明背景
  48.     //设置绘制属性
  49.     QPainter painter(&pixmap);     // 创建绘图对象
  50.     setPen(QPen(Qt::black));       // 设置画笔为黑色
  51.     setBrush(Qt::white);           // 设置画刷为白色
  52.     QStyleOptionGraphicsItem *styleOption = new QStyleOptionGraphicsItem;  // 创建绘图选项
  53.     //painter.translate(m_localRect.center().x(),m_localRect.center().y());  // 移动绘图位置(被注释掉了)
  54.     // 绘制图形: 实现不在代码中提供,因此图像的具体样式取决于 paint 函数如何绘制图形。
  55.     paint(&painter,styleOption);  
  56.     delete styleOption;  // 删除绘图选项
  57.     return pixmap;  // 返回图像
  58. }
  59. void GraphicsItem::updatehandles()
  60. {
  61.     const QRectF &geom = this->boundingRect();  // 获取图形项的边界矩形
  62.     // 获取控制柄列表的末尾
  63.     const Handles::iterator hend =  m_handles.end();  
  64.     for (Handles::iterator it = m_handles.begin(); it != hend; ++it) {
  65.         SizeHandleRect *hndl = *it;
  66.         // 根据控制柄的方向更新位置
  67.         switch (hndl->dir()) {  // hndl是哪个方向
  68.         case LeftTop: // 左上角
  69.             hndl->move(geom.x() , geom.y() );  // move 方法将手柄的左上角移动到 (x, y) 坐标。
  70.             break;
  71.         case Top:     // 是上边
  72.             hndl->move(geom.x() + geom.width() / 2 , geom.y() );  // 将手柄移动到图形项的上边中点位置。
  73.             break;
  74.         case RightTop:
  75.             hndl->move(geom.x() + geom.width() , geom.y() );
  76.             break;
  77.         case Right:
  78.             hndl->move(geom.x() + geom.width() , geom.y() + geom.height() / 2 );
  79.             break;
  80.         case RightBottom:
  81.             hndl->move(geom.x() + geom.width() , geom.y() + geom.height() );
  82.             break;
  83.         case Bottom:
  84.             hndl->move(geom.x() + geom.width() / 2 , geom.y() + geom.height() );
  85.             break;
  86.         case LeftBottom:
  87.             hndl->move(geom.x(), geom.y() + geom.height());
  88.             break;
  89.         case Left:
  90.             hndl->move(geom.x(), geom.y() + geom.height() / 2);
  91.             break;
  92.         default:
  93.             break;
  94.         }
  95.     }
  96. }
  97. void GraphicsItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
  98. {
  99.     Q_UNUSED(event);  // 忽略事件
  100. }
  101. // 从xml读取属性值,赋值给对象实例
  102. bool GraphicsItem::readBaseAttributes(QXmlStreamReader *xml)
  103. {
  104.     qreal x = xml->attributes().value(tr("x")).toDouble();
  105.     qreal y = xml->attributes().value(tr("y")).toDouble();
  106.     m_width = xml->attributes().value("width").toDouble();
  107.     m_height = xml->attributes().value("height").toDouble();
  108.     setZValue(xml->attributes().value("z").toDouble());
  109.     setRotation(xml->attributes().value("rotate").toDouble());
  110.     setPos(x,y);
  111.     return true;
  112. }
  113. // 从对象实例中,写入属性值到xml文件中
  114. bool GraphicsItem::writeBaseAttributes(QXmlStreamWriter *xml)
  115. {
  116.     xml->writeAttribute(tr("rotate"),QString("%1").arg(rotation()));  // "rotate",表示 XML 属性的名称。%1 是一个占位符
  117.     xml->writeAttribute(tr("x"),QString("%1").arg(pos().x()));
  118.     xml->writeAttribute(tr("y"),QString("%1").arg(pos().y()));
  119.     xml->writeAttribute(tr("z"),QString("%1").arg(zValue()));
  120.     xml->writeAttribute(tr("width"),QString("%1").arg(m_width));
  121.     xml->writeAttribute(tr("height"),QString("%1").arg(m_height));
  122.     return true;
  123. }
  124. QVariant GraphicsItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
  125. {
  126.     // 如果选择状态发生改变
  127.     if ( change == QGraphicsItem::ItemSelectedHasChanged ) {
  128.         // 如果不是一个组
  129.         QGraphicsItemGroup *g = dynamic_cast<QGraphicsItemGroup*>(parentItem());
  130.         if (!g)
  131.             // 设置控制柄的状态:SelectionHandleOff, SelectionHandleInactive, SelectionHandleActive
  132.             setState(value.toBool() ? SelectionHandleActive : SelectionHandleOff);  
  133.         // 如果是组,取消选择状态
  134.         else{
  135.             setSelected(false);
  136.             return QVariant::fromValue<bool>(false);
  137.         }
  138.     }
  139.     /*
  140.     else if (change == ItemPositionChange && scene()) {
  141.         // value is the new position.
  142.         QPointF newPos = value.toPointF();
  143.         QRectF rect = scene()->sceneRect();
  144.         if (!rect.contains(newPos)) {
  145.             // Keep the item inside the scene rect.
  146.             newPos.setX(qMin(rect.right()-boundingRect().width()/2, qMax(newPos.x(), rect.left()+boundingRect().width()/2)));
  147.             newPos.setY(qMin(rect.bottom()-boundingRect().height()/2, qMax(newPos.y(), rect.top()+boundingRect().height()/2)));
  148.             return newPos;
  149.         }
  150.     }
  151.     */
  152.     return QGraphicsItem::itemChange(change, value);  // 调用基类的 itemChange 方法
  153. }
复制代码
4.3 GraphicsRectItem类


 GraphicsRectItem 类的作用主要是为图形场景中的矩形、圆角矩形元素提供一个基础的图形项,并为其提供各种功能和操作接口。
  1. class GraphicsRectItem : public GraphicsItem
  2. {
  3. public:
  4.     // 构造函数,初始化矩形对象,rect为矩形区域,isRound表示是否是圆角矩形
  5.     GraphicsRectItem(const QRect & rect, bool isRound = false, QGraphicsItem * parent = 0 );
  6.     // 返回图形项的边界矩形
  7.     QRectF boundingRect() const;
  8.     // 返回图形项的形状,通常用于碰撞检测
  9.     QPainterPath shape() const;
  10.     // 控制柄操作,用于调整图形项
  11.     void control(int dir, const QPointF & delta);
  12.     // 拉伸操作,调整图形项的大小
  13.     void stretch(int handle , double sx , double sy , const QPointF & origin);
  14.     // 返回当前矩形区域
  15.     QRectF  rect() const {  return m_localRect;}
  16.     // 更新图形项的坐标
  17.     void updateCoordinate();
  18.     // 移动图形项
  19.     void move( const QPointF & point );
  20.     // 复制当前图形项
  21.     QGraphicsItem *duplicate () const ;
  22.     // 返回图形项的显示名称
  23.     QString displayName() const { return tr("rectangle"); }
  24.     // 从XML加载图形项
  25.     virtual bool loadFromXml(QXmlStreamReader * xml );
  26.     // 保存图形项到XML
  27.     virtual bool saveToXml( QXmlStreamWriter * xml );
  28. protected:
  29.     // 更新控制柄位置
  30.     void updatehandles();
  31.     // 绘制图形项
  32.     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
  33.     bool m_isRound;           // 是否为圆角矩形
  34.     qreal m_fRatioY;          // 控制圆角矩形的水平和垂直圆角半径比例
  35.     qreal m_fRatioX;          //
  36.     QRectF m_initialRect;     // m_initialRect、m_localRect: 初始矩形和当前矩形的位置和尺寸。
  37.     QPointF opposite_;        // 参考点,矩形的中心点
  38.     QPointF m_originPoint;    // 原点。m_originPoint、opposite_: 用于几何变换和定位的辅助点。
  39. };
  40. // 构造函数实现
  41. GraphicsRectItem::GraphicsRectItem(const QRect & rect , bool isRound , QGraphicsItem *parent)
  42.     :GraphicsItem(parent)    // 调用基类构造函数
  43.     ,m_isRound(isRound)      // 初始化是否圆角
  44.     ,m_fRatioX(1/10.0)       // 初始化宽度圆角比例
  45.     ,m_fRatioY(1/3.0)        // 初始化高度圆角比例
  46. {
  47.     m_width = rect.width();       // 设置矩形宽度
  48.     m_height = rect.height();     // 设置矩形高度
  49.     m_initialRect = rect;         // 初始化矩形区域
  50.     m_localRect = rect;           // 设置本地矩形区域
  51.     m_originPoint = QPointF(0,0); // 设置原点
  52.     // 如果是圆角矩形,添加控制柄用于调整圆角
  53.     if( m_isRound ){
  54.         SizeHandleRect *shr = new SizeHandleRect(this, 9 , true);
  55.         m_handles.push_back(shr);
  56.         shr = new SizeHandleRect(this, 10 , true);
  57.         m_handles.push_back(shr);
  58.         //shr = new SizeHandleRect(this, 11 , true);
  59.         //m_handles.push_back(shr);
  60.     }
  61.     updatehandles();  // 更新控制柄位置
  62. }
  63. // 返回图形项的边界矩形
  64. QRectF GraphicsRectItem::boundingRect() const
  65. {
  66.     return m_localRect;  // 使用当前的本地矩形作为边界
  67. }
  68. // 返回图形项的形状路径
  69. QPainterPath GraphicsRectItem::shape() const
  70. {
  71.     QPainterPath path;  // 创建绘制路径对象
  72.    
  73.     double rx,ry;       // 圆角的半径
  74.     if(m_fRatioX<=0)
  75.        rx=0;            // 如果宽度比例小于等于0,则圆角半径为0
  76.     else {
  77.         rx = m_width * m_fRatioX + 0.5;  // 否则计算圆角半径
  78.     }
  79.     if ( m_fRatioY <=0 )
  80.         ry = 0;        
  81.     else
  82.         ry = m_height * m_fRatioY + 0.5;
  83.     // 如果是圆角矩形
  84.     if ( m_isRound )
  85.         path.addRoundedRect(rect(),rx,ry);  // 添加圆角矩形到路径
  86.     else
  87.         path.addRect(rect());               // 否则添加普通矩形到路径
  88.     return path;
  89. }
  90. // 控制矩形的形状,通过手柄调整圆角的半径。
  91. void GraphicsRectItem::control(int dir, const QPointF & delta)
  92. {
  93.     // 从父项的坐标系转换到当前图形项(GraphicsRectItem)的本地坐标系中。
  94.     // 该坐标是10个关键点之一。
  95.     QPointF local_pt = mapFromParent(delta);  
  96.     // 根据不同的控制方向调整属性
  97.     switch (dir) {
  98.     // 如果是第9个关键点
  99.     // 更新垂直方向的圆角半径比例。
  100.     // 保证 y 不会超过矩形中心位置,也不会低于矩形顶部。
  101.     case 9:
  102.     {
  103.         QRectF rect_roi = rect();                  // 获取当前矩形区域
  104.         int local_y = local_pt.y();                // 获取本地的y坐标
  105.         
  106.         // 控制 local_y 位置在合理范围内,从而有效限制圆角的最大和最小半径。
  107.         if(local_y > rect_roi.center().y() )  local_y = rect_roi.center().y(); // 限制不超过矩形中心           
  108.         if(local_y < rect_roi.top())  local_y = rect_roi.top();   // 限制不超过顶部
  109.         // 计算 m_fRatioY: 当前手柄位置 y 到矩形顶部的距离占矩形高度的比例。
  110.         int H= rect_roi.height();     // 获取矩形高度
  111.         if(H==0)  H = 1;    // 避免除零
  112.         m_fRatioY = std::abs(((float)(rect_roi.top()- local_y)))/H;     // 计算并设置高度比例
  113.     }
  114.         break;
  115.      // 水平调整圆角
  116.      // 保证 x 不会低于中心位置,也不会超过矩形右侧。
  117.     case 10:   
  118.     {
  119.         QRectF delta1 = rect();
  120.         int x = local_pt.x();
  121.         // 保证 x 不会低于中心位置,也不会超过矩形右侧。
  122.         if(x < delta1.center().x() ) x = delta1.center().x();
  123.         if(x>delta1.right()) x = delta1.right();
  124.             
  125.         int W= delta1.width();
  126.         if(W==0) W=1;
  127.         m_fRatioX = std::abs(((float)(delta1.right()-x)))/W;
  128.         break;
  129.     }
  130.     // 用于调整原点位置
  131.     case 11:
  132.     {
  133. //        setTransform(transform().translate(-local.x(),-local.y()));
  134. //        setTransformOriginPoint(local.x(),local.y());
  135. //        setTransform(transform().translate(local.x(),local.y()));
  136.         m_originPoint = local_pt;  // 设置原点为当前本地坐标
  137.     }
  138.         break;
  139.    default:
  140.         break;
  141.     }
  142.     prepareGeometryChange();   // 准备几何图形的变化,通知Qt重新绘制
  143.     updatehandles();           // 更新控制柄位置
  144. }
  145. //  对矩形进行拉伸变换,根据给定的比例和参考点调整矩形的尺寸。
  146. void GraphicsRectItem::stretch(int handle , double sx, double sy, const QPointF & origin)
  147. {
  148.     QTransform trans;  // 创建一个变换对象
  149.     switch (handle) {  // 根据控制柄的不同类型调整拉伸比例
  150.     case Right:
  151.     case Left:
  152.         sy = 1;        // 如果是左右控制柄,则只拉伸宽度,不改变高度
  153.         break;
  154.     case Top:
  155.     case Bottom:       // 如果是上下控制柄,则只拉伸高度,不改变宽度
  156.         sx = 1;
  157.         break;
  158.     default:
  159.         break;
  160.     }
  161.     opposite_ = origin;  // 设置参考点,这里是矩形的中心点
  162.     // 这样做的好处是能够以特定点为中心进行变换(如缩放),而不是默认的图形左上角或中心。
  163.     trans.translate(origin.x(), origin.y());   // 将坐标系原点移动到指定位置
  164.     trans.scale(sx,sy);                        // 这样缩放是以新的原点(origin)为中心进行的。
  165.     trans.translate(-origin.x(), -origin.y()); // 再将坐标系原点移回去
  166.     prepareGeometryChange();                        // 准备几何图形变化,通知Qt重新绘制
  167.     m_localRect = trans.mapRect(m_initialRect);     // 使用变换对象映射初始矩形到新的大小
  168.     m_width = m_localRect.width();                  // 更新宽度
  169.     m_height = m_localRect.height();                // 更新高度
  170.     updatehandles();                                // 更新控制柄位置
  171. }
  172. // 更新图形项的坐标和变换,以适应其场景中的位置变化。
  173. void GraphicsRectItem::updateCoordinate()
  174. {
  175.     // 计算原点与矩形中心的偏移量
  176.     QPointF pt1,pt2,delta;  // 声明坐标点和差值
  177.     pt1 = mapToScene(transformOriginPoint());  // 获取变换原点的场景坐标
  178.     pt2 = mapToScene(m_localRect.center());    // 获取矩形中心的场景坐标
  179.     delta = pt1 - pt2;                         // 计算原点与中心点的差值
  180.     // 如果没有父项,其坐标变换相对的是场景坐标系,而不是其他父项的坐标系。
  181.     if (!parentItem() ){
  182.         prepareGeometryChange();    // 准备几何图形变化
  183.         // 移动m_localRect,将m_localRect的中心点移动到其原点位置。其中m_width是矩形的宽度
  184.         // 通过将原点设为中心,任何缩放、旋转等变换都会以图形的中心为参考点
  185.         m_localRect = QRectF(-m_width/2,-m_height/2,m_width,m_height);
  186.         // 更新宽度 高度
  187.         m_width = m_localRect.width();
  188.         m_height = m_localRect.height();
  189.         // 上面只是求变换参数,这里开始应用变换
  190.         // 首先将图形移动到坐标系原点,以对齐新的坐标系中心。
  191.         setTransform(transform().translate(delta.x(),delta.y()));  
  192.         // 设置变换原点为坐标系原点,也就是m_localRect中心点。
  193.         setTransformOriginPoint(m_localRect.center());
  194.         // 它会调整图形项的场景坐标位置,但不会影响项的局部坐标系,也不会影响项的变换矩阵
  195.         // 操作的是图形项的场景坐标,属于一种外部移动。
  196.         moveBy(-delta.x(),-delta.y());  
  197.         // 它会将变换矩阵中添加一个平移变换,改变图形项在其局部坐标系中的位置。
  198.         // 改变的是图形项内部的变换矩阵,属于一种内部变换。
  199.         setTransform(transform().translate(-delta.x(),-delta.y()));
  200.         // 重置相对点
  201.         opposite_ = QPointF(0,0);
  202.         // 更新控制柄位置
  203.         updatehandles();
  204.     }
  205.     m_initialRect = m_localRect;  // 更新初始矩形为当前矩形
  206. }
  207. // 移动图形项到指定点。
  208. void GraphicsRectItem::move(const QPointF &point)
  209. {
  210.     moveBy(point.x(),point.y());
  211. }
  212. // 创建一个当前图形项的副本,复制其位置、变换、状态等属性。
  213. QGraphicsItem *GraphicsRectItem::duplicate() const
  214. {
  215.     // 创建新的矩形项,复制当前矩形和是否圆角属性
  216.     GraphicsRectItem * item = new GraphicsRectItem( rect().toRect(),m_isRound);
  217.    
  218.     // 设置其属性
  219.     item->m_width = width();             //
  220.     item->m_height = height();           //
  221.     item->setPos(pos().x(),pos().y());   // 复制位置
  222.     item->setPen(pen());                 // 复制画笔
  223.     item->setBrush(brush());             // 复制画刷
  224.     item->setTransform(transform());     // 复制变换
  225.     item->setTransformOriginPoint(transformOriginPoint());   // 复制变换原点
  226.     item->setRotation(rotation());       // 复制旋转角度
  227.     item->setScale(scale());             // 复制缩放比例
  228.     item->setZValue(zValue()+0.1);       // 复制Z值并稍作提升
  229.     item->m_fRatioY = m_fRatioY;         //
  230.     item->m_fRatioX = m_fRatioX;         //
  231.     item->updateCoordinate();            // 更新复制项的坐标
  232.     return item;
  233. }
  234. // 从 XML 中加载图形项的状态。
  235. bool GraphicsRectItem::loadFromXml(QXmlStreamReader * xml )
  236. {
  237.     m_isRound = (xml->name() == tr("roundrect"));  // 判断是否为圆角矩形
  238.     if ( m_isRound ){
  239.         m_fRatioX = xml->attributes().value(tr("rx")).toDouble();
  240.         m_fRatioY = xml->attributes().value(tr("ry")).toDouble();
  241.     }
  242.     readBaseAttributes(xml);   // 读取基础属性
  243.     updateCoordinate();
  244.     xml->skipCurrentElement(); // 跳过当前元素
  245.     return true;
  246. }
  247. // 将图形项的状态保存到 XML 中。
  248. bool GraphicsRectItem::saveToXml(QXmlStreamWriter * xml)
  249. {
  250.     if ( m_isRound ){
  251.         xml->writeStartElement(tr("roundrect")); // 写入开始标签
  252.         xml->writeAttribute(tr("rx"),QString("%1").arg(m_fRatioX));
  253.         xml->writeAttribute(tr("ry"),QString("%1").arg(m_fRatioY));
  254.     }
  255.     else
  256.         xml->writeStartElement(tr("rect"));
  257.     writeBaseAttributes(xml); // 写入基础属性
  258.     xml->writeEndElement();   // 写入结束标签
  259.     return true;
  260. }
  261. // 更新手柄的位置,用于控制矩形的圆角和变换。
  262. void GraphicsRectItem::updatehandles()
  263. {
  264.     const QRectF &geom = this->boundingRect();   // 获取当前边界矩形
  265.     GraphicsItem::updatehandles();               // 调用基类的更新控制柄方法
  266.     // 如果是圆角矩形
  267.     if ( m_isRound ){
  268.         // 更新第8号控制柄位置,即控制圆角y方向半径比例
  269.         m_handles[8]->move( geom.right() , geom.top() + geom.height() * m_fRatioY );
  270.         // // 更新第9号控制柄位置,即控制圆角x方向半径比例
  271.         m_handles[9]->move( geom.right() - geom.width() * m_fRatioX , geom.top());
  272.         //m_handles[10]->move(m_originPoint.x(),m_originPoint.y());
  273.     }
  274. }
  275. // 静态函数,重新计算点集的边界矩形
  276. static
  277. QRectF RecalcBounds(const QPolygonF&  pts)
  278. {
  279.     // 以第一个点为初始边界,后面不断地更新,最终找到真正的边界点。
  280.     QRectF bounds(pts[0], QSize(0, 0));
  281.     // 遍历所有点
  282.     for (int i = 1; i < pts.count(); ++i)
  283.     {
  284.         if (pts[i].x() < bounds.left())    // 更新左边界
  285.             bounds.setLeft(pts[i].x());
  286.         if (pts[i].x() > bounds.right())   // 更新右边界
  287.             bounds.setRight(pts[i].x());
  288.         if (pts[i].y() < bounds.top())     // 更新上边界
  289.             bounds.setTop(pts[i].y());
  290.         if (pts[i].y() > bounds.bottom())  // 更新下边界
  291.             bounds.setBottom (pts[i].y());
  292.     }
  293.     // 确保矩形的表示形式统一,即总是左上角的坐标最小,宽度和高度为正值。
  294.     bounds = bounds.normalized();  // 将边界标准化
  295.     return bounds;
  296. }
  297. // 绘制图形项
  298. void GraphicsRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  299. {
  300.    painter->setPen(pen());      // 设置画笔
  301.    painter->setBrush(brush());  // 设置画刷
  302.    // 圆角的半径
  303.    double rx,ry;
  304.    if(m_fRatioX<=0) rx=0;                   // 如果宽度比例小于等于0,则圆角半径为0
  305.    else rx = m_width * m_fRatioX + 0.5;     // 否则计算宽度圆角半径,半径+0.5,防止为0
  306.    if ( m_fRatioY <=0 ) ry = 0;
  307.    else ry = m_height * m_fRatioY + 0.5;
  308.    // 绘制圆角矩形
  309.    if ( m_isRound ) painter->drawRoundedRect(rect(),rx,ry);
  310.    // 否则绘制普通矩形
  311.    else painter->drawRect(rect().toRect());
  312.    painter->setPen(Qt::blue);
  313.    // 在矩形的中心点位置,绘制一个十字
  314.    painter->drawLine(QLine(QPoint(opposite_.x()-6,opposite_.y()),QPoint(opposite_.x()+6,opposite_.y())));  // 绘制水平线
  315.    painter->drawLine(QLine(QPoint(opposite_.x(),opposite_.y()-6),QPoint(opposite_.x(),opposite_.y()+6)));  // 绘制垂直线
  316.    // 如果图形项被选中
  317.    if (option->state & QStyle::State_Selected)
  318.        qt_graphicsItem_highlightSelected(this, painter, option);  // 调用Qt函数高亮显示图形项
  319. }
复制代码
4.4 GraphicsEllipseItem类


GraphicsEllipseItem 类是一个用于管理和操作椭圆、扇形图形项的类,它继续自 GraphicsRectItem,扩展了矩形图形项的功能,以支持椭圆外形的绘制和交互。 
  1. class GraphicsEllipseItem :public GraphicsRectItem
  2. {
  3. public:
  4.     // 构造函数,初始化椭圆项
  5.     GraphicsEllipseItem(const QRect & rect , QGraphicsItem * parent = 0);
  6.     // 返回椭圆的绘制路径
  7.     QPainterPath shape() const;
  8.     // 根据控制方向和偏移量调整椭圆的属性
  9.     void control(int dir, const QPointF & delta );
  10.     // 返回椭圆的边界矩形
  11.     QRectF boundingRect() const ;
  12.     // 复制当前椭圆项并返回新的实例
  13.     QGraphicsItem *duplicate() const;
  14.     // 返回椭圆的显示名称
  15.     QString displayName() const { return tr("ellipse"); }
  16.     // 从 XML 文件加载椭圆的属性
  17.     virtual bool loadFromXml(QXmlStreamReader * xml );
  18.     // 保存椭圆的属性到 XML 文件
  19.     virtual bool saveToXml( QXmlStreamWriter * xml );
  20. protected:
  21.     // 更新控制柄的位置
  22.     void updatehandles();
  23.     // 绘制椭圆
  24.     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
  25.     int   m_startAngle;  // 起始角度
  26.     int   m_spanAngle;   // 跨度角度
  27. };
  28. GraphicsEllipseItem::GraphicsEllipseItem(const QRect & rect ,QGraphicsItem *parent)
  29.     :GraphicsRectItem(rect,parent)
  30. {
  31.     m_startAngle = 40;   // 初始化起始角度
  32.     m_spanAngle  = 400;  // 初始化跨度角度
  33.     // 创建用于控制起始角度的控制柄 (编号9)
  34.     SizeHandleRect *shr = new SizeHandleRect(this, 9 , true);  // 9特指是圆心点
  35.     m_handles.push_back(shr);
  36.     // 创建用于控制终止角度的控制柄 (编号10)
  37.     shr = new SizeHandleRect(this, 10 , true);                 // 10特指是圆上点
  38.     m_handles.push_back(shr);
  39.     // 更新所有控制柄的位置
  40.     updatehandles();
  41. }
  42. // 返回椭圆的绘制路径
  43. QPainterPath GraphicsEllipseItem::shape() const
  44. {
  45.     QPainterPath path;
  46.     // 使用较大的作为起始角度。
  47.     int startAngle = m_startAngle <= m_spanAngle ? m_startAngle : m_spanAngle;
  48.     // 使用较小的作为结束角度。
  49.     int endAngle = m_startAngle >= m_spanAngle ? m_startAngle : m_spanAngle;
  50.    
  51.     // 确保跨度不超过360度
  52.     if(endAngle - startAngle > 360) endAngle = startAngle + 360;
  53.     // 如果 m_localRect 为空矩形,返回空路径
  54.     if (m_localRect.isNull()) return path;
  55.     // 如果跨度不是完整的360度。说明不是完成的椭圆,是扇形结构
  56.     if ((endAngle - startAngle) % 360 != 0 ) {
  57.         path.moveTo(m_localRect.center());     // 移动到矩形中心     
  58.         path.arcTo(m_localRect, startAngle, endAngle - startAngle);  // 绘制扇形
  59.     }
  60.     // 说明是完整的椭圆
  61.     else {
  62.         path.addEllipse(m_localRect);
  63.     }
  64.     path.closeSubpath();  // 关闭子路径
  65.     return path;
  66. }
  67. // 根据控制方向和偏移量调整椭圆的属性
  68. void GraphicsEllipseItem::control(int dir, const QPointF & delta)
  69. {
  70.     QPointF local = mapFromScene(delta);  // 将控制点的场景坐标转换为本地坐标
  71.     switch (dir) {  // 根据不同的控制方向调整属性
  72.     // 起始点角度:操作点和中心点连线的角度
  73.     case 9:
  74.     {
  75.         qreal len_y = local.y() - m_localRect.center().y();
  76.         qreal len_x = local.x() - m_localRect.center().x();
  77.         m_startAngle = -atan2(len_y,len_x)*180/M_PI;
  78.     }
  79.         break;
  80.     // 结束点:操作点和中心点连线的角度
  81.     case 10:
  82.     {
  83.         qreal len_y = local.y() - m_localRect.center().y();
  84.         qreal len_x = local.x() - m_localRect.center().x();
  85.         m_spanAngle = -atan2(len_y,len_x)*180/M_PI;
  86.         break;
  87.     }
  88.    default:
  89.         break;
  90.     }
  91.     prepareGeometryChange(); // 准备几何形状变化
  92.     // 确保起始角度和终止角度的关系正确
  93.     if ( m_startAngle > m_spanAngle )
  94.         m_startAngle-=360;
  95.     if ( m_spanAngle < m_startAngle ){
  96.         qreal tmp = m_spanAngle;
  97.         m_spanAngle = m_startAngle;
  98.         m_startAngle = tmp;
  99.     }
  100.     // 确保跨度不超过360度
  101.     if ( qAbs(m_spanAngle-m_startAngle) > 360 ){
  102.         m_startAngle = 40;  // 重置为默认起始角度
  103.         m_spanAngle = 400;  // 重置为默认跨度角度
  104.     }
  105.     // 更新控制柄位置
  106.     updatehandles();
  107. }
  108. // 返回椭圆的边界矩形
  109. QRectF GraphicsEllipseItem::boundingRect() const
  110. {
  111.     return shape().controlPointRect();  // 使用形状的控制点矩形作为边界矩形
  112. }
  113. // 复制当前椭圆项并返回新的实例
  114. QGraphicsItem *GraphicsEllipseItem::duplicate() const
  115. {
  116.     // 创建新的椭圆项
  117.     GraphicsEllipseItem * item = new GraphicsEllipseItem( m_localRect.toRect() );
  118.     // 复制属性
  119.     item->m_width = width();
  120.     item->m_height = height();
  121.     item->m_startAngle = m_startAngle;
  122.     item->m_spanAngle   = m_spanAngle;   // 复制跨度角度m_startAngle - m_endAngle
  123.     // 复制其他属性
  124.     item->setPos(pos().x(),pos().y());
  125.     item->setPen(pen());
  126.     item->setBrush(brush());
  127.     item->setTransform(transform());
  128.     item->setTransformOriginPoint(transformOriginPoint());
  129.     item->setRotation(rotation());
  130.     item->setScale(scale());
  131.     item->setZValue(zValue()+0.1);  // 提升 z 值,使之稍微位于上层
  132.     item->updateCoordinate();
  133.     return item;
  134. }
  135. // 从 XML 文件加载椭圆的属性
  136. bool GraphicsEllipseItem::loadFromXml(QXmlStreamReader *xml)
  137. {
  138.     m_startAngle = xml->attributes().value("startAngle").toInt();
  139.     m_spanAngle  = xml->attributes().value("spanAngle").toInt();
  140.     readBaseAttributes(xml);
  141.     xml->skipCurrentElement();
  142.     updateCoordinate();
  143.     return true;
  144. }
  145. // 保存椭圆的属性到 XML 文件
  146. bool GraphicsEllipseItem::saveToXml(QXmlStreamWriter * xml)
  147. {
  148.     xml->writeStartElement(tr("ellipse"));  // 写入元素开始标签
  149.     xml->writeAttribute("startAngle",QString("%1").arg(m_startAngle));
  150.     xml->writeAttribute("spanAngle",QString("%1").arg(m_spanAngle));
  151.     writeBaseAttributes(xml);
  152.     xml->writeEndElement();
  153.     return true;
  154. }
  155. // 更新控制柄的位置,在调用此函数前,m_localRect的中心点移动到其原点位置
  156. void GraphicsEllipseItem::updatehandles()
  157. {
  158.     // 调用基类的更新控制柄方法
  159.     GraphicsItem::updatehandles();
  160.     // 计算本地矩形,将m_localRect的中心点移动到其原点位置
  161.     QRectF local = QRectF(-m_width/2,-m_height/2,m_width,m_height);
  162.     // 计算矩形中心点的偏移量
  163.     QPointF delta = local.center() - m_localRect.center();
  164.     //qDebug() << delta;  // 一直是(0,0),可能是确认确保两者没有偏移。
  165.     // 计算起始角度控制柄的位置
  166.     qreal x = (m_width/2) * cos( -m_startAngle * M_PI / 180 );
  167.     qreal y = (m_height/2) * sin( -m_startAngle * M_PI / 180);
  168.     m_handles.at(8)->move(x-delta.x(),y-delta.y());  // 移动控制柄到新的位置
  169.     // 计算终止角度控制柄的位置
  170.     x = (m_width/2) * cos( -m_spanAngle * M_PI / 180);
  171.     y = (m_height/2) * sin(-m_spanAngle * M_PI / 180);
  172.     m_handles.at(9)->move(x-delta.x(),y-delta.y());  // 移动控制柄到新的位置
  173. }
  174. // 绘制椭圆
  175. void GraphicsEllipseItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  176. {
  177.     Q_UNUSED(option);  // 忽略未使用的参数
  178.     Q_UNUSED(widget);  // 忽略未使用的参数
  179.     QColor c = brushColor();  // 获取画刷颜色
  180.     QRectF rc = m_localRect;  // 获取本地矩形
  181.     // 设置画笔
  182.     painter->setPen(pen());   
  183.     QBrush b(c);              
  184.     painter->setBrush(b);   
  185.     // 使用较大的作为起始角度
  186.     int startAngle = m_startAngle <= m_spanAngle ? m_startAngle : m_spanAngle;
  187.     // 使用较小的作为结束角度
  188.     int endAngle = m_startAngle >= m_spanAngle ? m_startAngle : m_spanAngle;
  189.     // 确保跨度不超过360度
  190.     if(endAngle - startAngle > 360) endAngle = startAngle + 360;
  191.     // 如果是完整的椭圆
  192.     if (qAbs(endAngle-startAngle) % (360) == 0) painter->drawEllipse(m_localRect);
  193.     // 否则绘制扇形
  194.     else painter->drawPie(m_localRect, startAngle * 16 , (endAngle-startAngle) * 16);
  195.     // 如果项处于选中状态,绘制选中效果
  196.     if (option->state & QStyle::State_Selected)
  197.         qt_graphicsItem_highlightSelected(this, painter, option);
  198. }
复制代码
4.5 GraphicsPolygonItem类


 GraphicsPolygonItem 类是一个自定义的 QGraphicsItem,用于在 Qt 图形视图框架中绘制和操作多边形图形。它继续自 GraphicsItem,提供了对多边形外形的机动控制和操作。
  1. class GraphicsPolygonItem : public GraphicsItem
  2. {
  3. public:
  4.     // 构造函数,初始化多边形项
  5.     GraphicsPolygonItem(QGraphicsItem * parent = 0);
  6.     // 返回多边形的边界矩形
  7.     QRectF boundingRect() const ;
  8.     // 返回多边形的绘制路径
  9.     QPainterPath shape() const;
  10.     // 向多边形中添加一个点
  11.     virtual void addPoint( const QPointF & point ) ;
  12.     // 结束当前多边形点的添加
  13.     virtual void endPoint(const QPointF & point );
  14.     // 根据控制方向和偏移量调整多边形的属性
  15.     void control(int dir, const QPointF & delta);
  16.     // 根据控制柄和缩放比例拉伸多边形
  17.     void stretch( int handle , double sx , double sy , const QPointF & origin );
  18.     // 更新多边形的坐标
  19.     void updateCoordinate ();
  20.     // 从 XML 文件加载多边形的属性
  21.     virtual bool loadFromXml(QXmlStreamReader * xml );
  22.     // 保存多边形的属性到 XML 文件
  23.     virtual bool saveToXml( QXmlStreamWriter * xml );
  24.     // 返回多边形的显示名称
  25.     QString displayName() const { return tr("polygon"); }
  26.     // 复制当前多边形项并返回新的实例
  27.     QGraphicsItem *duplicate() const;
  28. protected:
  29.     // 更新控制柄的位置
  30.     void updatehandles();
  31.     // 绘制多边形
  32.     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
  33.     QPolygonF m_points;           // 存储多边形的点
  34.     QPolygonF m_initialPoints;    // 存储多边形的初始点
  35. };
  36. GraphicsPolygonItem::GraphicsPolygonItem(QGraphicsItem *parent)
  37.     :GraphicsItem(parent)
  38. {
  39.     // handles
  40.     m_points.clear();          // 初始化点集合为空
  41.     m_pen = QPen(Qt::black);   // 设置画笔为黑色
  42. }
  43. // 返回多边形的边界矩形
  44. QRectF GraphicsPolygonItem::boundingRect() const
  45. {
  46.     return shape().controlPointRect();
  47. }
  48. // 返回多边形的绘制路径
  49. QPainterPath GraphicsPolygonItem::shape() const
  50. {
  51.     QPainterPath path;
  52.     // 将多边形的点添加到绘制路径
  53.     path.addPolygon(m_points);
  54.    
  55.     // 关闭子路径,使路径形成封闭的多边形
  56.     path.closeSubpath();
  57.     // 根据当前的画笔宽度调整路径,用于检测碰撞。
  58.     // 这个函数会根据画笔的宽度来扩展或缩小路径,从而创建一个包含画笔影响的路径。
  59.     return qt_graphicsItem_shapeFromPath(path,pen());
  60. }
  61. void GraphicsPolygonItem::addPoint(const QPointF &point)
  62. {
  63.     // 将场景坐标的点转换为本地坐标并添加到点集合中
  64.     m_points.append(mapFromScene(point));
  65.     // 获取当前点的方向编号:点的个数就是编号,从1+8=9开始,m_points初始化有一个(0,0)坐标。
  66.     int dir = m_points.count();
  67.     // 创建一个新的控制柄用于调整点的位置
  68.     SizeHandleRect *shr = new SizeHandleRect(this, dir+Left, true);
  69.     // 设置控制柄为活动状态
  70.     shr->setState(SelectionHandleActive);
  71.     // 将控制柄添加到控制柄集合中
  72.     m_handles.push_back(shr);
  73. }
  74. void GraphicsPolygonItem::control(int dir, const QPointF &delta)
  75. {
  76.     // 将控制点的场景坐标转换为本地坐标
  77.     QPointF pt = mapFromScene(delta);
  78.     // 检查控制方向是否有效,小于等于Left,说明一个点都没有。
  79.     if ( dir <= Left ) return ;
  80.     // 更新指定控制点的位置
  81.     m_points[dir - Left -1] = pt;
  82.     // 准备更新几何图形
  83.     prepareGeometryChange();
  84.     // 更新多边形的边界矩形
  85.     m_localRect = m_points.boundingRect();
  86.     // 更新多边形的宽度和高度
  87.     m_width = m_localRect.width();         
  88.     m_height = m_localRect.height();
  89.     // 保存更新后的点集合
  90.     m_initialPoints = m_points;
  91.     // 更新控制柄的位置
  92.     updatehandles();
  93. }
  94. void GraphicsPolygonItem::stretch(int handle, double sx, double sy, const QPointF &origin)
  95. {
  96.     QTransform trans;
  97.     // 根据控制柄方向调整缩放比例
  98.     switch (handle) {
  99.     case Right:
  100.     case Left:
  101.         sy = 1;          // 水平方向的控制柄只影响水平缩放
  102.         break;
  103.     case Top:
  104.     case Bottom:
  105.         sx = 1;          // 垂直方向的控制柄只影响垂直缩放
  106.         break;
  107.     default:
  108.         break;
  109.     }
  110.     trans.translate(origin.x(),origin.y());   // 将原点平移到目标点
  111.     trans.scale(sx,sy);    // 执行缩放
  112.     trans.translate(-origin.x(),-origin.y()); // 将原点平移到目标点
  113.     // 准备更新几何图形
  114.     prepareGeometryChange();
  115.     // 将初始点集合按变换矩阵变换,更新多边形点集合
  116.     m_points = trans.map(m_initialPoints);
  117.     // 更新边界矩形
  118.     m_localRect = m_points.boundingRect();
  119.     m_width = m_localRect.width();
  120.     m_height = m_localRect.height();
  121.     // 更新控制柄的位置
  122.     updatehandles();
  123. }
  124. void GraphicsPolygonItem::updateCoordinate()
  125. {
  126.     // 将局部顶点坐标转换成场景坐标。
  127.     QPolygonF pts = mapToScene(m_points);
  128.     QPointF pt1, pt2, delta;
  129.     if (parentItem()==NULL)
  130.     {
  131.         // 当前变换原点的场景坐标
  132.         pt1 = mapToScene(transformOriginPoint());
  133.         // 边界中心的场景坐标
  134.         pt2 = mapToScene(boundingRect().center());
  135.         delta = pt1 - pt2;
  136.         //qDebug() << delta << pt1 << pt2; //存在偏移。
  137.         // 更新所有点的坐标以考虑偏移量
  138.         for (int i = 0; i < pts.count() ; ++i )
  139.             pts[i]+=delta;
  140.         prepareGeometryChange();
  141.         // 更新多边形的本地点集合
  142.         m_points = mapFromScene(pts);
  143.         // 更新边界矩形
  144.         m_localRect = m_points.boundingRect();
  145.         m_width = m_localRect.width();
  146.         m_height = m_localRect.height();
  147.         // 更新图形的变换以适应新的原点
  148.         setTransform(transform().translate(delta.x(),delta.y()));
  149.         //setTransformOriginPoint(boundingRect().center());
  150.         moveBy(-delta.x(),-delta.y());
  151.         setTransform(transform().translate(-delta.x(),-delta.y()));
  152.         // 更新控制柄的位置
  153.         updatehandles();
  154.     }
  155.     // 保存更新后的点集合
  156.     m_initialPoints = m_points;
  157. }
  158. bool GraphicsPolygonItem::loadFromXml(QXmlStreamReader *xml)
  159. {
  160.     readBaseAttributes(xml);
  161.     // 逐个读取多边形的点
  162.     while(xml->readNextStartElement()){
  163.         if (xml->name()=="point"){
  164.             // 获取点的x和y坐标
  165.             qreal x = xml->attributes().value("x").toDouble();
  166.             qreal y = xml->attributes().value("y").toDouble();
  167.             m_points.append(QPointF(x,y));
  168.             // 创建控制柄
  169.             int dir = m_points.count();
  170.             SizeHandleRect *shr = new SizeHandleRect(this, dir+Left, true);
  171.             m_handles.push_back(shr);
  172.             // 跳过当前元素
  173.             xml->skipCurrentElement();
  174.         }else // 跳过非点元素
  175.             xml->skipCurrentElement();
  176.     }
  177.     // 更新多边形的坐标
  178.     updateCoordinate();
  179.     return true;
  180. }
  181. bool GraphicsPolygonItem::saveToXml(QXmlStreamWriter *xml)
  182. {
  183.     // 开始写入多边形元素
  184.     xml->writeStartElement("polygon");
  185.     writeBaseAttributes(xml);
  186.     // 保存每个点的坐标
  187.     for ( int i = 0 ; i < m_points.count();++i){
  188.         xml->writeStartElement("point");
  189.         xml->writeAttribute("x",QString("%1").arg(m_points[i].x()));
  190.         xml->writeAttribute("y",QString("%1").arg(m_points[i].y()));
  191.         xml->writeEndElement();
  192.     }
  193.     xml->writeEndElement();
  194.     return true;
  195. }
  196. void GraphicsPolygonItem::endPoint(const QPointF & point)
  197. {
  198.     Q_UNUSED(point);
  199.     int nPoints = m_points.count();
  200.     // 检查多边形最后两个点是否相同或水平对齐
  201.     if( nPoints > 2 && (m_points[nPoints-1] == m_points[nPoints-2]/*相同*/ ||  /*水平对齐,只相差一个像素*/
  202.         m_points[nPoints-1].x() - 1 == m_points[nPoints-2].x() &&
  203.         m_points[nPoints-1].y() == m_points[nPoints-2].y())){
  204.         // 删除重复的最后一个控制柄
  205.         delete m_handles[Left + nPoints-1];
  206.         // 移除重复点
  207.         m_points.remove(nPoints-1);
  208.         // 调整控制柄集合的大小
  209.         m_handles.resize(Left + nPoints-1);
  210.     }
  211.     // 更新初始点集合
  212.     m_initialPoints = m_points;
  213. }
  214. QGraphicsItem *GraphicsPolygonItem::duplicate() const
  215. {
  216.     // 创建一个新的多边形项并复制当前项的属性
  217.     GraphicsPolygonItem * item = new GraphicsPolygonItem( );
  218.     item->m_width = width();
  219.     item->m_height = height();
  220.     item->m_points = m_points;
  221.     // 为每个点创建控制柄
  222.     for ( int i = 0 ; i < m_points.size() ; ++i ){
  223.         item->m_handles.push_back(new SizeHandleRect(item,Left+i+1,true));
  224.     }
  225.     item->setPos(pos().x(),pos().y());
  226.     item->setPen(pen());
  227.     item->setBrush(brush());
  228.     item->setTransform(transform());
  229.     item->setTransformOriginPoint(transformOriginPoint());
  230.     item->setRotation(rotation());
  231.     item->setScale(scale());
  232.     item->setZValue(zValue()+0.1);  // 提升复制项的层级以避免重叠
  233.     item->updateCoordinate();
  234.     return item;
  235. }
  236. void GraphicsPolygonItem::updatehandles()
  237. {
  238.     // 调用基类的方法更新控制柄
  239.     GraphicsItem::updatehandles();
  240.     // 根据多边形的点更新控制柄的位置
  241.     for ( int i = 0 ; i < m_points.size(); ++i ){
  242.         m_handles[Left+i]->move(m_points[i].x() ,m_points[i].y() );
  243.     }
  244. }
  245. void GraphicsPolygonItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  246. {
  247.     Q_UNUSED(option);
  248.     Q_UNUSED(widget);
  249.     // 创建一个线性渐变的笔刷
  250.     QColor c = brushColor();
  251.     QLinearGradient result(boundingRect().topLeft(), boundingRect().topRight());
  252.     result.setColorAt(0, c.dark(150));
  253.     result.setColorAt(0.5, c.light(200));
  254.     result.setColorAt(1, c.dark(150));
  255.     painter->setBrush(result);
  256.     // 设置画笔并绘制多边形
  257.     painter->setPen(pen());
  258.     painter->drawPolygon(m_points);
  259.     // 如果项被选中,绘制选中高亮效果
  260.     if (option->state & QStyle::State_Selected)
  261.         qt_graphicsItem_highlightSelected(this, painter, option);
  262. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

滴水恩情

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表