okular/ui/annotationtools.cpp
Oliver Sander 19f22098e6 Mind the devicePixelRatio when drawing on-screen in presentation mode
Previously, when using a screen scaling factor larger than 1, the
drawings by mouse or stylus in presentation mode were slightly blocky.
The underlying cause was
a) a few integer types used for non-integer coordinates
b) the intermediate use of a QPixmap without the correct
   devicePixelRatio

Additionally, this patch uses a QPainterPath to draw the path,
rather than a sequence of individual lines.  This improves
the line quality a tiny bit, because the lines are properly
joined.

BUG: 384143

Differential Revision: https://phabricator.kde.org/D21836
2019-06-22 21:55:30 +02:00

239 lines
9.1 KiB
C++

/***************************************************************************
* Copyright (C) 2005 by Enrico Ros <eros.kde@email.it> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "annotationtools.h"
// qt / kde includes
#include <qcolor.h>
#include <qcursor.h>
#include <qevent.h>
#include <qpainter.h>
// local includes
#include "core/annotations.h"
AnnotatorEngine::AnnotatorEngine( const QDomElement & engineElement )
: m_engineElement( engineElement ), m_creationCompleted( false ), m_item( nullptr )
{
// parse common engine attributes
if ( engineElement.hasAttribute( QStringLiteral("color") ) )
m_engineColor = QColor( engineElement.attribute( QStringLiteral("color") ) );
// get the annotation element
QDomElement annElement = m_engineElement.firstChild().toElement();
if ( !annElement.isNull() && annElement.tagName() == QLatin1String("annotation") )
m_annotElement = annElement;
}
void AnnotatorEngine::decodeEvent( const QMouseEvent * mouseEvent, EventType * eventType, Button * button )
{
*eventType = AnnotatorEngine::Press;
if ( mouseEvent->type() == QEvent::MouseMove )
*eventType = AnnotatorEngine::Move;
else if ( mouseEvent->type() == QEvent::MouseButtonRelease )
*eventType = AnnotatorEngine::Release;
*button = AnnotatorEngine::None;
const Qt::MouseButtons buttonState = ( *eventType == AnnotatorEngine::Move ) ? mouseEvent->buttons() : mouseEvent->button();
if ( buttonState == Qt::LeftButton )
*button = AnnotatorEngine::Left;
else if ( buttonState == Qt::RightButton )
*button = AnnotatorEngine::Right;
}
void AnnotatorEngine::decodeEvent( const QTabletEvent * tabletEvent, EventType * eventType, Button * button )
{
switch ( tabletEvent->type() )
{
case QEvent::TabletPress:
// Tablet press event is equivalent to pressing the left mouse button
*button = AnnotatorEngine::Left;
*eventType = AnnotatorEngine::Press;
break;
case QEvent::TabletRelease:
// Tablet release event is equivalent to releasing the left mouse button
*button = AnnotatorEngine::Left;
*eventType = AnnotatorEngine::Release;
break;
case QEvent::TabletMove:
// Tablet events are only routed if the pen is down so
// this is equivalent to the left mouse button being pressed
*button = AnnotatorEngine::Left;
*eventType = AnnotatorEngine::Move;
break;
default:
Q_ASSERT(false);
break;
}
}
AnnotatorEngine::~AnnotatorEngine()
{
}
QCursor AnnotatorEngine::cursor() const
{
return Qt::CrossCursor;
}
SmoothPath::SmoothPath( const QLinkedList<Okular::NormalizedPoint> &points, const QPen &pen, qreal opacity, QPainter::CompositionMode compositionMode )
: points ( points ), pen ( pen ), opacity( opacity ), compositionMode( compositionMode )
{
}
/** SmoothPathEngine */
SmoothPathEngine::SmoothPathEngine( const QDomElement & engineElement )
: AnnotatorEngine( engineElement ), compositionMode( QPainter::CompositionMode_SourceOver )
{
// parse engine specific attributes
if ( engineElement.attribute( QStringLiteral("compositionMode"), QStringLiteral("sourceOver") ) == QLatin1String("clear") )
compositionMode = QPainter::CompositionMode_Clear;
}
QRect SmoothPathEngine::event( EventType type, Button button, double nX, double nY, double xScale, double yScale, const Okular::Page * /*page*/ )
{
// only proceed if pressing left button
if ( button != Left )
return QRect();
// start operation
if ( type == Press && points.isEmpty() )
{
lastPoint.x = nX;
lastPoint.y = nY;
totalRect.left = totalRect.right = lastPoint.x;
totalRect.top = totalRect.bottom = lastPoint.y;
points.append( lastPoint );
}
// add a point to the path
else if ( type == Move && points.count() > 0 )
{
//double dist = hypot( nX - points.last().x, nY - points.last().y );
//if ( dist > 0.0001 )
//{
// append mouse position (as normalized point) to the list
Okular::NormalizedPoint nextPoint = Okular::NormalizedPoint( nX, nY );
points.append( nextPoint );
// update total rect
double dX = 2.0 / (double)xScale;
double dY = 2.0 / (double)yScale;
totalRect.left = qMin( totalRect.left, nX - dX );
totalRect.top = qMin( totalRect.top, nY - dY );
totalRect.right = qMax( nX + dX, totalRect.right );
totalRect.bottom = qMax( nY + dY, totalRect.bottom );
// paint the difference to previous full rect
Okular::NormalizedRect incrementalRect;
incrementalRect.left = qMin( nextPoint.x, lastPoint.x ) - dX;
incrementalRect.right = qMax( nextPoint.x, lastPoint.x ) + dX;
incrementalRect.top = qMin( nextPoint.y, lastPoint.y ) - dY;
incrementalRect.bottom = qMax( nextPoint.y, lastPoint.y ) + dY;
lastPoint = nextPoint;
return incrementalRect.geometry( (int)xScale, (int)yScale );
//}
}
// terminate process
else if ( type == Release && points.count() > 0 )
{
if ( points.count() < 2 )
points.clear();
else
m_creationCompleted = true;
return totalRect.geometry( (int)xScale, (int)yScale );
}
return QRect();
}
void SmoothPathEngine::paint( QPainter * painter, double xScale, double yScale, const QRect & /*clipRect*/ )
{
const double penWidth = m_annotElement.attribute( QStringLiteral("width"), QStringLiteral("1") ).toInt();
const qreal opacity = m_annotElement.attribute( QStringLiteral("opacity"), QStringLiteral("1.0") ).toDouble();
// use engine's color for painting
const SmoothPath path( points, QPen( m_engineColor, penWidth ), opacity, compositionMode );
// draw the path
path.paint( painter, xScale, yScale );
}
void SmoothPath::paint( QPainter * painter, double xScale, double yScale ) const
{
// draw SmoothPaths with at least 2 points
if ( points.count() > 1 )
{
painter->setCompositionMode( compositionMode );
painter->setPen( pen );
painter->setOpacity( opacity );
QPainterPath path;
QLinkedList<Okular::NormalizedPoint>::const_iterator pIt = points.begin(), pEnd = points.end();
path.moveTo( QPointF( pIt->x * xScale, pIt->y * yScale ) );
++pIt;
for ( ; pIt != pEnd; ++pIt )
{
path.lineTo( QPointF( pIt->x * xScale, pIt->y * yScale ) );
}
painter->drawPath(path);
}
}
QList< Okular::Annotation* > SmoothPathEngine::end()
{
m_creationCompleted = false;
// find out annotation's description node
if ( m_annotElement.isNull() )
return QList< Okular::Annotation* >();
// find out annotation's type
Okular::Annotation * ann = nullptr;
QString typeString = m_annotElement.attribute( QStringLiteral("type") );
// create InkAnnotation from path
if ( typeString == QLatin1String("Ink") )
{
Okular::InkAnnotation * ia = new Okular::InkAnnotation();
ann = ia;
if ( m_annotElement.hasAttribute( QStringLiteral("width") ) )
ann->style().setWidth( m_annotElement.attribute( QStringLiteral("width") ).toDouble() );
// fill points
QList< QLinkedList<Okular::NormalizedPoint> > list = ia->inkPaths();
list.append( points );
ia->setInkPaths( list );
// set boundaries
ia->setBoundingRectangle( totalRect );
}
// safety check
if ( !ann )
return QList< Okular::Annotation* >();
// set common attributes
ann->style().setColor( m_annotElement.hasAttribute( QStringLiteral("color") ) ?
m_annotElement.attribute( QStringLiteral("color") ) : m_engineColor );
if ( m_annotElement.hasAttribute( QStringLiteral("opacity") ) )
ann->style().setOpacity( m_annotElement.attribute( QStringLiteral("opacity"), QStringLiteral("1.0") ).toDouble() );
// return annotation
return QList< Okular::Annotation* >() << ann;
}
SmoothPath SmoothPathEngine::endSmoothPath()
{
m_creationCompleted = false;
QColor color( m_annotElement.hasAttribute( QStringLiteral("color") ) ?
m_annotElement.attribute( QStringLiteral("color") ) : m_engineColor );
const int width = m_annotElement.attribute( QStringLiteral("width"), QStringLiteral("2") ).toInt();
const qreal opacity = m_annotElement.attribute( QStringLiteral("opacity"), QStringLiteral("1.0") ).toDouble();
return SmoothPath( points, QPen(color, width), opacity, compositionMode );
}
/* kate: replace-tabs on; indent-width 4; */