From ed25a3e2ee51e38ef5ab64f1753ed3e5f1d8c892 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 11 Dec 2018 12:30:14 +1000 Subject: [PATCH] [FEATURE][layouts] Add setting for label margin for map items This setting allows per-map control of how close labels are permitted to be placed to the map item's edges. Sizes can be set using mm/inches/pixels/etc, and data defined label margins are allowed. Fixes #10314 --- .../layout/qgslayoutobject.sip.in | 1 + src/app/layout/qgslayoutmapwidget.cpp | 58 ++++++++ src/app/layout/qgslayoutmapwidget.h | 22 +++ src/core/layout/qgslayoutitemmap.cpp | 24 +++- src/core/layout/qgslayoutitemmap.h | 3 + src/core/layout/qgslayoutobject.cpp | 1 + src/core/layout/qgslayoutobject.h | 1 + .../layout/qgslayoutmaplabelingwidgetbase.ui | 134 ++++++++++++++++++ src/ui/layout/qgslayoutmapwidgetbase.ui | 53 ++++--- tests/src/python/test_qgslayoutmap.py | 15 +- .../expected_composermap_dd_label_margin.png | Bin 0 -> 5241 bytes 11 files changed, 288 insertions(+), 24 deletions(-) create mode 100644 src/ui/layout/qgslayoutmaplabelingwidgetbase.ui create mode 100644 tests/testdata/control_images/composer_map/expected_composermap_dd_label_margin/expected_composermap_dd_label_margin.png diff --git a/python/core/auto_generated/layout/qgslayoutobject.sip.in b/python/core/auto_generated/layout/qgslayoutobject.sip.in index 6f8d6a194edf..7ad5a7f66816 100644 --- a/python/core/auto_generated/layout/qgslayoutobject.sip.in +++ b/python/core/auto_generated/layout/qgslayoutobject.sip.in @@ -135,6 +135,7 @@ A base class for objects which belong to a layout. MapAtlasMargin, MapLayers, MapStylePreset, + MapLabelMargin, //composer picture PictureSource, PictureSvgBackgroundColor, diff --git a/src/app/layout/qgslayoutmapwidget.cpp b/src/app/layout/qgslayoutmapwidget.cpp index a1ca51061e2a..3d53a896a63b 100644 --- a/src/app/layout/qgslayoutmapwidget.cpp +++ b/src/app/layout/qgslayoutmapwidget.cpp @@ -75,6 +75,8 @@ QgsLayoutMapWidget::QgsLayoutMapWidget( QgsLayoutItemMap *item ) connect( mOverviewCheckBox, &QgsCollapsibleGroupBoxBasic::toggled, this, &QgsLayoutMapWidget::mOverviewCheckBox_toggled ); connect( mOverviewListWidget, &QListWidget::currentItemChanged, this, &QgsLayoutMapWidget::mOverviewListWidget_currentItemChanged ); connect( mOverviewListWidget, &QListWidget::itemChanged, this, &QgsLayoutMapWidget::mOverviewListWidget_itemChanged ); + connect( mLabelSettingsButton, &QPushButton::clicked, this, &QgsLayoutMapWidget::showLabelSettings ); + setPanelTitle( tr( "Map Properties" ) ); mMapRotationSpinBox->setClearValue( 0 ); @@ -344,6 +346,12 @@ void QgsLayoutMapWidget::overviewSymbolChanged() mMapItem->update(); } +void QgsLayoutMapWidget::showLabelSettings() +{ + QgsLayoutMapLabelingWidget *w = new QgsLayoutMapLabelingWidget( mMapItem ); + openPanel( w ); +} + void QgsLayoutMapWidget::mAtlasCheckBox_toggled( bool checked ) { if ( !mMapItem ) @@ -1635,3 +1643,53 @@ void QgsLayoutMapWidget::mOverviewCenterCheckbox_toggled( bool state ) mMapItem->update(); mMapItem->endCommand(); } + +// +// QgsLayoutMapLabelingWidget +// + +QgsLayoutMapLabelingWidget::QgsLayoutMapLabelingWidget( QgsLayoutItemMap *map ) + : QgsLayoutItemBaseWidget( nullptr, map ) + , mMapItem( map ) +{ + setupUi( this ); + setPanelTitle( tr( "Label Settings" ) ); + + mLabelBoundarySpinBox->setClearValue( 0 ); + mLabelBoundarySpinBox->setShowClearButton( true ); + + mLabelBoundaryUnitsCombo->linkToWidget( mLabelBoundarySpinBox ); + mLabelBoundaryUnitsCombo->setConverter( &mMapItem->layout()->renderContext().measurementConverter() ); + + mLabelBoundarySpinBox->setValue( mMapItem->labelMargin().length() ); + mLabelBoundaryUnitsCombo->setUnit( mMapItem->labelMargin().units() ); + + connect( mLabelBoundaryUnitsCombo, &QgsLayoutUnitsComboBox::changed, this, &QgsLayoutMapLabelingWidget::labelMarginUnitsChanged ); + connect( mLabelBoundarySpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutMapLabelingWidget::labelMarginChanged ); + + registerDataDefinedButton( mLabelMarginDDBtn, QgsLayoutObject::MapLabelMargin ); + + updateDataDefinedButton( mLabelMarginDDBtn ); +} + +void QgsLayoutMapLabelingWidget::labelMarginChanged( double val ) +{ + if ( !mMapItem ) + return; + + mMapItem->layout()->undoStack()->beginCommand( mMapItem, tr( "Change Label Margin" ), QgsLayoutItem::UndoMapLabelMargin ); + mMapItem->setLabelMargin( QgsLayoutMeasurement( val, mLabelBoundaryUnitsCombo->unit() ) ); + mMapItem->layout()->undoStack()->endCommand(); + mMapItem->invalidateCache(); +} + +void QgsLayoutMapLabelingWidget::labelMarginUnitsChanged() +{ + if ( !mMapItem ) + return; + + mMapItem->layout()->undoStack()->beginCommand( mMapItem, tr( "Change Label Margin" ), QgsLayoutItem::UndoMapLabelMargin ); + mMapItem->setLabelMargin( QgsLayoutMeasurement( mLabelBoundarySpinBox->value(), mLabelBoundaryUnitsCombo->unit() ) ); + mMapItem->layout()->undoStack()->endCommand(); + mMapItem->invalidateCache(); +} diff --git a/src/app/layout/qgslayoutmapwidget.h b/src/app/layout/qgslayoutmapwidget.h index f35e25c0b6d4..df9643ff3b93 100644 --- a/src/app/layout/qgslayoutmapwidget.h +++ b/src/app/layout/qgslayoutmapwidget.h @@ -19,6 +19,7 @@ #define QGSLAYOUTMAPWIDGET_H #include "ui_qgslayoutmapwidgetbase.h" +#include "ui_qgslayoutmaplabelingwidgetbase.h" #include "qgslayoutitemwidget.h" #include "qgslayoutitemmapgrid.h" @@ -116,6 +117,7 @@ class QgsLayoutMapWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayoutM void mapCrsChanged( const QgsCoordinateReferenceSystem &crs ); void overviewSymbolChanged(); + void showLabelSettings(); private: QPointer< QgsLayoutItemMap > mMapItem; QgsLayoutItemPropertiesWidget *mItemPropertiesWidget = nullptr; @@ -164,4 +166,24 @@ class QgsLayoutMapWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayoutM }; + +/** + * \ingroup app + * Allows configuration of layout map labeling settings. + * */ +class QgsLayoutMapLabelingWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayoutMapLabelingWidgetBase +{ + Q_OBJECT + + public: + explicit QgsLayoutMapLabelingWidget( QgsLayoutItemMap *map ); + + private slots: + void labelMarginChanged( double val ); + void labelMarginUnitsChanged(); + + private: + QPointer< QgsLayoutItemMap > mMapItem; +}; + #endif diff --git a/src/core/layout/qgslayoutitemmap.cpp b/src/core/layout/qgslayoutitemmap.cpp index 6399f82e492d..4edcad80d759 100644 --- a/src/core/layout/qgslayoutitemmap.cpp +++ b/src/core/layout/qgslayoutitemmap.cpp @@ -742,7 +742,7 @@ bool QgsLayoutItemMap::readPropertiesFromElement( const QDomElement &itemElem, c mAtlasMargin = atlasElem.attribute( QStringLiteral( "margin" ), QStringLiteral( "0.1" ) ).toDouble(); } - mLabelMargin = QgsLayoutMeasurement::decodeMeasurement( itemElem.attribute( QStringLiteral( "labelMargin" ), QStringLiteral( "0" ) ) ); + setLabelMargin( QgsLayoutMeasurement::decodeMeasurement( itemElem.attribute( QStringLiteral( "labelMargin" ), QStringLiteral( "0" ) ) ) ); updateBoundingRect(); @@ -1131,11 +1131,11 @@ QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF // override the default text render format inherited from the labeling engine settings using the layout's render context setting jobMapSettings.setTextRenderFormat( mLayout->renderContext().textRenderFormat() ); - if ( mLabelMargin.length() > 0 ) + if ( mEvaluatedLabelMargin.length() > 0 ) { QPolygonF visiblePoly = jobMapSettings.visiblePolygon(); visiblePoly.append( visiblePoly.at( 0 ) ); //close polygon - const double layoutLabelMargin = mLayout->convertToLayoutUnits( mLabelMargin ); + const double layoutLabelMargin = mLayout->convertToLayoutUnits( mEvaluatedLabelMargin ); const double layoutLabelMarginInMapUnits = layoutLabelMargin / rect().width() * jobMapSettings.extent().width(); QgsGeometry mapBoundaryGeom = QgsGeometry::fromQPolygonF( visiblePoly ); mapBoundaryGeom = mapBoundaryGeom.buffer( -layoutLabelMarginInMapUnits, 0 ); @@ -1335,6 +1335,10 @@ void QgsLayoutItemMap::refreshDataDefinedProperty( const QgsLayoutObject::DataDe emit extentChanged(); } } + if ( property == QgsLayoutObject::MapLabelMargin || property == QgsLayoutObject::AllProperties ) + { + refreshLabelMargin( false ); + } //force redraw mCacheInvalidated = true; @@ -1436,6 +1440,7 @@ QgsLayoutMeasurement QgsLayoutItemMap::labelMargin() const void QgsLayoutItemMap::setLabelMargin( const QgsLayoutMeasurement &margin ) { mLabelMargin = margin; + refreshLabelMargin( false ); } void QgsLayoutItemMap::updateToolTip() @@ -1900,6 +1905,19 @@ void QgsLayoutItemMap::refreshMapExtents( const QgsExpressionContext *context ) } } +void QgsLayoutItemMap::refreshLabelMargin( bool updateItem ) +{ + //data defined label margin set? + double labelMargin = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapLabelMargin, createExpressionContext(), mLabelMargin.length() ); + mEvaluatedLabelMargin.setLength( labelMargin ); + mEvaluatedLabelMargin.setUnits( mLabelMargin.units() ); + + if ( updateItem ) + { + update(); + } +} + void QgsLayoutItemMap::updateAtlasFeature() { if ( !atlasDriven() || !mLayout->reportContext().layer() ) diff --git a/src/core/layout/qgslayoutitemmap.h b/src/core/layout/qgslayoutitemmap.h index 2433c7192563..1135a3601cf4 100644 --- a/src/core/layout/qgslayoutitemmap.h +++ b/src/core/layout/qgslayoutitemmap.h @@ -646,6 +646,7 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem bool mPainterCancelWait = false; QgsLayoutMeasurement mLabelMargin{ 0 }; + QgsLayoutMeasurement mEvaluatedLabelMargin{ 0 }; void init(); @@ -694,6 +695,8 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem */ void refreshMapExtents( const QgsExpressionContext *context = nullptr ); + void refreshLabelMargin( bool updateItem ); + void updateAtlasFeature(); QgsRectangle computeAtlasRectangle(); diff --git a/src/core/layout/qgslayoutobject.cpp b/src/core/layout/qgslayoutobject.cpp index ecdeda57ecb1..7f8b3b1b8d5f 100644 --- a/src/core/layout/qgslayoutobject.cpp +++ b/src/core/layout/qgslayoutobject.cpp @@ -63,6 +63,7 @@ void QgsLayoutObject::initPropertyDefinitions() { QgsLayoutObject::MapYMin, QgsPropertyDefinition( "dataDefinedMapYMin", QObject::tr( "Extent minimum Y" ), QgsPropertyDefinition::Double ) }, { QgsLayoutObject::MapXMax, QgsPropertyDefinition( "dataDefinedMapXMax", QObject::tr( "Extent maximum X" ), QgsPropertyDefinition::Double ) }, { QgsLayoutObject::MapYMax, QgsPropertyDefinition( "dataDefinedMapYMax", QObject::tr( "Extent maximum Y" ), QgsPropertyDefinition::Double ) }, + { QgsLayoutObject::MapLabelMargin, QgsPropertyDefinition( "dataDefinedMapLabelMargin", QObject::tr( "Label margin" ), QgsPropertyDefinition::DoublePositive ) }, { QgsLayoutObject::MapAtlasMargin, QgsPropertyDefinition( "dataDefinedMapAtlasMargin", QObject::tr( "Atlas margin" ), QgsPropertyDefinition::DoublePositive ) }, { QgsLayoutObject::MapLayers, QgsPropertyDefinition( "dataDefinedMapLayers", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map Layers" ), tr( "list of map layer names separated by | characters" ) ) }, { QgsLayoutObject::MapStylePreset, QgsPropertyDefinition( "dataDefinedMapStylePreset", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map theme" ), tr( "name of an existing map theme (case-sensitive)" ) ) }, diff --git a/src/core/layout/qgslayoutobject.h b/src/core/layout/qgslayoutobject.h index b9c1f0bd335a..4a1d0d24ebe2 100644 --- a/src/core/layout/qgslayoutobject.h +++ b/src/core/layout/qgslayoutobject.h @@ -163,6 +163,7 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe MapAtlasMargin, //!< Map atlas margin MapLayers, //!< Map layer set MapStylePreset, //!< Layer and style map theme + MapLabelMargin, //!< Map label margin //composer picture PictureSource, //!< Picture source url PictureSvgBackgroundColor, //!< SVG background color diff --git a/src/ui/layout/qgslayoutmaplabelingwidgetbase.ui b/src/ui/layout/qgslayoutmaplabelingwidgetbase.ui new file mode 100644 index 000000000000..d1135082b058 --- /dev/null +++ b/src/ui/layout/qgslayoutmaplabelingwidgetbase.ui @@ -0,0 +1,134 @@ + + + QgsLayoutMapLabelingWidgetBase + + + + 0 + 0 + 326 + 424 + + + + + 0 + 0 + + + + Map Options + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Label Settings + + + + + + of map edges + + + true + + + + + + + + + + 0.000000000000000 + + + 9999.000000000000000 + + + 0.000000000000000 + + + false + + + + + + + Avoid placing labels within + + + true + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + QgsCollapsibleGroupBoxBasic + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
+ + QgsDoubleSpinBox + QDoubleSpinBox +
qgsdoublespinbox.h
+
+ + QgsPropertyOverrideButton + QToolButton +
qgspropertyoverridebutton.h
+
+ + QgsLayoutUnitsComboBox + QComboBox +
qgslayoutunitscombobox.h
+
+
+ + +
diff --git a/src/ui/layout/qgslayoutmapwidgetbase.ui b/src/ui/layout/qgslayoutmapwidgetbase.ui index a0bf3cdaf9f6..3510a655b7cb 100644 --- a/src/ui/layout/qgslayoutmapwidgetbase.ui +++ b/src/ui/layout/qgslayoutmapwidgetbase.ui @@ -63,9 +63,9 @@ 0 - -1556 - 627 - 2265 + 0 + 368 + 1373 @@ -396,20 +396,27 @@ - Set to map canvas extent + Set to Map Canvas Extent - View extent in map canvas + View Extent in Map Canvas + + + + Label Settings… + + + @@ -600,7 +607,7 @@ - Modify grid… + Modify Grid… @@ -793,9 +800,10 @@ 1 - QgsPropertyOverrideButton - QToolButton -
qgspropertyoverridebutton.h
+ QgsCollapsibleGroupBoxBasic + QGroupBox +
qgscollapsiblegroupbox.h
+ 1
QgsDoubleSpinBox @@ -807,17 +815,21 @@ QSpinBox
qgsspinbox.h
- - QgsCollapsibleGroupBoxBasic - QGroupBox -
qgscollapsiblegroupbox.h
- 1 -
QgsBlendModeComboBox QComboBox
qgsblendmodecombobox.h
+ + QgsSymbolButton + QToolButton +
qgssymbolbutton.h
+
+ + QgsPropertyOverrideButton + QToolButton +
qgspropertyoverridebutton.h
+
QgsProjectionSelectionWidget QWidget @@ -829,11 +841,6 @@ QComboBox
qgslayoutitemcombobox.h
- - QgsSymbolButton - QToolButton -
qgssymbolbutton.h
-
scrollArea @@ -863,6 +870,7 @@ mYMaxDDBtn mSetToMapCanvasExtentButton mViewExtentInCanvasButton + mLabelSettingsButton mAtlasCheckBox mAtlasMarginRadio mAtlasMarginSpinBox @@ -920,6 +928,11 @@ + + + + + diff --git a/tests/src/python/test_qgslayoutmap.py b/tests/src/python/test_qgslayoutmap.py index ea1ccba7ae2c..144ce329ddbd 100644 --- a/tests/src/python/test_qgslayoutmap.py +++ b/tests/src/python/test_qgslayoutmap.py @@ -38,7 +38,9 @@ QgsVectorLayerSimpleLabeling, QgsLabelingEngineSettings, QgsLayoutMeasurement, - QgsUnitTypes) + QgsUnitTypes, + QgsLayoutObject, + QgsProperty) from qgis.testing import start_app, unittest from utilities import unitTestDataPath @@ -326,6 +328,17 @@ def testLabelMargin(self): self.report += checker.report() self.assertTrue(result, message) + # data defined + map.setMapRotation(0) + map.zoomToExtent(vl.extent()) + map.dataDefinedProperties().setProperty(QgsLayoutObject.MapLabelMargin, QgsProperty.fromExpression('1+3')) + map.refresh() + checker = QgsLayoutChecker('composermap_dd_label_margin', layout) + checker.setControlPathPrefix("composer_map") + result, message = checker.testLayout() + self.report += checker.report() + self.assertTrue(result, message) + if __name__ == '__main__': unittest.main() diff --git a/tests/testdata/control_images/composer_map/expected_composermap_dd_label_margin/expected_composermap_dd_label_margin.png b/tests/testdata/control_images/composer_map/expected_composermap_dd_label_margin/expected_composermap_dd_label_margin.png new file mode 100644 index 0000000000000000000000000000000000000000..7e394cb01100e0901f45aa73cbcc8a06f878f624 GIT binary patch literal 5241 zcmeHLjZ>0$9RHbisjaN2D-m&R3U|ZIw-zSE=CO+DVC#0-Ld%zooNr;K_=c`?5gkb< zH(yHWw%l#I5>rY^O`RCTG(Fvhk{Qy&GL;l_5-P|Z>|B39ct>~LJ0*M&d_2=iHLLWQVB}yZiGJ7mQpi(%5-;R%bEr~hdCv!4x7ms`p0=Wq+#pJL6S+^Mzd5wk&9}MD8Vrj)YTFr^wf7O?ooqn)ZMT zMrtlmBF5Q|u9|gBRAkf(osPm`9?nuIc2u2=V)XmuK334L=*hs}(N+LgD01tDazKj? zUreW+Pw-{Xj>zgK%LVj3eLqCik0O^f?LJ8|&}qiMlLaS+f}UOp9ufwqVNZZ|LSDw2 zH*Jwi2u5NFQO2!s(Em}w*h;;e(#u-2EDH_FIT3LOz2tK7Rbnx8A)E^P$SfgEsigQ< zu@Wo_8Y@9WLp32ALCcoCO^){0x?g)Qu5lKyRI#WYcBXsRg>YeX;21JhM;<5-9>RH0 zoj_VqMBKiGI(1G*Em{s|MF*GN#bqaTAROAgZD8#sinD~_cPWd7q%laXyabW~H|ZY# z=wfp?x&Xyb+7WW+E}~}dSO?-Fu4NZ?RbgWoCg8UPW`RL0XGSpNZOr(I zSw$(+Wn2gPBhwYar2jR&W*$r< zY9hs7z+qUP)}Nd!%Y$Rl5zOfOJl~WqE+II}nLedCHpn-B1iK#(*D=U5Vp$f5K?iav z`6#bmwgF#^^9#vF(q2M~Eetp4=Bvz<^i+paGcGnhB?J8-XvD;-A?I86M4 zD!~`$mz}AoPLk9iqT~CMYm@PCwr&jMx<8k*CtTWcP<%Sg89ToNJ742Ma{>YzSbK@Z zFJV~i25AxrRc2#gsd=zg!r(D?Px@t>m;}*nt=2%jHDA4!7A(>$E1kh(*NV>SNx50o zivvuEn?-RGY7HY7jaV7z-L6y%M z;ls+(B06kf?ZCHhqJ7;KSE%CKY zNo?ICY01Roo9Acce6L#5HPC)&(KORrg9WA}%iP?|sL}%-voKEz?0XBz_9nBRKXzKc X=ifDS^13Hwuqy!NGb&jW7IXGLbddIV literal 0 HcmV?d00001