mirror of
https://github.com/qgis/QGIS.git
synced 2025-11-22 00:14:55 -05:00
[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:
parent
622e2d14ab
commit
17d6da9adb
@ -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.
|
||||
|
||||
@ -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 )
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 )
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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 );
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user