37 #include <QButtonGroup>
38 #include <QMessageBox>
68 if ( !abstractMetadata )
70 QgsDebugMsg( QStringLiteral(
"Failed to find callout entry in registry: %1" ).arg( name ) );
76 QgsDebugMsg( QStringLiteral(
"Failed to cast callout's metadata: " ) .arg( name ) );
83 void QgsLabelingGui::initCalloutWidgets()
85 _initCalloutWidgetFunction( QStringLiteral(
"simple" ), QgsSimpleLineCalloutWidget::create );
86 _initCalloutWidgetFunction( QStringLiteral(
"manhattan" ), QgsManhattanLineCalloutWidget::create );
89 void QgsLabelingGui::updateCalloutWidget(
QgsCallout *callout )
93 mCalloutStackedWidget->setCurrentWidget( pageDummy );
97 if ( mCalloutStackedWidget->currentWidget() != pageDummy )
100 if (
QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
111 if ( mGeometryGeneratorGroupBox->isChecked() )
114 geometryType = mLayer->geometryType();
115 w->setGeometryType( geometryType );
116 w->setCallout( callout );
118 w->setContext( context() );
119 mCalloutStackedWidget->addWidget( w );
120 mCalloutStackedWidget->setCurrentWidget( w );
127 mCalloutStackedWidget->setCurrentWidget( pageDummy );
130 void QgsLabelingGui::showObstacleSettings()
144 auto applySettings = [ = ]
146 mObstacleSettings = widget->
settings();
149 emit widgetChanged();
165 dialog.buttonBox()->addButton( QDialogButtonBox::Help );
166 connect( dialog.buttonBox(), &QDialogButtonBox::helpRequested,
this, [ = ]
168 QgsHelp::openHelp( QStringLiteral(
"style_library/label_settings.html#obstacles" ) );
180 void QgsLabelingGui::showLineAnchorSettings()
194 auto applySettings = [ = ]
198 mLineSettings.setAnchorType( widgetSettings.
anchorType() );
201 emit widgetChanged();
227 , mSettings( layerSettings )
229 , mCanvas( mapCanvas )
231 mGeomType = geomType;
232 static std::once_flag initialized;
233 std::call_once( initialized, [ = ]( )
235 initCalloutWidgets();
244 connect( mBufferDrawChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
245 connect( mEnableMaskChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
246 connect( mShapeDrawChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
247 connect( mCalloutsDrawCheckBox, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
248 connect( mShadowDrawChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
249 connect( mDirectSymbChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
250 connect( mFormatNumChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
251 connect( mScaleBasedVisibilityChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
252 connect( mFontLimitPixelChkBox, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
253 connect( mGeometryGeneratorGroupBox, &QGroupBox::toggled,
this, &QgsLabelingGui::updateGeometryTypeBasedWidgets );
254 connect( mGeometryGeneratorType, qgis::overload<int>::of( &QComboBox::currentIndexChanged ),
this, &QgsLabelingGui::updateGeometryTypeBasedWidgets );
255 connect( mGeometryGeneratorExpressionButton, &QToolButton::clicked,
this, &QgsLabelingGui::showGeometryGeneratorExpressionBuilder );
256 connect( mGeometryGeneratorGroupBox, &QGroupBox::toggled,
this, &QgsLabelingGui::validateGeometryGeneratorExpression );
257 connect( mGeometryGenerator, &QgsCodeEditorExpression::textChanged,
this, &QgsLabelingGui::validateGeometryGeneratorExpression );
258 connect( mGeometryGeneratorType, qgis::overload<int>::of( &QComboBox::currentIndexChanged ),
this, &QgsLabelingGui::validateGeometryGeneratorExpression );
259 connect( mObstacleSettingsButton, &QAbstractButton::clicked,
this, &QgsLabelingGui::showObstacleSettings );
260 connect( mLineAnchorSettingsButton, &QAbstractButton::clicked,
this, &QgsLabelingGui::showLineAnchorSettings );
262 mFieldExpressionWidget->registerExpressionContextGenerator(
this );
264 mMinScaleWidget->setMapCanvas( mCanvas );
265 mMinScaleWidget->setShowCurrentScaleButton(
true );
266 mMaxScaleWidget->setMapCanvas( mCanvas );
267 mMaxScaleWidget->setShowCurrentScaleButton(
true );
270 for (
const QString &type : calloutTypes )
276 mGeometryGeneratorWarningLabel->setStyleSheet( QStringLiteral(
"color: #FFC107;" ) );
277 mGeometryGeneratorWarningLabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
278 connect( mGeometryGeneratorWarningLabel, &QLabel::linkActivated,
this, [
this](
const QString & link )
280 if ( link == QLatin1String(
"#determineGeometryGeneratorType" ) )
281 determineGeometryGeneratorType();
284 connect( mCalloutStyleComboBox,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsLabelingGui::calloutTypeChanged );
286 mLblNoObstacle1->installEventFilter(
this );
291 void QgsLabelingGui::setLayer(
QgsMapLayer *mapLayer )
306 mTextFormatsListWidget->setLayerType( mLayer ? mLayer->geometryType() : mGeomType );
307 mBackgroundSymbolButton->setLayer( mLayer );
310 updateGeometryTypeBasedWidgets();
312 mFieldExpressionWidget->setLayer( mLayer );
317 mFieldExpressionWidget->setGeomCalculator( da );
319 mFieldExpressionWidget->setEnabled( mMode == Labels || !mLayer );
320 mLabelingFrame->setEnabled( mMode == Labels || !mLayer );
322 blockInitSignals(
true );
324 mGeometryGenerator->setText( mSettings.geometryGenerator );
325 mGeometryGeneratorGroupBox->setChecked( mSettings.geometryGeneratorEnabled );
326 if ( !mSettings.geometryGeneratorEnabled )
327 mGeometryGeneratorGroupBox->setCollapsed(
true );
328 mGeometryGeneratorType->setCurrentIndex( mGeometryGeneratorType->findData( mSettings.geometryGeneratorType ) );
330 updateWidgetForFormat( mSettings.format() );
332 mFieldExpressionWidget->setRow( -1 );
333 mFieldExpressionWidget->setField( mSettings.fieldName );
334 mCheckBoxSubstituteText->setChecked( mSettings.useSubstitutions );
335 mSubstitutions = mSettings.substitutions;
338 mCentroidRadioWhole->setChecked( mSettings.centroidWhole );
339 mCentroidInsideCheckBox->setChecked( mSettings.centroidInside );
340 mFitInsidePolygonCheckBox->setChecked( mSettings.fitInPolygonOnly );
341 mLineDistanceSpnBx->setValue( mSettings.dist );
342 mLineDistanceUnitWidget->setUnit( mSettings.distUnits );
343 mLineDistanceUnitWidget->setMapUnitScale( mSettings.distMapUnitScale );
344 mOffsetTypeComboBox->setCurrentIndex( mOffsetTypeComboBox->findData( mSettings.offsetType ) );
345 mQuadrantBtnGrp->button(
static_cast<int>( mSettings.quadOffset ) )->setChecked(
true );
346 mPointOffsetXSpinBox->setValue( mSettings.xOffset );
347 mPointOffsetYSpinBox->setValue( mSettings.yOffset );
348 mPointOffsetUnitWidget->setUnit( mSettings.offsetUnits );
349 mPointOffsetUnitWidget->setMapUnitScale( mSettings.labelOffsetMapUnitScale );
350 mPointAngleSpinBox->setValue( mSettings.angleOffset );
356 mCheckAllowLabelsOutsidePolygons->setChecked( mSettings.polygonPlacementFlags() & QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
358 const int placementIndex = mPlacementModeComboBox->findData( mSettings.placement );
359 if ( placementIndex >= 0 )
361 mPlacementModeComboBox->setCurrentIndex( placementIndex );
366 mPlacementModeComboBox->setCurrentIndex( 0 );
370 mRepeatDistanceSpinBox->setValue( mSettings.repeatDistance );
371 mRepeatDistanceUnitWidget->setUnit( mSettings.repeatDistanceUnit );
372 mRepeatDistanceUnitWidget->setMapUnitScale( mSettings.repeatDistanceMapUnitScale );
374 mOverrunDistanceSpinBox->setValue( mSettings.lineSettings().overrunDistance() );
375 mOverrunDistanceUnitWidget->setUnit( mSettings.lineSettings().overrunDistanceUnit() );
376 mOverrunDistanceUnitWidget->setMapUnitScale( mSettings.lineSettings().overrunDistanceMapUnitScale() );
378 mPrioritySlider->setValue( mSettings.priority );
379 mChkNoObstacle->setChecked( mSettings.obstacleSettings().isObstacle() );
381 mObstacleSettings = mSettings.obstacleSettings();
382 mLineSettings = mSettings.lineSettings();
384 chkLabelPerFeaturePart->setChecked( mSettings.labelPerPart );
385 mPalShowAllLabelsForLayerChkBx->setChecked( mSettings.displayAll );
386 chkMergeLines->setChecked( mSettings.lineSettings().mergeLines() );
387 mMinSizeSpinBox->setValue( mSettings.thinningSettings().minimumFeatureSize() );
388 mLimitLabelChkBox->setChecked( mSettings.thinningSettings().limitNumberOfLabelsEnabled() );
389 mLimitLabelSpinBox->setValue( mSettings.thinningSettings().maximumNumberLabels() );
392 mDirectSymbChkBx->setChecked( mSettings.lineSettings().addDirectionSymbol() );
393 mDirectSymbLeftLineEdit->setText( mSettings.lineSettings().leftDirectionSymbol() );
394 mDirectSymbRightLineEdit->setText( mSettings.lineSettings().rightDirectionSymbol() );
395 mDirectSymbRevChkBx->setChecked( mSettings.lineSettings().reverseDirectionSymbol() );
397 mDirectSymbBtnGrp->button(
static_cast<int>( mSettings.lineSettings().directionSymbolPlacement() ) )->setChecked(
true );
398 mUpsidedownBtnGrp->button(
static_cast<int>( mSettings.upsidedownLabels ) )->setChecked(
true );
401 mMaxCharAngleInDSpinBox->setValue( mSettings.maxCurvedCharAngleIn );
403 mMaxCharAngleOutDSpinBox->setValue( std::fabs( mSettings.maxCurvedCharAngleOut ) );
405 wrapCharacterEdit->setText( mSettings.wrapChar );
406 mAutoWrapLengthSpinBox->setValue( mSettings.autoWrapLength );
407 mAutoWrapTypeComboBox->setCurrentIndex( mSettings.useMaxLineLengthForAutoWrap ? 0 : 1 );
409 if ( mFontMultiLineAlignComboBox->findData( mSettings.multilineAlign ) != -1 )
411 mFontMultiLineAlignComboBox->setCurrentIndex( mFontMultiLineAlignComboBox->findData( mSettings.multilineAlign ) );
417 mFontMultiLineAlignComboBox->setCurrentIndex( 0 );
420 chkPreserveRotation->setChecked( mSettings.preserveRotation );
422 mScaleBasedVisibilityChkBx->setChecked( mSettings.scaleVisibility );
423 mMinScaleWidget->setScale( mSettings.minimumScale );
424 mMaxScaleWidget->setScale( mSettings.maximumScale );
426 mFormatNumChkBx->setChecked( mSettings.formatNumbers );
427 mFormatNumDecimalsSpnBx->setValue( mSettings.decimals );
428 mFormatNumPlusSignChkBx->setChecked( mSettings.plusSign );
432 mFontLimitPixelChkBox->setChecked( mSettings.fontLimitPixelSize );
433 mMinPixelLimit = mSettings.fontMinPixelSize;
434 mFontMinPixelSpinBox->setValue( mSettings.fontMinPixelSize == 0 ? 3 : mSettings.fontMinPixelSize );
435 mFontMaxPixelSpinBox->setValue( mSettings.fontMaxPixelSize );
437 mZIndexSpinBox->setValue( mSettings.zIndex );
439 mDataDefinedProperties = mSettings.dataDefinedProperties();
442 if (
auto *lCallout = mSettings.callout() )
444 whileBlocking( mCalloutsDrawCheckBox )->setChecked( lCallout->enabled() );
445 whileBlocking( mCalloutStyleComboBox )->setCurrentIndex( mCalloutStyleComboBox->findData( lCallout->type() ) );
446 updateCalloutWidget( lCallout );
451 whileBlocking( mCalloutStyleComboBox )->setCurrentIndex( mCalloutStyleComboBox->findData( defaultCallout->type() ) );
453 updateCalloutWidget( defaultCallout.get() );
456 updatePlacementWidgets();
457 updateLinePlacementOptions();
460 blockInitSignals(
false );
464 populateDataDefinedButtons();
466 enableDataDefinedAlignment( mCoordXDDBtn->isActive() && mCoordYDDBtn->isActive() );
472 mSettings = settings;
476 void QgsLabelingGui::blockInitSignals(
bool block )
478 chkLineAbove->blockSignals( block );
479 chkLineBelow->blockSignals( block );
480 mPlacementModeComboBox->blockSignals( block );
483 void QgsLabelingGui::setLabelMode( LabelMode mode )
486 mFieldExpressionWidget->setEnabled( mMode == Labels );
487 mLabelingFrame->setEnabled( mMode == Labels );
494 lyr.
drawLabels = ( mMode == Labels ) || !mLayer;
497 lyr.
fieldName = mFieldExpressionWidget->currentField( &isExpression );
502 QgsLabeling::PolygonPlacementFlags polygonPlacementFlags = QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon;
503 if ( mCheckAllowLabelsOutsidePolygons->isChecked() )
504 polygonPlacementFlags |= QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
510 lyr.
dist = mLineDistanceSpnBx->value();
511 lyr.
distUnits = mLineDistanceUnitWidget->unit();
514 if ( mQuadrantBtnGrp )
518 lyr.
xOffset = mPointOffsetXSpinBox->value();
519 lyr.
yOffset = mPointOffsetYSpinBox->value();
524 QgsLabeling::LinePlacementFlags linePlacementFlags = QgsLabeling::LinePlacementFlags();
525 if ( chkLineAbove->isChecked() )
527 if ( chkLineBelow->isChecked() )
529 if ( chkLineOn->isChecked() )
531 if ( ! chkLineOrientationDependent->isChecked() )
545 lyr.
priority = mPrioritySlider->value();
547 mObstacleSettings.setIsObstacle( mChkNoObstacle->isChecked() || mMode == ObstaclesOnly );
554 lyr.
displayAll = mPalShowAllLabelsForLayerChkBx->isChecked();
567 lyr.
decimals = mFormatNumDecimalsSpnBx->value();
568 lyr.
plusSign = mFormatNumPlusSignChkBx->isChecked();
575 if ( mDirectSymbBtnGrp )
579 if ( mUpsidedownBtnGrp )
595 lyr.
wrapChar = wrapCharacterEdit->text();
604 lyr.
layerType = mLayer ? mLayer->geometryType() : mGeomType;
606 lyr.
zIndex = mZIndexSpinBox->value();
611 const QString calloutType = mCalloutStyleComboBox->currentData().toString();
612 std::unique_ptr< QgsCallout > callout;
613 if (
QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
615 callout.reset( pew->callout()->clone() );
620 callout->
setEnabled( mCalloutsDrawCheckBox->isChecked() );
628 if ( ddBtn->
isActive() && !chkBx->isChecked() )
630 chkBx->setChecked(
true );
632 f->setEnabled( chkBx->isChecked() );
635 bool QgsLabelingGui::eventFilter( QObject *
object, QEvent *event )
637 if (
object == mLblNoObstacle1 )
639 if ( event->type() == QEvent::MouseButtonPress &&
dynamic_cast< QMouseEvent *
>( event )->button() == Qt::LeftButton )
642 mChkNoObstacle->setChecked( !mChkNoObstacle->isChecked() );
647 return QgsTextFormatWidget::eventFilter(
object, event );
650 void QgsLabelingGui::updateUi()
654 syncDefinedCheckboxFrame( mBufferDrawDDBtn, mBufferDrawChkBx, mBufferFrame );
655 syncDefinedCheckboxFrame( mEnableMaskDDBtn, mEnableMaskChkBx, mMaskFrame );
656 syncDefinedCheckboxFrame( mShapeDrawDDBtn, mShapeDrawChkBx, mShapeFrame );
657 syncDefinedCheckboxFrame( mShadowDrawDDBtn, mShadowDrawChkBx, mShadowFrame );
658 syncDefinedCheckboxFrame( mCalloutDrawDDBtn, mCalloutsDrawCheckBox, mCalloutFrame );
660 syncDefinedCheckboxFrame( mDirectSymbDDBtn, mDirectSymbChkBx, mDirectSymbFrame );
661 syncDefinedCheckboxFrame( mFormatNumDDBtn, mFormatNumChkBx, mFormatNumFrame );
662 syncDefinedCheckboxFrame( mScaleBasedVisibilityDDBtn, mScaleBasedVisibilityChkBx, mScaleBasedVisibilityFrame );
663 syncDefinedCheckboxFrame( mFontLimitPixelDDBtn, mFontLimitPixelChkBox, mFontLimitPixelFrame );
665 chkMergeLines->setEnabled( !mDirectSymbChkBx->isChecked() );
666 if ( mDirectSymbChkBx->isChecked() )
668 chkMergeLines->setToolTip( tr(
"This option is not compatible with line direction symbols." ) );
672 chkMergeLines->setToolTip( QString() );
702 settings.
fieldName = mFieldExpressionWidget->currentField( &isExpression );
705 setSettings( settings );
713 if (
QgsCalloutWidget *cw = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
715 cw->setContext( context );
720 void QgsLabelingGui::saveFormat()
727 saveDlg.setDefaultTags( mTextFormatsListWidget->currentTagFilter() );
728 if ( !saveDlg.exec() )
731 if ( saveDlg.name().isEmpty() )
734 switch ( saveDlg.selectedType() )
741 int res = QMessageBox::warning(
this, tr(
"Save Text Format" ),
742 tr(
"Format with name '%1' already exists. Overwrite?" )
743 .arg( saveDlg.name() ),
744 QMessageBox::Yes | QMessageBox::No );
745 if ( res != QMessageBox::Yes )
751 QStringList symbolTags = saveDlg.tags().split(
',' );
755 style->
saveTextFormat( saveDlg.name(), newFormat, saveDlg.isFavorite(), symbolTags );
764 int res = QMessageBox::warning(
this, tr(
"Save Label Settings" ),
765 tr(
"Label settings with the name '%1' already exist. Overwrite?" )
766 .arg( saveDlg.name() ),
767 QMessageBox::Yes | QMessageBox::No );
768 if ( res != QMessageBox::Yes )
774 QStringList symbolTags = saveDlg.tags().split(
',' );
778 style->
saveLabelSettings( saveDlg.name(), newSettings, saveDlg.isFavorite(), symbolTags );
792 void QgsLabelingGui::updateGeometryTypeBasedWidgets()
796 if ( mGeometryGeneratorGroupBox->isChecked() )
799 geometryType = mLayer->geometryType();
809 mPlacementModeComboBox->clear();
811 switch ( geometryType )
838 qFatal(
"unknown geometry type unexpected" );
841 if ( mPlacementModeComboBox->findData( prevPlacement ) != -1 )
843 mPlacementModeComboBox->setCurrentIndex( mPlacementModeComboBox->findData( prevPlacement ) );
856 mFontMultiLineAlignComboBox->removeItem( idx );
859 updatePlacementWidgets();
860 updateLinePlacementOptions();
863 void QgsLabelingGui::showGeometryGeneratorExpressionBuilder()
867 expressionBuilder.setExpressionText( mGeometryGenerator->text() );
868 expressionBuilder.setExpressionContext( createExpressionContext() );
870 if ( expressionBuilder.exec() )
872 mGeometryGenerator->setText( expressionBuilder.expressionText() );
876 void QgsLabelingGui::validateGeometryGeneratorExpression()
880 if ( mGeometryGeneratorGroupBox->isChecked() )
882 if ( !mPreviewFeature.isValid() && mLayer )
883 mLayer->getFeatures(
QgsFeatureRequest().setLimit( 1 ) ).nextFeature( mPreviewFeature );
889 expression.prepare( &context );
891 if ( expression.hasParserError() )
893 mGeometryGeneratorWarningLabel->setText( expression.parserErrorString() );
898 const QVariant result = expression.evaluate( &context );
903 mGeometryGeneratorWarningLabel->setText( tr(
"Result of the expression is not a geometry" ) );
906 else if ( geometry.
type() != configuredGeometryType )
908 mGeometryGeneratorWarningLabel->setText( QStringLiteral(
"<p>%1</p><p><a href=\"#determineGeometryGeneratorType\">%2</a></p>" ).arg(
909 tr(
"Result of the expression does not match configured geometry type." ),
918 QTimer *timer =
new QTimer();
919 connect( timer, &QTimer::timeout,
this, [
this, valid]()
921 mGeometryGeneratorWarningLabel->setVisible( !valid );
923 connect( timer, &QTimer::timeout, timer, &QTimer::deleteLater );
927 void QgsLabelingGui::determineGeometryGeneratorType()
929 if ( !mPreviewFeature.isValid() && mLayer )
930 mLayer->getFeatures(
QgsFeatureRequest().setLimit( 1 ) ).nextFeature( mPreviewFeature );
936 expression.prepare( &context );
939 mGeometryGeneratorType->setCurrentIndex( mGeometryGeneratorType->findData( geometry.
type() ) );
942 void QgsLabelingGui::calloutTypeChanged()
944 QString newCalloutType = mCalloutStyleComboBox->currentData().toString();
945 QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() );
964 updateCalloutWidget( newCallout.get() );
977 QVBoxLayout *vLayout =
new QVBoxLayout();
978 mWidget =
new QgsLabelingGui( layer, mapCanvas, settings,
nullptr, geomType );
979 vLayout->addWidget( mWidget );
980 mButtonBox =
new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok, Qt::Horizontal );
981 connect( mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
982 connect( mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
983 connect( mButtonBox, &QDialogButtonBox::helpRequested,
this, &QgsLabelSettingsDialog::showHelp );
984 vLayout->addWidget( mButtonBox );
985 setLayout( vLayout );
986 setWindowTitle( tr(
"Label Settings" ) );
989 QDialogButtonBox *QgsLabelSettingsDialog::buttonBox()
const
994 void QgsLabelSettingsDialog::showHelp()