36 #include <QButtonGroup>
37 #include <QMessageBox>
67 if ( !abstractMetadata )
69 QgsDebugMsg( QStringLiteral(
"Failed to find callout entry in registry: %1" ).arg( name ) );
75 QgsDebugMsg( QStringLiteral(
"Failed to cast callout's metadata: " ) .arg( name ) );
82 void QgsLabelingGui::initCalloutWidgets()
84 _initCalloutWidgetFunction( QStringLiteral(
"simple" ), QgsSimpleLineCalloutWidget::create );
85 _initCalloutWidgetFunction( QStringLiteral(
"manhattan" ), QgsManhattanLineCalloutWidget::create );
88 void QgsLabelingGui::updateCalloutWidget(
QgsCallout *callout )
92 mCalloutStackedWidget->setCurrentWidget( pageDummy );
96 if ( mCalloutStackedWidget->currentWidget() != pageDummy )
99 if (
QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
110 if ( mGeometryGeneratorGroupBox->isChecked() )
113 geometryType = mLayer->geometryType();
114 w->setGeometryType( geometryType );
115 w->setCallout( callout );
117 w->setContext( context() );
118 mCalloutStackedWidget->addWidget( w );
119 mCalloutStackedWidget->setCurrentWidget( w );
126 mCalloutStackedWidget->setCurrentWidget( pageDummy );
129 void QgsLabelingGui::showObstacleSettings()
143 auto applySettings = [ = ]
145 mObstacleSettings = widget->
settings();
148 emit widgetChanged();
164 dialog.buttonBox()->addButton( QDialogButtonBox::Help );
165 connect( dialog.buttonBox(), &QDialogButtonBox::helpRequested,
this, [ = ]
167 QgsHelp::openHelp( QStringLiteral(
"style_library/label_settings.html#obstacles" ) );
181 , mGeomType( geomType )
182 , mSettings( layerSettings )
184 , mCanvas( mapCanvas )
186 static std::once_flag initialized;
187 std::call_once( initialized, [ = ]( )
189 initCalloutWidgets();
197 connect( mBufferDrawChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
198 connect( mEnableMaskChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
199 connect( mShapeDrawChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
200 connect( mCalloutsDrawCheckBox, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
201 connect( mShadowDrawChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
202 connect( mDirectSymbChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
203 connect( mFormatNumChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
204 connect( mScaleBasedVisibilityChkBx, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
205 connect( mFontLimitPixelChkBox, &QAbstractButton::toggled,
this, &QgsLabelingGui::updateUi );
206 connect( mGeometryGeneratorGroupBox, &QGroupBox::toggled,
this, &QgsLabelingGui::updateGeometryTypeBasedWidgets );
207 connect( mGeometryGeneratorType, qgis::overload<int>::of( &QComboBox::currentIndexChanged ),
this, &QgsLabelingGui::updateGeometryTypeBasedWidgets );
208 connect( mGeometryGeneratorExpressionButton, &QToolButton::clicked,
this, &QgsLabelingGui::showGeometryGeneratorExpressionBuilder );
209 connect( mGeometryGeneratorGroupBox, &QGroupBox::toggled,
this, &QgsLabelingGui::validateGeometryGeneratorExpression );
210 connect( mGeometryGenerator, &QgsCodeEditorExpression::textChanged,
this, &QgsLabelingGui::validateGeometryGeneratorExpression );
211 connect( mGeometryGeneratorType, qgis::overload<int>::of( &QComboBox::currentIndexChanged ),
this, &QgsLabelingGui::validateGeometryGeneratorExpression );
212 connect( mObstacleSettingsButton, &QAbstractButton::clicked,
this, &QgsLabelingGui::showObstacleSettings );
214 mFieldExpressionWidget->registerExpressionContextGenerator(
this );
216 mMinScaleWidget->setMapCanvas( mCanvas );
217 mMinScaleWidget->setShowCurrentScaleButton(
true );
218 mMaxScaleWidget->setMapCanvas( mCanvas );
219 mMaxScaleWidget->setShowCurrentScaleButton(
true );
222 for (
const QString &type : calloutTypes )
228 mGeometryGeneratorWarningLabel->setStyleSheet( QStringLiteral(
"color: #FFC107;" ) );
229 mGeometryGeneratorWarningLabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
230 connect( mGeometryGeneratorWarningLabel, &QLabel::linkActivated,
this, [
this](
const QString & link )
232 if ( link == QLatin1String(
"#determineGeometryGeneratorType" ) )
233 determineGeometryGeneratorType();
236 connect( mCalloutStyleComboBox,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsLabelingGui::calloutTypeChanged );
241 void QgsLabelingGui::setLayer(
QgsMapLayer *mapLayer )
256 mTextFormatsListWidget->setLayerType( mLayer ? mLayer->geometryType() : mGeomType );
257 mBackgroundSymbolButton->setLayer( mLayer );
260 updateGeometryTypeBasedWidgets();
262 mFieldExpressionWidget->setLayer( mLayer );
267 mFieldExpressionWidget->setGeomCalculator( da );
269 mFieldExpressionWidget->setEnabled( mMode == Labels || !mLayer );
270 mLabelingFrame->setEnabled( mMode == Labels || !mLayer );
272 blockInitSignals(
true );
274 mGeometryGenerator->setText( mSettings.geometryGenerator );
275 mGeometryGeneratorGroupBox->setChecked( mSettings.geometryGeneratorEnabled );
276 mGeometryGeneratorType->setCurrentIndex( mGeometryGeneratorType->findData( mSettings.geometryGeneratorType ) );
278 updateWidgetForFormat( mSettings.format() );
280 mFieldExpressionWidget->setRow( -1 );
281 mFieldExpressionWidget->setField( mSettings.fieldName );
282 mCheckBoxSubstituteText->setChecked( mSettings.useSubstitutions );
283 mSubstitutions = mSettings.substitutions;
286 mCentroidRadioWhole->setChecked( mSettings.centroidWhole );
287 mCentroidInsideCheckBox->setChecked( mSettings.centroidInside );
288 mFitInsidePolygonCheckBox->setChecked( mSettings.fitInPolygonOnly );
289 mLineDistanceSpnBx->setValue( mSettings.dist );
290 mLineDistanceUnitWidget->setUnit( mSettings.distUnits );
291 mLineDistanceUnitWidget->setMapUnitScale( mSettings.distMapUnitScale );
292 mOffsetTypeComboBox->setCurrentIndex( mOffsetTypeComboBox->findData( mSettings.offsetType ) );
293 mQuadrantBtnGrp->button(
static_cast<int>( mSettings.quadOffset ) )->setChecked(
true );
294 mPointOffsetXSpinBox->setValue( mSettings.xOffset );
295 mPointOffsetYSpinBox->setValue( mSettings.yOffset );
296 mPointOffsetUnitWidget->setUnit( mSettings.offsetUnits );
297 mPointOffsetUnitWidget->setMapUnitScale( mSettings.labelOffsetMapUnitScale );
298 mPointAngleSpinBox->setValue( mSettings.angleOffset );
304 mCheckAllowLabelsOutsidePolygons->setChecked( mSettings.polygonPlacementFlags() & QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
306 switch ( mSettings.placement )
309 radAroundPoint->setChecked(
true );
310 radAroundCentroid->setChecked(
true );
314 radOverPoint->setChecked(
true );
315 radOverCentroid->setChecked(
true );
318 radPredefinedOrder->setChecked(
true );
321 radLineParallel->setChecked(
true );
322 radPolygonPerimeter->setChecked(
true );
325 radLineCurved->setChecked(
true );
328 radPolygonHorizontal->setChecked(
true );
329 radLineHorizontal->setChecked(
true );
332 radPolygonFree->setChecked(
true );
335 radPolygonPerimeterCurved->setChecked(
true );
338 radPolygonOutside->setChecked(
true );
343 mRepeatDistanceSpinBox->setValue( mSettings.repeatDistance );
344 mRepeatDistanceUnitWidget->setUnit( mSettings.repeatDistanceUnit );
345 mRepeatDistanceUnitWidget->setMapUnitScale( mSettings.repeatDistanceMapUnitScale );
347 mOverrunDistanceSpinBox->setValue( mSettings.overrunDistance );
348 mOverrunDistanceUnitWidget->setUnit( mSettings.overrunDistanceUnit );
349 mOverrunDistanceUnitWidget->setMapUnitScale( mSettings.overrunDistanceMapUnitScale );
351 mPrioritySlider->setValue( mSettings.priority );
352 mChkNoObstacle->setChecked( mSettings.obstacleSettings().isObstacle() );
354 mObstacleSettings = mSettings.obstacleSettings();
356 chkLabelPerFeaturePart->setChecked( mSettings.labelPerPart );
357 mPalShowAllLabelsForLayerChkBx->setChecked( mSettings.displayAll );
358 chkMergeLines->setChecked( mSettings.mergeLines );
359 mMinSizeSpinBox->setValue( mSettings.thinningSettings().minimumFeatureSize() );
360 mLimitLabelChkBox->setChecked( mSettings.thinningSettings().limitNumberOfLabelsEnabled() );
361 mLimitLabelSpinBox->setValue( mSettings.thinningSettings().maximumNumberLabels() );
364 mDirectSymbChkBx->setChecked( mSettings.addDirectionSymbol );
365 mDirectSymbLeftLineEdit->setText( mSettings.leftDirectionSymbol );
366 mDirectSymbRightLineEdit->setText( mSettings.rightDirectionSymbol );
367 mDirectSymbRevChkBx->setChecked( mSettings.reverseDirectionSymbol );
369 mDirectSymbBtnGrp->button(
static_cast<int>( mSettings.placeDirectionSymbol ) )->setChecked(
true );
370 mUpsidedownBtnGrp->button(
static_cast<int>( mSettings.upsidedownLabels ) )->setChecked(
true );
373 mMaxCharAngleInDSpinBox->setValue( mSettings.maxCurvedCharAngleIn );
375 mMaxCharAngleOutDSpinBox->setValue( std::fabs( mSettings.maxCurvedCharAngleOut ) );
377 wrapCharacterEdit->setText( mSettings.wrapChar );
378 mAutoWrapLengthSpinBox->setValue( mSettings.autoWrapLength );
379 mAutoWrapTypeComboBox->setCurrentIndex( mSettings.useMaxLineLengthForAutoWrap ? 0 : 1 );
381 if ( mFontMultiLineAlignComboBox->findData( mSettings.multilineAlign ) != -1 )
383 mFontMultiLineAlignComboBox->setCurrentIndex( mFontMultiLineAlignComboBox->findData( mSettings.multilineAlign ) );
389 mFontMultiLineAlignComboBox->setCurrentIndex( 0 );
392 chkPreserveRotation->setChecked( mSettings.preserveRotation );
394 mScaleBasedVisibilityChkBx->setChecked( mSettings.scaleVisibility );
395 mMinScaleWidget->setScale( mSettings.minimumScale );
396 mMaxScaleWidget->setScale( mSettings.maximumScale );
398 mFormatNumChkBx->setChecked( mSettings.formatNumbers );
399 mFormatNumDecimalsSpnBx->setValue( mSettings.decimals );
400 mFormatNumPlusSignChkBx->setChecked( mSettings.plusSign );
404 mFontLimitPixelChkBox->setChecked( mSettings.fontLimitPixelSize );
405 mMinPixelLimit = mSettings.fontMinPixelSize;
406 mFontMinPixelSpinBox->setValue( mSettings.fontMinPixelSize == 0 ? 3 : mSettings.fontMinPixelSize );
407 mFontMaxPixelSpinBox->setValue( mSettings.fontMaxPixelSize );
409 mZIndexSpinBox->setValue( mSettings.zIndex );
411 mDataDefinedProperties = mSettings.dataDefinedProperties();
414 if ( mSettings.callout() )
416 whileBlocking( mCalloutsDrawCheckBox )->setChecked( mSettings.callout()->enabled() );
417 whileBlocking( mCalloutStyleComboBox )->setCurrentIndex( mCalloutStyleComboBox->findData( mSettings.callout()->type() ) );
418 updateCalloutWidget( mSettings.callout() );
423 whileBlocking( mCalloutStyleComboBox )->setCurrentIndex( mCalloutStyleComboBox->findData( defaultCallout->type() ) );
425 updateCalloutWidget( defaultCallout.get() );
428 updatePlacementWidgets();
429 updateLinePlacementOptions();
432 blockInitSignals(
false );
436 populateDataDefinedButtons();
438 enableDataDefinedAlignment( mCoordXDDBtn->isActive() && mCoordYDDBtn->isActive() );
444 mSettings = settings;
448 void QgsLabelingGui::blockInitSignals(
bool block )
450 chkLineAbove->blockSignals( block );
451 chkLineBelow->blockSignals( block );
452 mPlacePointBtnGrp->blockSignals( block );
453 mPlaceLineBtnGrp->blockSignals( block );
454 mPlacePolygonBtnGrp->blockSignals( block );
457 void QgsLabelingGui::setLabelMode( LabelMode mode )
460 mFieldExpressionWidget->setEnabled( mMode == Labels );
461 mLabelingFrame->setEnabled( mMode == Labels );
468 lyr.
drawLabels = ( mMode == Labels ) || !mLayer;
471 lyr.
fieldName = mFieldExpressionWidget->currentField( &isExpression );
477 QgsLabeling::PolygonPlacementFlags polygonPlacementFlags = QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon;
478 if ( mCheckAllowLabelsOutsidePolygons->isChecked() )
479 polygonPlacementFlags |= QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
482 QWidget *curPlacementWdgt = stackedPlacement->currentWidget();
486 lyr.
dist = mLineDistanceSpnBx->value();
487 lyr.
distUnits = mLineDistanceUnitWidget->unit();
490 if ( mQuadrantBtnGrp )
494 lyr.
xOffset = mPointOffsetXSpinBox->value();
495 lyr.
yOffset = mPointOffsetYSpinBox->value();
499 if ( chkLineAbove->isChecked() )
501 if ( chkLineBelow->isChecked() )
503 if ( chkLineOn->isChecked() )
505 if ( ! chkLineOrientationDependent->isChecked() )
507 if ( ( curPlacementWdgt == pagePoint && radAroundPoint->isChecked() )
508 || ( curPlacementWdgt == pagePolygon && radAroundCentroid->isChecked() ) )
512 else if ( ( curPlacementWdgt == pagePoint && radOverPoint->isChecked() )
513 || ( curPlacementWdgt == pagePolygon && radOverCentroid->isChecked() ) )
517 else if ( curPlacementWdgt == pagePoint && radPredefinedOrder->isChecked() )
521 else if ( ( curPlacementWdgt == pageLine && radLineParallel->isChecked() )
522 || ( curPlacementWdgt == pagePolygon && radPolygonPerimeter->isChecked() ) )
526 else if ( curPlacementWdgt == pageLine && radLineCurved->isChecked() )
530 else if ( curPlacementWdgt == pagePolygon && radPolygonPerimeterCurved->isChecked() )
534 else if ( ( curPlacementWdgt == pageLine && radLineHorizontal->isChecked() )
535 || ( curPlacementWdgt == pagePolygon && radPolygonHorizontal->isChecked() ) )
539 else if ( radPolygonFree->isChecked() )
543 else if ( radPolygonOutside->isChecked() )
549 qFatal(
"Invalid settings" );
560 lyr.
priority = mPrioritySlider->value();
562 mObstacleSettings.setIsObstacle( mChkNoObstacle->isChecked() || mMode == ObstaclesOnly );
566 lyr.
displayAll = mPalShowAllLabelsForLayerChkBx->isChecked();
579 lyr.
decimals = mFormatNumDecimalsSpnBx->value();
580 lyr.
plusSign = mFormatNumPlusSignChkBx->isChecked();
587 if ( mDirectSymbBtnGrp )
591 if ( mUpsidedownBtnGrp )
607 lyr.
wrapChar = wrapCharacterEdit->text();
616 lyr.
layerType = mLayer ? mLayer->geometryType() : mGeomType;
618 lyr.
zIndex = mZIndexSpinBox->value();
623 const QString calloutType = mCalloutStyleComboBox->currentData().toString();
624 std::unique_ptr< QgsCallout > callout;
625 if (
QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
627 callout.reset( pew->callout()->clone() );
632 callout->
setEnabled( mCalloutsDrawCheckBox->isChecked() );
640 if ( ddBtn->
isActive() && !chkBx->isChecked() )
642 chkBx->setChecked(
true );
644 f->setEnabled( chkBx->isChecked() );
647 void QgsLabelingGui::updateUi()
651 syncDefinedCheckboxFrame( mBufferDrawDDBtn, mBufferDrawChkBx, mBufferFrame );
652 syncDefinedCheckboxFrame( mEnableMaskDDBtn, mEnableMaskChkBx, mMaskFrame );
653 syncDefinedCheckboxFrame( mShapeDrawDDBtn, mShapeDrawChkBx, mShapeFrame );
654 syncDefinedCheckboxFrame( mShadowDrawDDBtn, mShadowDrawChkBx, mShadowFrame );
655 syncDefinedCheckboxFrame( mCalloutDrawDDBtn, mCalloutsDrawCheckBox, mCalloutFrame );
657 syncDefinedCheckboxFrame( mDirectSymbDDBtn, mDirectSymbChkBx, mDirectSymbFrame );
658 syncDefinedCheckboxFrame( mFormatNumDDBtn, mFormatNumChkBx, mFormatNumFrame );
659 syncDefinedCheckboxFrame( mScaleBasedVisibilityDDBtn, mScaleBasedVisibilityChkBx, mScaleBasedVisibilityFrame );
660 syncDefinedCheckboxFrame( mFontLimitPixelDDBtn, mFontLimitPixelChkBox, mFontLimitPixelFrame );
662 chkMergeLines->setEnabled( !mDirectSymbChkBx->isChecked() );
663 if ( mDirectSymbChkBx->isChecked() )
665 chkMergeLines->setToolTip( tr(
"This option is not compatible with line direction symbols." ) );
669 chkMergeLines->setToolTip( QString() );
698 settings.
fieldName = mFieldExpressionWidget->currentField( &isExpression );
701 setSettings( settings );
709 if (
QgsCalloutWidget *cw = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() ) )
711 cw->setContext( context );
716 void QgsLabelingGui::saveFormat()
723 saveDlg.setDefaultTags( mTextFormatsListWidget->currentTagFilter() );
724 if ( !saveDlg.exec() )
727 if ( saveDlg.name().isEmpty() )
730 switch ( saveDlg.selectedType() )
737 int res = QMessageBox::warning(
this, tr(
"Save Text Format" ),
738 tr(
"Format with name '%1' already exists. Overwrite?" )
739 .arg( saveDlg.name() ),
740 QMessageBox::Yes | QMessageBox::No );
741 if ( res != QMessageBox::Yes )
747 QStringList symbolTags = saveDlg.tags().split(
',' );
751 style->
saveTextFormat( saveDlg.name(), newFormat, saveDlg.isFavorite(), symbolTags );
760 int res = QMessageBox::warning(
this, tr(
"Save Label Settings" ),
761 tr(
"Label settings with the name '%1' already exist. Overwrite?" )
762 .arg( saveDlg.name() ),
763 QMessageBox::Yes | QMessageBox::No );
764 if ( res != QMessageBox::Yes )
770 QStringList symbolTags = saveDlg.tags().split(
',' );
774 style->
saveLabelSettings( saveDlg.name(), newSettings, saveDlg.isFavorite(), symbolTags );
787 void QgsLabelingGui::updateGeometryTypeBasedWidgets()
791 if ( mGeometryGeneratorGroupBox->isChecked() )
794 geometryType = mLayer->geometryType();
804 switch ( geometryType )
807 stackedPlacement->setCurrentWidget( pagePoint );
810 stackedPlacement->setCurrentWidget( pageLine );
813 stackedPlacement->setCurrentWidget( pagePolygon );
818 qFatal(
"unknown geometry type unexpected" );
831 mFontMultiLineAlignComboBox->removeItem( idx );
834 updatePlacementWidgets();
835 updateLinePlacementOptions();
838 void QgsLabelingGui::showGeometryGeneratorExpressionBuilder()
842 expressionBuilder.setExpressionText( mGeometryGenerator->text() );
843 expressionBuilder.setExpressionContext( createExpressionContext() );
845 if ( expressionBuilder.exec() )
847 mGeometryGenerator->setText( expressionBuilder.expressionText() );
851 void QgsLabelingGui::validateGeometryGeneratorExpression()
855 if ( mGeometryGeneratorGroupBox->isChecked() )
857 if ( !mPreviewFeature.isValid() && mLayer )
858 mLayer->getFeatures(
QgsFeatureRequest().setLimit( 1 ) ).nextFeature( mPreviewFeature );
864 expression.prepare( &context );
866 if ( expression.hasParserError() )
868 mGeometryGeneratorWarningLabel->setText( expression.parserErrorString() );
873 const QVariant result = expression.evaluate( &context );
878 mGeometryGeneratorWarningLabel->setText( tr(
"Result of the expression is not a geometry" ) );
881 else if ( geometry.
type() != configuredGeometryType )
883 mGeometryGeneratorWarningLabel->setText( QStringLiteral(
"<p>%1</p><p><a href=\"#determineGeometryGeneratorType\">%2</a></p>" ).arg(
884 tr(
"Result of the expression does not match configured geometry type." ),
893 QTimer *timer =
new QTimer();
894 connect( timer, &QTimer::timeout,
this, [
this, valid]()
896 mGeometryGeneratorWarningLabel->setVisible( !valid );
898 connect( timer, &QTimer::timeout, timer, &QTimer::deleteLater );
902 void QgsLabelingGui::determineGeometryGeneratorType()
904 if ( !mPreviewFeature.isValid() && mLayer )
905 mLayer->getFeatures(
QgsFeatureRequest().setLimit( 1 ) ).nextFeature( mPreviewFeature );
911 expression.prepare( &context );
914 mGeometryGeneratorType->setCurrentIndex( mGeometryGeneratorType->findData( geometry.
type() ) );
917 void QgsLabelingGui::calloutTypeChanged()
919 QString newCalloutType = mCalloutStyleComboBox->currentData().toString();
920 QgsCalloutWidget *pew = qobject_cast< QgsCalloutWidget * >( mCalloutStackedWidget->currentWidget() );
939 updateCalloutWidget( newCallout.get() );
952 QVBoxLayout *vLayout =
new QVBoxLayout();
953 mWidget =
new QgsLabelingGui( layer, mapCanvas, settings,
nullptr, geomType );
954 vLayout->addWidget( mWidget );
955 mButtonBox =
new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok, Qt::Horizontal );
956 connect( mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
957 connect( mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
958 connect( mButtonBox, &QDialogButtonBox::helpRequested,
this, &QgsLabelSettingsDialog::showHelp );
959 vLayout->addWidget( mButtonBox );
960 setLayout( vLayout );
961 setWindowTitle( tr(
"Label Settings" ) );
964 QDialogButtonBox *QgsLabelSettingsDialog::buttonBox()
const
969 void QgsLabelSettingsDialog::showHelp()