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

2024-09-07 16:28

本文主要是介绍qtdraw-使用qt绘图之开源源码学习,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 资源介绍

功能:使用qt在画板上绘制各种形状,并保持绘制内容到xml文件中。

项目源码:https://github.com/egan2015/qdraw

软件界面

1.1 支持shape

6种

1.2 支持的功能

6种,分别是对绘制的图形进行撤销undo,重做redo,裁剪,复制,粘贴,删除功能。

2. 总体类图关系

总体分割3个独立块。

2.1 绘制类

很奇怪,为何父类和子类都没有继承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.henum 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;  // 设置当前绘制形状为 selectionsetCursor(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;        elseselectMode =  move;         // 否则光标就是没有碰撞,而是在形状内部,设置为移动模式if ( nDragHandle!= Handle_None && nDragHandle <= Left ){   // 如果是大小调整模式opposite_ = item->opposite(nDragHandle);   // 获取相对点,即与当前拖动的控制点相对的点。if( opposite_.x() == 0 )opposite_.setX(1);  // 防止出现零值,设置为 1if (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 0if ( 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);  // 防止零值,设置为 1if (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 0if ( 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 管理图形项的旋转状态、旋转角度的计算以及旋转视觉反馈的显示,为用户提供了直观的旋转操作体验。通过管理 lastAngledashRectRotationTool 能够准确地计算和显示旋转效果,并在鼠标操作时给予用户实时反馈。

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;    // 初始化上一次角度为0dashRect = 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;  // 记录当前角度为 lastAngleselectMode = 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 为 NULLm_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++;                               // 点的数量加 1selectMode = 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 设置为 NULLselectMode = none;                           // 设置选择模式为 nonec_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;
}

待续。。。

这篇关于qtdraw-使用qt绘图之开源源码学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145562

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

AI绘图怎么变现?想做点副业的小白必看!

在科技飞速发展的今天,AI绘图作为一种新兴技术,不仅改变了艺术创作的方式,也为创作者提供了多种变现途径。本文将详细探讨几种常见的AI绘图变现方式,帮助创作者更好地利用这一技术实现经济收益。 更多实操教程和AI绘画工具,可以扫描下方,免费获取 定制服务:个性化的创意商机 个性化定制 AI绘图技术能够根据用户需求生成个性化的头像、壁纸、插画等作品。例如,姓氏头像在电商平台上非常受欢迎,

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传