Skip to content
Permalink
Browse files
[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.
  • Loading branch information
nyalldawson committed Jul 23, 2014
1 parent 622e2d1 commit 17d6da9adbffd565eac404115a5806eeefa3d1e9
@@ -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;
x = mRubberBandStartPos.x() - width;
y = mRubberBandStartPos.y() - height;
width *= 2.0;
height *= 2.0;
}
else

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 )
{
y = mRubberBandStartPos.y();
height = dy;
return;
}

if ( mRubberBandItem )
//snap to grid
QPointF snappedScenePoint = composition()->snapPointToGrid( pos );

QLineF newLine = QLineF( mRubberBandStartPos, snappedScenePoint );

if ( constrainAngles )
{
mRubberBandItem->setRect( 0, 0, width, height );
QTransform t;
t.translate( x, y );
mRubberBandItem->setTransform( t );
//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();

2 comments on commit 17d6da9

@olivierdalang

This comment has been minimized.

Copy link
Contributor

@olivierdalang olivierdalang replied Jul 23, 2014

@nyalldawson : thanks for this feature !
The adobe suite as well as some other software use the alt modifier instead of the ctrl modifier to "draw from center", did you consider using it ? (not sure how standard it is though, but alt seems more natural to me)

@nyalldawson

This comment has been minimized.

Copy link
Collaborator Author

@nyalldawson nyalldawson replied Jul 23, 2014

@olivierdalang I'll do a bit of an audit and check. I'm usually inclined to just follow what the adobe suite uses, so I'll switch the baheviour unless there's a strong trend to use ctrl

Please sign in to comment.