[FEATURE][composer] Holding shift while drawing new lines constrains lines to horizontal/vertical/diagonals, while drawing rectangles constrains items to squares. Holding control switches to a draw-from-center mode. Sponsored by City of Uster, Switzerland.

This commit is contained in:
Nyall Dawson 2014-07-22 07:41:06 +10:00
parent 622e2d14ab
commit 17d6da9adb
6 changed files with 254 additions and 33 deletions

View File

@ -33,6 +33,19 @@ class QgsComposerUtils
* @param y in/out: y cooreinate before / after the rotation
*/
static void rotate( const double angle, double& x, double& y );
/**Ensures that an angle is in the range 0 <= angle < 360
* @param angle angle in degrees
* @returns equivalent angle within the range [0, 360)
* @see snappedAngle
*/
static double normalizedAngle( const double angle );
/**Snaps an angle to its closest 45 degree angle
* @param angle angle in degrees
* @returns angle snapped to 0, 45/90/135/180/225/270 or 315 degrees
*/
static double snappedAngle( const double angle );
/**Calculates the largest scaled version of originalRect which fits within boundsRect, when it is rotated by
* a specified amount.

View File

@ -92,6 +92,60 @@ void QgsComposerUtils::rotate( const double angle, double &x, double &y )
y = yRot;
}
double QgsComposerUtils::normalizedAngle( const double angle )
{
double clippedAngle = angle;
if ( clippedAngle >= 360.0 || clippedAngle <= -360.0 )
{
clippedAngle = fmod( clippedAngle, 360.0 );
}
if ( clippedAngle < 0.0 )
{
clippedAngle += 360.0;
}
return clippedAngle;
}
double QgsComposerUtils::snappedAngle( const double angle )
{
//normalize angle to 0-360 degrees
double clippedAngle = normalizedAngle( angle );
//snap angle to 45 degree
if ( clippedAngle >= 22.5 && clippedAngle < 67.5 )
{
return 45.0;
}
else if ( clippedAngle >= 67.5 && clippedAngle < 112.5 )
{
return 90.0;
}
else if ( clippedAngle >= 112.5 && clippedAngle < 157.5 )
{
return 135.0;
}
else if ( clippedAngle >= 157.5 && clippedAngle < 202.5 )
{
return 180.0;
}
else if ( clippedAngle >= 202.5 && clippedAngle < 247.5 )
{
return 225.0;
}
else if ( clippedAngle >= 247.5 && clippedAngle < 292.5 )
{
return 270.0;
}
else if ( clippedAngle >= 292.5 && clippedAngle < 337.5 )
{
return 315.0;
}
else
{
return 0.0;
}
}
QRectF QgsComposerUtils::largestRotatedRectWithinBounds( const QRectF originalRect, const QRectF boundsRect, const double rotation )
{
double originalWidth = originalRect.width();
@ -100,7 +154,7 @@ QRectF QgsComposerUtils::largestRotatedRectWithinBounds( const QRectF originalRe
double boundsHeight = boundsRect.height();
double ratioBoundsRect = boundsWidth / boundsHeight;
double clippedRotation = fmod( rotation, 360.0 );
double clippedRotation = normalizedAngle( rotation );
//shortcut for some rotation values
if ( clippedRotation == 0 || clippedRotation == 90 || clippedRotation == 180 || clippedRotation == 270 )

View File

@ -55,6 +55,19 @@ class CORE_EXPORT QgsComposerUtils
*/
static void rotate( const double angle, double& x, double& y );
/**Ensures that an angle is in the range 0 <= angle < 360
* @param angle angle in degrees
* @returns equivalent angle within the range [0, 360)
* @see snappedAngle
*/
static double normalizedAngle( const double angle );
/**Snaps an angle to its closest 45 degree angle
* @param angle angle in degrees
* @returns angle snapped to 0, 45/90/135/180/225/270 or 315 degrees
*/
static double snappedAngle( const double angle );
/**Calculates the largest scaled version of originalRect which fits within boundsRect, when it is rotated by
* a specified amount.
* @param originalRect QRectF to be rotated and scaled

View File

@ -45,6 +45,7 @@
#include "qgspaperitem.h"
#include "qgsmapcanvas.h" //for QgsMapCanvas::WheelAction
#include "qgscursors.h"
#include "qgscomposerutils.h"
QgsComposerView::QgsComposerView( QWidget* parent, const char* name, Qt::WindowFlags f )
: QGraphicsView( parent )
@ -333,6 +334,7 @@ void QgsComposerView::mousePressEvent( QMouseEvent* e )
{
mRubberBandStartPos = QPointF( snappedScenePoint.x(), snappedScenePoint.y() );
mRubberBandLineItem = new QGraphicsLineItem( snappedScenePoint.x(), snappedScenePoint.y(), snappedScenePoint.x(), snappedScenePoint.y() );
mRubberBandLineItem->setPen( QPen( QBrush( QColor( 227, 22, 22, 200 ) ), 0 ) );
mRubberBandLineItem->setZValue( 1000 );
scene()->addItem( mRubberBandLineItem );
scene()->update();
@ -352,6 +354,8 @@ void QgsComposerView::mousePressEvent( QMouseEvent* e )
{
QTransform t;
mRubberBandItem = new QGraphicsRectItem( 0, 0, 0, 0 );
mRubberBandItem->setBrush( Qt::NoBrush );
mRubberBandItem->setPen( QPen( QBrush( QColor( 227, 22, 22, 200 ) ), 0 ) );
mRubberBandStartPos = QPointF( snappedScenePoint.x(), snappedScenePoint.y() );
t.translate( snappedScenePoint.x(), snappedScenePoint.y() );
mRubberBandItem->setTransform( t );
@ -486,8 +490,8 @@ void QgsComposerView::startMarqueeSelect( QPointF & scenePoint )
QTransform t;
mRubberBandItem = new QGraphicsRectItem( 0, 0, 0, 0 );
mRubberBandItem->setBrush( QBrush( QColor( 225, 50, 70, 25 ) ) );
mRubberBandItem->setPen( QPen( Qt::DotLine ) );
mRubberBandItem->setBrush( QBrush( QColor( 224, 178, 76, 63 ) ) );
mRubberBandItem->setPen( QPen( QBrush( QColor( 254, 58, 29, 100 ) ), 0, Qt::DotLine ) );
mRubberBandStartPos = QPointF( scenePoint.x(), scenePoint.y() );
t.translate( scenePoint.x(), scenePoint.y() );
mRubberBandItem->setTransform( t );
@ -742,9 +746,7 @@ void QgsComposerView::mouseReleaseEvent( QMouseEvent* e )
}
else
{
QPointF scenePoint = mapToScene( e->pos() );
QPointF snappedScenePoint = composition()->snapPointToGrid( scenePoint );
QgsComposerArrow* composerArrow = new QgsComposerArrow( mRubberBandStartPos, QPointF( snappedScenePoint.x(), snappedScenePoint.y() ), composition() );
QgsComposerArrow* composerArrow = new QgsComposerArrow( mRubberBandLineItem->line().p1(), mRubberBandLineItem->line().p2(), composition() );
composition()->addComposerArrow( composerArrow );
composition()->clearSelection();
@ -925,6 +927,19 @@ void QgsComposerView::mouseMoveEvent( QMouseEvent* e )
return;
}
bool shiftModifier = false;
bool controlModifier = false;
if ( e->modifiers() & Qt::ShiftModifier )
{
//shift key depressed
shiftModifier = true;
}
if ( e->modifiers() & Qt::ControlModifier )
{
//control key depressed
controlModifier = true;
}
mMouseCurrentXY = e->pos();
//update cursor position in composer status bar
emit cursorPosChanged( mapToScene( e->pos() ) );
@ -960,7 +975,7 @@ void QgsComposerView::mouseMoveEvent( QMouseEvent* e )
if ( mMarqueeSelect || mMarqueeZoom )
{
updateRubberBand( scenePoint );
updateRubberBandRect( scenePoint );
return;
}
@ -972,10 +987,7 @@ void QgsComposerView::mouseMoveEvent( QMouseEvent* e )
case AddArrow:
{
if ( mRubberBandLineItem )
{
mRubberBandLineItem->setLine( mRubberBandStartPos.x(), mRubberBandStartPos.y(), scenePoint.x(), scenePoint.y() );
}
updateRubberBandLine( scenePoint, shiftModifier );
break;
}
@ -990,7 +1002,7 @@ void QgsComposerView::mouseMoveEvent( QMouseEvent* e )
case AddTable:
//adjust rubber band item
{
updateRubberBand( scenePoint );
updateRubberBandRect( scenePoint, shiftModifier, controlModifier );
break;
}
@ -1011,8 +1023,13 @@ void QgsComposerView::mouseMoveEvent( QMouseEvent* e )
}
}
void QgsComposerView::updateRubberBand( QPointF & pos )
void QgsComposerView::updateRubberBandRect( QPointF & pos, const bool constrainSquare, const bool fromCenter )
{
if ( !mRubberBandItem )
{
return;
}
double x = 0;
double y = 0;
double width = 0;
@ -1021,35 +1038,82 @@ void QgsComposerView::updateRubberBand( QPointF & pos )
double dx = pos.x() - mRubberBandStartPos.x();
double dy = pos.y() - mRubberBandStartPos.y();
if ( dx < 0 )
if ( constrainSquare )
{
x = pos.x();
width = -dx;
if ( fabs( dx ) > fabs( dy ) )
{
width = fabs( dx );
height = width;
}
else
{
height = fabs( dy );
width = height;
}
x = mRubberBandStartPos.x() - (( dx < 0 ) ? width : 0 );
y = mRubberBandStartPos.y() - (( dy < 0 ) ? height : 0 );
}
else
{
x = mRubberBandStartPos.x();
width = dx;
//not constraining
if ( dx < 0 )
{
x = pos.x();
width = -dx;
}
else
{
x = mRubberBandStartPos.x();
width = dx;
}
if ( dy < 0 )
{
y = pos.y();
height = -dy;
}
else
{
y = mRubberBandStartPos.y();
height = dy;
}
}
if ( dy < 0 )
if ( fromCenter )
{
y = pos.y();
height = -dy;
}
else
{
y = mRubberBandStartPos.y();
height = dy;
x = mRubberBandStartPos.x() - width;
y = mRubberBandStartPos.y() - height;
width *= 2.0;
height *= 2.0;
}
if ( mRubberBandItem )
mRubberBandItem->setRect( 0, 0, width, height );
QTransform t;
t.translate( x, y );
mRubberBandItem->setTransform( t );
}
void QgsComposerView::updateRubberBandLine( const QPointF &pos, const bool constrainAngles )
{
if ( !mRubberBandLineItem )
{
mRubberBandItem->setRect( 0, 0, width, height );
QTransform t;
t.translate( x, y );
mRubberBandItem->setTransform( t );
return;
}
//snap to grid
QPointF snappedScenePoint = composition()->snapPointToGrid( pos );
QLineF newLine = QLineF( mRubberBandStartPos, snappedScenePoint );
if ( constrainAngles )
{
//movement is contrained to 45 degree angles
double angle = QgsComposerUtils::snappedAngle( newLine.angle() );
newLine.setAngle( angle );
}
mRubberBandLineItem->setLine( newLine );
}
void QgsComposerView::mouseDoubleClickEvent( QMouseEvent* e )

View File

@ -229,8 +229,10 @@ class GUI_EXPORT QgsComposerView: public QGraphicsView
/**Zoom composition from a mouse wheel event*/
void wheelZoom( QWheelEvent * event );
/**Redraws the rubber band*/
void updateRubberBand( QPointF & pos );
/**Redraws the rectangular rubber band*/
void updateRubberBandRect( QPointF & pos, const bool constrainSquare = false, const bool fromCenter = false );
/**Redraws the linear rubber band*/
void updateRubberBandLine( const QPointF & pos, const bool constrainAngles = false );
/**Removes the rubber band and cleans up*/
void removeRubberBand();

View File

@ -36,6 +36,8 @@ class TestQgsComposerUtils: public QObject
void drawArrowHead(); //test drawing an arrow head
void angle(); //test angle utility function
void rotate(); //test rotation helper function
void normalizedAngle(); //test normalised angle function
void snappedAngle(); //test snapped angle function
void largestRotatedRect(); //test largest rotated rect helper function
void pointsToMM(); //test conversion of point size to mm
void mmToPoints(); //test conversion of mm to point size
@ -157,6 +159,79 @@ void TestQgsComposerUtils::rotate()
}
}
void TestQgsComposerUtils::normalizedAngle()
{
QList< QPair< double, double > > testVals;
testVals << qMakePair( 0.0, 0.0 );
testVals << qMakePair( 90.0, 90.0 );
testVals << qMakePair( 180.0, 180.0 );
testVals << qMakePair( 270.0, 270.0 );
testVals << qMakePair( 360.0, 0.0 );
testVals << qMakePair( 390.0, 30.0 );
testVals << qMakePair( 720.0, 0.0 );
testVals << qMakePair( 730.0, 10.0 );
testVals << qMakePair( -10.0, 350.0 );
testVals << qMakePair( -360.0, 0.0 );
testVals << qMakePair( -370.0, 350.0 );
testVals << qMakePair( -760.0, 320.0 );
//test normalized angle helper function
QList< QPair< double, double > >::const_iterator it = testVals.constBegin();
for ( ; it != testVals.constEnd(); ++it )
{
QVERIFY( qgsDoubleNear( QgsComposerUtils::normalizedAngle(( *it ).first ), ( *it ).second ) );
}
}
void TestQgsComposerUtils::snappedAngle()
{
QList< QPair< double, double > > testVals;
testVals << qMakePair( 0.0, 0.0 );
testVals << qMakePair( 10.0, 0.0 );
testVals << qMakePair( 20.0, 0.0 );
testVals << qMakePair( 30.0, 45.0 );
testVals << qMakePair( 40.0, 45.0 );
testVals << qMakePair( 50.0, 45.0 );
testVals << qMakePair( 60.0, 45.0 );
testVals << qMakePair( 70.0, 90.0 );
testVals << qMakePair( 80.0, 90.0 );
testVals << qMakePair( 90.0, 90.0 );
testVals << qMakePair( 100.0, 90.0 );
testVals << qMakePair( 110.0, 90.0 );
testVals << qMakePair( 120.0, 135.0 );
testVals << qMakePair( 130.0, 135.0 );
testVals << qMakePair( 140.0, 135.0 );
testVals << qMakePair( 150.0, 135.0 );
testVals << qMakePair( 160.0, 180.0 );
testVals << qMakePair( 170.0, 180.0 );
testVals << qMakePair( 180.0, 180.0 );
testVals << qMakePair( 190.0, 180.0 );
testVals << qMakePair( 200.0, 180.0 );
testVals << qMakePair( 210.0, 225.0 );
testVals << qMakePair( 220.0, 225.0 );
testVals << qMakePair( 230.0, 225.0 );
testVals << qMakePair( 240.0, 225.0 );
testVals << qMakePair( 250.0, 270.0 );
testVals << qMakePair( 260.0, 270.0 );
testVals << qMakePair( 270.0, 270.0 );
testVals << qMakePair( 280.0, 270.0 );
testVals << qMakePair( 290.0, 270.0 );
testVals << qMakePair( 300.0, 315.0 );
testVals << qMakePair( 310.0, 315.0 );
testVals << qMakePair( 320.0, 315.0 );
testVals << qMakePair( 330.0, 315.0 );
testVals << qMakePair( 340.0, 0.0 );
testVals << qMakePair( 350.0, 0.0 );
testVals << qMakePair( 360.0, 0.0 );
//test snapped angle helper function
QList< QPair< double, double > >::const_iterator it = testVals.constBegin();
for ( ; it != testVals.constEnd(); ++it )
{
QVERIFY( qgsDoubleNear( QgsComposerUtils::snappedAngle(( *it ).first ), ( *it ).second ) );
}
}
void TestQgsComposerUtils::largestRotatedRect()
{
QRectF wideRect = QRectF( 0, 0, 2, 1 );