From 8882fd9aa18a14d79b685f0a4443b627868706f1 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Wed, 17 Jan 2024 14:46:16 +0200 Subject: [PATCH 1/4] duplicate composer map grid (fix #47511) --- .../layout/qgslayoutitemmapgrid.sip.in | 7 ++ .../layout/qgslayoutitemmapgrid.sip.in | 7 ++ src/core/layout/qgslayoutitemmapgrid.cpp | 78 ++++++++++++++++++- src/core/layout/qgslayoutitemmapgrid.h | 7 ++ src/gui/layout/qgslayoutmapwidget.cpp | 25 ++++++ src/gui/layout/qgslayoutmapwidget.h | 1 + src/ui/layout/qgslayoutmapwidgetbase.ui | 14 ++++ tests/src/python/test_qgslayoutmapgrid.py | 61 +++++++++++++++ 8 files changed, 197 insertions(+), 3 deletions(-) diff --git a/python/PyQt6/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in b/python/PyQt6/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in index 5fd12928fad7..815c4dbacb86 100644 --- a/python/PyQt6/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in +++ b/python/PyQt6/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in @@ -1115,6 +1115,13 @@ Retrieves the second fill color for the grid frame. virtual void refresh(); + void copyProperties( const QgsLayoutItemMapGrid *other ); +%Docstring +Copies properties from specified map grid. + +.. versionadded:: 3.38 +%End + signals: void crsChanged(); diff --git a/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in b/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in index 02444b6b5eb1..5e72cf12c8af 100644 --- a/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in +++ b/python/core/auto_generated/layout/qgslayoutitemmapgrid.sip.in @@ -1115,6 +1115,13 @@ Retrieves the second fill color for the grid frame. virtual void refresh(); + void copyProperties( const QgsLayoutItemMapGrid *other ); +%Docstring +Copies properties from specified map grid. + +.. versionadded:: 3.38 +%End + signals: void crsChanged(); diff --git a/src/core/layout/qgslayoutitemmapgrid.cpp b/src/core/layout/qgslayoutitemmapgrid.cpp index 4e6b18d0d1f6..48b06834dec2 100644 --- a/src/core/layout/qgslayoutitemmapgrid.cpp +++ b/src/core/layout/qgslayoutitemmapgrid.cpp @@ -1912,7 +1912,6 @@ bool QgsLayoutItemMapGrid::shouldShowForDisplayMode( QgsLayoutItemMapGrid::Annot || ( mode == QgsLayoutItemMapGrid::LongitudeOnly && coordinate == QgsLayoutItemMapGrid::Longitude ); } - QgsLayoutItemMapGrid::DisplayMode gridAnnotationDisplayModeFromDD( QString ddValue, QgsLayoutItemMapGrid::DisplayMode defValue ) { if ( ddValue.compare( QLatin1String( "x_only" ), Qt::CaseInsensitive ) == 0 ) @@ -1927,7 +1926,6 @@ QgsLayoutItemMapGrid::DisplayMode gridAnnotationDisplayModeFromDD( QString ddVal return defValue; } - void QgsLayoutItemMapGrid::refreshDataDefinedProperties() { const QgsExpressionContext context = createExpressionContext(); @@ -1986,7 +1984,6 @@ void QgsLayoutItemMapGrid::refreshDataDefinedProperties() mEvaluatedRightFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::DataDefinedProperty::MapGridFrameDivisionsRight, context ), mRightFrameDivisions ); mEvaluatedTopFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::DataDefinedProperty::MapGridFrameDivisionsTop, context ), mTopFrameDivisions ); mEvaluatedBottomFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::DataDefinedProperty::MapGridFrameDivisionsBottom, context ), mBottomFrameDivisions ); - } double QgsLayoutItemMapGrid::mapWidth() const @@ -2637,3 +2634,78 @@ QList QgsLayoutItemMapGrid::trimLinesToMap( const QPolygonF &line, co } return trimmedLines; } + +void QgsLayoutItemMapGrid::copyProperties( const QgsLayoutItemMapGrid *other ) +{ + // grid + setStyle( other->style() ); + setIntervalX( other->intervalX() ); + setIntervalY( other->intervalY() ); + setOffsetX( other->offsetX() ); + setOffsetY( other->offsetX() ); + setCrossLength( other->crossLength() ); + setFrameStyle( other->frameStyle() ); + setFrameSideFlags( other->frameSideFlags() ); + setFrameWidth( other->frameWidth() ); + setFrameMargin( other->frameMargin() ); + setFramePenSize( other->framePenSize() ); + setFramePenColor( other->framePenColor() ); + setFrameFillColor1( other->frameFillColor1() ); + setFrameFillColor2( other->frameFillColor2() ); + + setFrameDivisions( other->frameDivisions( QgsLayoutItemMapGrid::BorderSide::Left ), QgsLayoutItemMapGrid::BorderSide::Left ); + setFrameDivisions( other->frameDivisions( QgsLayoutItemMapGrid::BorderSide::Right ), QgsLayoutItemMapGrid::BorderSide::Right ); + setFrameDivisions( other->frameDivisions( QgsLayoutItemMapGrid::BorderSide::Bottom ), QgsLayoutItemMapGrid::BorderSide::Bottom ); + setFrameDivisions( other->frameDivisions( QgsLayoutItemMapGrid::BorderSide::Top ), QgsLayoutItemMapGrid::BorderSide::Top ); + + setRotatedTicksLengthMode( other->rotatedTicksLengthMode() ); + setRotatedTicksEnabled( other->rotatedTicksEnabled() ); + setRotatedTicksMinimumAngle( other->rotatedTicksMinimumAngle() ); + setRotatedTicksMarginToCorner( other->rotatedTicksMarginToCorner() ); + setRotatedAnnotationsLengthMode( other->rotatedAnnotationsLengthMode() ); + setRotatedAnnotationsEnabled( other->rotatedAnnotationsEnabled() ); + setRotatedAnnotationsMinimumAngle( other->rotatedAnnotationsMinimumAngle() ); + setRotatedAnnotationsMarginToCorner( other->rotatedAnnotationsMarginToCorner() ); + + if ( other->lineSymbol() ) + { + setLineSymbol( other->lineSymbol()->clone() ); + } + + if ( other->markerSymbol() ) + { + setMarkerSymbol( other->markerSymbol()->clone() ); + } + + setCrs( other->crs() ); + + setBlendMode( other->blendMode() ); + + //annotation + setAnnotationEnabled( other->annotationEnabled() ); + setAnnotationFormat( other->annotationFormat() ); + setAnnotationExpression( other->annotationExpression() ); + + setAnnotationPosition( other->annotationPosition( QgsLayoutItemMapGrid::BorderSide::Left ), QgsLayoutItemMapGrid::BorderSide::Left ); + setAnnotationPosition( other->annotationPosition( QgsLayoutItemMapGrid::BorderSide::Right ), QgsLayoutItemMapGrid::BorderSide::Right ); + setAnnotationPosition( other->annotationPosition( QgsLayoutItemMapGrid::BorderSide::Bottom ), QgsLayoutItemMapGrid::BorderSide::Bottom ); + setAnnotationPosition( other->annotationPosition( QgsLayoutItemMapGrid::BorderSide::Top ), QgsLayoutItemMapGrid::BorderSide::Top ); + setAnnotationDisplay( other->annotationDisplay( QgsLayoutItemMapGrid::BorderSide::Left ), QgsLayoutItemMapGrid::BorderSide::Left ); + setAnnotationDisplay( other->annotationDisplay( QgsLayoutItemMapGrid::BorderSide::Right ), QgsLayoutItemMapGrid::BorderSide::Right ); + setAnnotationDisplay( other->annotationDisplay( QgsLayoutItemMapGrid::BorderSide::Bottom ), QgsLayoutItemMapGrid::BorderSide::Bottom ); + setAnnotationDisplay( other->annotationDisplay( QgsLayoutItemMapGrid::BorderSide::Top ), QgsLayoutItemMapGrid::BorderSide::Top ); + setAnnotationDirection( other->annotationDirection( QgsLayoutItemMapGrid::BorderSide::Left ), QgsLayoutItemMapGrid::BorderSide::Left ); + setAnnotationDirection( other->annotationDirection( QgsLayoutItemMapGrid::BorderSide::Right ), QgsLayoutItemMapGrid::BorderSide::Right ); + setAnnotationDirection( other->annotationDirection( QgsLayoutItemMapGrid::BorderSide::Bottom ), QgsLayoutItemMapGrid::BorderSide::Bottom ); + setAnnotationDirection( other->annotationDirection( QgsLayoutItemMapGrid::BorderSide::Top ), QgsLayoutItemMapGrid::BorderSide::Top ); + setAnnotationFrameDistance( other->annotationFrameDistance() ); + setAnnotationTextFormat( other->annotationTextFormat() ); + + setAnnotationPrecision( other->annotationPrecision() ); + setUnits( other->units() ); + setMinimumIntervalWidth( other->minimumIntervalWidth() ); + setMaximumIntervalWidth( other->maximumIntervalWidth() ); + + setDataDefinedProperties( other->dataDefinedProperties() ); + refreshDataDefinedProperties(); +} diff --git a/src/core/layout/qgslayoutitemmapgrid.h b/src/core/layout/qgslayoutitemmapgrid.h index b2ee47cba078..c3e9309e71e6 100644 --- a/src/core/layout/qgslayoutitemmapgrid.h +++ b/src/core/layout/qgslayoutitemmapgrid.h @@ -1005,6 +1005,13 @@ class CORE_EXPORT QgsLayoutItemMapGrid : public QgsLayoutItemMapItem bool accept( QgsStyleEntityVisitorInterface *visitor ) const override; void refresh() override; + /** + * Copies properties from specified map grid. + * + * \since QGIS 3.38 + */ + void copyProperties( const QgsLayoutItemMapGrid *other ); + signals: /** diff --git a/src/gui/layout/qgslayoutmapwidget.cpp b/src/gui/layout/qgslayoutmapwidget.cpp index 63e27642933e..eb32d2ae2318 100644 --- a/src/gui/layout/qgslayoutmapwidget.cpp +++ b/src/gui/layout/qgslayoutmapwidget.cpp @@ -73,6 +73,7 @@ QgsLayoutMapWidget::QgsLayoutMapWidget( QgsLayoutItemMap *item, QgsMapCanvas *ma connect( mAtlasPredefinedScaleRadio, &QRadioButton::toggled, this, &QgsLayoutMapWidget::mAtlasPredefinedScaleRadio_toggled ); connect( mAddGridPushButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::mAddGridPushButton_clicked ); connect( mRemoveGridPushButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::mRemoveGridPushButton_clicked ); + connect( mCopyGridPushButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::mCopyGridPushButton_clicked ); connect( mGridUpButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::mGridUpButton_clicked ); connect( mGridDownButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::mGridDownButton_clicked ); connect( mGridListWidget, &QListWidget::currentItemChanged, this, &QgsLayoutMapWidget::mGridListWidget_currentItemChanged ); @@ -1245,6 +1246,30 @@ void QgsLayoutMapWidget::mRemoveGridPushButton_clicked() mMapItem->update(); } +void QgsLayoutMapWidget::mCopyGridPushButton_clicked() +{ + QListWidgetItem *item = mGridListWidget->currentItem(); + if ( !item ) + { + return; + } + + QgsLayoutItemMapGrid *sourceGrid = mMapItem->grids()->grid( item->data( Qt::UserRole ).toString() ); + const QString itemName = tr( "Grid %1" ).arg( mMapItem->grids()->size() + 1 ); + QgsLayoutItemMapGrid *grid = new QgsLayoutItemMapGrid( itemName, mMapItem ); + grid->copyProperties( sourceGrid ); + + mMapItem->layout()->undoStack()->beginCommand( mMapItem, tr( "Duplicate Map Grid" ) ); + mMapItem->grids()->addGrid( grid ); + mMapItem->layout()->undoStack()->endCommand(); + mMapItem->updateBoundingRect(); + mMapItem->update(); + + addGridListItem( grid->id(), grid->name() ); + mGridListWidget->setCurrentRow( 0 ); + mGridListWidget_currentItemChanged( mGridListWidget->currentItem(), nullptr ); +} + void QgsLayoutMapWidget::mGridUpButton_clicked() { QListWidgetItem *item = mGridListWidget->currentItem(); diff --git a/src/gui/layout/qgslayoutmapwidget.h b/src/gui/layout/qgslayoutmapwidget.h index 83a56c86f901..3ccab9b61aaa 100644 --- a/src/gui/layout/qgslayoutmapwidget.h +++ b/src/gui/layout/qgslayoutmapwidget.h @@ -86,6 +86,7 @@ class GUI_EXPORT QgsLayoutMapWidget: public QgsLayoutItemBaseWidget, private Ui: void mAddGridPushButton_clicked(); void mRemoveGridPushButton_clicked(); + void mCopyGridPushButton_clicked(); void mGridUpButton_clicked(); void mGridDownButton_clicked(); diff --git a/src/ui/layout/qgslayoutmapwidgetbase.ui b/src/ui/layout/qgslayoutmapwidgetbase.ui index 5a667e478743..31281d3f299c 100644 --- a/src/ui/layout/qgslayoutmapwidgetbase.ui +++ b/src/ui/layout/qgslayoutmapwidgetbase.ui @@ -752,6 +752,20 @@ + + + + Duplicate selected grid + + + + + + + :/images/themes/default/mActionEditCopy.svg:/images/themes/default/mActionEditCopy.svg + + + diff --git a/tests/src/python/test_qgslayoutmapgrid.py b/tests/src/python/test_qgslayoutmapgrid.py index 13acb4e1dbb9..9b7bea23d734 100644 --- a/tests/src/python/test_qgslayoutmapgrid.py +++ b/tests/src/python/test_qgslayoutmapgrid.py @@ -1097,6 +1097,67 @@ def testCrsChanged(self): grid.setCrs(QgsCoordinateReferenceSystem("EPSG:3111")) self.assertEqual(len(spy), 9) + def testCopyGrid(self): + layout = QgsLayout(QgsProject.instance()) + layout.initializeDefaults() + map = QgsLayoutItemMap(layout) + map.attemptSetSceneRect(QRectF(20, 20, 200, 100)) + map.setFrameEnabled(True) + map.setBackgroundColor(QColor(150, 100, 100)) + layout.addLayoutItem(map) + myRectangle = QgsRectangle(781662.375, 3339523.125, 793062.375, 3345223.125) + map.setExtent(myRectangle) + map.grid().setEnabled(True) + map.grid().setIntervalX(2000) + map.grid().setIntervalY(2000) + map.grid().setAnnotationEnabled(True) + map.grid().setGridLineColor(QColor(0, 255, 0)) + map.grid().setGridLineWidth(0.5) + + format = QgsTextFormat.fromQFont(getTestFont("Bold", 20)) + format.setColor(QColor(255, 0, 0)) + format.setOpacity(150 / 255) + map.grid().setAnnotationTextFormat(format) + + map.grid().setAnnotationPrecision(0) + map.grid().setAnnotationDisplay( + QgsLayoutItemMapGrid.DisplayMode.HideAll, QgsLayoutItemMapGrid.BorderSide.Left + ) + map.grid().setAnnotationPosition( + QgsLayoutItemMapGrid.AnnotationPosition.OutsideMapFrame, QgsLayoutItemMapGrid.BorderSide.Right + ) + map.grid().setAnnotationDisplay( + QgsLayoutItemMapGrid.DisplayMode.HideAll, QgsLayoutItemMapGrid.BorderSide.Top + ) + map.grid().setAnnotationPosition( + QgsLayoutItemMapGrid.AnnotationPosition.OutsideMapFrame, QgsLayoutItemMapGrid.BorderSide.Bottom + ) + map.grid().setAnnotationDirection( + QgsLayoutItemMapGrid.AnnotationDirection.Horizontal, QgsLayoutItemMapGrid.BorderSide.Right + ) + map.grid().setAnnotationDirection( + QgsLayoutItemMapGrid.AnnotationDirection.Horizontal, QgsLayoutItemMapGrid.BorderSide.Bottom + ) + map.grid().setBlendMode(QPainter.CompositionMode.CompositionMode_Overlay) + map.updateBoundingRect() + + map.grid().dataDefinedProperties().setProperty( + QgsLayoutObject.DataDefinedProperty.MapGridLabelDistance, QgsProperty.fromValue(10) + ) + map.grid().refresh() + + source_grid = map.grid() + grid = QgsLayoutItemMapGrid("testGrid", map) + grid.copyProperties(source_grid) + map.grids().removeGrid(source_grid.id()) + self.assertEqual(map.grids().size(), 0) + map.grids().addGrid(grid) + map.grid().refresh() + self.assertTrue( + self.render_layout_check("composermap_datadefined_annotationdistance", + layout) + ) + if __name__ == "__main__": unittest.main() From 1a5e52866099f1b3c549cd1561735f0fcfac86b3 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Tue, 6 Feb 2024 17:26:29 +0200 Subject: [PATCH 2/4] better names for grid copies --- src/gui/layout/qgslayoutmapwidget.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/gui/layout/qgslayoutmapwidget.cpp b/src/gui/layout/qgslayoutmapwidget.cpp index eb32d2ae2318..c098f4f5bdfc 100644 --- a/src/gui/layout/qgslayoutmapwidget.cpp +++ b/src/gui/layout/qgslayoutmapwidget.cpp @@ -1255,7 +1255,23 @@ void QgsLayoutMapWidget::mCopyGridPushButton_clicked() } QgsLayoutItemMapGrid *sourceGrid = mMapItem->grids()->grid( item->data( Qt::UserRole ).toString() ); - const QString itemName = tr( "Grid %1" ).arg( mMapItem->grids()->size() + 1 ); + int i = 0; + bool found = true; + QString itemName = tr( "%1 - Copy" ).arg( sourceGrid->name() ); + QList< QgsLayoutItemMapGrid * > grids = mMapItem->grids()->asList(); + while ( found ) + { + for ( const QgsLayoutItemMapGrid *grd : std::as_const( grids ) ) + { + if ( grd->name() == itemName ) + { + i++; + itemName = tr( "%1 - Copy %2" ).arg( sourceGrid->name() ).arg( i ); + } + } + found = false; + } + //const QString itemName = tr( "Grid %1" ).arg( mMapItem->grids()->size() + 1 ); QgsLayoutItemMapGrid *grid = new QgsLayoutItemMapGrid( itemName, mMapItem ); grid->copyProperties( sourceGrid ); From 1106a17ac2eeaefc691045dbebeb3b008bbd7781 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Wed, 7 Feb 2024 11:10:49 +0200 Subject: [PATCH 3/4] better implementation for name change --- src/gui/layout/qgslayoutmapwidget.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/gui/layout/qgslayoutmapwidget.cpp b/src/gui/layout/qgslayoutmapwidget.cpp index c098f4f5bdfc..f9495872a46f 100644 --- a/src/gui/layout/qgslayoutmapwidget.cpp +++ b/src/gui/layout/qgslayoutmapwidget.cpp @@ -1256,22 +1256,19 @@ void QgsLayoutMapWidget::mCopyGridPushButton_clicked() QgsLayoutItemMapGrid *sourceGrid = mMapItem->grids()->grid( item->data( Qt::UserRole ).toString() ); int i = 0; - bool found = true; QString itemName = tr( "%1 - Copy" ).arg( sourceGrid->name() ); QList< QgsLayoutItemMapGrid * > grids = mMapItem->grids()->asList(); - while ( found ) + while ( true ) { - for ( const QgsLayoutItemMapGrid *grd : std::as_const( grids ) ) + const auto it = std::find_if( grids.begin(), grids.end(), [&itemName]( const QgsLayoutItemMapGrid * grd ) { return grd->name() == itemName; } ); + if ( it != grids.end() ) { - if ( grd->name() == itemName ) - { - i++; - itemName = tr( "%1 - Copy %2" ).arg( sourceGrid->name() ).arg( i ); - } + i++; + itemName = tr( "%1 - Copy %2" ).arg( sourceGrid->name() ).arg( i ); + continue; } - found = false; + break; } - //const QString itemName = tr( "Grid %1" ).arg( mMapItem->grids()->size() + 1 ); QgsLayoutItemMapGrid *grid = new QgsLayoutItemMapGrid( itemName, mMapItem ); grid->copyProperties( sourceGrid ); From d1369880981ed99cb6b3501bf7688d07bc35152f Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Mon, 13 May 2024 10:49:08 +0100 Subject: [PATCH 4/4] address review --- src/gui/layout/qgslayoutmapwidget.cpp | 4 ++++ src/ui/layout/qgslayoutmapwidgetbase.ui | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/gui/layout/qgslayoutmapwidget.cpp b/src/gui/layout/qgslayoutmapwidget.cpp index f9495872a46f..92d30794b504 100644 --- a/src/gui/layout/qgslayoutmapwidget.cpp +++ b/src/gui/layout/qgslayoutmapwidget.cpp @@ -1255,6 +1255,10 @@ void QgsLayoutMapWidget::mCopyGridPushButton_clicked() } QgsLayoutItemMapGrid *sourceGrid = mMapItem->grids()->grid( item->data( Qt::UserRole ).toString() ); + if ( !sourceGrid ) + { + return; + } int i = 0; QString itemName = tr( "%1 - Copy" ).arg( sourceGrid->name() ); QList< QgsLayoutItemMapGrid * > grids = mMapItem->grids()->asList(); diff --git a/src/ui/layout/qgslayoutmapwidgetbase.ui b/src/ui/layout/qgslayoutmapwidgetbase.ui index 31281d3f299c..1cb6df2b49ca 100644 --- a/src/ui/layout/qgslayoutmapwidgetbase.ui +++ b/src/ui/layout/qgslayoutmapwidgetbase.ui @@ -88,9 +88,9 @@ 0 - -123 - 548 - 1478 + -798 + 539 + 1640 @@ -762,7 +762,7 @@ - :/images/themes/default/mActionEditCopy.svg:/images/themes/default/mActionEditCopy.svg + :/images/themes/default/mActionDuplicateLayout.svg:/images/themes/default/mActionDuplicateLayout.svg