QGIS API Documentation 3.27.0-Master (0a97e3138f)
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"
22#include "qgsproject.h"
23#include "qgsauxiliarystorage.h"
27#include "qgshelp.h"
28#include "qgsstylesavedialog.h"
29#include "qgscallout.h"
30#include "qgsapplication.h"
31#include "qgscalloutsregistry.h"
36
37#include <mutex>
38
39#include <QButtonGroup>
40#include <QMessageBox>
41
43
44QgsExpressionContext QgsLabelingGui::createExpressionContext() const
45{
46 QgsExpressionContext expContext;
50 if ( mCanvas )
51 expContext << QgsExpressionContextUtils::mapSettingsScope( mCanvas->mapSettings() );
52
53 if ( mLayer )
54 expContext << QgsExpressionContextUtils::layerScope( mLayer );
55
57
58 //TODO - show actual value
59 expContext.setOriginalValueVariable( QVariant() );
61
62 return expContext;
63}
64
65static bool _initCalloutWidgetFunction( const QString &name, QgsCalloutWidgetFunc f )
66{
68
69 QgsCalloutAbstractMetadata *abstractMetadata = registry->calloutMetadata( name );
70 if ( !abstractMetadata )
71 {
72 QgsDebugMsg( QStringLiteral( "Failed to find callout entry in registry: %1" ).arg( name ) );
73 return false;
74 }
75 QgsCalloutMetadata *metadata = dynamic_cast<QgsCalloutMetadata *>( abstractMetadata );
76 if ( !metadata )
77 {
78 QgsDebugMsg( QStringLiteral( "Failed to cast callout's metadata: " ) .arg( name ) );
79 return false;
80 }
81 metadata->setWidgetFunction( f );
82 return true;
83}
84
85void QgsLabelingGui::initCalloutWidgets()
86{
87 _initCalloutWidgetFunction( QStringLiteral( "simple" ), QgsSimpleLineCalloutWidget::create );
88 _initCalloutWidgetFunction( QStringLiteral( "manhattan" ), QgsManhattanLineCalloutWidget::create );
89 _initCalloutWidgetFunction( QStringLiteral( "curved" ), QgsCurvedLineCalloutWidget::create );
90 _initCalloutWidgetFunction( QStringLiteral( "balloon" ), QgsBalloonCalloutWidget::create );
91}
92
93void QgsLabelingGui::updateCalloutWidget( QgsCallout *callout )
94{
95 if ( !callout )
96 {
97 mCalloutStackedWidget->setCurrentWidget( pageDummy );
98 return;
99 }
100
101 if ( mCalloutStackedWidget->currentWidget() != pageDummy )
102 {
103 // stop updating from the original widget
104 if ( QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
105 disconnect( pew, &QgsCalloutWidget::changed, this, &QgsLabelingGui::updatePreview );
106 }
107
109 if ( QgsCalloutAbstractMetadata *am = registry->calloutMetadata( callout->type() ) )
110 {
111 if ( QgsCalloutWidget *w = am->createCalloutWidget( mLayer ) )
112 {
113
114 QgsWkbTypes::GeometryType geometryType = mGeomType;
115 if ( mGeometryGeneratorGroupBox->isChecked() )
116 geometryType = mGeometryGeneratorType->currentData().value<QgsWkbTypes::GeometryType>();
117 else if ( mLayer )
118 geometryType = mLayer->geometryType();
119 w->setGeometryType( geometryType );
120 w->setCallout( callout );
121
122 w->setContext( context() );
123 mCalloutStackedWidget->addWidget( w );
124 mCalloutStackedWidget->setCurrentWidget( w );
125 // start receiving updates from widget
126 connect( w, &QgsCalloutWidget::changed, this, &QgsLabelingGui::updatePreview );
127 return;
128 }
129 }
130 // When anything is not right
131 mCalloutStackedWidget->setCurrentWidget( pageDummy );
132}
133
134void QgsLabelingGui::showObstacleSettings()
135{
136 QgsExpressionContext context = createExpressionContext();
137
138 QgsSymbolWidgetContext symbolContext;
139 symbolContext.setExpressionContext( &context );
140 symbolContext.setMapCanvas( mMapCanvas );
141
142 QgsLabelObstacleSettingsWidget *widget = new QgsLabelObstacleSettingsWidget( nullptr, mLayer );
143 widget->setDataDefinedProperties( mDataDefinedProperties );
144 widget->setSettings( mObstacleSettings );
145 widget->setGeometryType( mLayer ? mLayer->geometryType() : QgsWkbTypes::UnknownGeometry );
146 widget->setContext( symbolContext );
147
148 auto applySettings = [ = ]
149 {
150 mObstacleSettings = widget->settings();
151 const QgsPropertyCollection obstacleDataDefinedProperties = widget->dataDefinedProperties();
152 widget->updateDataDefinedProperties( mDataDefinedProperties );
153 emit widgetChanged();
154 };
155
157 if ( panel && panel->dockMode() )
158 {
159 connect( widget, &QgsLabelSettingsWidgetBase::changed, this, [ = ]
160 {
161 applySettings();
162 } );
163 panel->openPanel( widget );
164 }
165 else
166 {
167 QgsLabelSettingsWidgetDialog dialog( widget, this );
168
169 dialog.buttonBox()->addButton( QDialogButtonBox::Help );
170 connect( dialog.buttonBox(), &QDialogButtonBox::helpRequested, this, [ = ]
171 {
172 QgsHelp::openHelp( QStringLiteral( "style_library/label_settings.html#obstacles" ) );
173 } );
174
175 if ( dialog.exec() )
176 {
177 applySettings();
178 }
179 // reactivate button's window
180 activateWindow();
181 }
182}
183
184void QgsLabelingGui::showLineAnchorSettings()
185{
186 QgsExpressionContext context = createExpressionContext();
187
188 QgsSymbolWidgetContext symbolContext;
189 symbolContext.setExpressionContext( &context );
190 symbolContext.setMapCanvas( mMapCanvas );
191
192 QgsLabelLineAnchorWidget *widget = new QgsLabelLineAnchorWidget( nullptr, mLayer );
193 widget->setDataDefinedProperties( mDataDefinedProperties );
194 widget->setSettings( mLineSettings );
195 widget->setGeometryType( mLayer ? mLayer->geometryType() : QgsWkbTypes::UnknownGeometry );
196 widget->setContext( symbolContext );
197
198 auto applySettings = [ = ]
199 {
200 const QgsLabelLineSettings widgetSettings = widget->settings();
201 mLineSettings.setLineAnchorPercent( widgetSettings.lineAnchorPercent() );
202 mLineSettings.setAnchorType( widgetSettings.anchorType() );
203 mLineSettings.setAnchorClipping( widgetSettings.anchorClipping() );
204 mLineSettings.setAnchorTextPoint( widgetSettings.anchorTextPoint() );
205 const QgsPropertyCollection obstacleDataDefinedProperties = widget->dataDefinedProperties();
206 widget->updateDataDefinedProperties( mDataDefinedProperties );
207 emit widgetChanged();
208 };
209
211 if ( panel && panel->dockMode() )
212 {
213 connect( widget, &QgsLabelSettingsWidgetBase::changed, this, [ = ]
214 {
215 applySettings();
216 } );
217 panel->openPanel( widget );
218 }
219 else
220 {
221 QgsLabelSettingsWidgetDialog dialog( widget, this );
222 if ( dialog.exec() )
223 {
224 applySettings();
225 }
226 // reactivate button's window
227 activateWindow();
228 }
229}
230
231QgsLabelingGui::QgsLabelingGui( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const QgsPalLayerSettings &layerSettings, QWidget *parent, QgsWkbTypes::GeometryType geomType )
232 : QgsTextFormatWidget( mapCanvas, parent, QgsTextFormatWidget::Labeling, layer )
233 , mSettings( layerSettings )
234 , mMode( NoLabels )
235 , mCanvas( mapCanvas )
236{
237 mGeomType = geomType;
238 static std::once_flag initialized;
239 std::call_once( initialized, [ = ]( )
240 {
241 initCalloutWidgets();
242 } );
243
244 mFontMultiLineAlignComboBox->addItem( tr( "Left" ), static_cast< int >( Qgis::LabelMultiLineAlignment::Left ) );
245 mFontMultiLineAlignComboBox->addItem( tr( "Center" ), static_cast< int >( Qgis::LabelMultiLineAlignment::Center ) );
246 mFontMultiLineAlignComboBox->addItem( tr( "Right" ), static_cast< int >( Qgis::LabelMultiLineAlignment::Right ) );
247 mFontMultiLineAlignComboBox->addItem( tr( "Justify" ), static_cast< int >( Qgis::LabelMultiLineAlignment::Justify ) );
248
249 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( QgsUnitTypes::AngleDegrees ), QgsUnitTypes::AngleDegrees );
250 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( QgsUnitTypes::AngleRadians ), QgsUnitTypes::AngleRadians );
251 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( QgsUnitTypes::AngleGon ), QgsUnitTypes::AngleGon );
254 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( QgsUnitTypes::AngleTurn ), QgsUnitTypes::AngleTurn );
256 mCoordRotationUnitComboBox->addItem( QgsUnitTypes::toString( QgsUnitTypes::AngleMilNATO ), QgsUnitTypes::AngleMilNATO );
257
258 // connections for groupboxes with separate activation checkboxes (that need to honor data defined setting)
259 connect( mBufferDrawChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
260 connect( mBufferDrawDDBtn, &QgsPropertyOverrideButton::changed, this, &QgsLabelingGui::updateUi );
261 connect( mEnableMaskChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
262 connect( mShapeDrawChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
263 connect( mCalloutsDrawCheckBox, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
264 connect( mShadowDrawChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
265 connect( mDirectSymbChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
266 connect( mFormatNumChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
267 connect( mScaleBasedVisibilityChkBx, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
268 connect( mFontLimitPixelChkBox, &QAbstractButton::toggled, this, &QgsLabelingGui::updateUi );
269 connect( mGeometryGeneratorGroupBox, &QGroupBox::toggled, this, &QgsLabelingGui::updateGeometryTypeBasedWidgets );
270 connect( mGeometryGeneratorType, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsLabelingGui::updateGeometryTypeBasedWidgets );
271 connect( mGeometryGeneratorExpressionButton, &QToolButton::clicked, this, &QgsLabelingGui::showGeometryGeneratorExpressionBuilder );
272 connect( mGeometryGeneratorGroupBox, &QGroupBox::toggled, this, &QgsLabelingGui::validateGeometryGeneratorExpression );
273 connect( mGeometryGenerator, &QgsCodeEditorExpression::textChanged, this, &QgsLabelingGui::validateGeometryGeneratorExpression );
274 connect( mGeometryGeneratorType, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsLabelingGui::validateGeometryGeneratorExpression );
275 connect( mObstacleSettingsButton, &QAbstractButton::clicked, this, &QgsLabelingGui::showObstacleSettings );
276 connect( mLineAnchorSettingsButton, &QAbstractButton::clicked, this, &QgsLabelingGui::showLineAnchorSettings );
277
278 mFieldExpressionWidget->registerExpressionContextGenerator( this );
279
280 mMinScaleWidget->setMapCanvas( mCanvas );
281 mMinScaleWidget->setShowCurrentScaleButton( true );
282 mMaxScaleWidget->setMapCanvas( mCanvas );
283 mMaxScaleWidget->setShowCurrentScaleButton( true );
284
285 const QStringList calloutTypes = QgsApplication::calloutRegistry()->calloutTypes();
286 for ( const QString &type : calloutTypes )
287 {
288 mCalloutStyleComboBox->addItem( QgsApplication::calloutRegistry()->calloutMetadata( type )->icon(),
289 QgsApplication::calloutRegistry()->calloutMetadata( type )->visibleName(), type );
290 }
291
292 mGeometryGeneratorWarningLabel->setStyleSheet( QStringLiteral( "color: #FFC107;" ) );
293 mGeometryGeneratorWarningLabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
294 connect( mGeometryGeneratorWarningLabel, &QLabel::linkActivated, this, [this]( const QString & link )
295 {
296 if ( link == QLatin1String( "#determineGeometryGeneratorType" ) )
297 determineGeometryGeneratorType();
298 } );
299
300 connect( mCalloutStyleComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLabelingGui::calloutTypeChanged );
301
302 mLblNoObstacle1->installEventFilter( this );
303
304 setLayer( layer );
305}
306
307void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer )
308{
309 mPreviewFeature = QgsFeature();
310
311 if ( ( !mapLayer || mapLayer->type() != QgsMapLayerType::VectorLayer ) && mGeomType == QgsWkbTypes::UnknownGeometry )
312 {
313 setEnabled( false );
314 return;
315 }
316
317 setEnabled( true );
318
319 QgsVectorLayer *layer = static_cast<QgsVectorLayer *>( mapLayer );
320 mLayer = layer;
321
322 mTextFormatsListWidget->setLayerType( mLayer ? mLayer->geometryType() : mGeomType );
323 mBackgroundMarkerSymbolButton->setLayer( mLayer );
324 mBackgroundFillSymbolButton->setLayer( mLayer );
325
326 // load labeling settings from layer
327 updateGeometryTypeBasedWidgets();
328
329 mFieldExpressionWidget->setLayer( mLayer );
331 if ( mLayer )
332 da.setSourceCrs( mLayer->crs(), QgsProject::instance()->transformContext() );
333 da.setEllipsoid( QgsProject::instance()->ellipsoid() );
334 mFieldExpressionWidget->setGeomCalculator( da );
335
336 mFieldExpressionWidget->setEnabled( mMode == Labels || !mLayer );
337 mLabelingFrame->setEnabled( mMode == Labels || !mLayer );
338
339 blockInitSignals( true );
340
341 mGeometryGenerator->setText( mSettings.geometryGenerator );
342 mGeometryGeneratorGroupBox->setChecked( mSettings.geometryGeneratorEnabled );
343 if ( !mSettings.geometryGeneratorEnabled )
344 mGeometryGeneratorGroupBox->setCollapsed( true );
345 mGeometryGeneratorType->setCurrentIndex( mGeometryGeneratorType->findData( mSettings.geometryGeneratorType ) );
346
347 updateWidgetForFormat( mSettings.format().isValid() ? mSettings.format() : QgsStyle::defaultTextFormatForProject( QgsProject::instance(), QgsStyle::TextFormatContext::Labeling ) );
348
349 mFieldExpressionWidget->setRow( -1 );
350 mFieldExpressionWidget->setField( mSettings.fieldName );
351 mCheckBoxSubstituteText->setChecked( mSettings.useSubstitutions );
352 mSubstitutions = mSettings.substitutions;
353
354 // populate placement options
355 mCentroidRadioWhole->setChecked( mSettings.centroidWhole );
356 mCentroidInsideCheckBox->setChecked( mSettings.centroidInside );
357 mFitInsidePolygonCheckBox->setChecked( mSettings.fitInPolygonOnly );
358 mLineDistanceSpnBx->setValue( mSettings.dist );
359 mLineDistanceUnitWidget->setUnit( mSettings.distUnits );
360 mLineDistanceUnitWidget->setMapUnitScale( mSettings.distMapUnitScale );
361 mOffsetTypeComboBox->setCurrentIndex( mOffsetTypeComboBox->findData( static_cast< int >( mSettings.offsetType ) ) );
362 mQuadrantBtnGrp->button( static_cast<int>( mSettings.quadOffset ) )->setChecked( true );
363 mPointOffsetXSpinBox->setValue( mSettings.xOffset );
364 mPointOffsetYSpinBox->setValue( mSettings.yOffset );
365 mPointOffsetUnitWidget->setUnit( mSettings.offsetUnits );
366 mPointOffsetUnitWidget->setMapUnitScale( mSettings.labelOffsetMapUnitScale );
367 mPointAngleSpinBox->setValue( mSettings.angleOffset );
368 chkLineAbove->setChecked( mSettings.lineSettings().placementFlags() & QgsLabeling::LinePlacementFlag::AboveLine );
369 chkLineBelow->setChecked( mSettings.lineSettings().placementFlags() & QgsLabeling::LinePlacementFlag::BelowLine );
370 chkLineOn->setChecked( mSettings.lineSettings().placementFlags() & QgsLabeling::LinePlacementFlag::OnLine );
371 chkLineOrientationDependent->setChecked( !( mSettings.lineSettings().placementFlags() & QgsLabeling::LinePlacementFlag::MapOrientation ) );
372
373 mCheckAllowLabelsOutsidePolygons->setChecked( mSettings.polygonPlacementFlags() & QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
374
375 const int placementIndex = mPlacementModeComboBox->findData( static_cast< int >( mSettings.placement ) );
376 if ( placementIndex >= 0 )
377 {
378 mPlacementModeComboBox->setCurrentIndex( placementIndex );
379 }
380 else
381 {
382 // use default placement for layer type
383 mPlacementModeComboBox->setCurrentIndex( 0 );
384 }
385
386 // Label repeat distance
387 mRepeatDistanceSpinBox->setValue( mSettings.repeatDistance );
388 mRepeatDistanceUnitWidget->setUnit( mSettings.repeatDistanceUnit );
389 mRepeatDistanceUnitWidget->setMapUnitScale( mSettings.repeatDistanceMapUnitScale );
390
391 mOverrunDistanceSpinBox->setValue( mSettings.lineSettings().overrunDistance() );
392 mOverrunDistanceUnitWidget->setUnit( mSettings.lineSettings().overrunDistanceUnit() );
393 mOverrunDistanceUnitWidget->setMapUnitScale( mSettings.lineSettings().overrunDistanceMapUnitScale() );
394
395 mPrioritySlider->setValue( mSettings.priority );
396 mChkNoObstacle->setChecked( mSettings.obstacleSettings().isObstacle() );
397
398 mObstacleSettings = mSettings.obstacleSettings();
399 mLineSettings = mSettings.lineSettings();
400
401 chkLabelPerFeaturePart->setChecked( mSettings.labelPerPart );
402
403 mComboOverlapHandling->setCurrentIndex( mComboOverlapHandling->findData( static_cast< int >( mSettings.placementSettings().overlapHandling() ) ) );
404 mCheckAllowDegradedPlacement->setChecked( mSettings.placementSettings().allowDegradedPlacement() );
405
406 chkMergeLines->setChecked( mSettings.lineSettings().mergeLines() );
407 mMinSizeSpinBox->setValue( mSettings.thinningSettings().minimumFeatureSize() );
408 mLimitLabelChkBox->setChecked( mSettings.thinningSettings().limitNumberOfLabelsEnabled() );
409 mLimitLabelSpinBox->setValue( mSettings.thinningSettings().maximumNumberLabels() );
410
411 // direction symbol(s)
412 mDirectSymbChkBx->setChecked( mSettings.lineSettings().addDirectionSymbol() );
413 mDirectSymbLeftLineEdit->setText( mSettings.lineSettings().leftDirectionSymbol() );
414 mDirectSymbRightLineEdit->setText( mSettings.lineSettings().rightDirectionSymbol() );
415 mDirectSymbRevChkBx->setChecked( mSettings.lineSettings().reverseDirectionSymbol() );
416
417 mDirectSymbBtnGrp->button( static_cast<int>( mSettings.lineSettings().directionSymbolPlacement() ) )->setChecked( true );
418 mUpsidedownBtnGrp->button( static_cast<int>( mSettings.upsidedownLabels ) )->setChecked( true );
419
420 // curved label max character angles
421 mMaxCharAngleInDSpinBox->setValue( mSettings.maxCurvedCharAngleIn );
422 // lyr.maxCurvedCharAngleOut must be negative, but it is shown as positive spinbox in GUI
423 mMaxCharAngleOutDSpinBox->setValue( std::fabs( mSettings.maxCurvedCharAngleOut ) );
424
425 wrapCharacterEdit->setText( mSettings.wrapChar );
426 mAutoWrapLengthSpinBox->setValue( mSettings.autoWrapLength );
427 mAutoWrapTypeComboBox->setCurrentIndex( mSettings.useMaxLineLengthForAutoWrap ? 0 : 1 );
428
429 if ( mFontMultiLineAlignComboBox->findData( static_cast< int >( mSettings.multilineAlign ) ) != -1 )
430 {
431 mFontMultiLineAlignComboBox->setCurrentIndex( mFontMultiLineAlignComboBox->findData( static_cast< int >( mSettings.multilineAlign ) ) );
432 }
433 else
434 {
435 // the default pal layer settings for multiline alignment is to follow label placement, which isn't always available
436 // revert to left alignment in such case
437 mFontMultiLineAlignComboBox->setCurrentIndex( 0 );
438 }
439
440 chkPreserveRotation->setChecked( mSettings.preserveRotation );
441
442 mCoordRotationUnitComboBox->setCurrentIndex( 0 );
443 if ( mCoordRotationUnitComboBox->findData( static_cast< unsigned int >( mSettings.rotationUnit() ) ) >= 0 )
444 mCoordRotationUnitComboBox->setCurrentIndex( mCoordRotationUnitComboBox->findData( static_cast< unsigned int >( mSettings.rotationUnit() ) ) );
445
446 mScaleBasedVisibilityChkBx->setChecked( mSettings.scaleVisibility );
447 mMinScaleWidget->setScale( mSettings.minimumScale );
448 mMaxScaleWidget->setScale( mSettings.maximumScale );
449
450 mFormatNumChkBx->setChecked( mSettings.formatNumbers );
451 mFormatNumDecimalsSpnBx->setValue( mSettings.decimals );
452 mFormatNumPlusSignChkBx->setChecked( mSettings.plusSign );
453
454 // set pixel size limiting checked state before unit choice so limiting can be
455 // turned on as a default for map units, if minimum trigger value of 0 is used
456 mFontLimitPixelChkBox->setChecked( mSettings.fontLimitPixelSize );
457 mMinPixelLimit = mSettings.fontMinPixelSize; // ignored after first settings save
458 mFontMinPixelSpinBox->setValue( mSettings.fontMinPixelSize == 0 ? 3 : mSettings.fontMinPixelSize );
459 mFontMaxPixelSpinBox->setValue( mSettings.fontMaxPixelSize );
460
461 mZIndexSpinBox->setValue( mSettings.zIndex );
462
463 mDataDefinedProperties = mSettings.dataDefinedProperties();
464
465 // callout settings, to move to custom widget when multiple styles exist
466 if ( auto *lCallout = mSettings.callout() )
467 {
468 whileBlocking( mCalloutsDrawCheckBox )->setChecked( lCallout->enabled() );
469 whileBlocking( mCalloutStyleComboBox )->setCurrentIndex( mCalloutStyleComboBox->findData( lCallout->type() ) );
470 updateCalloutWidget( lCallout );
471 }
472 else
473 {
474 std::unique_ptr< QgsCallout > defaultCallout( QgsCalloutRegistry::defaultCallout() );
475 whileBlocking( mCalloutStyleComboBox )->setCurrentIndex( mCalloutStyleComboBox->findData( defaultCallout->type() ) );
476 whileBlocking( mCalloutsDrawCheckBox )->setChecked( false );
477 updateCalloutWidget( defaultCallout.get() );
478 }
479
480 updatePlacementWidgets();
481 updateLinePlacementOptions();
482
483 // needs to come before data defined setup, so connections work
484 blockInitSignals( false );
485
486 // set up data defined toolbuttons
487 // do this after other widgets are configured, so they can be enabled/disabled
488 populateDataDefinedButtons();
489
490 updateUi(); // should come after data defined button setup
491}
492
493void QgsLabelingGui::setSettings( const QgsPalLayerSettings &settings )
494{
495 mSettings = settings;
496 setLayer( mLayer );
497}
498
499void QgsLabelingGui::blockInitSignals( bool block )
500{
501 chkLineAbove->blockSignals( block );
502 chkLineBelow->blockSignals( block );
503 mPlacementModeComboBox->blockSignals( block );
504}
505
506void QgsLabelingGui::setLabelMode( LabelMode mode )
507{
508 mMode = mode;
509 mFieldExpressionWidget->setEnabled( mMode == Labels );
510 mLabelingFrame->setEnabled( mMode == Labels );
511}
512
513QgsPalLayerSettings QgsLabelingGui::layerSettings()
514{
516
517 // restore properties which aren't exposed in GUI
518 lyr.setUnplacedVisibility( mSettings.unplacedVisibility() );
519
520 lyr.drawLabels = ( mMode == Labels ) || !mLayer;
521
522 bool isExpression;
523 lyr.fieldName = mFieldExpressionWidget->currentField( &isExpression );
524 lyr.isExpression = isExpression;
525
526 lyr.dist = 0;
527
528 QgsLabeling::PolygonPlacementFlags polygonPlacementFlags = QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon;
529 if ( mCheckAllowLabelsOutsidePolygons->isChecked() )
530 polygonPlacementFlags |= QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
531 lyr.setPolygonPlacementFlags( polygonPlacementFlags );
532
533 lyr.centroidWhole = mCentroidRadioWhole->isChecked();
534 lyr.centroidInside = mCentroidInsideCheckBox->isChecked();
535 lyr.fitInPolygonOnly = mFitInsidePolygonCheckBox->isChecked();
536 lyr.dist = mLineDistanceSpnBx->value();
537 lyr.distUnits = mLineDistanceUnitWidget->unit();
538 lyr.distMapUnitScale = mLineDistanceUnitWidget->getMapUnitScale();
539 lyr.offsetType = static_cast< Qgis::LabelOffsetType >( mOffsetTypeComboBox->currentData().toInt() );
540 if ( mQuadrantBtnGrp )
541 {
542 lyr.quadOffset = static_cast< Qgis::LabelQuadrantPosition >( mQuadrantBtnGrp->checkedId() );
543 }
544 lyr.xOffset = mPointOffsetXSpinBox->value();
545 lyr.yOffset = mPointOffsetYSpinBox->value();
546 lyr.offsetUnits = mPointOffsetUnitWidget->unit();
547 lyr.labelOffsetMapUnitScale = mPointOffsetUnitWidget->getMapUnitScale();
548 lyr.angleOffset = mPointAngleSpinBox->value();
549
550 QgsLabeling::LinePlacementFlags linePlacementFlags = QgsLabeling::LinePlacementFlags();
551 if ( chkLineAbove->isChecked() )
552 linePlacementFlags |= QgsLabeling::LinePlacementFlag::AboveLine;
553 if ( chkLineBelow->isChecked() )
554 linePlacementFlags |= QgsLabeling::LinePlacementFlag::BelowLine;
555 if ( chkLineOn->isChecked() )
556 linePlacementFlags |= QgsLabeling::LinePlacementFlag::OnLine;
557 if ( ! chkLineOrientationDependent->isChecked() )
558 linePlacementFlags |= QgsLabeling::LinePlacementFlag::MapOrientation;
559 lyr.lineSettings().setPlacementFlags( linePlacementFlags );
560
561 lyr.placement = static_cast< Qgis::LabelPlacement >( mPlacementModeComboBox->currentData().toInt() );
562
563 lyr.repeatDistance = mRepeatDistanceSpinBox->value();
564 lyr.repeatDistanceUnit = mRepeatDistanceUnitWidget->unit();
565 lyr.repeatDistanceMapUnitScale = mRepeatDistanceUnitWidget->getMapUnitScale();
566
567 lyr.lineSettings().setOverrunDistance( mOverrunDistanceSpinBox->value() );
568 lyr.lineSettings().setOverrunDistanceUnit( mOverrunDistanceUnitWidget->unit() );
569 lyr.lineSettings().setOverrunDistanceMapUnitScale( mOverrunDistanceUnitWidget->getMapUnitScale() );
570
571 lyr.priority = mPrioritySlider->value();
572
573 mObstacleSettings.setIsObstacle( mChkNoObstacle->isChecked() || mMode == ObstaclesOnly );
574 lyr.setObstacleSettings( mObstacleSettings );
575
576 lyr.lineSettings().setLineAnchorPercent( mLineSettings.lineAnchorPercent() );
577 lyr.lineSettings().setAnchorType( mLineSettings.anchorType() );
578 lyr.lineSettings().setAnchorClipping( mLineSettings.anchorClipping() );
579 lyr.lineSettings().setAnchorTextPoint( mLineSettings.anchorTextPoint() );
580
581 lyr.labelPerPart = chkLabelPerFeaturePart->isChecked();
582 lyr.placementSettings().setOverlapHandling( static_cast< Qgis::LabelOverlapHandling>( mComboOverlapHandling->currentData().toInt() ) );
583 lyr.placementSettings().setAllowDegradedPlacement( mCheckAllowDegradedPlacement->isChecked() );
584
585 lyr.lineSettings().setMergeLines( chkMergeLines->isChecked() );
586
587 lyr.scaleVisibility = mScaleBasedVisibilityChkBx->isChecked();
588 lyr.minimumScale = mMinScaleWidget->scale();
589 lyr.maximumScale = mMaxScaleWidget->scale();
590 lyr.useSubstitutions = mCheckBoxSubstituteText->isChecked();
591 lyr.substitutions = mSubstitutions;
592
593 lyr.setFormat( format( false ) );
594
595 // format numbers
596 lyr.formatNumbers = mFormatNumChkBx->isChecked();
597 lyr.decimals = mFormatNumDecimalsSpnBx->value();
598 lyr.plusSign = mFormatNumPlusSignChkBx->isChecked();
599
600 // direction symbol(s)
601 lyr.lineSettings().setAddDirectionSymbol( mDirectSymbChkBx->isChecked() );
602 lyr.lineSettings().setLeftDirectionSymbol( mDirectSymbLeftLineEdit->text() );
603 lyr.lineSettings().setRightDirectionSymbol( mDirectSymbRightLineEdit->text() );
604 lyr.lineSettings().setReverseDirectionSymbol( mDirectSymbRevChkBx->isChecked() );
605 if ( mDirectSymbBtnGrp )
606 {
607 lyr.lineSettings().setDirectionSymbolPlacement( static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( mDirectSymbBtnGrp->checkedId() ) );
608 }
609 if ( mUpsidedownBtnGrp )
610 {
611 lyr.upsidedownLabels = static_cast< Qgis::UpsideDownLabelHandling >( mUpsidedownBtnGrp->checkedId() );
612 }
613
614 lyr.maxCurvedCharAngleIn = mMaxCharAngleInDSpinBox->value();
615 // lyr.maxCurvedCharAngleOut must be negative, but it is shown as positive spinbox in GUI
616 lyr.maxCurvedCharAngleOut = -mMaxCharAngleOutDSpinBox->value();
617
618
619 lyr.thinningSettings().setMinimumFeatureSize( mMinSizeSpinBox->value() );
620 lyr.thinningSettings().setLimitNumberLabelsEnabled( mLimitLabelChkBox->isChecked() );
621 lyr.thinningSettings().setMaximumNumberLabels( mLimitLabelSpinBox->value() );
622 lyr.fontLimitPixelSize = mFontLimitPixelChkBox->isChecked();
623 lyr.fontMinPixelSize = mFontMinPixelSpinBox->value();
624 lyr.fontMaxPixelSize = mFontMaxPixelSpinBox->value();
625 lyr.wrapChar = wrapCharacterEdit->text();
626 lyr.autoWrapLength = mAutoWrapLengthSpinBox->value();
627 lyr.useMaxLineLengthForAutoWrap = mAutoWrapTypeComboBox->currentIndex() == 0;
628 lyr.multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( mFontMultiLineAlignComboBox->currentData().toInt() );
629 lyr.preserveRotation = chkPreserveRotation->isChecked();
630 lyr.setRotationUnit( static_cast< QgsUnitTypes::AngleUnit >( mCoordRotationUnitComboBox->currentData().toInt() ) );
631 lyr.geometryGenerator = mGeometryGenerator->text();
632 lyr.geometryGeneratorType = mGeometryGeneratorType->currentData().value<QgsWkbTypes::GeometryType>();
633 lyr.geometryGeneratorEnabled = mGeometryGeneratorGroupBox->isChecked();
634
635 lyr.layerType = mLayer ? mLayer->geometryType() : mGeomType;
636
637 lyr.zIndex = mZIndexSpinBox->value();
638
639 lyr.setDataDefinedProperties( mDataDefinedProperties );
640
641 // callout settings
642 const QString calloutType = mCalloutStyleComboBox->currentData().toString();
643 std::unique_ptr< QgsCallout > callout;
644 if ( QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
645 {
646 callout.reset( pew->callout()->clone() );
647 }
648 if ( !callout )
649 callout.reset( QgsApplication::calloutRegistry()->createCallout( calloutType ) );
650
651 callout->setEnabled( mCalloutsDrawCheckBox->isChecked() );
652 lyr.setCallout( callout.release() );
653
654 return lyr;
655}
656
657void QgsLabelingGui::syncDefinedCheckboxFrame( QgsPropertyOverrideButton *ddBtn, QCheckBox *chkBx, QFrame *f )
658{
659 f->setEnabled( chkBx->isChecked() || ddBtn->isActive() );
660}
661
662bool QgsLabelingGui::eventFilter( QObject *object, QEvent *event )
663{
664 if ( object == mLblNoObstacle1 )
665 {
666 if ( event->type() == QEvent::MouseButtonPress && qgis::down_cast< QMouseEvent * >( event )->button() == Qt::LeftButton )
667 {
668 // clicking the obstacle label toggles the checkbox, just like a "normal" checkbox label...
669 mChkNoObstacle->setChecked( !mChkNoObstacle->isChecked() );
670 return true;
671 }
672 return false;
673 }
674 return QgsTextFormatWidget::eventFilter( object, event );
675}
676
677void QgsLabelingGui::updateUi()
678{
679 // enable/disable inline groupbox-like setups (that need to honor data defined setting)
680
681 syncDefinedCheckboxFrame( mBufferDrawDDBtn, mBufferDrawChkBx, mBufferFrame );
682 syncDefinedCheckboxFrame( mEnableMaskDDBtn, mEnableMaskChkBx, mMaskFrame );
683 syncDefinedCheckboxFrame( mShapeDrawDDBtn, mShapeDrawChkBx, mShapeFrame );
684 syncDefinedCheckboxFrame( mShadowDrawDDBtn, mShadowDrawChkBx, mShadowFrame );
685 syncDefinedCheckboxFrame( mCalloutDrawDDBtn, mCalloutsDrawCheckBox, mCalloutFrame );
686
687 syncDefinedCheckboxFrame( mDirectSymbDDBtn, mDirectSymbChkBx, mDirectSymbFrame );
688 syncDefinedCheckboxFrame( mFormatNumDDBtn, mFormatNumChkBx, mFormatNumFrame );
689 syncDefinedCheckboxFrame( mScaleBasedVisibilityDDBtn, mScaleBasedVisibilityChkBx, mScaleBasedVisibilityFrame );
690 syncDefinedCheckboxFrame( mFontLimitPixelDDBtn, mFontLimitPixelChkBox, mFontLimitPixelFrame );
691
692 chkMergeLines->setEnabled( !mDirectSymbChkBx->isChecked() );
693 if ( mDirectSymbChkBx->isChecked() )
694 {
695 chkMergeLines->setToolTip( tr( "This option is not compatible with line direction symbols." ) );
696 }
697 else
698 {
699 chkMergeLines->setToolTip( QString() );
700 }
701}
702
703void QgsLabelingGui::setFormatFromStyle( const QString &name, QgsStyle::StyleEntity type, const QString &stylePath )
704{
705 QgsStyle *style = QgsProject::instance()->styleSettings()->styleAtPath( stylePath );
706
707 if ( !style )
708 style = QgsStyle::defaultStyle();
709
710 switch ( type )
711 {
719 {
720 QgsTextFormatWidget::setFormatFromStyle( name, type, stylePath );
721 return;
722 }
723
725 {
726 if ( !style->labelSettingsNames().contains( name ) )
727 return;
728
729 QgsPalLayerSettings settings = style->labelSettings( name );
730 if ( settings.fieldName.isEmpty() )
731 {
732 // if saved settings doesn't have a field name stored, retain the current one
733 bool isExpression;
734 settings.fieldName = mFieldExpressionWidget->currentField( &isExpression );
735 settings.isExpression = isExpression;
736 }
737 setSettings( settings );
738 break;
739 }
740 }
741}
742
743void QgsLabelingGui::setContext( const QgsSymbolWidgetContext &context )
744{
745 if ( QgsCalloutWidget *cw = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
746 {
747 cw->setContext( context );
748 }
750}
751
752void QgsLabelingGui::saveFormat()
753{
755 saveDlg.setDefaultTags( mTextFormatsListWidget->currentTagFilter() );
756 if ( !saveDlg.exec() )
757 return;
758
759 if ( saveDlg.name().isEmpty() )
760 return;
761
762 QgsStyle *style = saveDlg.destinationStyle();
763 if ( !style )
764 return;
765
766 switch ( saveDlg.selectedType() )
767 {
769 {
770 // check if there is no format with same name
771 if ( style->textFormatNames().contains( saveDlg.name() ) )
772 {
773 const int res = QMessageBox::warning( this, tr( "Save Text Format" ),
774 tr( "Format with name '%1' already exists. Overwrite?" )
775 .arg( saveDlg.name() ),
776 QMessageBox::Yes | QMessageBox::No );
777 if ( res != QMessageBox::Yes )
778 {
779 return;
780 }
781 style->removeTextFormat( saveDlg.name() );
782 }
783 const QStringList symbolTags = saveDlg.tags().split( ',' );
784
785 const QgsTextFormat newFormat = format();
786 style->addTextFormat( saveDlg.name(), newFormat );
787 style->saveTextFormat( saveDlg.name(), newFormat, saveDlg.isFavorite(), symbolTags );
788 break;
789 }
790
792 {
793 // check if there is no settings with same name
794 if ( style->labelSettingsNames().contains( saveDlg.name() ) )
795 {
796 const int res = QMessageBox::warning( this, tr( "Save Label Settings" ),
797 tr( "Label settings with the name '%1' already exist. Overwrite?" )
798 .arg( saveDlg.name() ),
799 QMessageBox::Yes | QMessageBox::No );
800 if ( res != QMessageBox::Yes )
801 {
802 return;
803 }
804 style->removeLabelSettings( saveDlg.name() );
805 }
806 const QStringList symbolTags = saveDlg.tags().split( ',' );
807
808 const QgsPalLayerSettings newSettings = layerSettings();
809 style->addLabelSettings( saveDlg.name(), newSettings );
810 style->saveLabelSettings( saveDlg.name(), newSettings, saveDlg.isFavorite(), symbolTags );
811 break;
812 }
813
820 break;
821 }
822}
823
824void QgsLabelingGui::updateGeometryTypeBasedWidgets()
825{
826 QgsWkbTypes::GeometryType geometryType = mGeomType;
827
828 if ( mGeometryGeneratorGroupBox->isChecked() )
829 geometryType = mGeometryGeneratorType->currentData().value<QgsWkbTypes::GeometryType>();
830 else if ( mLayer )
831 geometryType = mLayer->geometryType();
832
833 // show/hide options based upon geometry type
834 chkMergeLines->setVisible( geometryType == QgsWkbTypes::LineGeometry );
835 mDirectSymbolsFrame->setVisible( geometryType == QgsWkbTypes::LineGeometry );
836 mMinSizeFrame->setVisible( geometryType != QgsWkbTypes::PointGeometry );
837 mPolygonFeatureOptionsFrame->setVisible( geometryType == QgsWkbTypes::PolygonGeometry );
838
839
840 const Qgis::LabelPlacement prevPlacement = static_cast< Qgis::LabelPlacement >( mPlacementModeComboBox->currentData().toInt() );
841 mPlacementModeComboBox->clear();
842
843 switch ( geometryType )
844 {
846 mPlacementModeComboBox->addItem( tr( "Cartographic" ), static_cast< int >( Qgis::LabelPlacement::OrderedPositionsAroundPoint ) );
847 mPlacementModeComboBox->addItem( tr( "Around Point" ), static_cast< int >( Qgis::LabelPlacement::AroundPoint ) );
848 mPlacementModeComboBox->addItem( tr( "Offset from Point" ), static_cast< int >( Qgis::LabelPlacement::OverPoint ) );
849 break;
850
852 mPlacementModeComboBox->addItem( tr( "Parallel" ), static_cast< int >( Qgis::LabelPlacement::Line ) );
853 mPlacementModeComboBox->addItem( tr( "Curved" ), static_cast< int >( Qgis::LabelPlacement::Curved ) );
854 mPlacementModeComboBox->addItem( tr( "Horizontal" ), static_cast< int >( Qgis::LabelPlacement::Horizontal ) );
855 break;
856
858 mPlacementModeComboBox->addItem( tr( "Offset from Centroid" ), static_cast< int >( Qgis::LabelPlacement::OverPoint ) );
859 mPlacementModeComboBox->addItem( tr( "Around Centroid" ), static_cast< int >( Qgis::LabelPlacement::AroundPoint ) );
860 mPlacementModeComboBox->addItem( tr( "Horizontal" ), static_cast< int >( Qgis::LabelPlacement::Horizontal ) );
861 mPlacementModeComboBox->addItem( tr( "Free (Angled)" ), static_cast< int >( Qgis::LabelPlacement::Free ) );
862 mPlacementModeComboBox->addItem( tr( "Using Perimeter" ), static_cast< int >( Qgis::LabelPlacement::Line ) );
863 mPlacementModeComboBox->addItem( tr( "Using Perimeter (Curved)" ), static_cast< int >( Qgis::LabelPlacement::PerimeterCurved ) );
864 mPlacementModeComboBox->addItem( tr( "Outside Polygons" ), static_cast< int >( Qgis::LabelPlacement::OutsidePolygons ) );
865 break;
866
868 break;
870 qFatal( "unknown geometry type unexpected" );
871 }
872
873 if ( mPlacementModeComboBox->findData( static_cast< int >( prevPlacement ) ) != -1 )
874 {
875 mPlacementModeComboBox->setCurrentIndex( mPlacementModeComboBox->findData( static_cast< int >( prevPlacement ) ) );
876 }
877
878 if ( geometryType == QgsWkbTypes::PointGeometry || geometryType == QgsWkbTypes::PolygonGeometry )
879 {
880 // follow placement alignment is only valid for point or polygon layers
881 if ( mFontMultiLineAlignComboBox->findData( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) ) == -1 )
882 mFontMultiLineAlignComboBox->addItem( tr( "Follow Label Placement" ), static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) );
883 }
884 else
885 {
886 const int idx = mFontMultiLineAlignComboBox->findData( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) );
887 if ( idx >= 0 )
888 mFontMultiLineAlignComboBox->removeItem( idx );
889 }
890
891 updatePlacementWidgets();
892 updateLinePlacementOptions();
893}
894
895void QgsLabelingGui::showGeometryGeneratorExpressionBuilder()
896{
897 QgsExpressionBuilderDialog expressionBuilder( mLayer );
898
899 expressionBuilder.setExpressionText( mGeometryGenerator->text() );
900 expressionBuilder.setExpressionContext( createExpressionContext() );
901
902 if ( expressionBuilder.exec() )
903 {
904 mGeometryGenerator->setText( expressionBuilder.expressionText() );
905 }
906}
907
908void QgsLabelingGui::validateGeometryGeneratorExpression()
909{
910 bool valid = true;
911
912 if ( mGeometryGeneratorGroupBox->isChecked() )
913 {
914 if ( !mPreviewFeature.isValid() && mLayer )
915 mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( mPreviewFeature );
916
917 QgsExpression expression( mGeometryGenerator->text() );
918 QgsExpressionContext context = createExpressionContext();
919 context.setFeature( mPreviewFeature );
920
921 expression.prepare( &context );
922
923 if ( expression.hasParserError() )
924 {
925 mGeometryGeneratorWarningLabel->setText( expression.parserErrorString() );
926 valid = false;
927 }
928 else
929 {
930 const QVariant result = expression.evaluate( &context );
931 const QgsGeometry geometry = result.value<QgsGeometry>();
932 const QgsWkbTypes::GeometryType configuredGeometryType = mGeometryGeneratorType->currentData().value<QgsWkbTypes::GeometryType>();
933 if ( geometry.isNull() )
934 {
935 mGeometryGeneratorWarningLabel->setText( tr( "Result of the expression is not a geometry" ) );
936 valid = false;
937 }
938 else if ( geometry.type() != configuredGeometryType )
939 {
940 mGeometryGeneratorWarningLabel->setText( QStringLiteral( "<p>%1</p><p><a href=\"#determineGeometryGeneratorType\">%2</a></p>" ).arg(
941 tr( "Result of the expression does not match configured geometry type." ),
942 tr( "Change to %1" ).arg( QgsWkbTypes::geometryDisplayString( geometry.type() ) ) ) );
943 valid = false;
944 }
945 }
946 }
947
948 // The collapsible groupbox internally changes the visibility of this
949 // Work around by setting the visibility deferred in the next event loop cycle.
950 QTimer *timer = new QTimer();
951 connect( timer, &QTimer::timeout, this, [this, valid]()
952 {
953 mGeometryGeneratorWarningLabel->setVisible( !valid );
954 } );
955 connect( timer, &QTimer::timeout, timer, &QTimer::deleteLater );
956 timer->start( 0 );
957}
958
959void QgsLabelingGui::determineGeometryGeneratorType()
960{
961 if ( !mPreviewFeature.isValid() && mLayer )
962 mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( mPreviewFeature );
963
964 QgsExpression expression( mGeometryGenerator->text() );
965 QgsExpressionContext context = createExpressionContext();
966 context.setFeature( mPreviewFeature );
967
968 expression.prepare( &context );
969 const QgsGeometry geometry = expression.evaluate( &context ).value<QgsGeometry>();
970
971 mGeometryGeneratorType->setCurrentIndex( mGeometryGeneratorType->findData( geometry.type() ) );
972}
973
974void QgsLabelingGui::calloutTypeChanged()
975{
976 const QString newCalloutType = mCalloutStyleComboBox->currentData().toString();
977 QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() );
978 if ( pew )
979 {
980 if ( pew->callout() && pew->callout()->type() == newCalloutType )
981 return;
982 }
983
984 // get creation function for new callout from registry
986 QgsCalloutAbstractMetadata *am = registry->calloutMetadata( newCalloutType );
987 if ( !am ) // check whether the metadata is assigned
988 return;
989
990 // change callout to a new one (with different type)
991 // base new callout on existing callout's properties
992 const std::unique_ptr< QgsCallout > newCallout( am->createCallout( pew && pew->callout() ? pew->callout()->properties( QgsReadWriteContext() ) : QVariantMap(), QgsReadWriteContext() ) );
993 if ( !newCallout )
994 return;
995
996 updateCalloutWidget( newCallout.get() );
997 updatePreview();
998}
999
1000
1001//
1002// QgsLabelSettingsDialog
1003//
1004
1005QgsLabelSettingsDialog::QgsLabelSettingsDialog( const QgsPalLayerSettings &settings, QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, QWidget *parent,
1006 QgsWkbTypes::GeometryType geomType )
1007 : QDialog( parent )
1008{
1009 QVBoxLayout *vLayout = new QVBoxLayout();
1010 mWidget = new QgsLabelingGui( layer, mapCanvas, settings, nullptr, geomType );
1011 vLayout->addWidget( mWidget );
1012 mButtonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok, Qt::Horizontal );
1013 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
1014 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
1015 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsLabelSettingsDialog::showHelp );
1016 vLayout->addWidget( mButtonBox );
1017 setLayout( vLayout );
1018 setWindowTitle( tr( "Label Settings" ) );
1019}
1020
1021QDialogButtonBox *QgsLabelSettingsDialog::buttonBox() const
1022{
1023 return mButtonBox;
1024}
1025
1026void QgsLabelSettingsDialog::showHelp()
1027{
1028 QgsHelp::openHelp( QStringLiteral( "style_library/label_settings.html" ) );
1029}
1030
1031
1032
LabelOffsetType
Behavior modifier for label offset and distance, only applies in some label placement modes.
Definition: qgis.h:608
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition: qgis.h:561
@ 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....
LabelQuadrantPosition
Label quadrant positions.
Definition: qgis.h:622
LabelMultiLineAlignment
Text alignment for multi-line labels.
Definition: qgis.h:658
LabelOverlapHandling
Label overlap handling.
Definition: qgis.h:546
UpsideDownLabelHandling
Handling techniques for upside down labels.
Definition: qgis.h:643
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:187
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:79
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:125
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
QgsWkbTypes::GeometryType type
Definition: qgsgeometry.h:128
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
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(QgsLabeling::LinePlacementFlags 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 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 setOverrunDistanceUnit(const QgsUnitTypes::RenderUnit &unit)
Sets the unit for label overrun distance.
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.
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 setGeometryType(QgsWkbTypes::GeometryType type) override
Sets the geometry type of the features to customize the widget accordingly.
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 ...
void setDataDefinedProperties(const QgsPropertyCollection &dataDefinedProperties)
Sets the current data defined properties to show in the widget.
virtual void setGeometryType(QgsWkbTypes::GeometryType type)
Sets the geometry type of the features to customize the widget accordingly.
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
QgsMapLayerType 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...
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.
QgsWkbTypes::GeometryType geometryGeneratorType
The type of the result geometry of the geometry generator.
QgsUnitTypes::RenderUnit offsetUnits
Units for offsets of label.
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.
bool labelPerPart
true if every part of a multi-part feature should be labeled.
QgsUnitTypes::RenderUnit distUnits
Units the distance from feature to the label.
QgsUnitTypes::RenderUnit repeatDistanceUnit
Units for repeating labels for a single feature.
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.
void setPolygonPlacementFlags(QgsLabeling::PolygonPlacementFlags flags)
Sets the polygon placement flags, which dictate how polygon labels can be placed.
void setRotationUnit(QgsUnitTypes::AngleUnit angleUnit)
Set unit for rotation of labels.
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.
QgsMapUnitScale repeatDistanceMapUnitScale
Map unit scale for repeating labels for a single feature.
bool centroidWhole
true if feature centroid should be calculated from the whole feature, or false if only the visible pa...
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.
QgsWkbTypes::GeometryType layerType
Geometry type of layers associated with these settings.
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:478
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:110
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:1053
bool saveLabelSettings(const QString &name, const QgsPalLayerSettings &settings, bool favorite, const QStringList &tags)
Adds label settings to the database.
Definition: qgsstyle.cpp:1017
QStringList textFormatNames() const
Returns a list of names of text formats in the style.
Definition: qgsstyle.cpp:2130
bool removeTextFormat(const QString &name)
Removes a text format from the style.
Definition: qgsstyle.cpp:977
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:2194
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:1217
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:337
QgsPalLayerSettings labelSettings(const QString &name) const
Returns the label settings with the specified name.
Definition: qgsstyle.cpp:2140
@ 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:941
bool addLabelSettings(const QString &name, const QgsPalLayerSettings &settings, bool update=false)
Adds label settings with the specified name to the style.
Definition: qgsstyle.cpp:358
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:41
AngleUnit
Units of angles.
Definition: qgsunittypes.h:132
@ AngleGon
Gon/gradian.
Definition: qgsunittypes.h:135
@ AngleRadians
Square kilometers.
Definition: qgsunittypes.h:134
@ AngleDegrees
Degrees.
Definition: qgsunittypes.h:133
@ AngleMilNATO
Angular mil (NATO definition, 6400 mil = 2PI radians)
Definition: qgsunittypes.h:140
@ AngleMilliradiansSI
Angular milliradians (SI definition, 1/1000 of radian)
Definition: qgsunittypes.h:139
@ AngleTurn
Turn/revolutions.
Definition: qgsunittypes.h:138
@ AngleMinutesOfArc
Minutes of arc.
Definition: qgsunittypes.h:136
@ AngleSecondsOfArc
Seconds of arc.
Definition: qgsunittypes.h:137
static Q_INVOKABLE QString toString(QgsUnitTypes::DistanceUnit unit)
Returns a translated string representing a distance unit.
Represents a vector layer which manages a vector based data sets.
static QString geometryDisplayString(GeometryType type) SIP_HOLDGIL
Returns a display string for a geometry type.
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:141
@ VectorLayer
Vector layer.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2330
QgsCalloutWidget *(* QgsCalloutWidgetFunc)(QgsVectorLayer *)
#define QgsDebugMsg(str)
Definition: qgslogger.h:38