week05–SVG编辑器
在Visual Studio 2019下使用QT环境编写SVG编辑器,要求编辑器可以根据鼠标移动绘出各种基础图形(直线、矩形、圆形、五边形、六边形、五角星、文本、铅笔自由线)以及可以设置各种图形的填充颜色和边框颜色,并且在鼠标选中时可以进行拖拽移动、边框伸缩、顶点拖拽转动,画布及其图形缩放,以及对各种图形的上下层进行设置,同时对于画布可以设置颜色。并且对与svg文件保存方面,可以将画布及其上面的图形样式保存为SVG,以及打开通用SVG文件,并且可以将SVG文件导出为PNG文件。鼠标右键菜单栏可以删除图形和撤回上一步所画图形。
程序测试说明
程序点击左侧图形可以进行图形的绘画,
点击鼠标图标:
可以对图形进行移动和拖拽。以及选中图形进行颜色和边框的填充。
点击放大镜图标:
后滚动滑轮,可以对画布进行放缩操作。
点击其他各种图形包括文本,可以画出对应的需要的图形。
测试内存泄漏:
经测试,开始是存在内存泄露的,在一顿查找后,找到是在GlobalData类中的静态成员变量中产生了内存泄漏,C++在程序析构时,静态成员变量和静态成员函数是系统自动析构的,但是由于多重调用,导致该类的静态成员变量未完全析构,之后我选择写一个静态成员函数,在主函数析构的时候调用静态成员函数对该静态成员变量进行析构,果真未再出现内存泄漏。所以程序现在已经不存在内存泄漏。
程序具体实现思路:
由于svg编辑器类似于画图软件,因此在实现的时候,对于鼠标点击位置以及拖拽位置和鼠标释放位置要求都不一样,因此本次程序的重点是重写鼠标点击事件、鼠标移动事件、鼠标释放事件,同时由于各种图形的绘画需要一个对象统一管理,因此使用工厂模式设置ShapeFactory类对各种不同的图形、自由线、Text文本进行管理,同时也大大增加了程序的可扩展性,由于画板类的各种操作需要跟其他类,例如属性类,工具栏类,菜单栏类进行交互,因此使用单例模式设置一个统一的数据类(GlobalData)类对各种属性以及操作进行保存,调用和设置。增加代码统一管理的有序性,以及健壮性。
程序文件结构:
各种文件主要分为图形基类、具体图形类、画布类、图形工厂类、各种工具类、主事件循环类,几大类。
具体图形类继承图形基类及其其中的虚函数进行具体的图形实现。
画布类实现鼠标的点击,移动,释放事件控制各种图形类进行重绘,保证程序中的画笔跟随鼠标的点击拖拽进行重绘操作,达到图形跟随绘制的效果。
图形工厂类对图形进行总的管理,根据画布类返回出来的信号,创建需要的图形对象,进行设置。
工具类,主要是对图形的重绘进行修饰,边框颜色,填充颜色等等。
主事件循环类,主要是主事件在其中进行循环操作,直到用户关闭程序结束循环。
具体介绍:
Shape.h图形基类
图形基类中定义了枚举类型的ShapeType,对创建的图形进行分类,同时创建了绘画虚函数,让具体实现图形类对其进行重写,以及图形的各种移动方法,包括(拖拽、左上左下、右上右下、上下左右),以及成员变量的get和set接口。
#ifndef __K_SHAPE_H_
#define __K_SHAPE_H_
enum class KShapeType
{
None = 0,
Pen,
Line,
Circle,
Rect,
Pent,
Hexa,
Star,
Text,
Zoom,
};
class KShape
{
public:
KShape();
virtual ~KShape();
QPoint getStartPoint();
QPoint getEndPoint();
void setStartPoint(QPoint point);
void setEndPoint(QPoint point);
QRect getShapeRect();
bool isValid();
// 在图形子类中进行重写, 实现不同的图形的绘制
virtual void drawShape(QPaintDevice* parent = Q_NULLPTR) = 0;
void drawOutline(QPaintDevice* parent = Q_NULLPTR);
void move(QPoint offset);
void moveTop(QPoint pos);
void moveBottom(QPoint pos);
void moveLeft(QPoint pos);
void moveRight(QPoint pos);
void moveTopLeft(QPoint pos);
void moveTopRight(QPoint pos);
void moveBottomLeft(QPoint pos);
void moveBottomRight(QPoint pos);
virtual KShapeType getShapeType();
QString getBorderColor();
void setBorderColor(QString col);
QString getContentColor();
void setContentColor(QString col);
QString getText();
void setText(QString str);
private:
QPoint m_startPoint; // 起始坐标
QPoint m_endPoint; // 结束坐标
QString m_borderColor = "000000"; //边框颜色
QString m_contentColor = "FFFFFF"; //图形颜色
QString m_text= "";
};
#endif
GlobalData.h全局数据类
全局数据类中保存的是图形的各种属性,以及获取的图形的类型,画笔的类型,当前图形,以及图形的边框颜色、内部颜色等等。
#ifndef __K_GLOBAL_DATA_H_
#define __K_GLOBAL_DATA_H_
class GlobalData
{
public:
enum class DrawFlag
{
NoneFlag = 0,
MouseFlag,
PenFlag,
LineFlag,
RectFlag,
CircleFlag,
PentFlag,
HexaFlag,
StarFlag,
TextFlag,
ZoomFlag
};
static GlobalData* getGlobalDataIntance();
static void deleteGlobalData();
~GlobalData();
int getCanvasWidth();
int getCanvasHeight();
void setCanvasWidth(int width);
void setCanvasHeight(int height);
void setCanvasColor(QString colorStr);
QString getCanvasColor();
DrawFlag getDrawFlag();
void setDrawFlag(DrawFlag flag);
QString getFilePath();
void setFilePath(QString path);
qreal getCanvasScale();
void setCanvasScale(qreal scale);
void setCurrentShape(KShape* shape); //设置当前图形
Shape* getCurrentShape(); //获取当前图形
void setBorderColor(QString str);
QString getBorderColor();
private:
GlobalData();
GlobalData(const GlobalData& other)
{}
int m_canvasWidth = 600;
int m_canvasHeight = 400;
QString m_canvasColor; // 当前画布的颜色
static GlobalData* s_globalDataObj;
DrawFlag m_drawFlag;
QString m_filePath;
qreal m_canvasScale = 1;
QString m_borderColor = "000000";
KShape* m_currentShape = NULL;
};
#endif
svgcanvas.h
画布类定义了枚举类型的移动类型,以及重新了绘画事件、鼠标按压事件、鼠标移动事件、鼠标释放事件,对画图的具体实现进行了详细定义,点击的时候会判断是画图形还是选中图形进行移动,以及移动的方向、鼠标指针的样式以及移动过程中的update()重绘事件。
#ifndef __K_SVGCANVAS_H_
#define __K_SVGCANVAS_H_
// 移动类型(绘制图形 or 移动图形)
enum class TransType
{
None = 0,
TopLeft,
Top,
TopRight,
Left,
Contains,// 全部
Right,
BottomLeft,
Bottom,
BottomRight
};
class KSvgCanvas : public QWidget
{
Q_OBJECT
public:
KSvgCanvas(QWidget *parent = Q_NULLPTR);
~KSvgCanvas();
virtual void paintEvent(QPaintEvent* event) override;
virtual void mousePressEvent(QMouseEvent* event) override;
virtual void mouseMoveEvent(QMouseEvent* event) override;
virtual void mouseReleaseEvent(QMouseEvent* event) override;
virtual void mouseDoubleClickEvent(QMouseEvent* event) override;
KShape* getCurrentShape(QPoint pos); // 获取当前位置图形
TransType getTransType(QPoint pos); // 获取移动类型
QList<KShape*>* getShapeList();
QLineEdit* getLineEdit();
void initLineEditStyle(); // 设置文本编辑框样式
public slots:
void saveSvg();
void openSvg();
void newSvg();
void svgToPng();
void rightBtnBackShape();
void rightBtnDelShape();
void rightBtnClearShape();
void rightBtnTopShow();
void rightBtnBehindShow();
signals:
void transBorderColor(QString col);
void transContentColor(QString col);
void notSelected();
void isSelected();
private:
KShape* m_pCurrentShape;
QList<KShape*>* m_pShapeList; // 存储当前画布中的图形
QPoint m_lastPos;// 记录前一次的位置
TransType m_TransType;// 记录移动类型
bool m_isLPress = false; // 标记鼠标左键
bool m_isDrawing = false; // 是否为绘图
bool m_isSelected = false; // 是否为选中
QLineEdit* m_lineEdit;
QPoint point;
};
#endif
具体图形类的实现:(Rect.h)
#ifndef __K_RECT_H_
#define __K_RECT_H_
#include "kshape.h"
class KRect : public KShape
{
public:
KRect();
virtual ~KRect();
virtual void drawShape(QPaintDevice* parent = Q_NULLPTR) override;
virtual KShapeType getShapeType();
};
#endif
Rect.cpp
#include "krect.h"
#include <QDebug>
KRect::KRect()
{
}
KRect::~KRect()
{
}
void KRect::drawShape(QPaintDevice* parent)
{
QPainter painter(parent);
//获取边框颜色
QPen pen(QColor(getBorderColor().toUInt(NULL, 16)));
painter.setPen(pen);
//获取内部颜色
QBrush brush(QColor(getContentColor().toUInt(NULL, 16)));
painter.setBrush(brush);
//放缩
qreal scale = KGlobalData::getGlobalDataIntance()->getCanvasScale();
painter.scale(scale, scale); // 系统自带的放缩函数
painter.setRenderHint(QPainter::Antialiasing);
// 绘制矩形
painter.drawRect(QRect(getStartPoint(), getEndPoint()));
}
KShapeType KRect::getShapeType()
{
return KShapeType::Rect;
}