diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index 996c4386320..2ff4bf46472 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -52,6 +52,11 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt LowerRight, }; + enum UndoCommand + { + UndoIncrementalMove, + }; + explicit QgsLayoutItem( QgsLayout *layout, bool manageZValue = true ); %Docstring Constructor for QgsLayoutItem, with the specified parent ``layout``. @@ -445,9 +450,9 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt Emitted on item rotation change. %End - void sizeChanged(); + void sizePositionChanged(); %Docstring - Emitted when the item's size changes. + Emitted when the item's size or position changes. %End protected: diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index eba703f526b..ed44f404f72 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -293,8 +293,8 @@ void QgsLayoutItem::attemptResize( const QgsLayoutSize &size ) mItemSize = actualSizeTargetUnits; setRect( 0, 0, actualSizeLayoutUnits.width(), actualSizeLayoutUnits.height() ); - emit sizeChanged(); refreshItemPosition(); + emit sizePositionChanged(); } void QgsLayoutItem::attemptMove( const QgsLayoutPoint &point ) @@ -317,8 +317,8 @@ void QgsLayoutItem::attemptMove( const QgsLayoutPoint &point ) QgsLayoutPoint referencePointTargetUnits = mLayout->convertFromLayoutUnits( evaluatedPointLayoutUnits, point.units() ); mItemPosition = referencePointTargetUnits; - setScenePos( topLeftPointLayoutUnits ); + emit sizePositionChanged(); } void QgsLayoutItem::setScenePos( const QPointF &destinationPos ) diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index fb5b410fcc0..247fddb1210 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -79,6 +79,12 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt LowerRight, //!< Lower right corner of item }; + //! Layout item undo commands, used for collapsing undo commands + enum UndoCommand + { + UndoIncrementalMove = 1, //!< Layout item incremental movement, e.g. as a result of a keypress + }; + /** * Constructor for QgsLayoutItem, with the specified parent \a layout. * @@ -443,9 +449,9 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt void rotationChanged( double newRotation ); /** - * Emitted when the item's size changes. + * Emitted when the item's size or position changes. */ - void sizeChanged(); + void sizePositionChanged(); protected: diff --git a/src/gui/layout/qgslayoutmousehandles.cpp b/src/gui/layout/qgslayoutmousehandles.cpp index 701c4c87686..42c8b29ef61 100644 --- a/src/gui/layout/qgslayoutmousehandles.cpp +++ b/src/gui/layout/qgslayoutmousehandles.cpp @@ -189,14 +189,14 @@ void QgsLayoutMouseHandles::selectionChanged() if ( item->isSelected() ) { - connect( item, &QgsLayoutItem::sizeChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged ); + connect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged ); connect( item, &QgsLayoutItem::rotationChanged, this, &QgsLayoutMouseHandles::selectedItemRotationChanged ); connect( item, &QgsLayoutItem::frameChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged ); connect( item, &QgsLayoutItem::lockChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged ); } else { - disconnect( item, &QgsLayoutItem::sizeChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged ); + disconnect( item, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged ); disconnect( item, &QgsLayoutItem::rotationChanged, this, &QgsLayoutMouseHandles::selectedItemRotationChanged ); disconnect( item, &QgsLayoutItem::frameChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged ); disconnect( item, &QgsLayoutItem::lockChanged, this, &QgsLayoutMouseHandles::selectedItemSizeChanged ); diff --git a/src/gui/layout/qgslayoutview.cpp b/src/gui/layout/qgslayoutview.cpp index f7a4ef504a0..050e8a4e745 100644 --- a/src/gui/layout/qgslayoutview.cpp +++ b/src/gui/layout/qgslayoutview.cpp @@ -731,22 +731,86 @@ void QgsLayoutView::keyPressEvent( QKeyEvent *event ) mTool->keyPressEvent( event ); } - if ( !mTool || !event->isAccepted() ) + if ( mTool && event->isAccepted() ) + return; + + if ( event->key() == Qt::Key_Space && ! event->isAutoRepeat() ) { - if ( event->key() == Qt::Key_Space && ! event->isAutoRepeat() ) + if ( !( event->modifiers() & Qt::ControlModifier ) ) { - if ( !( event->modifiers() & Qt::ControlModifier ) ) - { - // Pan layout with space bar - setTool( mSpacePanTool ); - } - else - { - //ctrl+space pressed, so switch to temporary keyboard based zoom tool - setTool( mSpaceZoomTool ); - } - event->accept(); + // Pan layout with space bar + setTool( mSpacePanTool ); } + else + { + //ctrl+space pressed, so switch to temporary keyboard based zoom tool + setTool( mSpaceZoomTool ); + } + event->accept(); + } + else if ( event->key() == Qt::Key_Left + || event->key() == Qt::Key_Right + || event->key() == Qt::Key_Up + || event->key() == Qt::Key_Down ) + { + QgsLayout *l = currentLayout(); + const QList layoutItemList = l->selectedLayoutItems(); + + // increment used for cursor key item movement + double increment = 1.0; + if ( event->modifiers() & Qt::ShiftModifier ) + { + //holding shift while pressing cursor keys results in a big step + increment = 10.0; + } + else if ( event->modifiers() & Qt::AltModifier ) + { + //holding alt while pressing cursor keys results in a 1 pixel step + double viewScale = transform().m11(); + if ( viewScale > 0 ) + { + increment = 1 / viewScale; + } + } + + double deltaX = 0; + double deltaY = 0; + switch ( event->key() ) + { + case Qt::Key_Left: + deltaX = -increment; + break; + case Qt::Key_Right: + deltaX = increment; + break; + case Qt::Key_Up: + deltaY = -increment; + break; + case Qt::Key_Down: + deltaY = increment; + break; + default: + break; + } + + auto moveItem = [ l, deltaX, deltaY ]( QgsLayoutItem * item ) + { + QgsLayoutPoint itemPos = item->positionWithUnits(); + QgsLayoutPoint deltaPos = l->convertFromLayoutUnits( QPointF( deltaX, deltaY ), itemPos.units() ); + itemPos.setX( itemPos.x() + deltaPos.x() ); + itemPos.setY( itemPos.y() + deltaPos.y() ); + item->attemptMove( itemPos ); + }; + + l->undoStack()->beginMacro( tr( "Item moved" ) ); + for ( QgsLayoutItem *item : layoutItemList ) + { + l->undoStack()->beginCommand( item, tr( "Item moved" ), QgsLayoutItem::UndoIncrementalMove ); + moveItem( item ); + l->undoStack()->endCommand(); + } + l->undoStack()->endMacro(); + event->accept(); } } diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index 0851631b681..25ee2ff900e 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -691,16 +691,17 @@ void TestQgsLayoutItem::resize() //resize test item (no restrictions), same units as layout l.setUnits( QgsUnitTypes::LayoutMillimeters ); TestItem *item = new TestItem( &l ); - QSignalSpy spySizeChanged( item, &QgsLayoutItem::sizeChanged ); + QSignalSpy spySizeChanged( item, &QgsLayoutItem::sizePositionChanged ); item->setRect( 0, 0, 55, 45 ); item->attemptMove( QgsLayoutPoint( 27, 29 ) ); + QCOMPARE( spySizeChanged.count(), 1 ); item->attemptResize( QgsLayoutSize( 100.0, 200.0, QgsUnitTypes::LayoutMillimeters ) ); + QCOMPARE( spySizeChanged.count(), 2 ); QCOMPARE( item->rect().width(), 100.0 ); QCOMPARE( item->rect().height(), 200.0 ); QCOMPARE( item->scenePos().x(), 27.0 ); //item should not move QCOMPARE( item->scenePos().y(), 29.0 ); - QCOMPARE( spySizeChanged.count(), 1 ); //test conversion of units l.setUnits( QgsUnitTypes::LayoutCentimeters ); @@ -708,41 +709,41 @@ void TestQgsLayoutItem::resize() item->attemptResize( QgsLayoutSize( 0.30, 0.45, QgsUnitTypes::LayoutMeters ) ); QCOMPARE( item->rect().width(), 30.0 ); QCOMPARE( item->rect().height(), 45.0 ); - QCOMPARE( spySizeChanged.count(), 2 ); + QCOMPARE( spySizeChanged.count(), 4 ); //test pixel -> page conversion l.setUnits( QgsUnitTypes::LayoutInches ); l.context().setDpi( 100.0 ); item->refresh(); - QCOMPARE( spySizeChanged.count(), 3 ); + QCOMPARE( spySizeChanged.count(), 6 ); item->setRect( 0, 0, 1, 2 ); item->attemptResize( QgsLayoutSize( 140, 280, QgsUnitTypes::LayoutPixels ) ); QCOMPARE( item->rect().width(), 1.4 ); QCOMPARE( item->rect().height(), 2.8 ); - QCOMPARE( spySizeChanged.count(), 4 ); + QCOMPARE( spySizeChanged.count(), 7 ); //changing the dpi should resize the item l.context().setDpi( 200.0 ); item->refresh(); QCOMPARE( item->rect().width(), 0.7 ); QCOMPARE( item->rect().height(), 1.4 ); - QCOMPARE( spySizeChanged.count(), 5 ); + QCOMPARE( spySizeChanged.count(), 8 ); //test page -> pixel conversion l.setUnits( QgsUnitTypes::LayoutPixels ); l.context().setDpi( 100.0 ); item->refresh(); item->setRect( 0, 0, 2, 2 ); - QCOMPARE( spySizeChanged.count(), 6 ); + QCOMPARE( spySizeChanged.count(), 10 ); item->attemptResize( QgsLayoutSize( 1, 3, QgsUnitTypes::LayoutInches ) ); QCOMPARE( item->rect().width(), 100.0 ); QCOMPARE( item->rect().height(), 300.0 ); - QCOMPARE( spySizeChanged.count(), 7 ); + QCOMPARE( spySizeChanged.count(), 11 ); //changing dpi results in item resize l.context().setDpi( 200.0 ); item->refresh(); QCOMPARE( item->rect().width(), 200.0 ); QCOMPARE( item->rect().height(), 600.0 ); - QCOMPARE( spySizeChanged.count(), 8 ); + QCOMPARE( spySizeChanged.count(), 13 ); l.setUnits( QgsUnitTypes::LayoutMillimeters ); }