QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
qgslabelinggui.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslabelinggui.cpp
3 Smart labeling for vector layers
4 -------------------
5 begin : June 2009
6 copyright : (C) Martin Dobias
7 email : wonder dot sk at gmail dot com
8
9 ***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgslabelinggui.h"
19#include "qgsvectorlayer.h"
20#include "qgsmapcanvas.h"
21#include "qgsproject.h"
24#include "qgshelp.h"
25#include "qgsstylesavedialog.h"
26#include "qgscallout.h"
27#include "qgsapplication.h"
28#include "qgscalloutsregistry.h"
33
34#include <mutex>
35
36#include <QButtonGroup>
37#include <QMessageBox>
38
40
41QgsExpressionContext QgsLabelingGui::createExpressionContext() const
42{
43 QgsExpressionContext expContext;
47 if ( mCanvas )
48 expContext << QgsExpressionContextUtils::mapSettingsScope( mCanvas->mapSettings() );
49
50 if ( mLayer )
51 expContext << QgsExpressionContextUtils::layerScope( mLayer );
52
54
55 //TODO - show actual value
56 expContext.setOriginalValueVariable( QVariant() );
58
59 return expContext;
60}
61
62static bool _initCalloutWidgetFunction( const QString &name, QgsCalloutWidgetFunc f )
63{
65
66 QgsCalloutAbstractMetadata *abstractMetadata = registry->calloutMetadata( name );
67 if ( !abstractMetadata )
68 {
69 QgsDebugError( QStringLiteral( "Failed to find callout entry in registry: %1" ).arg( name ) );
70 return false;
71 }
72 QgsCalloutMetadata *metadata = dynamic_cast<QgsCalloutMetadata *>( abstractMetadata );
73 if ( !metadata )
74 {
75 QgsDebugError( QStringLiteral( "Failed to cast callout's metadata: " ) .arg( name ) );
76 return false;
77 }
78 metadata->setWidgetFunction( f );
79 return true;
80}
81
82void QgsLabelingGui::initCalloutWidgets()
83{
84 _initCalloutWidgetFunction( QStringLiteral( "simple" ), QgsSimpleLineCalloutWidget::create );
85 _initCalloutWidgetFunction( QStringLiteral( "manhattan" ), QgsManhattanLineCalloutWidget::create );
86 _initCalloutWidgetFunction( QStringLiteral( "curved" ), QgsCurvedLineCalloutWidget::create );
87 _initCalloutWidgetFunction( QStringLiteral( "balloon" ), QgsBalloonCalloutWidget::create );
88}
89
90void QgsLabelingGui::updateCalloutWidget( QgsCallout *callout )
91{
92 if ( !callout )
93 {
94 mCalloutStackedWidget->setCurrentWidget( pageDummy );
95 return;
96 }
97
98 if ( mCalloutStackedWidget->currentWidget() != pageDummy )
99 {
100 // stop updating from the original widget
101 if ( QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
102 disconnect( pew, &QgsCalloutWidget::changed, this, &QgsLabelingGui::updatePreview );
103 }
104
106 if ( QgsCalloutAbstractMetadata *am = registry->calloutMetadata( callout->type() ) )
107 {
108 if ( QgsCalloutWidget *w = am->createCalloutWidget( mLayer ) )
109 {
110
111 Qgis::GeometryType geometryType = mGeomType;
112 if ( mGeometryGeneratorGroupBox->isChecked() )
113 geometryType = mGeometryGeneratorType->currentData().value<Qgis::GeometryType>();
114 else if ( mLayer )
115 geometryType = mLayer->geometryType();
116 w->setGeometryType( geometryType );
117 w->setCallout( callout );
118
119 w->setContext( context() );
120 mCalloutStackedWidget->addWidget( w );
121 mCalloutStackedWidget->setCurrentWidget( w );
122 // start receiving updates from widget
123 connect( w, &QgsCalloutWidget::changed, this, &QgsLabelingGui::updatePreview );
124 return;
125 }
126 }
127 // When anything is not right
128 mCalloutStackedWidget->setCurrentWidget( pageDummy );
129}
130
131void QgsLabelingGui::showObstacleSettings()
132{
133 QgsExpressionContext context = createExpressionContext();
134
135 QgsSymbolWidgetContext symbolContext;
136 symbolContext.setExpressionContext( &context );
137 symbolContext.setMapCanvas( mMapCanvas );
138
139 QgsLabelObstacleSettingsWidget *widget = new QgsLabelObstacleSettingsWidget( nullptr, mLayer );
140 widget->setDataDefinedProperties( mDataDefinedProperties );
141 widget->setSettings( mObstacleSettings );
142 widget->setGeometryType( mLayer ? mLayer->geometryType() : Qgis::GeometryType::Unknown );
143 widget->setContext( symbolContext );
144
145 auto applySettings = [ = ]
146 {
147 mObstacleSettings = widget->settings();
148 const QgsPropertyCollection obstacleDataDefinedProperties = widget->dataDefinedProperties();
149 widget->updateDataDefinedProperties( mDataDefinedProperties );
150 emit widgetChanged();
151 };
152
154 if ( panel && panel->dockMode() )
155 {
156 connect( widget, &QgsLabelSettingsWidgetBase::changed, this, [ = ]
157 {
158 applySettings();
159 } );
160 panel->openPanel( widget );
161 }
162 else
163 {
164 QgsLabelSettingsWidgetDialog dialog( widget, this );
165
166 dialog.buttonBox()->addButton( QDialogButtonBox::Help );
167 connect( dialog.buttonBox(), &QDialogButtonBox::helpRequested, this, [ = ]
168 {
169 QgsHelp::openHelp( QStringLiteral( "style_library/label_settings.html#obstacles" ) );
170 } );
171
172 if ( dialog.exec() )
173 {
174 applySettings();
175 }
176 // reactivate button's window
177 activateWindow();
178 }
179}
180
181void QgsLabelingGui::showLineAnchorSettings()
182{
183 QgsExpressionContext context = createExpressionContext();
184
185 QgsSymbolWidgetContext symbolContext;
186 symbolContext.setExpressionContext( &context );
187 symbolContext.setMapCanvas( mMapCanvas );
188
189 QgsLabelLineAnchorWidget *widget = new QgsLabelLineAnchorWidget( nullptr, mLayer );
190 widget->setDataDefinedProperties( mDataDefinedProperties );
191 widget->setSettings( mLineSettings );
192 widget->setGeometryType( mLayer ? mLayer->geometryType() : Qgis::GeometryType::Unknown );
193 widget->setContext( symbolContext );
194
195 auto applySettings = [ = ]
196 {
197 const QgsLabelLineSettings widgetSettings = widget->settings();
198 mLineSettings.setLineAnchorPercent( widgetSettings.lineAnchorPercent() );
199 mLineSettings.setAnchorType( widgetSettings.anchorType() );
200 mLineSettings.setAnchorClipping( widgetSettings.anchorClipping() );
201 mLineSettings.setAnchorTextPoint( widgetSettings.anchorTextPoint() );
202 const QgsPropertyCollection obstacleDataDefinedProperties = widget->dataDefinedProperties();
203 widget->updateDataDefinedProperties( mDataDefinedProperties );
204 emit widgetChanged();
205 };
206
208 if ( panel && panel->dockMode() )
209 {
210 connect( widget, &QgsLabelSettingsWidgetBase::changed, this, [ = ]
211 {
212 applySettings();
213 } );
214 panel->openPanel( widget );
215 }
216 else
217 {
218 QgsLabelSettingsWidgetDialog dialog( widget, this );
219
220 dialog.buttonBox()->addButton( QDialogButtonBox::Help );
221 connect( dialog.buttonBox(), &QDialogButtonBox::helpRequested, this, [ = ]
222 {
223 QgsHelp::openHelp( QStringLiteral( "style_library/label_settings.html#placement-for-line-layers" ) );
224 } );
225
226 if ( dialog.exec() )
227 {
228 applySettings();
229 }
230 // reactivate button's window
231 activateWindow();
232 }
233}
234
235QgsLabelingGui::QgsLabelingGui( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsPalLayerSettings &layerSettings, QWidget *parent, Qgis::GeometryType geomType )
236 : QgsTextFormatWidget( mapCanvas, parent, QgsTextFormatWidget::Labeling, layer )
237 , mSettings( layerSettings )
238 , mMode( NoLabels )
239 , mCanvas( mapCanvas )
240{
241 mGeomType = geomType;
242 static std::once_flag initialized;
243 std::call_once( initialized, [ = ]( )
244 {
245 initCalloutWidgets();
246 } );
247
248 mFontMultiLineAlignComboBox->addItem( tr( "Left" ), static_cast< int >( Qgis::LabelMultiLineAlignment::Left ) );
249 mFontMultiLineAlignComboBox->addItem( tr( "Center" ), static_cast< int >( Qgis::LabelMultiLineAlignment::Center ) );
250 mFontMultiLineAlignComboBox->addItem( tr( "Right" ), static_cast< int >( Qgis::LabelMultiLineAlignment::Right ) );
251 mFontMultiLineAlignComboBox->addItem( tr( "Justify" ), static_cast< int >( Qgis::LabelMultiLineAlignment::Justify ) );
252
253 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( Qgis::AngleUnit::Degrees ), static_cast< int >( Qgis::AngleUnit::Degrees ) );
254 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( Qgis::AngleUnit::Radians ), static_cast< int >( Qgis::AngleUnit::Radians ) );
255 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( Qgis::AngleUnit::Gon ), static_cast< int >( Qgis::AngleUnit::Gon ) );
256 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( Qgis::AngleUnit::MinutesOfArc ), static_cast< int >( Qgis::AngleUnit::MinutesOfArc ) );
257 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( Qgis::AngleUnit::SecondsOfArc ), static_cast< int >( Qgis::AngleUnit::SecondsOfArc ) );
258 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( Qgis::AngleUnit::Turn ), static_cast< int >( Qgis::AngleUnit::Turn ) );
259 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( Qgis::AngleUnit::MilliradiansSI ), static_cast< int >( Qgis::AngleUnit::MilliradiansSI ) );
260 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( Qgis::AngleUnit::MilNATO ), static_cast< int >( Qgis::AngleUnit::MilNATO ) );
261
262 // connections for groupboxes with separate activation checkboxes (that need to honor data defined setting)
263 connect( mBufferDrawChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
264 connect( mBufferDrawDDBtn, &QgsPropertyOverrideButton::changed, this, &QgsLabelingGui::updateUi );
265 connect( mEnableMaskChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
266 connect( mShapeDrawChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
267 connect( mCalloutsDrawCheckBox, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
268 connect( mShadowDrawChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
269 connect( mDirectSymbChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
270 connect( mFormatNumChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
271 connect( mScaleBasedVisibilityChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
272 connect( mFontLimitPixelChkBox, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
273 connect( mGeometryGeneratorGroupBox, &QGroupBox::toggled, this, &QgsLabelingGui::updateGeometryTypeBasedWidgets );
274 connect( mGeometryGeneratorType, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsLabelingGui::updateGeometryTypeBasedWidgets );
275 connect( mGeometryGeneratorExpressionButton, &QToolButton::clicked, this, &QgsLabelingGui::showGeometryGeneratorExpressionBuilder );
276 connect( mGeometryGeneratorGroupBox, &QGroupBox::toggled, this, &QgsLabelingGui::validateGeometryGeneratorExpression );
277 connect( mGeometryGenerator, &QgsCodeEditorExpression::textChanged, this, &QgsLabelingGui::validateGeometryGeneratorExpression );
278 connect( mGeometryGeneratorType, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsLabelingGui::validateGeometryGeneratorExpression );
279 connect( mObstacleSettingsButton, &QAbstractButton::clicked, this, &QgsLabelingGui::showObstacleSettings );
280 connect( mLineAnchorSettingsButton, &QAbstractButton::clicked, this, &QgsLabelingGui::showLineAnchorSettings );
281
282 mFieldExpressionWidget->registerExpressionContextGenerator( this );
283
284 mMinScaleWidget->setMapCanvas( mCanvas );
285 mMinScaleWidget->setShowCurrentScaleButton( true );
286 mMaxScaleWidget->setMapCanvas( mCanvas );
287 mMaxScaleWidget->setShowCurrentScaleButton( true );
288
289 const QStringList calloutTypes = QgsApplication::calloutRegistry()->calloutTypes();
290 for ( const QString &type : calloutTypes )
291 {
292 mCalloutStyleComboBox->addItem( QgsApplication::calloutRegistry()->calloutMetadata( type )->icon(),
293 QgsApplication::calloutRegistry()->calloutMetadata( type )->visibleName(), type );
294 }
295
296 mGeometryGeneratorWarningLabel->setStyleSheet( QStringLiteral( "color: #FFC107;" ) );
297 mGeometryGeneratorWarningLabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
298 connect( mGeometryGeneratorWarningLabel, &QLabel::linkActivated, this, [this]( const QString & link )
299 {
300 if ( link == QLatin1String( "#determineGeometryGeneratorType" ) )
301 determineGeometryGeneratorType();
302 } );
303
304 connect( mCalloutStyleComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLabelingGui::calloutTypeChanged );
305
306 mLblNoObstacle1->installEventFilter( this );
307
308 setLayer( layer );
309}
310
311void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer )
312{
313 mPreviewFeature = QgsFeature();
314
315 if ( ( !mapLayer || mapLayer->type() != Qgis::LayerType::Vector ) && mGeomType == Qgis::GeometryType::Unknown )
316 {
317 setEnabled( false );
318 return;
319 }
320
321 setEnabled( true );
322
323 QgsVectorLayer *layer = static_cast<QgsVectorLayer *>( mapLayer );
324 mLayer = layer;
325
326 mTextFormatsListWidget->setLayerType( mLayer ? mLayer->geometryType() : mGeomType );
327 mBackgroundMarkerSymbolButton->setLayer( mLayer );
328 mBackgroundFillSymbolButton->setLayer( mLayer );
329
330 // load labeling settings from layer
331 updateGeometryTypeBasedWidgets();
332
333 mFieldExpressionWidget->setLayer( mLayer );
335 if ( mLayer )
336 da.setSourceCrs( mLayer->crs(), QgsProject::instance()->transformContext() );
337 da.setEllipsoid( QgsProject::instance()->ellipsoid() );
338 mFieldExpressionWidget->setGeomCalculator( da );
339
340 mFieldExpressionWidget->setEnabled( mMode == Labels || !mLayer );
341 mLabelingFrame->setEnabled( mMode == Labels || !mLayer );
342
343 blockInitSignals( true );
344
345 mGeometryGenerator->setText( mSettings.geometryGenerator );
346 mGeometryGeneratorGroupBox->setChecked( mSettings.geometryGeneratorEnabled );
347 if ( !mSettings.geometryGeneratorEnabled )
348 mGeometryGeneratorGroupBox->setCollapsed( true );
349 mGeometryGeneratorType->setCurrentIndex( mGeometryGeneratorType->findData( QVariant::fromValue( mSettings.geometryGeneratorType ) ) );
350
351 updateWidgetForFormat( mSettings.format().isValid() ? mSettings.format() : QgsStyle::defaultTextFormatForProject( QgsProject::instance(), QgsStyle::TextFormatContext::Labeling ) );
352
353 mFieldExpressionWidget->setRow( -1 );
354 mFieldExpressionWidget->setField( mSettings.fieldName );
355 mCheckBoxSubstituteText->setChecked( mSettings.useSubstitutions );
356 mSubstitutions = mSettings.substitutions;
357
358 // populate placement options
359 mCentroidRadioWhole->setChecked( mSettings.centroidWhole );
360 mCentroidInsideCheckBox->setChecked( mSettings.centroidInside );
361 mFitInsidePolygonCheckBox->setChecked( mSettings.fitInPolygonOnly );
362 mLineDistanceSpnBx->setValue( mSettings.dist );
363 mLineDistanceUnitWidget->setUnit( mSettings.distUnits );
364 mLineDistanceUnitWidget->setMapUnitScale( mSettings.distMapUnitScale );
365 mOffsetTypeComboBox->setCurrentIndex( mOffsetTypeComboBox->findData( static_cast< int >( mSettings.offsetType ) ) );
366 mQuadrantBtnGrp->button( static_cast<int>( mSettings.quadOffset ) )->setChecked( true );
367 mPointOffsetXSpinBox->setValue( mSettings.xOffset );
368 mPointOffsetYSpinBox->setValue( mSettings.yOffset );
369 mPointOffsetUnitWidget->setUnit( mSettings.offsetUnits );
370 mPointOffsetUnitWidget->setMapUnitScale( mSettings.labelOffsetMapUnitScale );
371 mPointAngleSpinBox->setValue( mSettings.angleOffset );
372 chkLineAbove->setChecked( mSettings.lineSettings().placementFlags() & Qgis::LabelLinePlacementFlag::AboveLine );
373 chkLineBelow->setChecked( mSettings.lineSettings().placementFlags() & Qgis::LabelLinePlacementFlag::BelowLine );
374 chkLineOn->setChecked( mSettings.lineSettings().placementFlags() & Qgis::LabelLinePlacementFlag::OnLine );
375 chkLineOrientationDependent->setChecked( !( mSettings.lineSettings().placementFlags() & Qgis::LabelLinePlacementFlag::MapOrientation ) );
376
377 mCheckAllowLabelsOutsidePolygons->setChecked( mSettings.polygonPlacementFlags() & Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
378
379 const int placementIndex = mPlacementModeComboBox->findData( static_cast< int >( mSettings.placement ) );
380 if ( placementIndex >= 0 )
381 {
382 mPlacementModeComboBox->setCurrentIndex( placementIndex );
383 }
384 else
385 {
386 // use default placement for layer type
387 mPlacementModeComboBox->setCurrentIndex( 0 );
388 }
389
390 // Label repeat distance
391 mRepeatDistanceSpinBox->setValue( mSettings.repeatDistance );
392 mRepeatDistanceUnitWidget->setUnit( mSettings.repeatDistanceUnit );
393 mRepeatDistanceUnitWidget->setMapUnitScale( mSettings.repeatDistanceMapUnitScale );
394
395 mOverrunDistanceSpinBox->setValue( mSettings.lineSettings().overrunDistance() );
396 mOverrunDistanceUnitWidget->setUnit( mSettings.lineSettings().overrunDistanceUnit() );
397 mOverrunDistanceUnitWidget->setMapUnitScale( mSettings.lineSettings().overrunDistanceMapUnitScale() );
398
399 mPrioritySlider->setValue( mSettings.priority );
400 mChkNoObstacle->setChecked( mSettings.obstacleSettings().isObstacle() );
401
402 mObstacleSettings = mSettings.obstacleSettings();
403 mLineSettings = mSettings.lineSettings();
404
405 chkLabelPerFeaturePart->setChecked( mSettings.labelPerPart );
406
407 mComboOverlapHandling->setCurrentIndex( mComboOverlapHandling->findData( static_cast< int >( mSettings.placementSettings().overlapHandling() ) ) );
408 mCheckAllowDegradedPlacement->setChecked( mSettings.placementSettings().allowDegradedPlacement() );
409
410 chkMergeLines->setChecked( mSettings.lineSettings().mergeLines() );
411 mMinSizeSpinBox->setValue( mSettings.thinningSettings().minimumFeatureSize() );
412 mLimitLabelChkBox->setChecked( mSettings.thinningSettings().limitNumberOfLabelsEnabled() );
413 mLimitLabelSpinBox->setValue( mSettings.thinningSettings().maximumNumberLabels() );
414
415 // direction symbol(s)
416 mDirectSymbChkBx->setChecked( mSettings.lineSettings().addDirectionSymbol() );
417 mDirectSymbLeftLineEdit->setText( mSettings.lineSettings().leftDirectionSymbol() );
418 mDirectSymbRightLineEdit->setText( mSettings.lineSettings().rightDirectionSymbol() );
419 mDirectSymbRevChkBx->setChecked( mSettings.lineSettings().reverseDirectionSymbol() );
420
421 mDirectSymbBtnGrp->button( static_cast<int>( mSettings.lineSettings().directionSymbolPlacement() ) )->setChecked( true );
422 mUpsidedownBtnGrp->button( static_cast<int>( mSettings.upsidedownLabels ) )->setChecked( true );
423
424 // curved label max character angles
425 mMaxCharAngleInDSpinBox->setValue( mSettings.maxCurvedCharAngleIn );
426 // lyr.maxCurvedCharAngleOut must be negative, but it is shown as positive spinbox in GUI
427 mMaxCharAngleOutDSpinBox->setValue( std::fabs( mSettings.maxCurvedCharAngleOut ) );
428
429 wrapCharacterEdit->setText( mSettings.wrapChar );
430 mAutoWrapLengthSpinBox->setValue( mSettings.autoWrapLength );
431 mAutoWrapTypeComboBox->setCurrentIndex( mSettings.useMaxLineLengthForAutoWrap ? 0 : 1 );
432
433 if ( mFontMultiLineAlignComboBox->findData( static_cast< int >( mSettings.multilineAlign ) ) != -1 )
434 {
435 mFontMultiLineAlignComboBox->setCurrentIndex( mFontMultiLineAlignComboBox->findData( static_cast< int >( mSettings.multilineAlign ) ) );
436 }
437 else
438 {
439 // the default pal layer settings for multiline alignment is to follow label placement, which isn't always available
440 // revert to left alignment in such case
441 mFontMultiLineAlignComboBox->setCurrentIndex( 0 );
442 }
443
444 chkPreserveRotation->setChecked( mSettings.preserveRotation );
445
446 mCoordRotationUnitComboBox->setCurrentIndex( 0 );
447 if ( mCoordRotationUnitComboBox->findData( static_cast< unsigned int >( mSettings.rotationUnit() ) ) >= 0 )
448 mCoordRotationUnitComboBox->setCurrentIndex( mCoordRotationUnitComboBox->findData( static_cast< unsigned int >( mSettings.rotationUnit() ) ) );
449
450 mScaleBasedVisibilityChkBx->setChecked( mSettings.scaleVisibility );
451 mMinScaleWidget->setScale( mSettings.minimumScale );
452 mMaxScaleWidget->setScale( mSettings.maximumScale );
453
454 mFormatNumChkBx->setChecked( mSettings.formatNumbers );
455 mFormatNumDecimalsSpnBx->setValue( mSettings.decimals );
456 mFormatNumPlusSignChkBx->setChecked( mSettings.plusSign );
457
458 // set pixel size limiting checked state before unit choice so limiting can be
459 // turned on as a default for map units, if minimum trigger value of 0 is used
460 mFontLimitPixelChkBox->setChecked( mSettings.fontLimitPixelSize );
461 mMinPixelLimit = mSettings.fontMinPixelSize; // ignored after first settings save
462 mFontMinPixelSpinBox->setValue( mSettings.fontMinPixelSize == 0 ? 3 : mSettings.fontMinPixelSize );
463 mFontMaxPixelSpinBox->setValue( mSettings.fontMaxPixelSize );
464
465 mZIndexSpinBox->setValue( mSettings.zIndex );
466
467 mDataDefinedProperties = mSettings.dataDefinedProperties();
468
469 // callout settings, to move to custom widget when multiple styles exist
470 if ( auto *lCallout = mSettings.callout() )
471 {
472 whileBlocking( mCalloutsDrawCheckBox )->setChecked( lCallout->enabled() );
473 whileBlocking( mCalloutStyleComboBox )->setCurrentIndex( mCalloutStyleComboBox->findData( lCallout->type() ) );
474 updateCalloutWidget( lCallout );
475 }
476 else
477 {
478 std::unique_ptr< QgsCallout > defaultCallout( QgsCalloutRegistry::defaultCallout() );
479 whileBlocking( mCalloutStyleComboBox )->setCurrentIndex( mCalloutStyleComboBox->findData( defaultCallout->type() ) );
480 whileBlocking( mCalloutsDrawCheckBox )->setChecked( false );
481 updateCalloutWidget( defaultCallout.get() );
482 }
483
484 updatePlacementWidgets();
485 updateLinePlacementOptions();
486
487 // needs to come before data defined setup, so connections work
488 blockInitSignals( false );
489
490 // set up data defined toolbuttons
491 // do this after other widgets are configured, so they can be enabled/disabled
492 populateDataDefinedButtons();
493
494 updateUi(); // should come after data defined button setup
495}
496
497void QgsLabelingGui::setSettings( const QgsPalLayerSettings &settings )
498{
499 mSettings = settings;
500 setLayer( mLayer );
501}
502
503void QgsLabelingGui::blockInitSignals( bool block )
504{
505 chkLineAbove->blockSignals( block );
506 chkLineBelow->blockSignals( block );
507 mPlacementModeComboBox->blockSignals( block );
508}
509
510void QgsLabelingGui::setLabelMode( LabelMode mode )
511{
512 mMode = mode;
513 mFieldExpressionWidget->setEnabled( mMode == Labels );
514 mLabelingFrame->setEnabled( mMode == Labels );
515}
516
517QgsPalLayerSettings QgsLabelingGui::layerSettings()
518{
520
521 // restore properties which aren't exposed in GUI
522 lyr.setUnplacedVisibility( mSettings.unplacedVisibility() );
523
524 lyr.drawLabels = ( mMode == Labels ) || !mLayer;
525
526 bool isExpression;
527 lyr.fieldName = mFieldExpressionWidget->currentField( &isExpression );
528 lyr.isExpression = isExpression;
529
530 lyr.dist = 0;
531
532 Qgis::LabelPolygonPlacementFlags polygonPlacementFlags = Qgis::LabelPolygonPlacementFlag::AllowPlacementInsideOfPolygon;
533 if ( mCheckAllowLabelsOutsidePolygons->isChecked() )
535 lyr.setPolygonPlacementFlags( polygonPlacementFlags );
536
537 lyr.centroidWhole = mCentroidRadioWhole->isChecked();
538 lyr.centroidInside = mCentroidInsideCheckBox->isChecked();
539 lyr.fitInPolygonOnly = mFitInsidePolygonCheckBox->isChecked();
540 lyr.dist = mLineDistanceSpnBx->value();
541 lyr.distUnits = mLineDistanceUnitWidget->unit();
542 lyr.distMapUnitScale = mLineDistanceUnitWidget->getMapUnitScale();
543 lyr.offsetType = static_cast< Qgis::LabelOffsetType >( mOffsetTypeComboBox->currentData().toInt() );
544 if ( mQuadrantBtnGrp )
545 {
546 lyr.quadOffset = static_cast< Qgis::LabelQuadrantPosition >( mQuadrantBtnGrp->checkedId() );
547 }
548 lyr.xOffset = mPointOffsetXSpinBox->value();
549 lyr.yOffset = mPointOffsetYSpinBox->value();
550 lyr.offsetUnits = mPointOffsetUnitWidget->unit();
551 lyr.labelOffsetMapUnitScale = mPointOffsetUnitWidget->getMapUnitScale();
552 lyr.angleOffset = mPointAngleSpinBox->value();
553
554 Qgis::LabelLinePlacementFlags linePlacementFlags = Qgis::LabelLinePlacementFlags();
555 if ( chkLineAbove->isChecked() )
556 linePlacementFlags |= Qgis::LabelLinePlacementFlag::AboveLine;
557 if ( chkLineBelow->isChecked() )
558 linePlacementFlags |= Qgis::LabelLinePlacementFlag::BelowLine;
559 if ( chkLineOn->isChecked() )
560 linePlacementFlags |= Qgis::LabelLinePlacementFlag::OnLine;
561 if ( ! chkLineOrientationDependent->isChecked() )
563 lyr.lineSettings().setPlacementFlags( linePlacementFlags );
564
565 lyr.placement = static_cast< Qgis::LabelPlacement >( mPlacementModeComboBox->currentData().toInt() );
566
567 lyr.repeatDistance = mRepeatDistanceSpinBox->value();
568 lyr.repeatDistanceUnit = mRepeatDistanceUnitWidget->unit();
569 lyr.repeatDistanceMapUnitScale = mRepeatDistanceUnitWidget->getMapUnitScale();
570
571 lyr.lineSettings().setOverrunDistance( mOverrunDistanceSpinBox->value() );
572 lyr.lineSettings().setOverrunDistanceUnit( mOverrunDistanceUnitWidget->unit() );
573 lyr.lineSettings().setOverrunDistanceMapUnitScale( mOverrunDistanceUnitWidget->getMapUnitScale() );
574
575 lyr.priority = mPrioritySlider->value();
576
577 mObstacleSettings.setIsObstacle( mChkNoObstacle->isChecked() || mMode == ObstaclesOnly );
578 lyr.setObstacleSettings( mObstacleSettings );
579
580 lyr.lineSettings().setLineAnchorPercent( mLineSettings.lineAnchorPercent() );
581 lyr.lineSettings().setAnchorType( mLineSettings.anchorType() );
582 lyr.lineSettings().setAnchorClipping( mLineSettings.anchorClipping() );
583 lyr.lineSettings().setAnchorTextPoint( mLineSettings.anchorTextPoint() );
584
585 lyr.labelPerPart = chkLabelPerFeaturePart->isChecked();
586 lyr.placementSettings().setOverlapHandling( static_cast< Qgis::LabelOverlapHandling>( mComboOverlapHandling->currentData().toInt() ) );
587 lyr.placementSettings().setAllowDegradedPlacement( mCheckAllowDegradedPlacement->isChecked() );
588
589 lyr.lineSettings().setMergeLines( chkMergeLines->isChecked() );
590
591 lyr.scaleVisibility = mScaleBasedVisibilityChkBx->isChecked();
592 lyr.minimumScale = mMinScaleWidget->scale();
593 lyr.maximumScale = mMaxScaleWidget->scale();
594 lyr.useSubstitutions = mCheckBoxSubstituteText->isChecked();
595 lyr.substitutions = mSubstitutions;
596
597 lyr.setFormat( format( false ) );
598
599 // format numbers
600 lyr.formatNumbers = mFormatNumChkBx->isChecked();
601 lyr.decimals = mFormatNumDecimalsSpnBx->value();
602 lyr.plusSign = mFormatNumPlusSignChkBx->isChecked();
603
604 // direction symbol(s)
605 lyr.lineSettings().setAddDirectionSymbol( mDirectSymbChkBx->isChecked() );
606 lyr.lineSettings().setLeftDirectionSymbol( mDirectSymbLeftLineEdit->text() );
607 lyr.lineSettings().setRightDirectionSymbol( mDirectSymbRightLineEdit->text() );
608 lyr.lineSettings().setReverseDirectionSymbol( mDirectSymbRevChkBx->isChecked() );
609 if ( mDirectSymbBtnGrp )
610 {
611 lyr.lineSettings().setDirectionSymbolPlacement( static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( mDirectSymbBtnGrp->checkedId() ) );
612 }
613 if ( mUpsidedownBtnGrp )
614 {
615 lyr.upsidedownLabels = static_cast< Qgis::UpsideDownLabelHandling >( mUpsidedownBtnGrp->checkedId() );
616 }
617
618 lyr.maxCurvedCharAngleIn = mMaxCharAngleInDSpinBox->value();
619 // lyr.maxCurvedCharAngleOut must be negative, but it is shown as positive spinbox in GUI
620 lyr.maxCurvedCharAngleOut = -mMaxCharAngleOutDSpinBox->value();
621
622
623 lyr.thinningSettings().setMinimumFeatureSize( mMinSizeSpinBox->value() );
624 lyr.thinningSettings().setLimitNumberLabelsEnabled( mLimitLabelChkBox->isChecked() );
625 lyr.thinningSettings().setMaximumNumberLabels( mLimitLabelSpinBox->value() );
626 lyr.fontLimitPixelSize = mFontLimitPixelChkBox->isChecked();
627 lyr.fontMinPixelSize = mFontMinPixelSpinBox->value();
628 lyr.fontMaxPixelSize = mFontMaxPixelSpinBox->value();
629 lyr.wrapChar = wrapCharacterEdit->text();
630 lyr.autoWrapLength = mAutoWrapLengthSpinBox->value();
631 lyr.useMaxLineLengthForAutoWrap = mAutoWrapTypeComboBox->currentIndex() == 0;
632 lyr.multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( mFontMultiLineAlignComboBox->currentData().toInt() );
633 lyr.preserveRotation = chkPreserveRotation->isChecked();
634 lyr.setRotationUnit( static_cast< Qgis::AngleUnit >( mCoordRotationUnitComboBox->currentData().toInt() ) );
635 lyr.geometryGenerator = mGeometryGenerator->text();
636 lyr.geometryGeneratorType = mGeometryGeneratorType->currentData().value<Qgis::GeometryType>();
637 lyr.geometryGeneratorEnabled = mGeometryGeneratorGroupBox->isChecked();
638
639 lyr.layerType = mLayer ? mLayer->geometryType() : mGeomType;
640
641 lyr.zIndex = mZIndexSpinBox->value();
642
643 lyr.setDataDefinedProperties( mDataDefinedProperties );
644
645 // callout settings
646 const QString calloutType = mCalloutStyleComboBox->currentData().toString();
647 std::unique_ptr< QgsCallout > callout;
648 if ( QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
649 {
650 callout.reset( pew->callout()->clone() );
651 }
652 if ( !callout )
653 callout.reset( QgsApplication::calloutRegistry()->createCallout( calloutType ) );
654
655 callout->setEnabled( mCalloutsDrawCheckBox->isChecked() );
656 lyr.setCallout( callout.release() );
657
658 return lyr;
659}
660
661void QgsLabelingGui::syncDefinedCheckboxFrame( QgsPropertyOverrideButton *ddBtn, QCheckBox *chkBx, QFrame *f )
662{
663 f->setEnabled( chkBx->isChecked() || ddBtn->isActive() );
664}
665
666bool QgsLabelingGui::eventFilter( QObject *object, QEvent *event )
667{
668 if ( object == mLblNoObstacle1 )
669 {
670 if ( event->type() == QEvent::MouseButtonPress && qgis::down_cast< QMouseEvent * >( event )->button() == Qt::LeftButton )
671 {
672 // clicking the obstacle label toggles the checkbox, just like a "normal" checkbox label...
673 mChkNoObstacle->setChecked( !mChkNoObstacle->isChecked() );
674 return true;
675 }
676 return false;
677 }
678 return QgsTextFormatWidget::eventFilter( object, event );
679}
680
681void QgsLabelingGui::updateUi()
682{
683 // enable/disable inline groupbox-like setups (that need to honor data defined setting)
684
685 syncDefinedCheckboxFrame( mBufferDrawDDBtn, mBufferDrawChkBx, mBufferFrame );
686 syncDefinedCheckboxFrame( mEnableMaskDDBtn, mEnableMaskChkBx, mMaskFrame );
687 syncDefinedCheckboxFrame( mShapeDrawDDBtn, mShapeDrawChkBx, mShapeFrame );
688 syncDefinedCheckboxFrame( mShadowDrawDDBtn, mShadowDrawChkBx, mShadowFrame );
689 syncDefinedCheckboxFrame( mCalloutDrawDDBtn, mCalloutsDrawCheckBox, mCalloutFrame );
690
691 syncDefinedCheckboxFrame( mDirectSymbDDBtn, mDirectSymbChkBx, mDirectSymbFrame );
692 syncDefinedCheckboxFrame( mFormatNumDDBtn, mFormatNumChkBx, mFormatNumFrame );
693 syncDefinedCheckboxFrame( mScaleBasedVisibilityDDBtn, mScaleBasedVisibilityChkBx, mScaleBasedVisibilityFrame );
694 syncDefinedCheckboxFrame( mFontLimitPixelDDBtn, mFontLimitPixelChkBox, mFontLimitPixelFrame );
695
696 chkMergeLines->setEnabled( !mDirectSymbChkBx->isChecked() );
697 if ( mDirectSymbChkBx->isChecked() )
698 {
699 chkMergeLines->setToolTip( tr( "This option is not compatible with line direction symbols." ) );
700 }
701 else
702 {
703 chkMergeLines->setToolTip( QString() );
704 }
705}
706
707void QgsLabelingGui::setFormatFromStyle( const QString &name, QgsStyle::StyleEntity type, const QString &stylePath )
708{
709 QgsStyle *style = QgsProject::instance()->styleSettings()->styleAtPath( stylePath );
710
711 if ( !style )
712 style = QgsStyle::defaultStyle();
713
714 switch ( type )
715 {
723 {
724 QgsTextFormatWidget::setFormatFromStyle( name, type, stylePath );
725 return;
726 }
727
729 {
730 if ( !style->labelSettingsNames().contains( name ) )
731 return;
732
733 QgsPalLayerSettings settings = style->labelSettings( name );
734 if ( settings.fieldName.isEmpty() )
735 {
736 // if saved settings doesn't have a field name stored, retain the current one
737 bool isExpression;
738 settings.fieldName = mFieldExpressionWidget->currentField( &isExpression );
739 settings.isExpression = isExpression;
740 }
741 setSettings( settings );
742 break;
743 }
744 }
745}
746
747void QgsLabelingGui::setContext( const QgsSymbolWidgetContext &context )
748{
749 if ( QgsCalloutWidget *cw = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
750 {
751 cw->setContext( context );
752 }
754}
755
756void QgsLabelingGui::saveFormat()
757{
759 saveDlg.setDefaultTags( mTextFormatsListWidget->currentTagFilter() );
760 if ( !saveDlg.exec() )
761 return;
762
763 if ( saveDlg.name().isEmpty() )
764 return;
765
766 QgsStyle *style = saveDlg.destinationStyle();
767 if ( !style )
768 return;
769
770 switch ( saveDlg.selectedType() )
771 {
773 {
774 // check if there is no format with same name
775 if ( style->textFormatNames().contains( saveDlg.name() ) )
776 {
777 const int res = QMessageBox::warning( this, tr( "Save Text Format" ),
778 tr( "Format with name '%1' already exists. Overwrite?" )
779 .arg( saveDlg.name() ),
780 QMessageBox::Yes | QMessageBox::No );
781 if ( res != QMessageBox::Yes )
782 {
783 return;
784 }
785 style->removeTextFormat( saveDlg.name() );
786 }
787 const QStringList symbolTags = saveDlg.tags().split( ',' );
788
789 const QgsTextFormat newFormat = format();
790 style->addTextFormat( saveDlg.name(), newFormat );
791 style->saveTextFormat( saveDlg.name(), newFormat, saveDlg.isFavorite(), symbolTags );
792 break;
793 }
794
796 {
797 // check if there is no settings with same name
798 if ( style->labelSettingsNames().contains( saveDlg.name() ) )
799 {
800 const int res = QMessageBox::warning( this, tr( "Save Label Settings" ),
801 tr( "Label settings with the name '%1' already exist. Overwrite?" )
802 .arg( saveDlg.name() ),
803 QMessageBox::Yes | QMessageBox::No );
804 if ( res != QMessageBox::Yes )
805 {
806 return;
807 }
808 style->removeLabelSettings( saveDlg.name() );
809 }
810 const QStringList symbolTags = saveDlg.tags().split( ',' );
811
812 const QgsPalLayerSettings newSettings = layerSettings();
813 style->addLabelSettings( saveDlg.name(), newSettings );
814 style->saveLabelSettings( saveDlg.name(), newSettings, saveDlg.isFavorite(), symbolTags );
815 break;
816 }
817
824 break;
825 }
826}
827
828void QgsLabelingGui::updateGeometryTypeBasedWidgets()
829{
830 Qgis::GeometryType geometryType = mGeomType;
831
832 if ( mGeometryGeneratorGroupBox->isChecked() )
833 geometryType = mGeometryGeneratorType->currentData().value<Qgis::GeometryType>();
834 else if ( mLayer )
835 geometryType = mLayer->geometryType();
836
837 // show/hide options based upon geometry type
838 chkMergeLines->setVisible( geometryType == Qgis::GeometryType::Line );
839 mDirectSymbolsFrame->setVisible( geometryType == Qgis::GeometryType::Line );
840 mMinSizeFrame->setVisible( geometryType != Qgis::GeometryType::Point );
841 mPolygonFeatureOptionsFrame->setVisible( geometryType == Qgis::GeometryType::Polygon );
842
843
844 const Qgis::LabelPlacement prevPlacement = static_cast< Qgis::LabelPlacement >( mPlacementModeComboBox->currentData().toInt() );
845 mPlacementModeComboBox->clear();
846
847 switch ( geometryType )
848 {
849 case Qgis::GeometryType::Point:
850 mPlacementModeComboBox->addItem( tr( "Cartographic" ), static_cast< int >( Qgis::LabelPlacement::OrderedPositionsAroundPoint ) );
851 mPlacementModeComboBox->addItem( tr( "Around Point" ), static_cast< int >( Qgis::LabelPlacement::AroundPoint ) );
852 mPlacementModeComboBox->addItem( tr( "Offset from Point" ), static_cast< int >( Qgis::LabelPlacement::OverPoint ) );
853 break;
854
855 case Qgis::GeometryType::Line:
856 mPlacementModeComboBox->addItem( tr( "Parallel" ), static_cast< int >( Qgis::LabelPlacement::Line ) );
857 mPlacementModeComboBox->addItem( tr( "Curved" ), static_cast< int >( Qgis::LabelPlacement::Curved ) );
858 mPlacementModeComboBox->addItem( tr( "Horizontal" ), static_cast< int >( Qgis::LabelPlacement::Horizontal ) );
859 break;
860
861 case Qgis::GeometryType::Polygon:
862 mPlacementModeComboBox->addItem( tr( "Offset from Centroid" ), static_cast< int >( Qgis::LabelPlacement::OverPoint ) );
863 mPlacementModeComboBox->addItem( tr( "Around Centroid" ), static_cast< int >( Qgis::LabelPlacement::AroundPoint ) );
864 mPlacementModeComboBox->addItem( tr( "Horizontal" ), static_cast< int >( Qgis::LabelPlacement::Horizontal ) );
865 mPlacementModeComboBox->addItem( tr( "Free (Angled)" ), static_cast< int >( Qgis::LabelPlacement::Free ) );
866 mPlacementModeComboBox->addItem( tr( "Using Perimeter" ), static_cast< int >( Qgis::LabelPlacement::Line ) );
867 mPlacementModeComboBox->addItem( tr( "Using Perimeter (Curved)" ), static_cast< int >( Qgis::LabelPlacement::PerimeterCurved ) );
868 mPlacementModeComboBox->addItem( tr( "Outside Polygons" ), static_cast< int >( Qgis::LabelPlacement::OutsidePolygons ) );
869 break;
870
871 case Qgis::GeometryType::Null:
872 break;
873 case Qgis::GeometryType::Unknown:
874 qFatal( "unknown geometry type unexpected" );
875 }
876
877 if ( mPlacementModeComboBox->findData( static_cast< int >( prevPlacement ) ) != -1 )
878 {
879 mPlacementModeComboBox->setCurrentIndex( mPlacementModeComboBox->findData( static_cast< int >( prevPlacement ) ) );
880 }
881
882 if ( geometryType == Qgis::GeometryType::Point || geometryType == Qgis::GeometryType::Polygon )
883 {
884 // follow placement alignment is only valid for point or polygon layers
885 if ( mFontMultiLineAlignComboBox->findData( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) ) == -1 )
886 mFontMultiLineAlignComboBox->addItem( tr( "Follow Label Placement" ), static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) );
887 }
888 else
889 {
890 const int idx = mFontMultiLineAlignComboBox->findData( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) );
891 if ( idx >= 0 )
892 mFontMultiLineAlignComboBox->removeItem( idx );
893 }
894
895 updatePlacementWidgets();
896 updateLinePlacementOptions();
897}
898
899void QgsLabelingGui::showGeometryGeneratorExpressionBuilder()
900{
901 QgsExpressionBuilderDialog expressionBuilder( mLayer );
902
903 expressionBuilder.setExpressionText( mGeometryGenerator->text() );
904 expressionBuilder.setExpressionContext( createExpressionContext() );
905
906 if ( expressionBuilder.exec() )
907 {
908 mGeometryGenerator->setText( expressionBuilder.expressionText() );
909 }
910}
911
912void QgsLabelingGui::validateGeometryGeneratorExpression()
913{
914 bool valid = true;
915
916 if ( mGeometryGeneratorGroupBox->isChecked() )
917 {
918 if ( !mPreviewFeature.isValid() && mLayer )
919 mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( mPreviewFeature );
920
921 QgsExpression expression( mGeometryGenerator->text() );
922 QgsExpressionContext context = createExpressionContext();
923 context.setFeature( mPreviewFeature );
924
925 expression.prepare( &context );
926
927 if ( expression.hasParserError() )
928 {
929 mGeometryGeneratorWarningLabel->setText( expression.parserErrorString() );
930 valid = false;
931 }
932 else
933 {
934 const QVariant result = expression.evaluate( &context );
935 const QgsGeometry geometry = result.value<QgsGeometry>();
936 const Qgis::GeometryType configuredGeometryType = mGeometryGeneratorType->currentData().value<Qgis::GeometryType>();
937 if ( geometry.isNull() )
938 {
939 mGeometryGeneratorWarningLabel->setText( tr( "Result of the expression is not a geometry" ) );
940 valid = false;
941 }
942 else if ( geometry.type() != configuredGeometryType )
943 {
944 mGeometryGeneratorWarningLabel->setText( QStringLiteral( "<p>%1</p><p><a href=\"#determineGeometryGeneratorType\">%2</a></p>" ).arg(
945 tr( "Result of the expression does not match configured geometry type." ),
946 tr( "Change to %1" ).arg( QgsWkbTypes::geometryDisplayString( geometry.type() ) ) ) );
947 valid = false;
948 }
949 }
950 }
951
952 // The collapsible groupbox internally changes the visibility of this
953 // Work around by setting the visibility deferred in the next event loop cycle.
954 QTimer *timer = new QTimer();
955 connect( timer, &QTimer::timeout, this, [this, valid]()
956 {
957 mGeometryGeneratorWarningLabel->setVisible( !valid );
958 } );
959 connect( timer, &QTimer::timeout, timer, &QTimer::deleteLater );
960 timer->start( 0 );
961}
962
963void QgsLabelingGui::determineGeometryGeneratorType()
964{
965 if ( !mPreviewFeature.isValid() && mLayer )
966 mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( mPreviewFeature );
967
968 QgsExpression expression( mGeometryGenerator->text() );
969 QgsExpressionContext context = createExpressionContext();
970 context.setFeature( mPreviewFeature );
971
972 expression.prepare( &context );
973 const QgsGeometry geometry = expression.evaluate( &context ).value<QgsGeometry>();
974
975 mGeometryGeneratorType->setCurrentIndex( mGeometryGeneratorType->findData( QVariant::fromValue( geometry.type() ) ) );
976}
977
978void QgsLabelingGui::calloutTypeChanged()
979{
980 const QString newCalloutType = mCalloutStyleComboBox->currentData().toString();
981 QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() );
982 if ( pew )
983 {
984 if ( pew->callout() && pew->callout()->type() == newCalloutType )
985 return;
986 }
987
988 // get creation function for new callout from registry
990 QgsCalloutAbstractMetadata *am = registry->calloutMetadata( newCalloutType );
991 if ( !am ) // check whether the metadata is assigned
992 return;
993
994 // change callout to a new one (with different type)
995 // base new callout on existing callout's properties
996 const std::unique_ptr< QgsCallout > newCallout( am->createCallout( pew && pew->callout() ? pew->callout()->properties( QgsReadWriteContext() ) : QVariantMap(), QgsReadWriteContext() ) );
997 if ( !newCallout )
998 return;
999
1000 updateCalloutWidget( newCallout.get() );
1001 updatePreview();
1002}
1003
1004
1005//
1006// QgsLabelSettingsDialog
1007//
1008
1009QgsLabelSettingsDialog::QgsLabelSettingsDialog( const QgsPalLayerSettings &settings, QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, QWidget *parent,
1010 Qgis::GeometryType geomType )
1011 : QDialog( parent )
1012{
1013 QVBoxLayout *vLayout = new QVBoxLayout();
1014 mWidget = new QgsLabelingGui( layer, mapCanvas, settings, nullptr, geomType );
1015 vLayout->addWidget( mWidget );
1016 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok, Qt::Horizontal );
1017 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
1018 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
1019 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsLabelSettingsDialog::showHelp );
1020 vLayout->addWidget( mButtonBox );
1021 setLayout( vLayout );
1022 setWindowTitle( tr( "Label Settings" ) );
1023}
1024
1025QDialogButtonBox *QgsLabelSettingsDialog::buttonBox() const
1026{
1027 return mButtonBox;
1028}
1029
1030void QgsLabelSettingsDialog::showHelp()
1031{
1032 QgsHelp::openHelp( QStringLiteral( "style_library/label_settings.html" ) );
1033}
1034
1035
1036
@ BelowLine
Labels can be placed below a line feature. Unless MapOrientation is also specified this mode respects...
@ MapOrientation
Signifies that the AboveLine and BelowLine flags should respect the map's orientation rather than the...
@ OnLine
Labels can be placed directly over a line feature.
@ AboveLine
Labels can be placed above a line feature. Unless MapOrientation is also specified this mode respects...
AngleUnit
Units of angles.
Definition: qgis.h:3396
LabelOffsetType
Behavior modifier for label offset and distance, only applies in some label placement modes.
Definition: qgis.h:828
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition: qgis.h:782
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
@ Curved
Arranges candidates following the curvature of a line feature. Applies to line layers only.
@ AroundPoint
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
@ Free
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...
@ OrderedPositionsAroundPoint
Candidates are placed in predefined positions around a point. Preference is given to positions with g...
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
@ PerimeterCurved
Arranges candidates following the curvature of a polygon's boundary. Applies to polygon layers only.
@ OutsidePolygons
Candidates are placed outside of polygon boundaries. Applies to polygon layers only....
@ AllowPlacementInsideOfPolygon
Labels can be placed inside a polygon feature.
@ AllowPlacementOutsideOfPolygon
Labels can be placed outside of a polygon feature.
LabelQuadrantPosition
Label quadrant positions.
Definition: qgis.h:842
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition: qgis.h:227
LabelMultiLineAlignment
Text alignment for multi-line labels.
Definition: qgis.h:925
LabelOverlapHandling
Label overlap handling.
Definition: qgis.h:767
UpsideDownLabelHandling
Polygon placement flags, which control how candidates are generated for a polygon feature.
Definition: qgis.h:910
static QgsCalloutRegistry * calloutRegistry()
Returns the application's callout registry, used for managing callout types.
Stores metadata about one callout renderer class.
virtual QgsCallout * createCallout(const QVariantMap &properties, const QgsReadWriteContext &context)=0
Create a callout of this type given the map of properties.
Convenience metadata class that uses static functions to create callouts and their widgets.
void setWidgetFunction(QgsCalloutWidgetFunc f)
Registry of available callout classes.
QgsCalloutAbstractMetadata * calloutMetadata(const QString &type) const
Returns the metadata for specified the specified callout type.
static QgsCallout * defaultCallout()
Create a new instance of a callout with default settings.
QStringList calloutTypes() const
Returns a list of all available callout types.
QgsCallout * createCallout(const QString &type, const QVariantMap &properties=QVariantMap(), const QgsReadWriteContext &context=QgsReadWriteContext()) const
Creates a new instance of a callout, given the callout type and properties.
Base class for widgets which allow control over the properties of callouts.
virtual QgsCallout * callout()=0
Returns the callout defined by the current settings in the widget.
void changed()
Should be emitted whenever configuration changes happened on this symbol layer configuration.
Abstract base class for callout renderers.
Definition: qgscallout.h:53
void setEnabled(bool enabled)
Sets whether the callout is enabled.
Definition: qgscallout.cpp:188
virtual QString type() const =0
Returns a unique string representing the callout type.
virtual QVariantMap properties(const QgsReadWriteContext &context) const
Returns the properties describing the callout encoded in a string format.
Definition: qgscallout.cpp:80
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
A generic dialog for building expression strings.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * updateSymbolScope(const QgsSymbol *symbol, QgsExpressionContextScope *symbolScope=nullptr)
Updates a symbol scope related to a QgsSymbol to an expression context.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * atlasScope(const QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
static const QString EXPR_SYMBOL_COLOR
Inbuilt variable name for symbol color variable.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void setHighlightedVariables(const QStringList &variableNames)
Sets the list of variable names within the context intended to be highlighted to the user.
static const QString EXPR_ORIGINAL_VALUE
Inbuilt variable name for value original value variable.
Class for parsing and evaluation of expressions (formerly called "search strings").
This class wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
Q_GADGET bool isNull
Definition: qgsgeometry.h:166
Qgis::GeometryType type
Definition: qgsgeometry.h:167
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:38
A widget for customising label line anchor settings.
void updateDataDefinedProperties(QgsPropertyCollection &properties) override
Updates a data defined properties collection, correctly setting the values for any properties related...
QgsLabelLineSettings settings() const
Returns the line settings defined by the widget.
void setSettings(const QgsLabelLineSettings &settings)
Sets the line settings to show in the widget.
Contains settings related to how the label engine places and formats labels for line features (or pol...
void setPlacementFlags(Qgis::LabelLinePlacementFlags flags)
Returns the line placement flags, which dictate how line labels can be placed above or below the line...
void setLineAnchorPercent(double percent)
Sets the percent along the line at which labels should be placed.
void setDirectionSymbolPlacement(DirectionSymbolPlacement placement)
Sets the placement for direction symbols.
AnchorType anchorType() const
Returns the line anchor type, which dictates how the lineAnchorPercent() setting is handled.
void setAnchorTextPoint(AnchorTextPoint point)
Sets the line anchor text point, which dictates which part of the label text should be placed at the ...
void setLeftDirectionSymbol(const QString &symbol)
Sets the string to use for left direction arrows.
AnchorTextPoint anchorTextPoint() const
Returns the line anchor text point, which dictates which part of the label text should be placed at t...
void setMergeLines(bool merge)
Sets whether connected line features with identical label text should be merged prior to generating l...
DirectionSymbolPlacement
Placement options for direction symbols.
void setRightDirectionSymbol(const QString &symbol)
Sets the string to use for right direction arrows.
void setAnchorClipping(AnchorClipping clipping)
Sets the line anchor clipping mode, which dictates how line strings are clipped before calculating th...
void setOverrunDistanceMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for label overrun distance.
double lineAnchorPercent() const
Returns the percent along the line at which labels should be placed.
void setAnchorType(AnchorType type)
Sets the line anchor type, which dictates how the lineAnchorPercent() setting is handled.
void setOverrunDistanceUnit(const Qgis::RenderUnit &unit)
Sets the unit for label overrun distance.
void setOverrunDistance(double distance)
Sets the distance which labels are allowed to overrun past the start or end of line features.
AnchorClipping anchorClipping() const
Returns the line anchor clipping mode, which dictates how line strings are clipped before calculating...
void setReverseDirectionSymbol(bool reversed)
Sets whether the direction symbols should be reversed.
void setAddDirectionSymbol(bool enabled)
Sets whether '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) will...
A widget for customising label obstacle settings.
void setGeometryType(Qgis::GeometryType type) override
Sets the geometry type of the features to customize the widget accordingly.
QgsLabelObstacleSettings settings() const
Returns the obstacle settings defined by the widget.
void updateDataDefinedProperties(QgsPropertyCollection &properties) override
Updates a data defined properties collection, correctly setting the values for any properties related...
void setSettings(const QgsLabelObstacleSettings &settings)
Sets the obstacle settings to show in the widget.
void setOverlapHandling(Qgis::LabelOverlapHandling handling)
Sets the technique used to handle overlapping labels.
void setAllowDegradedPlacement(bool allow)
Sets whether labels can be placed in inferior fallback positions if they cannot otherwise be placed.
void changed()
Emitted when any of the settings described by the widget are changed.
virtual void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression ...
virtual void setGeometryType(Qgis::GeometryType type)
Sets the geometry type of the features to customize the widget accordingly.
void setDataDefinedProperties(const QgsPropertyCollection &dataDefinedProperties)
Sets the current data defined properties to show in the widget.
QgsPropertyCollection dataDefinedProperties() const
Returns the current data defined properties state as specified in the widget.
A blocking dialog containing a QgsLabelSettingsWidgetBase.
void setMaximumNumberLabels(int number)
Sets the maximum number of labels which should be drawn for this layer.
void setLimitNumberLabelsEnabled(bool enabled)
Sets whether the the number of labels drawn for the layer should be limited.
void setMinimumFeatureSize(double size)
Sets the minimum feature size (in millimeters) for a feature to be labelled.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:90
Base class for all map layer types.
Definition: qgsmaplayer.h:73
Qgis::LayerType type
Definition: qgsmaplayer.h:80
Contains settings for how a map layer will be labeled.
bool fitInPolygonOnly
true if only labels which completely fit within a polygon are allowed.
double yOffset
Vertical offset of label.
QgsMapUnitScale labelOffsetMapUnitScale
Map unit scale for label offset.
int fontMaxPixelSize
Maximum pixel size for showing rendered map unit labels (1 - 10000).
void setObstacleSettings(const QgsLabelObstacleSettings &settings)
Sets the label obstacle settings.
const QgsLabelPlacementSettings & placementSettings() const
Returns the label placement settings.
double maxCurvedCharAngleIn
Maximum angle between inside curved label characters (valid range 20.0 to 60.0).
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
double zIndex
Z-Index of label, where labels with a higher z-index are rendered on top of labels with a lower z-ind...
void setPolygonPlacementFlags(Qgis::LabelPolygonPlacementFlags flags)
Sets the polygon placement flags, which dictate how polygon labels can be placed.
QString wrapChar
Wrapping character string.
Qgis::LabelOffsetType offsetType
Offset type for layer (only applies in certain placement modes)
double xOffset
Horizontal offset of label.
Qgis::LabelPlacement placement
Label placement mode.
bool drawLabels
Whether to draw labels for this layer.
bool fontLimitPixelSize
true if label sizes should be limited by pixel size.
double minimumScale
The minimum map scale (i.e.
Qgis::LabelQuadrantPosition quadOffset
Sets the quadrant in which to offset labels from feature.
bool scaleVisibility
Set to true to limit label visibility to a range of scales.
double repeatDistance
Distance for repeating labels for a single feature.
bool geometryGeneratorEnabled
Defines if the geometry generator is enabled or not. If disabled, the standard geometry will be taken...
Qgis::LabelMultiLineAlignment multilineAlign
Horizontal alignment of multi-line labels.
bool centroidInside
true if centroid positioned labels must be placed inside their corresponding feature polygon,...
int priority
Label priority.
Qgis::GeometryType geometryGeneratorType
The type of the result geometry of the geometry generator.
bool labelPerPart
true if every part of a multi-part feature should be labeled.
int fontMinPixelSize
Minimum pixel size for showing rendered map unit labels (1 - 1000).
double angleOffset
Label rotation, in degrees clockwise.
double maxCurvedCharAngleOut
Maximum angle between outside curved label characters (valid range -20.0 to -95.0)
const QgsLabelThinningSettings & thinningSettings() const
Returns the label thinning settings.
Qgis::GeometryType layerType
Geometry type of layers associated with these settings.
Qgis::RenderUnit offsetUnits
Units for offsets of label.
void setDataDefinedProperties(const QgsPropertyCollection &collection)
Sets the label's property collection, used for data defined overrides.
bool isExpression
true if this label is made from a expression string, e.g., FieldName || 'mm'
bool preserveRotation
True if label rotation should be preserved during label pin/unpin operations.
bool plusSign
Whether '+' signs should be prepended to positive numeric labels.
QString geometryGenerator
The geometry generator expression. Null if disabled.
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
QgsMapUnitScale distMapUnitScale
Map unit scale for label feature distance.
QgsStringReplacementCollection substitutions
Substitution collection for automatic text substitution with labels.
int decimals
Number of decimal places to show for numeric labels.
double dist
Distance from feature to the label.
void setRotationUnit(Qgis::AngleUnit angleUnit)
Set unit for rotation of labels.
QgsMapUnitScale repeatDistanceMapUnitScale
Map unit scale for repeating labels for a single feature.
Qgis::RenderUnit distUnits
Units the distance from feature to the label.
bool centroidWhole
true if feature centroid should be calculated from the whole feature, or false if only the visible pa...
Qgis::RenderUnit repeatDistanceUnit
Units for repeating labels for a single feature.
Qgis::UpsideDownLabelHandling upsidedownLabels
Controls whether upside down labels are displayed and how they are handled.
QString fieldName
Name of field (or an expression) to use for label text.
bool formatNumbers
Set to true to format numeric label text as numbers (e.g.
void setCallout(QgsCallout *callout)
Sets the label callout renderer, responsible for drawing label callouts.
double maximumScale
The maximum map scale (i.e.
int autoWrapLength
If non-zero, indicates that label text should be automatically wrapped to (ideally) the specified num...
bool useMaxLineLengthForAutoWrap
If true, indicates that when auto wrapping label text the autoWrapLength length indicates the maximum...
void setUnplacedVisibility(Qgis::UnplacedLabelVisibility visibility)
Sets the layer's unplaced label visibility.
bool useSubstitutions
True if substitutions should be applied.
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
bool dockMode()
Returns the dock mode state.
QgsStyle * styleAtPath(const QString &path)
Returns a reference to the style database associated with the project with matching file path.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:484
const QgsProjectStyleSettings * styleSettings() const
Returns the project's style settings, which contains settings and properties relating to how a QgsPro...
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:113
A grouped map of multiple QgsProperty objects, each referenced by a integer key value.
A button for controlling property overrides which may apply to a widget.
bool isActive() const
Returns true if the button has an active property.
void changed()
Emitted when property definition changes.
The class is used as a container of context for various read/write operations on other objects.
a dialog for setting properties of a newly saved style.
bool removeLabelSettings(const QString &name)
Removes label settings from the style.
Definition: qgsstyle.cpp:1059
bool saveLabelSettings(const QString &name, const QgsPalLayerSettings &settings, bool favorite, const QStringList &tags)
Adds label settings to the database.
Definition: qgsstyle.cpp:1023
QStringList textFormatNames() const
Returns a list of names of text formats in the style.
Definition: qgsstyle.cpp:2136
bool removeTextFormat(const QString &name)
Removes a text format from the style.
Definition: qgsstyle.cpp:983
StyleEntity
Enum for Entities involved in a style.
Definition: qgsstyle.h:179
@ LabelSettingsEntity
Label settings.
Definition: qgsstyle.h:185
@ TextFormatEntity
Text formats.
Definition: qgsstyle.h:184
@ SmartgroupEntity
Smart groups.
Definition: qgsstyle.h:183
@ Symbol3DEntity
3D symbol entity (since QGIS 3.14)
Definition: qgsstyle.h:187
@ SymbolEntity
Symbols.
Definition: qgsstyle.h:180
@ TagEntity
Tags.
Definition: qgsstyle.h:181
@ ColorrampEntity
Color ramps.
Definition: qgsstyle.h:182
@ LegendPatchShapeEntity
Legend patch shape (since QGIS 3.14)
Definition: qgsstyle.h:186
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition: qgsstyle.cpp:145
QStringList labelSettingsNames() const
Returns a list of names of label settings in the style.
Definition: qgsstyle.cpp:2200
static QgsTextFormat defaultTextFormatForProject(QgsProject *project, QgsStyle::TextFormatContext context=QgsStyle::TextFormatContext::Labeling)
Returns the default text format to use for new text based objects for the specified project,...
Definition: qgsstyle.cpp:1223
bool addTextFormat(const QString &name, const QgsTextFormat &format, bool update=false)
Adds a text format with the specified name to the style.
Definition: qgsstyle.cpp:343
QgsPalLayerSettings labelSettings(const QString &name) const
Returns the label settings with the specified name.
Definition: qgsstyle.cpp:2146
@ Labeling
Text format used in labeling.
bool saveTextFormat(const QString &name, const QgsTextFormat &format, bool favorite, const QStringList &tags)
Adds a text format to the database.
Definition: qgsstyle.cpp:947
bool addLabelSettings(const QString &name, const QgsPalLayerSettings &settings, bool update=false)
Adds label settings with the specified name to the style.
Definition: qgsstyle.cpp:364
Contains settings which reflect the context in which a symbol (or renderer) widget is shown,...
void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
void setExpressionContext(QgsExpressionContext *context)
Sets the optional expression context used for the widget.
A widget for customizing text formatting settings.
virtual void setContext(const QgsSymbolWidgetContext &context)
Sets the context in which the widget is shown, e.g., the associated map canvas and expression context...
virtual void setFormatFromStyle(const QString &name, QgsStyle::StyleEntity type, const QString &stylePath)
Sets the current text settings from a style entry.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:42
static Q_INVOKABLE QString toString(Qgis::DistanceUnit unit)
Returns a translated string representing a distance unit.
Represents a vector layer which manages a vector based data sets.
static QString geometryDisplayString(Qgis::GeometryType type) SIP_HOLDGIL
Returns a display string for a geometry type.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:3914
QgsCalloutWidget *(* QgsCalloutWidgetFunc)(QgsVectorLayer *)
#define QgsDebugError(str)
Definition: qgslogger.h:38