Qt Examples之affine工程难点、亮点汇总

工程存放的路径在:Examples\Qt-XX.XX.XX\widgets\painting\affine

其中XX.XX.XX为Qt的版本号,如:5.14.1。

该工程有如下个人认为的难点、亮点:

对XFormView类的drawPixmapType函数解释:

void XFormView::drawPixmapType(QPainter *painter)
{
    QPointF center(m_pixmap.width() / qreal(2), m_pixmap.height() / qreal(2));

    /* 将painter移动(ctrlPoints.at(0) - center)距离,也即将HoverPoints类对象pts
       的第一个控制点移动到位图的中心位置,以便第一个控制点绘制在位图的中心*/
    painter->translate(ctrlPoints.at(0) - center);

    // 进行变换,以便位图绕着自己的中心轴旋转
    painter->translate(center);
    painter->rotate(m_rotation);
    painter->scale(m_scale, m_scale);
    painter->shear(0, m_shear);
    painter->translate(-center);

    .............................// 其它代码
}

如果被绘制对象(本例是指m_pixmap对象表示的图片)的中心点和绘图对象painter的中心点不重合,这时候旋转被绘制对象,则这个对象会围绕painter设置的中心点进行旋转,会转一个大圈。那么怎么做才能让它在任何位置的时候,都围绕自己的轴心进行旋转?解决思路如下:

1. 先保存物体在世界坐标系下的坐标,即物体在世界坐标系下的中心点坐标
2. 再将物体移动到世界坐标系的原点。
3. 在世界坐标系的原点旋转好后,再移动回原来的位置,即步骤1中的提到的坐标。

上段代码的思路就是按这样来的。

  • drawTextType函数同drawPixmapType函数,不再赘述。
  • HoverPoints分析

构造函数分析:

HoverPoints::HoverPoints(QWidget *widget, PointShape shape)
	: QObject(widget)
{
	m_widget = widget;
	widget->installEventFilter(this);
	widget->setAttribute(Qt::WA_AcceptTouchEvents);

	m_connectionType = CurveConnection;
	m_sortType = NoSort;
	m_shape = shape;
	m_pointPen = QPen(QColor(255, 255, 255, 191), 1);
	m_connectionPen = QPen(QColor(255, 255, 255, 127), 2);
	m_pointBrush = QBrush(QColor(191, 191, 191, 127));
	m_pointSize = QSize(11, 11);
	m_currentIndex = -1;
	m_editable = true;
	m_enabled = true;

	connect(this, &HoverPoints::pointsChanged,
		m_widget, QOverload<>::of(&QWidget::update));
}

构造函数初始化了一些变量,对各个变量作用解释如下:

  •  m_widget:外层传入的窗体部件对象,也即HoverPoints类附属在哪个窗体上,通过通读代码可以发现其是指外层的XFormView窗体对象。
  • 函数的第5行为外层传入的窗体部件对象安装了事件过滤器。installEventFilter函数的用法请参见《installEventFilter、eventFilter函数理解》
  • 第6行设置触屏事件,从后续代码可以看到本例支持触屏操作。
  • m_connectionType:连接类型。取值为:直线、曲线(贝塞尔曲线)和无
  • m_sortType: 点的排序类型。取值为:不排序、按点的x坐标排序、按点的y坐标排序。
  • m_shape:悬浮点的现状。取值为:圆形、矩形。
  • m_pointPen:用于画悬浮点的画笔。
  • m_connectionPen:用于画连接线的画笔。
  • m_pointBrush:用于点的画刷。
  • m_pointSize:点所在形状的外围矩形宽高。
  • m_currentIndex:当前在控制点容器m_points中的点的索引。
  • m_editable:是否可编辑。
  • m_enabled:是否可用。

     eventFilter 中的鼠标左键按下流程分析:

     如下鼠标左键按下时的流程:(图片下载到本地放大看):

说明:

  1.  本程序不仅支持鼠标还支持触摸显示屏操作。上述流程是支持鼠标且鼠标左键按下流程。
  2. 程序根据m_sortType来决定坐标排序类型,在构造函数中默认不排序。当排序类型为XSort    时,则从控制点的容器m_points中找到第1个横坐标大于鼠标按下时鼠标单击点的横坐标的点。当排序类型为YSort 时,则从控制点的容器m_points中找到第1个纵坐标大于鼠标按下时鼠标单击点的纵坐标的点,并记录该点在m_points中的索引号。这样做的效果是为了实现如下效果:

起始m_points中只有A、B两个控制点,则画出上图类似的A到B的线,当鼠标左键单击的点为C时,且C在A、B之间时,则画出如下直线:

 当C在B 的右侧时,则画出如下的:

3 .鼠标左键按下时,当步骤2完成后,将符合条件的鼠标左键单击时所在的点插入m_points、m_locks,插入的索引号为步骤2记录的索引号然后调用 firePointChange函数。

   4. firePointChange函数中根据m_sortType表示的排序规则将m_points排序。排序后再次从m_points中找到步骤2鼠标左键按下时的点在m_points中的索引m_currentIndex,然后发送pointsChanged信号。

eventFilter 中的触摸事件流程分析:

QEvent::TouchBegin、QEvent::TouchUpdate、Qt::TouchPointReleased、QEvent::TouchEnd事件。其中最需要搞懂的是QEvent::TouchBegin、QEvent::TouchUpdate中的Qt::TouchPointPressed,实现代码如下:

// find the point, move it
const auto mappedPoints = m_fingerPointMapping.values();
QSet<int> activePoints = QSet<int>(mappedPoints.begin(), mappedPoints.end());
int activePoint = -1;
qreal distance = -1;
const int pointsCount = m_points.size();
const int activePointCount = activePoints.size();
if (pointsCount == 2 && activePointCount == 1)
{ // only two points
    activePoint = activePoints.contains(0) ? 1 : 0;
}
else 
{
    for (int i = 0; i < pointsCount; ++i) 
    {
        if (activePoints.contains(i))
            continue;

        qreal d = QLineF(touchPoint.pos(), m_points.at(i)).length();
        if ((distance < 0 && d < 12 * pointSize) || d < distance) 
        {
            distance = d;
            activePoint = i;
        }

    }
}
if (activePoint != -1) 
{
    m_fingerPointMapping.insert(touchPoint.id(), activePoint);
    movePoint(activePoint, touchPoint.pos());
}

其基本思路是:将触摸点的id和触摸点索引值作为QHash的键值对插入到类型为QHash的成员变量m_fingerPointMapping,以后移动、释放都从m_fingerPointMapping取出相应的点进行操作。

eventFilter 中的QEvent::Paint流程分析:

代码如下:

            QWidget *that_widget = m_widget;
            m_widget = nullptr;
            QCoreApplication::sendEvent(object, event);
            m_widget = that_widget;
            paintPoints();
            return true;

这里需要着重说明的是:在发送QEvent::Paint事件之前必须m_widget将先保存起来(第1句代码),然后将m_widget设置为nullptr(第2句),发送完后再将m_widget恢复为原来的。设置为nullptr是避免eventFilter 函数无限递归调用导致栈耗尽程序崩溃,恢复回来为了下次再次发送QEvent::Paint事件。

updateCtrlPoints函数分析:

void XFormView::updateCtrlPoints(const QPolygonF &points)
{
	QPointF trans = points.at(0) - ctrlPoints.at(0);

	if (qAbs(points.at(0).x() - points.at(1).x()) < 10
		&& qAbs(points.at(0).y() - points.at(1).y()) < 10)
		pts->setPoints(ctrlPoints);
	if (!trans.isNull()) {
		ctrlPoints[0] = points.at(0);
		ctrlPoints[1] += trans;
		pts->setPoints(ctrlPoints);
	}
	ctrlPoints = points;

	QLineF line(ctrlPoints.at(0), ctrlPoints.at(1));
	m_rotation = 360 - QLineF(0, 0, 1, 0).angleTo(line);
	if (trans.isNull())
		emit rotationChanged(int(m_rotation * 10));
}

第3行代码:就是计算通过信号pointsChanged发送过来的m_points第一个控制点和当前的第一个控制点的在x、y坐标上的偏移量。

第5-7行:检测m_points第1个控制点、第2个控制点x、y坐标上的偏移量都小于10,如果是,则控制点还是设置为当前控制点。

第8-11行:如果第3句代码算出的偏移量不为空,则当前第1个控制点更新为m_points中的第1个点,注意:在m_sortType为NoSort时,且鼠标左键按下时,m_points中的第1个点为鼠标左键按下时的点,而在XFormView类的paint函数绘制函数如:drawPixmapType、drawTextType经过如下代码将绘制中心移动到了被绘制对象(如:图片、文本)的中心了(参见前面对drawPixmapType的分析):

painter->translate(ctrlPoints.at(0) - center);

        所以这样造成的现象是鼠标左键在哪单击,被绘制物体的中心就移动到鼠标左键单击的点。

当前第2个控制点累加第3行产生的偏移量,并重新设置HoverPoints类的控制点容器m_points

第15-16行根据更新后的控制点ctrlPoints算出其和水平线的夹角,并设置旋转成员变量m_rotation。根据Qt的元系统属性技术( 属性通过Q_PROPERTY关键字标识),一旦m_rotation被更新,则XFormView类的setRotation函数会自动调用,从而导致被绘制的物体呈现旋转。

程序bug说明:

HoverPoints类的m_sortType 为 XSort或YSort时,会崩溃,更改如下:

movePoint函数加入如下代码:

void HoverPoints::movePoint(int index, const QPointF &point, bool emitUpdate)
{
	// 索引越界了,要判断下
	if ((index < 0) || (m_points.size() <= index) )
	{
		return;
	}

	if ( m_locks.size() <= index )
	{
		return;
	}


   // 其它代码
}

firePointChange()函数加入如下代码:

void HoverPoints::firePointChange()
{
	//    printf("HoverPoints::firePointChange(), current=%d\n", m_currentIndex);

	if (m_sortType != NoSort) {

		QPointF oldCurrent;
		/*if (m_currentIndex != -1) {*/

		// 这里有崩溃,索引越界了,要判断下
		if ((0 <= m_currentIndex) && (m_currentIndex < m_points.size()))
		{
			 
			oldCurrent = m_points[m_currentIndex];
		}

// 其它代码

}

 XFormView类的updateCtrlPoints函数加入如下判断:

void XFormView::updateCtrlPoints(const QPolygonF &points)
{
	// 右键在控制点单击时,这里有崩溃,索引越界了,要判断下
	if (points.size() < 2)
	{
		return;
	}


     ..................
}

未完,待续!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值