1 /***************************************************************************
2                           qgslabelpropertydialog.cpp
3                           --------------------------
4     begin                : 2010-11-12
5     copyright            : (C) 2010 by Marco Hugentobler
6     email                : marco dot hugentobler at sourcepole dot ch
7 ***************************************************************************/
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 "qgslabelpropertydialog.h"
19 #include "qgscallout.h"
20 #include "qgsfontutils.h"
21 #include "qgslogger.h"
22 #include "qgsfeatureiterator.h"
23 #include "qgsproject.h"
24 #include "qgsvectorlayer.h"
25 #include "qgisapp.h"
26 #include "qgsmapcanvas.h"
27 #include "qgsvectorlayerlabeling.h"
28 #include "qgsproperty.h"
29 #include "qgssettings.h"
30 #include "qgsexpressioncontextutils.h"
31 #include "qgsgui.h"
32 #include "qgshelp.h"
33 #include "qgsexpressionnodeimpl.h"
34 
35 #include <QColorDialog>
36 #include <QFontDatabase>
37 #include <QDialogButtonBox>
38 
39 
QgsLabelPropertyDialog(const QString & layerId,const QString & providerId,QgsFeatureId featureId,const QFont & labelFont,const QString & labelText,bool isPinned,const QgsPalLayerSettings & layerSettings,QgsMapCanvas * canvas,QWidget * parent,Qt::WindowFlags f)40 QgsLabelPropertyDialog::QgsLabelPropertyDialog( const QString &layerId, const QString &providerId, QgsFeatureId featureId, const QFont &labelFont, const QString &labelText, bool isPinned, const QgsPalLayerSettings &layerSettings, QgsMapCanvas *canvas, QWidget *parent, Qt::WindowFlags f )
41   : QDialog( parent, f )
42   , mCanvas( canvas )
43   , mLabelFont( labelFont )
44   , mIsPinned( isPinned )
45 {
46   setupUi( this );
47   QgsGui::instance()->enableAutoGeometryRestore( this );
48 
49   // set defaults to layer defaults
50   mLabelAllPartsCheckBox->setChecked( layerSettings.labelPerPart );
51 
52   connect( buttonBox, &QDialogButtonBox::clicked, this, &QgsLabelPropertyDialog::buttonBox_clicked );
53   connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsLabelPropertyDialog::showHelp );
54   connect( mShowLabelChkbx, &QCheckBox::toggled, this, &QgsLabelPropertyDialog::mShowLabelChkbx_toggled );
55   connect( mAlwaysShowChkbx, &QCheckBox::toggled, this, &QgsLabelPropertyDialog::mAlwaysShowChkbx_toggled );
56   connect( mShowCalloutChkbx, &QCheckBox::toggled, this, &QgsLabelPropertyDialog::showCalloutToggled );
57   connect( mBufferDrawChkbx, &QCheckBox::toggled, this, &QgsLabelPropertyDialog::bufferDrawToggled );
58   connect( mLabelDistanceSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLabelPropertyDialog::mLabelDistanceSpinBox_valueChanged );
59   connect( mXCoordSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLabelPropertyDialog::mXCoordSpinBox_valueChanged );
60   connect( mYCoordSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLabelPropertyDialog::mYCoordSpinBox_valueChanged );
61   connect( mFontFamilyCmbBx, &QFontComboBox::currentFontChanged, this, &QgsLabelPropertyDialog::mFontFamilyCmbBx_currentFontChanged );
62   connect( mFontStyleCmbBx, &QComboBox::currentTextChanged, this, &QgsLabelPropertyDialog::mFontStyleCmbBx_currentIndexChanged );
63   connect( mFontUnderlineBtn, &QToolButton::toggled, this, &QgsLabelPropertyDialog::mFontUnderlineBtn_toggled );
64   connect( mFontStrikethroughBtn, &QToolButton::toggled, this, &QgsLabelPropertyDialog::mFontStrikethroughBtn_toggled );
65   connect( mFontBoldBtn, &QToolButton::toggled, this, &QgsLabelPropertyDialog::mFontBoldBtn_toggled );
66   connect( mFontItalicBtn, &QToolButton::toggled, this, &QgsLabelPropertyDialog::mFontItalicBtn_toggled );
67   connect( mFontSizeSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLabelPropertyDialog::mFontSizeSpinBox_valueChanged );
68   connect( mBufferSizeSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLabelPropertyDialog::mBufferSizeSpinBox_valueChanged );
69   connect( mRotationSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLabelPropertyDialog::mRotationSpinBox_valueChanged );
70   connect( mFontColorButton, &QgsColorButton::colorChanged, this, &QgsLabelPropertyDialog::mFontColorButton_colorChanged );
71   connect( mBufferColorButton, &QgsColorButton::colorChanged, this, &QgsLabelPropertyDialog::mBufferColorButton_colorChanged );
72   connect( mMultiLineAlignComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLabelPropertyDialog::mMultiLineAlignComboBox_currentIndexChanged );
73   connect( mHaliComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLabelPropertyDialog::mHaliComboBox_currentIndexChanged );
74   connect( mValiComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLabelPropertyDialog::mValiComboBox_currentIndexChanged );
75   connect( mLabelTextLineEdit, &QLineEdit::textChanged, this, &QgsLabelPropertyDialog::mLabelTextLineEdit_textChanged );
76   connect( mLabelAllPartsCheckBox, &QCheckBox::toggled, this, &QgsLabelPropertyDialog::labelAllPartsToggled );
77 
78   mRotationSpinBox->setClearValue( 0 );
79   fillMultiLineAlignComboBox();
80   fillHaliComboBox();
81   fillValiComboBox();
82 
83   init( layerId, providerId, featureId, labelText );
84 
85   connect( mMinScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsLabelPropertyDialog::minScaleChanged );
86   connect( mMaxScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsLabelPropertyDialog::maxScaleChanged );
87 }
88 
setMapCanvas(QgsMapCanvas * canvas)89 void QgsLabelPropertyDialog::setMapCanvas( QgsMapCanvas *canvas )
90 {
91   mMinScaleWidget->setMapCanvas( canvas );
92   mMinScaleWidget->setShowCurrentScaleButton( true );
93   mMaxScaleWidget->setMapCanvas( canvas );
94   mMaxScaleWidget->setShowCurrentScaleButton( true );
95 }
96 
buttonBox_clicked(QAbstractButton * button)97 void QgsLabelPropertyDialog::buttonBox_clicked( QAbstractButton *button )
98 {
99   if ( buttonBox->buttonRole( button ) == QDialogButtonBox::ApplyRole )
100   {
101     emit applied();
102   }
103 }
104 
init(const QString & layerId,const QString & providerId,QgsFeatureId featureId,const QString & labelText)105 void QgsLabelPropertyDialog::init( const QString &layerId, const QString &providerId, QgsFeatureId featureId, const QString &labelText )
106 {
107   //get feature attributes
108   QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( mCanvas->layer( layerId ) );
109   if ( !vlayer )
110   {
111     return;
112   }
113   if ( !vlayer->labeling() )
114   {
115     return;
116   }
117 
118   if ( !vlayer->getFeatures( QgsFeatureRequest().setFilterFid( featureId ).setFlags( QgsFeatureRequest::NoGeometry ) ).nextFeature( mCurLabelFeat ) )
119   {
120     return;
121   }
122   const QgsAttributes attributeValues = mCurLabelFeat.attributes();
123 
124   //get layerproperties. Problem: only for pallabeling...
125 
126   blockElementSignals( true );
127 
128   QgsPalLayerSettings layerSettings = vlayer->labeling()->settings( providerId );
129 
130   //get label field and fill line edit
131   if ( layerSettings.isExpression && !labelText.isNull() )
132   {
133     mLabelTextLineEdit->setText( labelText );
134     mLabelTextLineEdit->setEnabled( false );
135     mLabelTextLabel->setText( tr( "Expression result" ) );
136   }
137   else
138   {
139     const QString labelFieldName = layerSettings.fieldName;
140     if ( !labelFieldName.isEmpty() )
141     {
142       mCurLabelField = vlayer->fields().lookupField( labelFieldName );
143       if ( mCurLabelField >= 0 )
144       {
145         mLabelTextLineEdit->setText( attributeValues.at( mCurLabelField ).toString() );
146 
147         if ( vlayer->isEditable() )
148           mLabelTextLineEdit->setEnabled( true );
149         else
150           mLabelTextLineEdit->setEnabled( false );
151 
152         const QgsFields &layerFields = vlayer->fields();
153         switch ( layerFields.at( mCurLabelField ).type() )
154         {
155           case QVariant::Double:
156             mLabelTextLineEdit->setValidator( new QDoubleValidator( this ) );
157             break;
158           case QVariant::Int:
159           case QVariant::UInt:
160           case QVariant::LongLong:
161             mLabelTextLineEdit->setValidator( new QIntValidator( this ) );
162             break;
163           default:
164             break;
165         }
166       }
167       else
168       {
169         mLabelTextLineEdit->setEnabled( false );
170       }
171     }
172   }
173 
174   //get attributes of the feature and fill data defined values
175 
176   // font is set directly from QgsLabelPosition
177   updateFont( mLabelFont, false );
178 
179   QgsTextFormat format = layerSettings.format();
180   const QgsTextBufferSettings buffer = format.buffer();
181 
182   //set all the gui elements to the default layer-level values
183   mLabelDistanceSpinBox->clear();
184   mLabelDistanceSpinBox->setSpecialValueText( tr( "Layer default (%1)" ).arg( QString::number( layerSettings.dist, 'f', mLabelDistanceSpinBox->decimals() ) ) );
185   mBufferSizeSpinBox->clear();
186   mBufferSizeSpinBox->setSpecialValueText( tr( "Layer default (%1)" ).arg( QString::number( buffer.size(), 'f', mBufferSizeSpinBox->decimals() ) ) );
187   mRotationSpinBox->clear();
188   mXCoordSpinBox->clear();
189   mYCoordSpinBox->clear();
190 
191   mShowLabelChkbx->setChecked( true );
192   mBufferDrawChkbx->setChecked( buffer.enabled() );
193   mShowCalloutChkbx->setChecked( layerSettings.callout() ? layerSettings.callout()->enabled() : false );
194   mFontColorButton->setColor( format.color() );
195   mBufferColorButton->setColor( buffer.color() );
196   mMinScaleWidget->setScale( layerSettings.minimumScale );
197   mMaxScaleWidget->setScale( layerSettings.maximumScale );
198 
199   QString defaultMultilineAlign;
200   switch ( layerSettings.multilineAlign )
201   {
202     case QgsPalLayerSettings::MultiLeft:
203       defaultMultilineAlign = QStringLiteral( "left" );
204       break;
205     case QgsPalLayerSettings::MultiCenter:
206       defaultMultilineAlign = QStringLiteral( "center" );
207       break;
208     case QgsPalLayerSettings::MultiRight:
209       defaultMultilineAlign = QStringLiteral( "right" );
210       break;
211     case QgsPalLayerSettings::MultiJustify:
212       defaultMultilineAlign = QStringLiteral( "justify" );
213       break;
214     case QgsPalLayerSettings::MultiFollowPlacement:
215       defaultMultilineAlign = QStringLiteral( "follow label placement" );
216       break;
217   }
218   mMultiLineAlignComboBox->setItemText( mMultiLineAlignComboBox->findData( "" ), tr( "Layer default (%1)" ).arg( defaultMultilineAlign ) );
219   mMultiLineAlignComboBox->setCurrentIndex( mMultiLineAlignComboBox->findData( "" ) );
220 
221   mHaliComboBox->setCurrentIndex( mHaliComboBox->findData( "left", Qt::UserRole, Qt::MatchFixedString ) );
222   mValiComboBox->setCurrentIndex( mValiComboBox->findData( "bottom", Qt::UserRole, Qt::MatchFixedString ) );
223   mFontColorButton->setColorDialogTitle( tr( "Font Color" ) );
224   mBufferColorButton->setColorDialogTitle( tr( "Buffer Color" ) );
225 
226   disableGuiElements();
227 
228   mDataDefinedProperties = layerSettings.dataDefinedProperties();
229 
230   //set widget values from data defined results
231   setDataDefinedValues( vlayer );
232   //enable widgets connected to data defined fields
233   enableDataDefinedWidgets( vlayer );
234 
235   blockElementSignals( false );
236 }
237 
disableGuiElements()238 void QgsLabelPropertyDialog::disableGuiElements()
239 {
240   mShowLabelChkbx->setEnabled( false );
241   mAlwaysShowChkbx->setEnabled( false );
242   mShowCalloutChkbx->setEnabled( false );
243   mMinScaleWidget->setEnabled( false );
244   mMaxScaleWidget->setEnabled( false );
245   mFontFamilyCmbBx->setEnabled( false );
246   mFontStyleCmbBx->setEnabled( false );
247   mFontUnderlineBtn->setEnabled( false );
248   mFontStrikethroughBtn->setEnabled( false );
249   mFontBoldBtn->setEnabled( false );
250   mFontItalicBtn->setEnabled( false );
251   mFontSizeSpinBox->setEnabled( false );
252   mBufferSizeSpinBox->setEnabled( false );
253   mFontColorButton->setEnabled( false );
254   mBufferColorButton->setEnabled( false );
255   mBufferDrawChkbx->setEnabled( false );
256   mLabelDistanceSpinBox->setEnabled( false );
257   mXCoordSpinBox->setEnabled( false );
258   mYCoordSpinBox->setEnabled( false );
259   mMultiLineAlignComboBox->setEnabled( false );
260   mHaliComboBox->setEnabled( false );
261   mValiComboBox->setEnabled( false );
262   mRotationSpinBox->setEnabled( false );
263   mLabelAllPartsCheckBox->setEnabled( false );
264 }
265 
blockElementSignals(bool block)266 void QgsLabelPropertyDialog::blockElementSignals( bool block )
267 {
268   mShowLabelChkbx->blockSignals( block );
269   mAlwaysShowChkbx->blockSignals( block );
270   mShowCalloutChkbx->blockSignals( block );
271   mMinScaleWidget->blockSignals( block );
272   mMaxScaleWidget->blockSignals( block );
273   mFontFamilyCmbBx->blockSignals( block );
274   mFontStyleCmbBx->blockSignals( block );
275   mFontUnderlineBtn->blockSignals( block );
276   mFontStrikethroughBtn->blockSignals( block );
277   mFontBoldBtn->blockSignals( block );
278   mFontItalicBtn->blockSignals( block );
279   mFontSizeSpinBox->blockSignals( block );
280   mBufferSizeSpinBox->blockSignals( block );
281   mFontColorButton->blockSignals( block );
282   mBufferColorButton->blockSignals( block );
283   mBufferDrawChkbx->blockSignals( block );
284   mLabelDistanceSpinBox->blockSignals( block );
285   mXCoordSpinBox->blockSignals( block );
286   mYCoordSpinBox->blockSignals( block );
287   mMultiLineAlignComboBox->blockSignals( block );
288   mHaliComboBox->blockSignals( block );
289   mValiComboBox->blockSignals( block );
290   mRotationSpinBox->blockSignals( block );
291   mLabelAllPartsCheckBox->blockSignals( block );
292 }
293 
dataDefinedColumnIndex(QgsPalLayerSettings::Property p,const QgsVectorLayer * vlayer,const QgsExpressionContext & context) const294 int QgsLabelPropertyDialog::dataDefinedColumnIndex( QgsPalLayerSettings::Property p, const QgsVectorLayer *vlayer, const QgsExpressionContext &context ) const
295 {
296   if ( !mDataDefinedProperties.isActive( p ) )
297     return -1;
298 
299   const QgsProperty property = mDataDefinedProperties.property( p );
300 
301   QString fieldName;
302   switch ( property.propertyType() )
303   {
304     case QgsProperty::InvalidProperty:
305     case QgsProperty::StaticProperty:
306       break;
307 
308     case QgsProperty::FieldBasedProperty:
309       fieldName = property.field();
310       break;
311 
312     case QgsProperty::ExpressionBasedProperty:
313     {
314       // an expression based property may still be a effectively a single field reference in the map canvas context.
315       // e.g. if it is a expression like '"some_field"', or 'case when @some_project_var = 'a' then "field_a" else "field_b" end'
316       QgsExpression expression( property.expressionString() );
317       if ( expression.prepare( &context ) )
318       {
319         const QgsExpressionNode *node = expression.rootNode()->effectiveNode();
320         if ( node->nodeType() == QgsExpressionNode::ntColumnRef )
321         {
322           const QgsExpressionNodeColumnRef *columnRef = qgis::down_cast<const QgsExpressionNodeColumnRef *>( node );
323           fieldName = columnRef->name();
324         }
325         // ok, it's not. But let's be super smart and helpful for users!
326         // maybe it's a COALESCE("some field", 'some' || 'fallback' || 'expression') type expression, where the user wants to override
327         // some labels with a value stored in a field but all others use some expression
328         else if ( node->nodeType() == QgsExpressionNode::ntFunction )
329         {
330           const QgsExpressionNodeFunction *functionNode = qgis::down_cast<const QgsExpressionNodeFunction *>( node );
331           if ( const QgsExpressionFunction *function = QgsExpression::QgsExpression::Functions()[functionNode->fnIndex()] )
332           {
333             if ( function->name() == QLatin1String( "coalesce" ) )
334             {
335               if ( const QgsExpressionNode *firstArg = functionNode->args()->list().value( 0 ) )
336               {
337                 const QgsExpressionNode *firstArgNode = firstArg->effectiveNode();
338                 if ( firstArgNode->nodeType() == QgsExpressionNode::ntColumnRef )
339                 {
340                   const QgsExpressionNodeColumnRef *columnRef = qgis::down_cast<const QgsExpressionNodeColumnRef *>( firstArgNode );
341                   fieldName = columnRef->name();
342                 }
343               }
344             }
345           }
346         }
347       }
348       break;
349     }
350   }
351 
352   if ( !fieldName.isEmpty() )
353     return vlayer->fields().lookupField( fieldName );
354   return -1;
355 }
356 
setDataDefinedValues(QgsVectorLayer * vlayer)357 void QgsLabelPropertyDialog::setDataDefinedValues( QgsVectorLayer *vlayer )
358 {
359   //loop through data defined properties and set all the GUI widget values. We can do this
360   //even if the data defined property is set to an expression, as it's useful to show
361   //users what the evaluated property is...
362 
363   QgsExpressionContext context;
364   context << QgsExpressionContextUtils::globalScope()
365           << QgsExpressionContextUtils::projectScope( QgsProject::instance() )
366           << QgsExpressionContextUtils::atlasScope( nullptr )
367           << QgsExpressionContextUtils::mapSettingsScope( QgisApp::instance()->mapCanvas()->mapSettings() )
368           << QgsExpressionContextUtils::layerScope( vlayer );
369   context.setFeature( mCurLabelFeat );
370 
371   const auto constPropertyKeys = mDataDefinedProperties.propertyKeys();
372   for ( const int key : constPropertyKeys )
373   {
374     if ( !mDataDefinedProperties.isActive( key ) )
375       continue;
376 
377     //TODO - pass expression context
378     const QVariant result = mDataDefinedProperties.value( key, context );
379     if ( !result.isValid() || result.isNull() )
380     {
381       //could not evaluate data defined value
382       continue;
383     }
384 
385     bool ok = false;
386     switch ( key )
387     {
388       case QgsPalLayerSettings::Show:
389       {
390         const int showLabel = result.toInt( &ok );
391         mShowLabelChkbx->setChecked( !ok || showLabel != 0 );
392         break;
393       }
394       case QgsPalLayerSettings::AlwaysShow:
395         mAlwaysShowChkbx->setChecked( result.toBool() );
396         break;
397 
398       case QgsPalLayerSettings::CalloutDraw:
399         mShowCalloutChkbx->setChecked( result.toBool() );
400         break;
401 
402       case QgsPalLayerSettings::LabelAllParts:
403         mLabelAllPartsCheckBox->setChecked( result.toBool() );
404         break;
405 
406       case QgsPalLayerSettings::MinimumScale:
407       {
408         const double minScale = result.toDouble( &ok );
409         if ( ok )
410         {
411           mMinScaleWidget->setScale( minScale );
412         }
413         break;
414       }
415       case QgsPalLayerSettings::MaximumScale:
416       {
417         const double maxScale = result.toDouble( &ok );
418         if ( ok )
419         {
420           mMaxScaleWidget->setScale( maxScale );
421         }
422         break;
423       }
424       case QgsPalLayerSettings::BufferSize:
425       {
426         const double bufferSize = result.toDouble( &ok );
427         if ( ok )
428         {
429           mBufferSizeSpinBox->setValue( bufferSize );
430         }
431         break;
432       }
433       case QgsPalLayerSettings::PositionX:
434       {
435         const double posX = result.toDouble( &ok );
436         if ( ok )
437         {
438           mXCoordSpinBox->setValue( posX );
439         }
440         break;
441       }
442       case QgsPalLayerSettings::PositionY:
443       {
444         const double posY = result.toDouble( &ok );
445         if ( ok )
446         {
447           mYCoordSpinBox->setValue( posY );
448         }
449         break;
450       }
451       case QgsPalLayerSettings::LabelDistance:
452       {
453         const double labelDist = result.toDouble( &ok );
454         if ( ok )
455         {
456           mLabelDistanceSpinBox->setValue( labelDist );
457         }
458         break;
459       }
460       case QgsPalLayerSettings::MultiLineAlignment:
461         mMultiLineAlignComboBox->setCurrentIndex( mMultiLineAlignComboBox->findData( result.toString(), Qt::UserRole, Qt::MatchFixedString ) );
462         break;
463       case QgsPalLayerSettings::Hali:
464         mHaliComboBox->setCurrentIndex( mHaliComboBox->findData( result.toString(), Qt::UserRole, Qt::MatchFixedString ) );
465         break;
466       case QgsPalLayerSettings::Vali:
467         mValiComboBox->setCurrentIndex( mValiComboBox->findData( result.toString(), Qt::UserRole, Qt::MatchFixedString ) );
468         break;
469       case QgsPalLayerSettings::BufferColor:
470         mBufferColorButton->setColor( QColor( result.toString() ) );
471         break;
472 
473       case QgsPalLayerSettings::BufferDraw:
474         mBufferDrawChkbx->setChecked( result.toBool() );
475         break;
476 
477       case QgsPalLayerSettings::Color:
478         mFontColorButton->setColor( QColor( result.toString() ) );
479         break;
480       case QgsPalLayerSettings::LabelRotation:
481       {
482         const double rot = result.toDouble( &ok );
483         if ( ok )
484         {
485           mRotationSpinBox->setValue( rot );
486         }
487         break;
488       }
489 
490       case QgsPalLayerSettings::Size:
491       {
492         const double size = result.toDouble( &ok );
493         if ( ok )
494         {
495           mFontSizeSpinBox->setValue( size );
496         }
497         else
498         {
499           mFontSizeSpinBox->setValue( 0 );
500         }
501         break;
502       }
503       default:
504         break;
505     }
506   }
507   enableWidgetsForPinnedLabels();
508 }
509 
enableDataDefinedWidgets(QgsVectorLayer * vlayer)510 void QgsLabelPropertyDialog::enableDataDefinedWidgets( QgsVectorLayer *vlayer )
511 {
512   QgsExpressionContext context = mCanvas->createExpressionContext();
513   context.appendScope( vlayer->createExpressionContextScope() );
514 
515   //loop through data defined properties, this time setting whether or not the widgets are enabled
516   //this can only be done for properties which are assigned to fields
517   const auto constPropertyKeys = mDataDefinedProperties.propertyKeys();
518   for ( const int key : constPropertyKeys )
519   {
520     const QgsProperty prop = mDataDefinedProperties.property( key );
521     if ( !prop || !prop.isActive() )
522     {
523       continue;
524     }
525 
526     const int ddIndex = dataDefinedColumnIndex( static_cast< QgsPalLayerSettings::Property >( key ), vlayer, context );
527     mPropertyToFieldMap[ key ] = ddIndex;
528     if ( ddIndex < 0 )
529       continue; // can only modify attributes with an active data definition of a mapped field
530 
531     switch ( key )
532     {
533       case QgsPalLayerSettings::Show:
534         mShowLabelChkbx->setEnabled( true );
535         break;
536       case QgsPalLayerSettings::AlwaysShow:
537         mAlwaysShowChkbx->setEnabled( true );
538         break;
539       case QgsPalLayerSettings::MinimumScale:
540         mMinScaleWidget->setEnabled( true );
541         break;
542       case QgsPalLayerSettings::MaximumScale:
543         mMaxScaleWidget->setEnabled( true );
544         break;
545       case QgsPalLayerSettings::BufferSize:
546         mBufferSizeSpinBox->setEnabled( true );
547         break;
548       case QgsPalLayerSettings::PositionX:
549         mXCoordSpinBox->setEnabled( true );
550         break;
551       case QgsPalLayerSettings::PositionY:
552         mYCoordSpinBox->setEnabled( true );
553         break;
554       case QgsPalLayerSettings::LabelDistance:
555         mLabelDistanceSpinBox->setEnabled( true );
556         break;
557       case QgsPalLayerSettings::MultiLineAlignment:
558         mMultiLineAlignComboBox->setEnabled( true );
559         break;
560       case QgsPalLayerSettings::Hali:
561         mCanSetHAlignment = true;
562         mHaliComboBox->setEnabled( mIsPinned );
563         break;
564       case QgsPalLayerSettings::Vali:
565         mCanSetVAlignment = true;
566         mValiComboBox->setEnabled( mIsPinned );
567         break;
568       case QgsPalLayerSettings::BufferColor:
569         mBufferColorButton->setEnabled( true );
570         break;
571       case QgsPalLayerSettings::BufferDraw:
572         mBufferDrawChkbx->setEnabled( true );
573         break;
574       case QgsPalLayerSettings::Color:
575         mFontColorButton->setEnabled( true );
576         break;
577       case QgsPalLayerSettings::LabelRotation:
578         mRotationSpinBox->setEnabled( true );
579         break;
580       //font related properties
581       case QgsPalLayerSettings::Family:
582         mFontFamilyCmbBx->setEnabled( true );
583         break;
584       case QgsPalLayerSettings::FontStyle:
585         mFontStyleCmbBx->setEnabled( true );
586         break;
587       case QgsPalLayerSettings::Underline:
588         mFontUnderlineBtn->setEnabled( true );
589         break;
590       case QgsPalLayerSettings::Strikeout:
591         mFontStrikethroughBtn->setEnabled( true );
592         break;
593       case QgsPalLayerSettings::Bold:
594         mFontBoldBtn->setEnabled( true );
595         break;
596       case QgsPalLayerSettings::Italic:
597         mFontItalicBtn->setEnabled( true );
598         break;
599       case QgsPalLayerSettings::Size:
600         mFontSizeSpinBox->setEnabled( true );
601         break;
602       case QgsPalLayerSettings::CalloutDraw:
603         mShowCalloutChkbx->setEnabled( true );
604         break;
605       case QgsPalLayerSettings::LabelAllParts:
606         mLabelAllPartsCheckBox->setEnabled( true );
607         break;
608       default:
609         break;
610     }
611   }
612 }
613 
updateFont(const QFont & font,bool block)614 void QgsLabelPropertyDialog::updateFont( const QFont &font, bool block )
615 {
616   // update background reference font
617   if ( font != mLabelFont )
618   {
619     mLabelFont = font;
620   }
621 
622   if ( block )
623     blockElementSignals( true );
624 
625   mFontFamilyCmbBx->setCurrentFont( mLabelFont );
626   populateFontStyleComboBox();
627   mFontUnderlineBtn->setChecked( mLabelFont.underline() );
628   mFontStrikethroughBtn->setChecked( mLabelFont.strikeOut() );
629   mFontBoldBtn->setChecked( mLabelFont.bold() );
630   mFontItalicBtn->setChecked( mLabelFont.italic() );
631   if ( block )
632     blockElementSignals( false );
633 }
634 
populateFontStyleComboBox()635 void QgsLabelPropertyDialog::populateFontStyleComboBox()
636 {
637   mFontStyleCmbBx->clear();
638   const auto constFamily = mFontDB.styles( mLabelFont.family() );
639   for ( const QString &style : constFamily )
640   {
641     mFontStyleCmbBx->addItem( style );
642   }
643 
644   int curIndx = 0;
645   const int stylIndx = mFontStyleCmbBx->findText( mFontDB.styleString( mLabelFont ) );
646   if ( stylIndx > -1 )
647   {
648     curIndx = stylIndx;
649   }
650 
651   mFontStyleCmbBx->setCurrentIndex( curIndx );
652 }
653 
fillMultiLineAlignComboBox()654 void QgsLabelPropertyDialog::fillMultiLineAlignComboBox()
655 {
656   mMultiLineAlignComboBox->addItem( tr( "Layer Default" ), "" );
657   mMultiLineAlignComboBox->addItem( tr( "Left" ), "Left" );
658   mMultiLineAlignComboBox->addItem( tr( "Center" ), "Center" );
659   mMultiLineAlignComboBox->addItem( tr( "Right" ), "Right" );
660   mMultiLineAlignComboBox->addItem( tr( "Justify" ), "Justify" );
661 }
662 
fillHaliComboBox()663 void QgsLabelPropertyDialog::fillHaliComboBox()
664 {
665   mHaliComboBox->addItem( tr( "Left" ), "Left" );
666   mHaliComboBox->addItem( tr( "Center" ), "Center" );
667   mHaliComboBox->addItem( tr( "Right" ), "Right" );
668 }
669 
fillValiComboBox()670 void QgsLabelPropertyDialog::fillValiComboBox()
671 {
672   mValiComboBox->addItem( tr( "Bottom" ), "Bottom" );
673   mValiComboBox->addItem( tr( "Base" ), "Base" );
674   mValiComboBox->addItem( tr( "Half" ), "Half" );
675   mValiComboBox->addItem( tr( "Cap" ), "Cap" );
676   mValiComboBox->addItem( tr( "Top" ), "Top" );
677 }
678 
mShowLabelChkbx_toggled(bool chkd)679 void QgsLabelPropertyDialog::mShowLabelChkbx_toggled( bool chkd )
680 {
681   insertChangedValue( QgsPalLayerSettings::Show, ( chkd ? 1 : 0 ) );
682 }
683 
mAlwaysShowChkbx_toggled(bool chkd)684 void QgsLabelPropertyDialog::mAlwaysShowChkbx_toggled( bool chkd )
685 {
686   insertChangedValue( QgsPalLayerSettings::AlwaysShow, ( chkd ? 1 : 0 ) );
687 }
688 
labelAllPartsToggled(bool checked)689 void QgsLabelPropertyDialog::labelAllPartsToggled( bool checked )
690 {
691   insertChangedValue( QgsPalLayerSettings::LabelAllParts, ( checked ? 1 : 0 ) );
692 }
693 
showCalloutToggled(bool chkd)694 void QgsLabelPropertyDialog::showCalloutToggled( bool chkd )
695 {
696   insertChangedValue( QgsPalLayerSettings::CalloutDraw, ( chkd ? 1 : 0 ) );
697 }
698 
bufferDrawToggled(bool chkd)699 void QgsLabelPropertyDialog::bufferDrawToggled( bool chkd )
700 {
701   insertChangedValue( QgsPalLayerSettings::BufferDraw, ( chkd ? 1 : 0 ) );
702 }
703 
minScaleChanged(double scale)704 void QgsLabelPropertyDialog::minScaleChanged( double scale )
705 {
706   insertChangedValue( QgsPalLayerSettings::MinimumScale, scale );
707 }
708 
maxScaleChanged(double scale)709 void QgsLabelPropertyDialog::maxScaleChanged( double scale )
710 {
711   insertChangedValue( QgsPalLayerSettings::MaximumScale, scale );
712 }
713 
mLabelDistanceSpinBox_valueChanged(double d)714 void QgsLabelPropertyDialog::mLabelDistanceSpinBox_valueChanged( double d )
715 {
716   QVariant distance( d );
717   if ( d < 0 )
718   {
719     //null value so that distance is reset to default
720     distance.clear();
721   }
722   insertChangedValue( QgsPalLayerSettings::LabelDistance, distance );
723 }
724 
mXCoordSpinBox_valueChanged(double d)725 void QgsLabelPropertyDialog::mXCoordSpinBox_valueChanged( double d )
726 {
727   QVariant x( d );
728   if ( d < mXCoordSpinBox->minimum() + mXCoordSpinBox->singleStep() )
729   {
730     //null value
731     x.clear();
732   }
733 
734   insertChangedValue( QgsPalLayerSettings::PositionX, x );
735   enableWidgetsForPinnedLabels();
736 }
737 
mYCoordSpinBox_valueChanged(double d)738 void QgsLabelPropertyDialog::mYCoordSpinBox_valueChanged( double d )
739 {
740   QVariant y( d );
741   if ( d < mYCoordSpinBox->minimum() + mYCoordSpinBox->singleStep() )
742   {
743     //null value
744     y.clear();
745   }
746   insertChangedValue( QgsPalLayerSettings::PositionY, y );
747   enableWidgetsForPinnedLabels();
748 }
749 
mFontFamilyCmbBx_currentFontChanged(const QFont & f)750 void QgsLabelPropertyDialog::mFontFamilyCmbBx_currentFontChanged( const QFont &f )
751 {
752   mLabelFont.setFamily( f.family() );
753   updateFont( mLabelFont );
754   insertChangedValue( QgsPalLayerSettings::Family, f.family() );
755 }
756 
mFontStyleCmbBx_currentIndexChanged(const QString & text)757 void QgsLabelPropertyDialog::mFontStyleCmbBx_currentIndexChanged( const QString &text )
758 {
759   QgsFontUtils::updateFontViaStyle( mLabelFont, text );
760   updateFont( mLabelFont );
761   insertChangedValue( QgsPalLayerSettings::FontStyle, text );
762 }
763 
mFontUnderlineBtn_toggled(bool ckd)764 void QgsLabelPropertyDialog::mFontUnderlineBtn_toggled( bool ckd )
765 {
766   mLabelFont.setUnderline( ckd );
767   updateFont( mLabelFont );
768   insertChangedValue( QgsPalLayerSettings::Underline, ckd );
769 }
770 
mFontStrikethroughBtn_toggled(bool ckd)771 void QgsLabelPropertyDialog::mFontStrikethroughBtn_toggled( bool ckd )
772 {
773   mLabelFont.setStrikeOut( ckd );
774   updateFont( mLabelFont );
775   insertChangedValue( QgsPalLayerSettings::Strikeout, ckd );
776 }
777 
mFontBoldBtn_toggled(bool ckd)778 void QgsLabelPropertyDialog::mFontBoldBtn_toggled( bool ckd )
779 {
780   mLabelFont.setBold( ckd );
781   updateFont( mLabelFont );
782   insertChangedValue( QgsPalLayerSettings::Bold, ckd );
783 }
784 
mFontItalicBtn_toggled(bool ckd)785 void QgsLabelPropertyDialog::mFontItalicBtn_toggled( bool ckd )
786 {
787   mLabelFont.setItalic( ckd );
788   updateFont( mLabelFont );
789   insertChangedValue( QgsPalLayerSettings::Italic, ckd );
790 }
791 
mFontSizeSpinBox_valueChanged(double d)792 void QgsLabelPropertyDialog::mFontSizeSpinBox_valueChanged( double d )
793 {
794   QVariant size( d );
795   if ( d <= 0 )
796   {
797     //null value so that font size is reset to default
798     size.clear();
799   }
800   insertChangedValue( QgsPalLayerSettings::Size, size );
801 }
802 
mBufferSizeSpinBox_valueChanged(double d)803 void QgsLabelPropertyDialog::mBufferSizeSpinBox_valueChanged( double d )
804 {
805   QVariant size( d );
806   if ( d < 0 )
807   {
808     //null value so that size is reset to default
809     size.clear();
810   }
811   insertChangedValue( QgsPalLayerSettings::BufferSize, size );
812 }
813 
mRotationSpinBox_valueChanged(double d)814 void QgsLabelPropertyDialog::mRotationSpinBox_valueChanged( double d )
815 {
816   QVariant rotation( d );
817   if ( d < 0 )
818   {
819     //null value so that size is reset to default
820     rotation.clear();
821   }
822   insertChangedValue( QgsPalLayerSettings::LabelRotation, rotation );
823 }
824 
mFontColorButton_colorChanged(const QColor & color)825 void QgsLabelPropertyDialog::mFontColorButton_colorChanged( const QColor &color )
826 {
827   insertChangedValue( QgsPalLayerSettings::Color, color.name() );
828 }
829 
mBufferColorButton_colorChanged(const QColor & color)830 void QgsLabelPropertyDialog::mBufferColorButton_colorChanged( const QColor &color )
831 {
832   insertChangedValue( QgsPalLayerSettings::BufferColor, color.name() );
833 }
834 
mMultiLineAlignComboBox_currentIndexChanged(const int index)835 void QgsLabelPropertyDialog::mMultiLineAlignComboBox_currentIndexChanged( const int index )
836 {
837   insertChangedValue( QgsPalLayerSettings::MultiLineAlignment, mMultiLineAlignComboBox->itemData( index ) );
838 }
839 
mHaliComboBox_currentIndexChanged(const int index)840 void QgsLabelPropertyDialog::mHaliComboBox_currentIndexChanged( const int index )
841 {
842   insertChangedValue( QgsPalLayerSettings::Hali, mHaliComboBox->itemData( index ) );
843 }
844 
mValiComboBox_currentIndexChanged(const int index)845 void QgsLabelPropertyDialog::mValiComboBox_currentIndexChanged( const int index )
846 {
847   insertChangedValue( QgsPalLayerSettings::Vali, mValiComboBox->itemData( index ) );
848 }
849 
mLabelTextLineEdit_textChanged(const QString & text)850 void QgsLabelPropertyDialog::mLabelTextLineEdit_textChanged( const QString &text )
851 {
852   if ( mCurLabelField != -1 )
853   {
854     mChangedProperties.insert( mCurLabelField, text );
855   }
856 }
857 
insertChangedValue(QgsPalLayerSettings::Property p,const QVariant & value)858 void QgsLabelPropertyDialog::insertChangedValue( QgsPalLayerSettings::Property p, const QVariant &value )
859 {
860   if ( mDataDefinedProperties.isActive( p ) )
861   {
862     const QgsProperty prop = mDataDefinedProperties.property( p );
863     if ( const int index = mPropertyToFieldMap.value( p ); index >= 0 )
864     {
865       mChangedProperties.insert( index, value );
866     }
867   }
868 }
869 
enableWidgetsForPinnedLabels()870 void QgsLabelPropertyDialog::enableWidgetsForPinnedLabels()
871 {
872   const bool pinned = mXCoordSpinBox->value() >= ( mXCoordSpinBox->minimum() + mXCoordSpinBox->singleStep() ) &&
873                       mYCoordSpinBox->value() >= ( mYCoordSpinBox->minimum() + mYCoordSpinBox->singleStep() );
874 
875   mHaliComboBox->setEnabled( pinned && mCanSetHAlignment );
876   mValiComboBox->setEnabled( pinned && mCanSetVAlignment );
877 
878   if ( !pinned )
879   {
880     mHaliComboBox->setToolTip( tr( "Alignment can only be set for pinned labels" ) );
881     mValiComboBox->setToolTip( tr( "Alignment can only be set for pinned labels" ) );
882   }
883   else
884   {
885     mHaliComboBox->setToolTip( QString() );
886     mValiComboBox->setToolTip( QString() );
887   }
888 }
889 
showHelp()890 void QgsLabelPropertyDialog::showHelp()
891 {
892   QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#the-label-toolbar" ) );
893 }
894