allow numerical input in rotation map tool

* use a combo box to set the angle
* allow snapping to angles (directly enabled when shift + click)
* uses click-click behavior
* rotation anchor is defined on CTRL+click and not on mouse move (TODO: snapping for anchor placement)
This commit is contained in:
Denis Rouzaud 2015-04-28 08:01:11 +02:00
parent 15c1dbc438
commit 96560c89b9
2 changed files with 382 additions and 199 deletions

View File

@ -21,34 +21,179 @@
#include "qgsvectordataprovider.h"
#include "qgsvectorlayer.h"
#include "qgstolerance.h"
#include "qgsvertexmarker.h"
#include "qgisapp.h"
#include "qgsspinbox.h"
#include <QMouseEvent>
#include <QSettings>
#include <QDoubleSpinBox>
#include <QEvent>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLabel>
#include <limits>
#include <math.h>
#include "qgsvertexmarker.h"
#define PI 3.14159265
QgsAngleMagnetWidget::QgsAngleMagnetWidget( QString label , QWidget *parent )
: QWidget( parent )
{
mLayout = new QHBoxLayout( this );
mLayout->setContentsMargins( 0, 0, 0, 0 );
//mLayout->setAlignment( Qt::AlignLeft );
setLayout( mLayout );
if ( !label.isNull() )
{
QLabel* lbl = new QLabel( label, this );
lbl->setAlignment( Qt::AlignRight );
mLayout->addWidget( lbl );
}
mAngleSpinBox = new QDoubleSpinBox( this );
mAngleSpinBox->setMinimum( -360 );
mAngleSpinBox->setMaximum( 360 );
mAngleSpinBox->setSuffix( QString::fromUtf8( "°" ) );
mAngleSpinBox->setSingleStep( 1 );
mAngleSpinBox->setValue( 0 );
mLayout->addWidget( mAngleSpinBox );
mMagnetSpinBox = new QgsSpinBox( this );
mMagnetSpinBox->setMinimum( 0 );
mMagnetSpinBox->setMaximum( 180 );
mMagnetSpinBox->setPrefix( tr( "snapp to " ) );
mMagnetSpinBox->setSuffix( QString::fromUtf8( "°" ) );
mMagnetSpinBox->setSingleStep( 15 );
mMagnetSpinBox->setValue( 0 );
mMagnetSpinBox->setClearValue( 0, tr( "do not snapp" ) );
mLayout->addWidget( mMagnetSpinBox );
// connect signals
mAngleSpinBox->installEventFilter( this );
connect( mAngleSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( angleSpinBoxValueChanged( double ) ) );
// config focus
setFocusProxy( mAngleSpinBox );
}
QgsAngleMagnetWidget::~QgsAngleMagnetWidget()
{
}
void QgsAngleMagnetWidget::setAngle( double angle )
{
const int magnet = mMagnetSpinBox->value();
if ( magnet )
{
mAngleSpinBox->setValue( qRound( angle / magnet ) * magnet );
}
else
{
mAngleSpinBox->setValue( angle );
}
}
double QgsAngleMagnetWidget::angle()
{
return mAngleSpinBox->value();
}
void QgsAngleMagnetWidget::setMagnet( int magnet )
{
mMagnetSpinBox->setValue( magnet );
}
bool QgsAngleMagnetWidget::eventFilter( QObject *obj, QEvent *ev )
{
if ( obj == mAngleSpinBox && ev->type() == QEvent::KeyPress )
{
QKeyEvent *event = static_cast<QKeyEvent *>( ev );
if ( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return )
{
emit angleEditingFinished( angle() );
return true;
}
}
return false;
}
void QgsAngleMagnetWidget::angleSpinBoxValueChanged( double angle )
{
emit angleChanged( angle );
}
QgsMapToolRotateFeature::QgsMapToolRotateFeature( QgsMapCanvas* canvas )
: QgsMapToolEdit( canvas )
, mRubberBand( 0 )
, mRotation( 0 )
, mRotationOffset( 0 )
, mAnchorPoint( 0 )
, mCtrl( false )
, mRotationActive( false )
, mRotationBarItem( 0 )
, mRotationWidget( 0 )
{
}
QgsMapToolRotateFeature::~QgsMapToolRotateFeature()
{
deleteRotationWidget();
delete mAnchorPoint;
delete mRubberBand;
deleteRubberband();
}
void QgsMapToolRotateFeature::canvasMoveEvent( QMouseEvent * e )
{
if ( mCtrl )
if ( mRotationActive )
{
const double XDistance = e->pos().x() - mStPoint.x();
const double YDistance = e->pos().y() - mStPoint.y();
double rotation = atan2( YDistance, XDistance ) * ( 180 / PI );
if ( mRotationWidget )
{
mRotationWidget->setAngle( rotation - mRotationOffset );
mRotationWidget->setFocus( Qt::TabFocusReason );
}
else
{
updateRubberband( rotation - mRotationOffset );
}
}
}
void QgsMapToolRotateFeature::canvasReleaseEvent( QMouseEvent * e )
{
deleteRotationWidget();
if ( !mCanvas )
{
return;
}
QgsVectorLayer* vlayer = currentVectorLayer();
if ( !vlayer )
{
deleteRubberband();
notifyNotVectorLayer();
return;
}
if ( e->button() == Qt::RightButton )
{
deleteRubberband();
mRotationActive = false;
return;
}
// place anchor point on CTRL + click
if ( e->modifiers() & Qt::ControlModifier )
{
if ( !mAnchorPoint )
{
@ -58,147 +203,153 @@ void QgsMapToolRotateFeature::canvasMoveEvent( QMouseEvent * e )
mStartPointMapCoords = toMapCoordinates( e->pos() );
mStPoint = e->pos();
return;
}
if ( mRubberBand )
// Initialize rotation if not yet active
if ( !mRotationActive )
{
double XDistance = e->pos().x() - mStPoint.x();
double YDistance = e->pos().y() - mStPoint.y();
mRotation = atan2( YDistance, XDistance ) * ( 180 / PI );
mRotation = mRotation - mRotationOffset;
mRotation = 0;
mRotationOffset = 0;
deleteRubberband();
mInitialPos = e->pos();
if ( !vlayer->isEditable() )
{
notifyNotEditableLayer();
return;
}
QgsPoint layerCoords = toLayerCoordinates( vlayer, e->pos() );
double searchRadius = QgsTolerance::vertexSearchRadius( mCanvas->currentLayer(), mCanvas->mapSettings() );
QgsRectangle selectRect( layerCoords.x() - searchRadius, layerCoords.y() - searchRadius,
layerCoords.x() + searchRadius, layerCoords.y() + searchRadius );
if ( vlayer->selectedFeatureCount() == 0 )
{
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( selectRect ).setSubsetOfAttributes( QgsAttributeList() ) );
//find the closest feature
QgsGeometry* pointGeometry = QgsGeometry::fromPoint( layerCoords );
if ( !pointGeometry )
{
return;
}
double minDistance = std::numeric_limits<double>::max();
QgsFeature cf;
QgsFeature f;
while ( fit.nextFeature( f ) )
{
if ( f.geometry() )
{
double currentDistance = pointGeometry->distance( *f.geometry() );
if ( currentDistance < minDistance )
{
minDistance = currentDistance;
cf = f;
}
}
}
delete pointGeometry;
if ( minDistance == std::numeric_limits<double>::max() )
{
return;
}
QgsRectangle bound = cf.geometry()->boundingBox();
mStartPointMapCoords = toMapCoordinates( vlayer, bound.center() );
if ( !mAnchorPoint )
{
mAnchorPoint = new QgsVertexMarker( mCanvas );
}
mAnchorPoint->setIconType( QgsVertexMarker::ICON_CROSS );
mAnchorPoint->setCenter( mStartPointMapCoords );
mStPoint = toCanvasCoordinates( mStartPointMapCoords );
mRotatedFeatures.clear();
mRotatedFeatures << cf.id(); //todo: take the closest feature, not the first one...
mRubberBand = createRubberBand( vlayer->geometryType() );
mRubberBand->setToGeometry( cf.geometry(), vlayer );
}
else
{
mRotatedFeatures = vlayer->selectedFeaturesIds();
mRubberBand = createRubberBand( vlayer->geometryType() );
QgsFeature feat;
QgsFeatureIterator it = vlayer->selectedFeaturesIterator();
while ( it.nextFeature( feat ) )
{
mRubberBand->addGeometry( feat.geometry(), vlayer );
}
}
mRubberBand->setColor( QColor( 255, 0, 0, 65 ) );
mRubberBand->setWidth( 2 );
mRubberBand->show();
double XDistance = mInitialPos.x() - mAnchorPoint->x();
double YDistance = mInitialPos.y() - mAnchorPoint->y() ;
mRotationOffset = atan2( YDistance, XDistance ) * ( 180 / PI );
createRotationWidget();
if ( e->modifiers() & Qt::ShiftModifier )
{
if ( mRotationWidget )
{
mRotationWidget->setMagnet( 45 );
}
}
mRotationActive = true;
return;
}
applyRotation( mRotation );
}
void QgsMapToolRotateFeature::updateRubberband( double rotation )
{
if ( mRotationActive )
{
mRotation = rotation;
mStPoint = toCanvasCoordinates( mStartPointMapCoords );
double offsetX = mStPoint.x() - mRubberBand->x();
double offsetY = mStPoint.y() - mRubberBand->y();
mRubberBand->setTransform( QTransform().translate( offsetX, offsetY ).rotate( mRotation ).translate( -1 * offsetX, -1 * offsetY ) );
mRubberBand->update();
if ( mRubberBand )
{
mRubberBand->setTransform( QTransform().translate( offsetX, offsetY ).rotate( mRotation ).translate( -1 * offsetX, -1 * offsetY ) );
mRubberBand->update();
}
}
}
void QgsMapToolRotateFeature::canvasPressEvent( QMouseEvent * e )
void QgsMapToolRotateFeature::applyRotation( double rotation )
{
mRotation = 0;
if ( mCtrl )
{
return;
}
delete mRubberBand;
mRubberBand = 0;
mInitialPos = e->pos();
mRotation = rotation;
mRotationActive = false;
QgsVectorLayer* vlayer = currentVectorLayer();
if ( !vlayer )
{
deleteRubberband();
notifyNotVectorLayer();
return;
}
if ( !vlayer->isEditable() )
{
notifyNotEditableLayer();
return;
}
QgsPoint layerCoords = toLayerCoordinates( vlayer, e->pos() );
double searchRadius = QgsTolerance::vertexSearchRadius( mCanvas->currentLayer(), mCanvas->mapSettings() );
QgsRectangle selectRect( layerCoords.x() - searchRadius, layerCoords.y() - searchRadius,
layerCoords.x() + searchRadius, layerCoords.y() + searchRadius );
if ( vlayer->selectedFeatureCount() == 0 )
{
QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( selectRect ).setSubsetOfAttributes( QgsAttributeList() ) );
//find the closest feature
QgsGeometry* pointGeometry = QgsGeometry::fromPoint( layerCoords );
if ( !pointGeometry )
{
return;
}
double minDistance = std::numeric_limits<double>::max();
QgsFeature cf;
QgsFeature f;
while ( fit.nextFeature( f ) )
{
if ( f.geometry() )
{
double currentDistance = pointGeometry->distance( *f.geometry() );
if ( currentDistance < minDistance )
{
minDistance = currentDistance;
cf = f;
}
}
}
delete pointGeometry;
if ( minDistance == std::numeric_limits<double>::max() )
{
return;
}
QgsRectangle bound = cf.geometry()->boundingBox();
mStartPointMapCoords = toMapCoordinates( vlayer, bound.center() );
if ( !mAnchorPoint )
{
mAnchorPoint = new QgsVertexMarker( mCanvas );
}
mAnchorPoint->setIconType( QgsVertexMarker::ICON_CROSS );
mAnchorPoint->setCenter( mStartPointMapCoords );
mStPoint = toCanvasCoordinates( mStartPointMapCoords );
mRotatedFeatures.clear();
mRotatedFeatures << cf.id(); //todo: take the closest feature, not the first one...
mRubberBand = createRubberBand( vlayer->geometryType() );
mRubberBand->setToGeometry( cf.geometry(), vlayer );
}
else
{
mRotatedFeatures = vlayer->selectedFeaturesIds();
mRubberBand = createRubberBand( vlayer->geometryType() );
QgsFeature feat;
QgsFeatureIterator it = vlayer->selectedFeaturesIterator();
while ( it.nextFeature( feat ) )
{
mRubberBand->addGeometry( feat.geometry(), vlayer );
}
}
mRubberBand->setColor( QColor( 255, 0, 0, 65 ) );
mRubberBand->setWidth( 2 );
mRubberBand->show();
double XDistance = mInitialPos.x() - mAnchorPoint->x();
double YDistance = mInitialPos.y() - mAnchorPoint->y() ;
mRotationOffset = atan2( YDistance, XDistance ) * ( 180 / PI );
}
void QgsMapToolRotateFeature::canvasReleaseEvent( QMouseEvent * e )
{
Q_UNUSED( e );
if ( !mRubberBand )
{
return;
}
QgsVectorLayer* vlayer = currentVectorLayer();
if ( !vlayer )
{
return;
}
//calculations for affine transformation
double angle = -1 * mRotation * ( PI / 180 );
QgsPoint anchorPoint = toLayerCoordinates( vlayer, mStartPointMapCoords );
@ -247,65 +398,11 @@ void QgsMapToolRotateFeature::canvasReleaseEvent( QMouseEvent * e )
mAnchorPoint->setCenter( QgsPoint( anchorX, anchorY ) );
delete mRubberBand;
mRubberBand = 0;
deleteRotationWidget();
deleteRubberband();
mCanvas->refresh();
vlayer->endEditCommand();
}
void QgsMapToolRotateFeature::resetAnchor()
{
QgsVectorLayer* vlayer = currentVectorLayer();
if ( !vlayer )
{
return;
}
if ( vlayer->selectedFeatureCount() == 0 )
{
return;
}
else
{
QgsRectangle bound = vlayer->boundingBoxOfSelected();
mStartPointMapCoords = toMapCoordinates( vlayer, bound.center() );
mAnchorPoint->setCenter( mStartPointMapCoords );
mStPoint = toCanvasCoordinates( mStartPointMapCoords );
}
}
void QgsMapToolRotateFeature::keyPressEvent( QKeyEvent* e )
{
if ( e->key() == Qt::Key_Control )
{
mCtrl = true;
mCanvas->viewport()->setMouseTracking( true );
return;
}
if ( e->key() == Qt::Key_Escape )
{
this->resetAnchor();
}
}
void QgsMapToolRotateFeature::keyReleaseEvent( QKeyEvent* e )
{
if ( e->key() == Qt::Key_Control )
{
mCtrl = false;
mCanvas->viewport()->setMouseTracking( false );
return;
}
}
void QgsMapToolRotateFeature::activate()
@ -328,7 +425,6 @@ void QgsMapToolRotateFeature::activate()
}
else
{
QgsRectangle bound = vlayer->boundingBoxOfSelected();
mStartPointMapCoords = toMapCoordinates( vlayer, bound.center() );
@ -342,13 +438,54 @@ void QgsMapToolRotateFeature::activate()
}
}
void QgsMapToolRotateFeature::deactivate()
void QgsMapToolRotateFeature::deleteRubberband()
{
delete mRubberBand;
delete mAnchorPoint;
mRubberBand = 0;
}
void QgsMapToolRotateFeature::deactivate()
{
deleteRotationWidget();
mRotationActive = false;
delete mAnchorPoint;
mAnchorPoint = 0;
mRotationOffset = 0;
deleteRubberband();
QgsMapTool::deactivate();
}
void QgsMapToolRotateFeature::createRotationWidget()
{
if ( !mCanvas )
{
return;
}
deleteRotationWidget();
mRotationWidget = new QgsAngleMagnetWidget( "Rotation:" );
mRotationBarItem = QgisApp::instance()->messageBar()->pushWidget( mRotationWidget );
mRotationWidget->setFocus( Qt::TabFocusReason );
QObject::connect( mRotationWidget, SIGNAL( angleChanged( double ) ), this, SLOT( updateRubberband( double ) ) );
QObject::connect( mRotationWidget, SIGNAL( angleEditingFinished( double ) ), this, SLOT( applyRotation( double ) ) );
}
void QgsMapToolRotateFeature::deleteRotationWidget()
{
if ( mRotationWidget )
{
QObject::disconnect( mRotationWidget, SIGNAL( angleChanged( double ) ), this, SLOT( updateRubberband( double ) ) );
QObject::disconnect( mRotationWidget, SIGNAL( angleEditingFinished( double ) ), this, SLOT( applyRotation( double ) ) );
mRotationWidget->releaseKeyboard();
}
if ( mRotationBarItem )
{
QgisApp::instance()->messageBar()->popWidget( mRotationBarItem );
}
mRotationBarItem = 0;
mRotationWidget = 0;
}

View File

@ -16,12 +16,56 @@
#ifndef QGSMAPTOOLROTATEFEATURE_H
#define QGSMAPTOOLROTATEFEATURE_H
#include <QWidget>
#include "qgsmaptooledit.h"
#include "qgsvectorlayer.h"
class QgsVertexMarker;
/**Map tool for translating feature position by mouse drag*/
class QDoubleSpinBox;
class QHBoxLayout;
class QgsSpinBox;
class QgsVertexMarker;
class QgsMessageBarItem;
class APP_EXPORT QgsAngleMagnetWidget : public QWidget
{
Q_OBJECT
public:
explicit QgsAngleMagnetWidget( QString label = QString( "" ), QWidget *parent = 0 );
~QgsAngleMagnetWidget();
void setAngle( double angle );
double angle();
void setMagnet( int magnet );
signals:
void angleChanged( double angle );
void angleEditingFinished( double angle );
public slots:
protected:
bool eventFilter( QObject *obj, QEvent *ev );
private slots:
void angleSpinBoxValueChanged( double angle );
private:
QHBoxLayout* mLayout;
QDoubleSpinBox* mAngleSpinBox;
QgsSpinBox* mMagnetSpinBox;
};
/** Map tool to rotate features */
class APP_EXPORT QgsMapToolRotateFeature: public QgsMapToolEdit
{
Q_OBJECT
@ -31,27 +75,25 @@ class APP_EXPORT QgsMapToolRotateFeature: public QgsMapToolEdit
virtual void canvasMoveEvent( QMouseEvent * e ) override;
virtual void canvasPressEvent( QMouseEvent * e ) override;
virtual void canvasReleaseEvent( QMouseEvent * e ) override;
void keyPressEvent( QKeyEvent* e ) override;
void keyReleaseEvent( QKeyEvent* e ) override;
//! to reset the rotation anchor to selectionbound center
void resetAnchor();
//! called when map tool is being deactivated
void deactivate() override;
void activate() override;
private slots:
void updateRubberband( double rotation );
void applyRotation( double rotation );
private:
QgsGeometry rotateGeometry( QgsGeometry geom, QgsPoint point, double angle );
QgsPoint rotatePoint( QgsPoint point, double angle );
void deleteRubberband();
void createRotationWidget();
void deleteRotationWidget();
/**Start point of the move in map coordinates*/
QgsPoint mStartPointMapCoords;
@ -68,8 +110,12 @@ class APP_EXPORT QgsMapToolRotateFeature: public QgsMapToolEdit
QPoint mStPoint;
QgsVertexMarker* mAnchorPoint;
/** flag if crtl is pressed */
bool mCtrl;
bool mRotationActive;
/** Message bar item for the angle magnet widget*/
QgsMessageBarItem* mRotationBarItem;
/** Shows current angle value and allows numerical editing*/
QgsAngleMagnetWidget* mRotationWidget;
};
#endif