1 /***************************************************************************
2                           qgsdlgvectorlayerproperties.cpp
3                    Unified property dialog for vector layers
4                              -------------------
5     begin                : 2004-01-28
6     copyright            : (C) 2004 by Gary E.Sherman
7     email                : sherman at mrcc.com
8 ***************************************************************************/
9 
10 /***************************************************************************
11  *                                                                         *
12  *   This program is free software; you can redistribute it and/or modify  *
13  *   it under the terms of the GNU General Public License as published by  *
14  *   the Free Software Foundation; either version 2 of the License, or     *
15  *   (at your option) any later version.                                   *
16  *                                                                         *
17  ***************************************************************************/
18 
19 #include <memory>
20 #include <limits>
21 
22 #include "qgsactionmanager.h"
23 #include "qgsjoindialog.h"
24 #include "qgswmsdimensiondialog.h"
25 #include "qgsapplication.h"
26 #include "qgsattributeactiondialog.h"
27 #include "qgscoordinatetransform.h"
28 #include "qgsdatumtransformdialog.h"
29 #include "qgsdiagramproperties.h"
30 #include "qgsdiagramrenderer.h"
31 #include "qgsexpressionbuilderdialog.h"
32 #include "qgsfieldcalculator.h"
33 #include "qgssourcefieldsproperties.h"
34 #include "qgsattributesformproperties.h"
35 #include "qgslabelingwidget.h"
36 #include "qgsprojectionselectiondialog.h"
37 #include "qgslogger.h"
38 #include "qgsmapcanvas.h"
39 #include "qgsmaplayerconfigwidgetfactory.h"
40 #include "qgsmaplayerstyleguiutils.h"
41 #include "qgsmetadatawidget.h"
42 #include "qgsnative.h"
43 #include "qgsproject.h"
44 #include "qgsvectorlayer.h"
45 #include "qgsvectorlayerjoininfo.h"
46 #include "qgsvectorlayerproperties.h"
47 #include "qgsconfig.h"
48 #include "qgsvectordataprovider.h"
49 #include "qgsquerybuilder.h"
50 #include "qgsdatasourceuri.h"
51 #include "qgsrenderer.h"
52 #include "qgsexpressioncontext.h"
53 #include "qgssettings.h"
54 #include "qgsrendererpropertiesdialog.h"
55 #include "qgsstyle.h"
56 #include "qgsauxiliarystorage.h"
57 #include "qgsnewauxiliarylayerdialog.h"
58 #include "qgsnewauxiliaryfielddialog.h"
59 #include "qgslabelinggui.h"
60 #include "qgssymbollayer.h"
61 #include "qgsgeometryoptions.h"
62 #include "qgsvectorlayersavestyledialog.h"
63 #include "qgsmaplayerloadstyledialog.h"
64 #include "qgsmessagebar.h"
65 #include "qgssymbolwidgetcontext.h"
66 #include "qgsexpressioncontextutils.h"
67 #include "qgsmaskingwidget.h"
68 #include "qgsvectorlayertemporalpropertieswidget.h"
69 
70 #include "layertree/qgslayertreelayer.h"
71 #include "qgslayertree.h"
72 
73 #include <QDesktopServices>
74 #include <QMessageBox>
75 #include <QDir>
76 #include <QFile>
77 #include <QFileDialog>
78 #include <QFileInfo>
79 #include <QFontDialog>
80 #include <QComboBox>
81 #include <QCheckBox>
82 #include <QHeaderView>
83 #include <QColorDialog>
84 #include <QMenu>
85 #include <QUrl>
86 
87 #include "qgsrendererpropertiesdialog.h"
88 #include "qgsstyle.h"
89 
90 
QgsVectorLayerProperties(QgsMapCanvas * canvas,QgsMessageBar * messageBar,QgsVectorLayer * lyr,QWidget * parent,Qt::WindowFlags fl)91 QgsVectorLayerProperties::QgsVectorLayerProperties(
92   QgsMapCanvas *canvas,
93   QgsMessageBar *messageBar,
94   QgsVectorLayer *lyr,
95   QWidget *parent,
96   Qt::WindowFlags fl
97 )
98   : QgsOptionsDialogBase( QStringLiteral( "VectorLayerProperties" ), parent, fl )
99   , mCanvas( canvas )
100   , mMessageBar( messageBar )
101   , mLayer( lyr )
102   , mOriginalSubsetSQL( lyr->subsetString() )
103 {
104   setupUi( this );
105   connect( mLayerOrigNameLineEdit, &QLineEdit::textEdited, this, &QgsVectorLayerProperties::mLayerOrigNameLineEdit_textEdited );
106   connect( pbnQueryBuilder, &QPushButton::clicked, this, &QgsVectorLayerProperties::pbnQueryBuilder_clicked );
107   connect( pbnIndex, &QPushButton::clicked, this, &QgsVectorLayerProperties::pbnIndex_clicked );
108   connect( mCrsSelector, &QgsProjectionSelectionWidget::crsChanged, this, &QgsVectorLayerProperties::mCrsSelector_crsChanged );
109   connect( pbnUpdateExtents, &QPushButton::clicked, this, &QgsVectorLayerProperties::pbnUpdateExtents_clicked );
110   connect( mButtonAddJoin, &QPushButton::clicked, this, &QgsVectorLayerProperties::mButtonAddJoin_clicked );
111   connect( mButtonEditJoin, &QPushButton::clicked, this, &QgsVectorLayerProperties::mButtonEditJoin_clicked );
112   connect( mJoinTreeWidget, &QTreeWidget::itemDoubleClicked, this, &QgsVectorLayerProperties::mJoinTreeWidget_itemDoubleClicked );
113   connect( mButtonRemoveJoin, &QPushButton::clicked, this, &QgsVectorLayerProperties::mButtonRemoveJoin_clicked );
114   connect( mButtonAddWmsDimension, &QPushButton::clicked, this, &QgsVectorLayerProperties::mButtonAddWmsDimension_clicked );
115   connect( mButtonEditWmsDimension, &QPushButton::clicked, this, &QgsVectorLayerProperties::mButtonEditWmsDimension_clicked );
116   connect( mWmsDimensionsTreeWidget, &QTreeWidget::itemDoubleClicked, this, &QgsVectorLayerProperties::mWmsDimensionsTreeWidget_itemDoubleClicked );
117   connect( mButtonRemoveWmsDimension, &QPushButton::clicked, this, &QgsVectorLayerProperties::mButtonRemoveWmsDimension_clicked );
118   connect( mSimplifyDrawingGroupBox, &QGroupBox::toggled, this, &QgsVectorLayerProperties::mSimplifyDrawingGroupBox_toggled );
119   connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsVectorLayerProperties::showHelp );
120 
121   // QgsOptionsDialogBase handles saving/restoring of geometry, splitter and current tab states,
122   // switching vertical tabs between icon/text to icon-only modes (splitter collapsed to left),
123   // and connecting QDialogButtonBox's accepted/rejected signals to dialog's accept/reject slots
124   initOptionsBase( false );
125 
126   mBtnStyle = new QPushButton( tr( "Style" ), this );
127   QMenu *menuStyle = new QMenu( this );
128   mActionLoadStyle = menuStyle->addAction( tr( "Load Style…" ) );
129   connect( mActionLoadStyle, &QAction::triggered, this, &QgsVectorLayerProperties::loadStyle );
130 
131   // If we have multiple styles, offer an option to save them at once
132   if ( lyr->styleManager()->styles().count() > 1 )
133   {
134     mActionSaveStyle = menuStyle->addAction( tr( "Save Current Style…" ) );
135     mActionSaveMultipleStyles = menuStyle->addAction( tr( "Save All Styles…" ) );
136     connect( mActionSaveMultipleStyles, &QAction::triggered, this, &QgsVectorLayerProperties::saveMultipleStylesAs );
137   }
138   else
139   {
140     mActionSaveStyle = menuStyle->addAction( tr( "Save Style…" ) );
141   }
142   connect( mActionSaveStyle, &QAction::triggered, this, &QgsVectorLayerProperties::saveStyleAs );
143 
144   menuStyle->addSeparator();
145   menuStyle->addAction( tr( "Save as Default" ), this, &QgsVectorLayerProperties::saveDefaultStyle_clicked );
146   menuStyle->addAction( tr( "Restore Default" ), this, &QgsVectorLayerProperties::loadDefaultStyle_clicked );
147   mBtnStyle->setMenu( menuStyle );
148   connect( menuStyle, &QMenu::aboutToShow, this, &QgsVectorLayerProperties::aboutToShowStyleMenu );
149   buttonBox->addButton( mBtnStyle, QDialogButtonBox::ResetRole );
150 
151   mBtnMetadata = new QPushButton( tr( "Metadata" ), this );
152   QMenu *menuMetadata = new QMenu( this );
153   mActionLoadMetadata = menuMetadata->addAction( tr( "Load Metadata…" ), this, &QgsVectorLayerProperties::loadMetadata );
154   mActionSaveMetadataAs = menuMetadata->addAction( tr( "Save Metadata…" ), this, &QgsVectorLayerProperties::saveMetadataAs );
155   menuMetadata->addSeparator();
156   menuMetadata->addAction( tr( "Save as Default" ), this, &QgsVectorLayerProperties::saveDefaultMetadata );
157   menuMetadata->addAction( tr( "Restore Default" ), this, &QgsVectorLayerProperties::loadDefaultMetadata );
158   mBtnMetadata->setMenu( menuMetadata );
159   buttonBox->addButton( mBtnMetadata, QDialogButtonBox::ResetRole );
160 
161   connect( lyr->styleManager(), &QgsMapLayerStyleManager::currentStyleChanged, this, &QgsVectorLayerProperties::syncToLayer );
162 
163   connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsVectorLayerProperties::apply );
164   connect( this, &QDialog::accepted, this, &QgsVectorLayerProperties::apply );
165   connect( this, &QDialog::rejected, this, &QgsVectorLayerProperties::onCancel );
166 
167   mContext << QgsExpressionContextUtils::globalScope()
168            << QgsExpressionContextUtils::projectScope( QgsProject::instance() )
169            << QgsExpressionContextUtils::atlasScope( nullptr )
170            << QgsExpressionContextUtils::mapSettingsScope( mCanvas->mapSettings() )
171            << QgsExpressionContextUtils::layerScope( mLayer );
172 
173   mMapTipExpressionFieldWidget->setLayer( lyr );
174   mMapTipExpressionFieldWidget->registerExpressionContextGenerator( this );
175   mDisplayExpressionWidget->setLayer( lyr );
176   mDisplayExpressionWidget->registerExpressionContextGenerator( this );
177 
178   connect( mInsertExpressionButton, &QAbstractButton::clicked, this, &QgsVectorLayerProperties::insertFieldOrExpression );
179 
180   if ( !mLayer )
181     return;
182 
183   QVBoxLayout *layout = nullptr;
184 
185   if ( mLayer->isSpatial() )
186   {
187     // Create the Labeling dialog tab
188     layout = new QVBoxLayout( labelingFrame );
189     layout->setContentsMargins( 0, 0, 0, 0 );
190     labelingDialog = new QgsLabelingWidget( mLayer, mCanvas, labelingFrame );
191     labelingDialog->layout()->setContentsMargins( 0, 0, 0, 0 );
192     connect( labelingDialog, &QgsLabelingWidget::auxiliaryFieldCreated, this, [ = ] { updateAuxiliaryStoragePage(); } );
193     layout->addWidget( labelingDialog );
194     labelingFrame->setLayout( layout );
195 
196     // Create the masking dialog tab
197     layout = new QVBoxLayout( mMaskingFrame );
198     layout->setContentsMargins( 0, 0, 0, 0 );
199     mMaskingWidget = new QgsMaskingWidget( mMaskingFrame );
200     mMaskingWidget->setLayer( mLayer );
201     mMaskingWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
202     layout->addWidget( mMaskingWidget );
203     mMaskingFrame->setLayout( layout );
204   }
205   else
206   {
207     labelingDialog = nullptr;
208     mOptsPage_Labels->setEnabled( false ); // disable labeling item
209     mOptsPage_Masks->setEnabled( false ); // disable masking item
210     mGeomGroupBox->setEnabled( false );
211     mGeomGroupBox->setVisible( false );
212     mCrsGroupBox->setEnabled( false );
213     mCrsGroupBox->setVisible( false );
214   }
215 
216   // Create the Actions dialog tab
217   QVBoxLayout *actionLayout = new QVBoxLayout( actionOptionsFrame );
218   actionLayout->setContentsMargins( 0, 0, 0, 0 );
219   mActionDialog = new QgsAttributeActionDialog( *mLayer->actions(), actionOptionsFrame );
220   mActionDialog->layout()->setContentsMargins( 0, 0, 0, 0 );
221   actionLayout->addWidget( mActionDialog );
222 
223   mSourceFieldsPropertiesDialog = new QgsSourceFieldsProperties( mLayer, mSourceFieldsFrame );
224   mSourceFieldsPropertiesDialog->layout()->setContentsMargins( 0, 0, 0, 0 );
225   mSourceFieldsFrame->setLayout( new QVBoxLayout( mSourceFieldsFrame ) );
226   mSourceFieldsFrame->layout()->setContentsMargins( 0, 0, 0, 0 );
227   mSourceFieldsFrame->layout()->addWidget( mSourceFieldsPropertiesDialog );
228 
229   connect( mSourceFieldsPropertiesDialog, &QgsSourceFieldsProperties::toggleEditing, this, static_cast<void ( QgsVectorLayerProperties::* )()>( &QgsVectorLayerProperties::toggleEditing ) );
230 
231   mAttributesFormPropertiesDialog = new QgsAttributesFormProperties( mLayer, mAttributesFormFrame );
232   mAttributesFormPropertiesDialog->layout()->setContentsMargins( 0, 0, 0, 0 );
233   mAttributesFormFrame->setLayout( new QVBoxLayout( mAttributesFormFrame ) );
234   mAttributesFormFrame->layout()->setContentsMargins( 0, 0, 0, 0 );
235   mAttributesFormFrame->layout()->addWidget( mAttributesFormPropertiesDialog );
236 
237   // Metadata tab, before the syncToLayer
238   QVBoxLayout *metadataLayout = new QVBoxLayout( metadataFrame );
239   metadataLayout->setContentsMargins( 0, 0, 0, 0 );
240   mMetadataWidget = new QgsMetadataWidget( this, mLayer );
241   mMetadataWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
242   mMetadataWidget->setMapCanvas( mCanvas );
243   metadataLayout->addWidget( mMetadataWidget );
244   metadataFrame->setLayout( metadataLayout );
245 
246   QVBoxLayout *temporalLayout = new QVBoxLayout( temporalFrame );
247   temporalLayout->setContentsMargins( 0, 0, 0, 0 );
248   mTemporalWidget = new QgsVectorLayerTemporalPropertiesWidget( this, mLayer );
249   temporalLayout->addWidget( mTemporalWidget );
250 
251   syncToLayer();
252 
253   if ( mLayer->dataProvider() )
254   {
255     //enable spatial index button group if supported by provider, or if one already exists
256     QgsVectorDataProvider::Capabilities capabilities = mLayer->dataProvider()->capabilities();
257     if ( !( capabilities & QgsVectorDataProvider::CreateSpatialIndex ) )
258     {
259       pbnIndex->setEnabled( false );
260     }
261     if ( mLayer->dataProvider()->hasSpatialIndex() == QgsFeatureSource::SpatialIndexPresent )
262     {
263       pbnIndex->setEnabled( false );
264       pbnIndex->setText( tr( "Spatial Index Exists" ) );
265     }
266 
267     if ( capabilities & QgsVectorDataProvider::SelectEncoding )
268     {
269       cboProviderEncoding->addItems( QgsVectorDataProvider::availableEncodings() );
270       QString enc = mLayer->dataProvider()->encoding();
271       int encindex = cboProviderEncoding->findText( enc );
272       if ( encindex < 0 )
273       {
274         cboProviderEncoding->insertItem( 0, enc );
275         encindex = 0;
276       }
277       cboProviderEncoding->setCurrentIndex( encindex );
278     }
279     else if ( mLayer->providerType() == QLatin1String( "ogr" ) )
280     {
281       // if OGR_L_TestCapability(OLCStringsAsUTF8) returns true, OGR provider encoding can be set to only UTF-8
282       // so make encoding box grayed out
283       cboProviderEncoding->addItem( mLayer->dataProvider()->encoding() );
284       cboProviderEncoding->setEnabled( false );
285     }
286     else
287     {
288       // other providers do not use mEncoding, so hide the group completely
289       mDataSourceEncodingFrame->hide();
290     }
291   }
292 
293   mCrsSelector->setCrs( mLayer->crs() );
294 
295   //insert existing join info
296   const QList< QgsVectorLayerJoinInfo > &joins = mLayer->vectorJoins();
297   for ( const QgsVectorLayerJoinInfo &join : joins )
298   {
299     addJoinToTreeWidget( join );
300   }
301 
302   mOldJoins = mLayer->vectorJoins();
303 
304   QVBoxLayout *diagLayout = new QVBoxLayout( mDiagramFrame );
305   diagLayout->setContentsMargins( 0, 0, 0, 0 );
306   diagramPropertiesDialog = new QgsDiagramProperties( mLayer, mDiagramFrame, mCanvas );
307   diagramPropertiesDialog->layout()->setContentsMargins( 0, 0, 0, 0 );
308   connect( diagramPropertiesDialog, &QgsDiagramProperties::auxiliaryFieldCreated, this, [ = ] { updateAuxiliaryStoragePage(); } );
309   diagLayout->addWidget( diagramPropertiesDialog );
310   mDiagramFrame->setLayout( diagLayout );
311 
312   // Legend tab
313   mLegendWidget->setMapCanvas( mCanvas );
314   mLegendWidget->setLayer( mLayer );
315   mLegendConfigEmbeddedWidget->setLayer( mLayer );
316 
317   // WMS Name as layer short name
318   mLayerShortNameLineEdit->setText( mLayer->shortName() );
319   // WMS Name validator
320   QValidator *shortNameValidator = new QRegExpValidator( QgsApplication::shortNameRegExp(), this );
321   mLayerShortNameLineEdit->setValidator( shortNameValidator );
322 
323   //layer title and abstract
324   mLayerTitleLineEdit->setText( mLayer->title() );
325   mLayerAbstractTextEdit->setPlainText( mLayer->abstract() );
326   mLayerKeywordListLineEdit->setText( mLayer->keywordList() );
327   mLayerDataUrlLineEdit->setText( mLayer->dataUrl() );
328   mLayerDataUrlFormatComboBox->setCurrentIndex(
329     mLayerDataUrlFormatComboBox->findText(
330       mLayer->dataUrlFormat()
331     )
332   );
333   //layer attribution and metadataUrl
334   mLayerAttributionLineEdit->setText( mLayer->attribution() );
335   mLayerAttributionUrlLineEdit->setText( mLayer->attributionUrl() );
336   mLayerMetadataUrlLineEdit->setText( mLayer->metadataUrl() );
337   mLayerMetadataUrlTypeComboBox->setCurrentIndex(
338     mLayerMetadataUrlTypeComboBox->findText(
339       mLayer->metadataUrlType()
340     )
341   );
342   mLayerMetadataUrlFormatComboBox->setCurrentIndex(
343     mLayerMetadataUrlFormatComboBox->findText(
344       mLayer->metadataUrlFormat()
345     )
346   );
347   mLayerLegendUrlLineEdit->setText( mLayer->legendUrl() );
348   mLayerLegendUrlFormatComboBox->setCurrentIndex(
349     mLayerLegendUrlFormatComboBox->findText(
350       mLayer->legendUrlFormat()
351     )
352   );
353 
354   //insert existing dimension info
355   const QList<QgsVectorLayerServerProperties::WmsDimensionInfo> &wmsDims = mLayer->serverProperties()->wmsDimensions();
356   for ( const QgsVectorLayerServerProperties::WmsDimensionInfo &dim : wmsDims )
357   {
358     addWmsDimensionInfoToTreeWidget( dim );
359   }
360 
361   QString myStyle = QgsApplication::reportStyleSheet();
362   myStyle.append( QStringLiteral( "body { margin: 10px; }\n " ) );
363   teMetadataViewer->clear();
364   teMetadataViewer->document()->setDefaultStyleSheet( myStyle );
365   teMetadataViewer->setHtml( htmlMetadata() );
366   teMetadataViewer->setOpenLinks( false );
367   connect( teMetadataViewer, &QTextBrowser::anchorClicked, this, &QgsVectorLayerProperties::urlClicked );
368   mMetadataFilled = true;
369 
370   QgsSettings settings;
371   // if dialog hasn't been opened/closed yet, default to Styles tab, which is used most often
372   // this will be read by restoreOptionsBaseUi()
373   if ( !settings.contains( QStringLiteral( "/Windows/VectorLayerProperties/tab" ) ) )
374   {
375     settings.setValue( QStringLiteral( "Windows/VectorLayerProperties/tab" ),
376                        mOptStackedWidget->indexOf( mOptsPage_Style ) );
377   }
378 
379   QString title = tr( "Layer Properties — %1" ).arg( mLayer->name() );
380   if ( !mLayer->styleManager()->isDefault( mLayer->styleManager()->currentStyle() ) )
381     title += QStringLiteral( " (%1)" ).arg( mLayer->styleManager()->currentStyle() );
382   restoreOptionsBaseUi( title );
383 
384   QList<QgsMapLayer *> dependencySources;
385   const QSet<QgsMapLayerDependency> constDependencies = mLayer->dependencies();
386   for ( const QgsMapLayerDependency &dep : constDependencies )
387   {
388     QgsMapLayer *layer = QgsProject::instance()->mapLayer( dep.layerId() );
389     if ( layer )
390       dependencySources << layer;
391   }
392 
393   mLayersDependenciesTreeModel = new QgsLayerTreeFilterProxyModel( this );
394   mLayersDependenciesTreeModel->setLayerTreeModel( new QgsLayerTreeModel( QgsProject::instance()->layerTreeRoot(), mLayersDependenciesTreeModel ) );
395   mLayersDependenciesTreeModel->setCheckedLayers( dependencySources );
396   connect( QgsProject::instance(), &QObject::destroyed, this, [ = ] {mLayersDependenciesTreeView->setModel( nullptr );} );
397   mLayersDependenciesTreeView->setModel( mLayersDependenciesTreeModel );
398 
399   connect( mRefreshLayerCheckBox, &QCheckBox::toggled, mRefreshLayerIntervalSpinBox, &QDoubleSpinBox::setEnabled );
400 
401   // auxiliary layer
402   QMenu *menu = new QMenu( this );
403 
404   mAuxiliaryLayerActionNew = new QAction( tr( "Create" ), this );
405   menu->addAction( mAuxiliaryLayerActionNew );
406   connect( mAuxiliaryLayerActionNew, &QAction::triggered, this, &QgsVectorLayerProperties::onAuxiliaryLayerNew );
407 
408   mAuxiliaryLayerActionClear = new QAction( tr( "Clear" ), this );
409   menu->addAction( mAuxiliaryLayerActionClear );
410   connect( mAuxiliaryLayerActionClear, &QAction::triggered, this, &QgsVectorLayerProperties::onAuxiliaryLayerClear );
411 
412   mAuxiliaryLayerActionDelete = new QAction( tr( "Delete" ), this );
413   menu->addAction( mAuxiliaryLayerActionDelete );
414   connect( mAuxiliaryLayerActionDelete, &QAction::triggered, this, &QgsVectorLayerProperties::onAuxiliaryLayerDelete );
415 
416   mAuxiliaryLayerActionExport = new QAction( tr( "Export" ), this );
417   menu->addAction( mAuxiliaryLayerActionExport );
418   connect( mAuxiliaryLayerActionExport, &QAction::triggered, this, [ = ] { emit exportAuxiliaryLayer( mLayer->auxiliaryLayer() ); } );
419 
420   mAuxiliaryStorageActions->setMenu( menu );
421 
422   connect( mAuxiliaryStorageFieldsDeleteBtn, &QPushButton::clicked, this, &QgsVectorLayerProperties::onAuxiliaryLayerDeleteField );
423   connect( mAuxiliaryStorageFieldsAddBtn, &QPushButton::clicked, this, &QgsVectorLayerProperties::onAuxiliaryLayerAddField );
424 
425   updateAuxiliaryStoragePage();
426 
427   mOptsPage_Information->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#information-properties" ) );
428   mOptsPage_Source->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#source-properties" ) );
429   mOptsPage_Style->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#symbology-properties" ) );
430   mOptsPage_Labels->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#labels-properties" ) );
431   mOptsPage_Masks->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#masks-properties" ) );
432   mOptsPage_Diagrams->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#diagrams-properties" ) );
433   mOptsPage_SourceFields->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#fields-properties" ) );
434   mOptsPage_AttributesForm->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#attributes-form-properties" ) );
435   mOptsPage_Joins->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#joins-properties" ) );
436   mOptsPage_AuxiliaryStorage->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#auxiliary-storage-properties" ) );
437   mOptsPage_Actions->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#actions-properties" ) );
438   mOptsPage_Display->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#display-properties" ) );
439   mOptsPage_Rendering->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#rendering-properties" ) );
440   mOptsPage_Variables->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#variables-properties" ) );
441   mOptsPage_Metadata->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#metadata-properties" ) );
442   mOptsPage_DataDependencies->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#dependencies-properties" ) ) ;
443   mOptsPage_Legend->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#legend-properties" ) );
444   mOptsPage_Server->setProperty( "helpPage", QStringLiteral( "working_with_vector/vector_properties.html#qgis-server-properties" ) );
445 
446 
447   optionsStackedWidget_CurrentChanged( mOptStackedWidget->currentIndex() );
448 }
449 
toggleEditing()450 void QgsVectorLayerProperties::toggleEditing()
451 {
452   if ( !mLayer )
453     return;
454 
455   emit toggleEditing( mLayer );
456 
457   setPbnQueryBuilderEnabled();
458 }
459 
addPropertiesPageFactory(QgsMapLayerConfigWidgetFactory * factory)460 void QgsVectorLayerProperties::addPropertiesPageFactory( QgsMapLayerConfigWidgetFactory *factory )
461 {
462   if ( !factory->supportsLayer( mLayer ) || !factory->supportLayerPropertiesDialog() )
463   {
464     return;
465   }
466 
467   QgsMapLayerConfigWidget *page = factory->createWidget( mLayer, nullptr, false, this );
468   mLayerPropertiesPages << page;
469 
470   const QString beforePage = factory->layerPropertiesPagePositionHint();
471   if ( beforePage.isEmpty() )
472     addPage( factory->title(), factory->title(), factory->icon(), page );
473   else
474     insertPage( factory->title(), factory->title(), factory->icon(), page, beforePage );
475 }
476 
insertFieldOrExpression()477 void QgsVectorLayerProperties::insertFieldOrExpression()
478 {
479   // Convert the selected field to an expression and
480   // insert it into the action at the cursor position
481   QString expression = QStringLiteral( "[% " );
482   expression += mMapTipExpressionFieldWidget->asExpression();
483   expression += QLatin1String( " %]" );
484 
485   mMapTipWidget->insertText( expression );
486 }
487 
488 // in raster props, this method is called sync()
syncToLayer()489 void QgsVectorLayerProperties::syncToLayer()
490 {
491   // populate the general information
492   mLayerOrigNameLineEdit->setText( mLayer->name() );
493   txtDisplayName->setText( mLayer->name() );
494 
495   //see if we are dealing with a pg layer here
496   mSubsetGroupBox->setEnabled( true );
497   txtSubsetSQL->setText( mLayer->subsetString() );
498   // if the user is allowed to type an adhoc query, the app will crash if the query
499   // is bad. For this reason, the sql box is disabled and the query must be built
500   // using the query builder, either by typing it in by hand or using the buttons, etc
501   // on the builder. If the ability to enter a query directly into the box is required,
502   // a mechanism to check it must be implemented.
503   txtSubsetSQL->setReadOnly( true );
504   txtSubsetSQL->setCaretWidth( 0 );
505   txtSubsetSQL->setCaretLineVisible( false );
506   setPbnQueryBuilderEnabled();
507 
508   mMapTipWidget->setText( mLayer->mapTipTemplate() );
509   mDisplayExpressionWidget->setField( mLayer->displayExpression() );
510 
511   // set up the scale based layer visibility stuff....
512   mScaleRangeWidget->setScaleRange( mLayer->minimumScale(), mLayer->maximumScale() );
513   mScaleVisibilityGroupBox->setChecked( mLayer->hasScaleBasedVisibility() );
514   mScaleRangeWidget->setMapCanvas( mCanvas );
515 
516   // get simplify drawing configuration
517   const QgsVectorSimplifyMethod &simplifyMethod = mLayer->simplifyMethod();
518   mSimplifyDrawingGroupBox->setChecked( simplifyMethod.simplifyHints() != QgsVectorSimplifyMethod::NoSimplification );
519   mSimplifyDrawingSpinBox->setValue( simplifyMethod.threshold() );
520   mSimplifyDrawingSpinBox->setClearValue( 1.0 );
521 
522   QString remark = QStringLiteral( " (%1)" ).arg( tr( "Not supported" ) );
523   const QgsVectorDataProvider *provider = mLayer->dataProvider();
524   if ( !( provider && ( provider->capabilities() & QgsVectorDataProvider::SimplifyGeometries ) ) )
525   {
526     mSimplifyDrawingAtProvider->setChecked( false );
527     mSimplifyDrawingAtProvider->setEnabled( false );
528     if ( !mSimplifyDrawingAtProvider->text().endsWith( remark ) )
529       mSimplifyDrawingAtProvider->setText( mSimplifyDrawingAtProvider->text().append( remark ) );
530   }
531   else
532   {
533     mSimplifyDrawingAtProvider->setChecked( !simplifyMethod.forceLocalOptimization() );
534     mSimplifyDrawingAtProvider->setEnabled( mSimplifyDrawingGroupBox->isChecked() );
535     if ( mSimplifyDrawingAtProvider->text().endsWith( remark ) )
536     {
537       QString newText = mSimplifyDrawingAtProvider->text();
538       newText.chop( remark.size() );
539       mSimplifyDrawingAtProvider->setText( newText );
540     }
541   }
542 
543   // disable simplification for point layers, now it is not implemented
544   if ( mLayer->geometryType() == QgsWkbTypes::PointGeometry )
545   {
546     mSimplifyDrawingGroupBox->setChecked( false );
547     mSimplifyDrawingGroupBox->setEnabled( false );
548   }
549 
550   // Default local simplification algorithm
551   mSimplifyAlgorithmComboBox->addItem( tr( "Distance" ), QgsVectorSimplifyMethod::Distance );
552   mSimplifyAlgorithmComboBox->addItem( tr( "SnapToGrid" ), QgsVectorSimplifyMethod::SnapToGrid );
553   mSimplifyAlgorithmComboBox->addItem( tr( "Visvalingam" ), QgsVectorSimplifyMethod::Visvalingam );
554   mSimplifyAlgorithmComboBox->setCurrentIndex( mSimplifyAlgorithmComboBox->findData( simplifyMethod.simplifyAlgorithm() ) );
555 
556   QStringList myScalesList = Qgis::defaultProjectScales().split( ',' );
557   myScalesList.append( QStringLiteral( "1:1" ) );
558   mSimplifyMaximumScaleComboBox->updateScales( myScalesList );
559   mSimplifyMaximumScaleComboBox->setScale( simplifyMethod.maximumScale() );
560 
561   mForceRasterCheckBox->setChecked( mLayer->renderer() && mLayer->renderer()->forceRasterRender() );
562 
563   mRefreshLayerCheckBox->setChecked( mLayer->hasAutoRefreshEnabled() );
564   mRefreshLayerIntervalSpinBox->setEnabled( mLayer->hasAutoRefreshEnabled() );
565   mRefreshLayerIntervalSpinBox->setValue( mLayer->autoRefreshInterval() / 1000.0 );
566 
567   mRefreshLayerNotificationCheckBox->setChecked( mLayer->isRefreshOnNotifyEnabled() );
568   mNotificationMessageCheckBox->setChecked( !mLayer->refreshOnNotifyMessage().isEmpty() );
569   mNotifyMessagValueLineEdit->setText( mLayer->refreshOnNotifyMessage() );
570 
571 
572   // load appropriate symbology page (V1 or V2)
573   updateSymbologyPage();
574 
575   mActionDialog->init( *mLayer->actions(), mLayer->attributeTableConfig() );
576 
577   if ( labelingDialog )
578     labelingDialog->adaptToLayer();
579 
580   mSourceFieldsPropertiesDialog->init();
581   mAttributesFormPropertiesDialog->init();
582 
583   // set initial state for variable editor
584   updateVariableEditor();
585 
586   if ( diagramPropertiesDialog )
587     diagramPropertiesDialog->syncToLayer();
588 
589   // sync all plugin dialogs
590   const auto constMLayerPropertiesPages = mLayerPropertiesPages;
591   for ( QgsMapLayerConfigWidget *page : constMLayerPropertiesPages )
592   {
593     page->syncToLayer( mLayer );
594   }
595 
596   mMetadataWidget->setMetadata( &mLayer->metadata() );
597 
598   mTemporalWidget->syncToLayer();
599 
600   mLegendWidget->setLayer( mLayer );
601 
602 }
603 
apply()604 void QgsVectorLayerProperties::apply()
605 {
606   if ( labelingDialog )
607   {
608     labelingDialog->writeSettingsToLayer();
609   }
610 
611   // apply legend settings
612   mLegendWidget->applyToLayer();
613   mLegendConfigEmbeddedWidget->applyToLayer();
614 
615   // save metadata
616   mMetadataWidget->acceptMetadata();
617   mMetadataFilled = false;
618 
619   // save masking settings
620   if ( mMaskingWidget && mMaskingWidget->hasBeenPopulated() )
621     mMaskingWidget->apply();
622 
623   //
624   // Set up sql subset query if applicable
625   //
626   mSubsetGroupBox->setEnabled( true );
627 
628   if ( txtSubsetSQL->text() != mLayer->subsetString() )
629   {
630     // set the subset sql for the layer
631     mLayer->setSubsetString( txtSubsetSQL->text() );
632     mMetadataFilled = false;
633   }
634   mOriginalSubsetSQL = mLayer->subsetString();
635 
636   // set up the scale based layer visibility stuff....
637   mLayer->setScaleBasedVisibility( mScaleVisibilityGroupBox->isChecked() );
638   mLayer->setMaximumScale( mScaleRangeWidget->maximumScale() );
639   mLayer->setMinimumScale( mScaleRangeWidget->minimumScale() );
640 
641   // provider-specific options
642   if ( mLayer->dataProvider() )
643   {
644     if ( mLayer->dataProvider()->capabilities() & QgsVectorDataProvider::SelectEncoding )
645     {
646       mLayer->setProviderEncoding( cboProviderEncoding->currentText() );
647     }
648   }
649 
650   mLayer->setDisplayExpression( mDisplayExpressionWidget->asExpression() );
651   mLayer->setMapTipTemplate( mMapTipWidget->text() );
652 
653   mLayer->actions()->clearActions();
654   const auto constActions = mActionDialog->actions();
655   for ( const QgsAction &action : constActions )
656   {
657     mLayer->actions()->addAction( action );
658   }
659   QgsAttributeTableConfig attributeTableConfig = mLayer->attributeTableConfig();
660   attributeTableConfig.update( mLayer->fields() );
661   attributeTableConfig.setActionWidgetStyle( mActionDialog->attributeTableWidgetStyle() );
662   QVector<QgsAttributeTableConfig::ColumnConfig> columns = attributeTableConfig.columns();
663 
664   for ( int i = 0; i < columns.size(); ++i )
665   {
666     if ( columns.at( i ).type == QgsAttributeTableConfig::Action )
667     {
668       columns[i].hidden = !mActionDialog->showWidgetInAttributeTable();
669     }
670   }
671 
672   attributeTableConfig.setColumns( columns );
673 
674   mLayer->setAttributeTableConfig( attributeTableConfig );
675 
676   mLayer->setName( mLayerOrigNameLineEdit->text() );
677 
678   mAttributesFormPropertiesDialog->apply();
679   mSourceFieldsPropertiesDialog->apply();
680 
681   // Update temporal properties
682   mTemporalWidget->saveTemporalProperties();
683 
684   if ( mLayer->renderer() )
685   {
686     QgsRendererPropertiesDialog *dlg = static_cast<QgsRendererPropertiesDialog *>( widgetStackRenderers->currentWidget() );
687     dlg->apply();
688   }
689 
690   //apply diagram settings
691   diagramPropertiesDialog->apply();
692 
693   // apply all plugin dialogs
694   const auto constMLayerPropertiesPages = mLayerPropertiesPages;
695   for ( QgsMapLayerConfigWidget *page : constMLayerPropertiesPages )
696   {
697     page->apply();
698   }
699 
700   //layer title and abstract
701   if ( mLayer->shortName() != mLayerShortNameLineEdit->text() )
702     mMetadataFilled = false;
703   mLayer->setShortName( mLayerShortNameLineEdit->text() );
704 
705   if ( mLayer->title() != mLayerTitleLineEdit->text() )
706     mMetadataFilled = false;
707   mLayer->setTitle( mLayerTitleLineEdit->text() );
708 
709   if ( mLayer->abstract() != mLayerAbstractTextEdit->toPlainText() )
710     mMetadataFilled = false;
711   mLayer->setAbstract( mLayerAbstractTextEdit->toPlainText() );
712 
713   if ( mLayer->keywordList() != mLayerKeywordListLineEdit->text() )
714     mMetadataFilled = false;
715   mLayer->setKeywordList( mLayerKeywordListLineEdit->text() );
716 
717   if ( mLayer->dataUrl() != mLayerDataUrlLineEdit->text() )
718     mMetadataFilled = false;
719   mLayer->setDataUrl( mLayerDataUrlLineEdit->text() );
720 
721   if ( mLayer->dataUrlFormat() != mLayerDataUrlFormatComboBox->currentText() )
722     mMetadataFilled = false;
723   mLayer->setDataUrlFormat( mLayerDataUrlFormatComboBox->currentText() );
724 
725   //layer attribution and metadataUrl
726   if ( mLayer->attribution() != mLayerAttributionLineEdit->text() )
727     mMetadataFilled = false;
728   mLayer->setAttribution( mLayerAttributionLineEdit->text() );
729 
730   if ( mLayer->attributionUrl() != mLayerAttributionUrlLineEdit->text() )
731     mMetadataFilled = false;
732   mLayer->setAttributionUrl( mLayerAttributionUrlLineEdit->text() );
733 
734   if ( mLayer->metadataUrl() != mLayerMetadataUrlLineEdit->text() )
735     mMetadataFilled = false;
736   mLayer->setMetadataUrl( mLayerMetadataUrlLineEdit->text() );
737 
738   if ( mLayer->metadataUrlType() != mLayerMetadataUrlTypeComboBox->currentText() )
739     mMetadataFilled = false;
740   mLayer->setMetadataUrlType( mLayerMetadataUrlTypeComboBox->currentText() );
741 
742   if ( mLayer->metadataUrlFormat() != mLayerMetadataUrlFormatComboBox->currentText() )
743     mMetadataFilled = false;
744   mLayer->setMetadataUrlFormat( mLayerMetadataUrlFormatComboBox->currentText() );
745 
746   // LegendURL
747   if ( mLayer->legendUrl() != mLayerLegendUrlLineEdit->text() )
748     mMetadataFilled = false;
749   mLayer->setLegendUrl( mLayerLegendUrlLineEdit->text() );
750 
751   if ( mLayer->legendUrlFormat() != mLayerLegendUrlFormatComboBox->currentText() )
752     mMetadataFilled = false;
753   mLayer->setLegendUrlFormat( mLayerLegendUrlFormatComboBox->currentText() );
754 
755   //layer simplify drawing configuration
756   QgsVectorSimplifyMethod::SimplifyHints simplifyHints = QgsVectorSimplifyMethod::NoSimplification;
757   if ( mSimplifyDrawingGroupBox->isChecked() )
758   {
759     simplifyHints |= QgsVectorSimplifyMethod::GeometrySimplification;
760     if ( mSimplifyDrawingSpinBox->value() > 1 ) simplifyHints |= QgsVectorSimplifyMethod::AntialiasingSimplification;
761   }
762   QgsVectorSimplifyMethod simplifyMethod = mLayer->simplifyMethod();
763   simplifyMethod.setSimplifyHints( simplifyHints );
764   simplifyMethod.setSimplifyAlgorithm( static_cast< QgsVectorSimplifyMethod::SimplifyAlgorithm >( mSimplifyAlgorithmComboBox->currentData().toInt() ) );
765   simplifyMethod.setThreshold( mSimplifyDrawingSpinBox->value() );
766   simplifyMethod.setForceLocalOptimization( !mSimplifyDrawingAtProvider->isChecked() );
767   simplifyMethod.setMaximumScale( mSimplifyMaximumScaleComboBox->scale() );
768   mLayer->setSimplifyMethod( simplifyMethod );
769 
770   if ( mLayer->renderer() )
771     mLayer->renderer()->setForceRasterRender( mForceRasterCheckBox->isChecked() );
772 
773   mLayer->setAutoRefreshInterval( mRefreshLayerIntervalSpinBox->value() * 1000.0 );
774   mLayer->setAutoRefreshEnabled( mRefreshLayerCheckBox->isChecked() );
775 
776   mLayer->setRefreshOnNotifyEnabled( mRefreshLayerNotificationCheckBox->isChecked() );
777   mLayer->setRefreshOnNofifyMessage( mNotificationMessageCheckBox->isChecked() ? mNotifyMessagValueLineEdit->text() : QString() );
778 
779   mOldJoins = mLayer->vectorJoins();
780 
781   //save variables
782   QgsExpressionContextUtils::setLayerVariables( mLayer, mVariableEditor->variablesInActiveScope() );
783   updateVariableEditor();
784 
785   // save dependencies
786   QSet<QgsMapLayerDependency> deps;
787   const auto checkedLayers = mLayersDependenciesTreeModel->checkedLayers();
788   for ( const QgsMapLayer *layer : checkedLayers )
789     deps << QgsMapLayerDependency( layer->id() );
790   if ( ! mLayer->setDependencies( deps ) )
791   {
792     QMessageBox::warning( nullptr, tr( "Save Dependency" ), tr( "This configuration introduces a cycle in data dependencies and will be ignored." ) );
793   }
794 
795   mLayer->triggerRepaint();
796   // notify the project we've made a change
797   QgsProject::instance()->setDirty( true );
798 }
799 
onCancel()800 void QgsVectorLayerProperties::onCancel()
801 {
802   if ( mOldJoins != mLayer->vectorJoins() )
803   {
804     // need to undo changes in vector layer joins - they are applied directly to the layer (not in apply())
805     // so other parts of the properties dialog can use the fields from the joined layers
806 
807     const auto constVectorJoins = mLayer->vectorJoins();
808     for ( const QgsVectorLayerJoinInfo &info : constVectorJoins )
809       mLayer->removeJoin( info.joinLayerId() );
810 
811     for ( const QgsVectorLayerJoinInfo &info : qgis::as_const( mOldJoins ) )
812       mLayer->addJoin( info );
813   }
814 
815   if ( mOriginalSubsetSQL != mLayer->subsetString() )
816   {
817     // need to undo changes in subset string - they are applied directly to the layer (not in apply())
818     // by QgsQueryBuilder::accept()
819 
820     mLayer->setSubsetString( mOriginalSubsetSQL );
821   }
822 
823   if ( mOldStyle.xmlData() != mLayer->styleManager()->style( mLayer->styleManager()->currentStyle() ).xmlData() )
824   {
825     // need to reset style to previous - style applied directly to the layer (not in apply())
826     QString myMessage;
827     QDomDocument doc( QStringLiteral( "qgis" ) );
828     int errorLine, errorColumn;
829     doc.setContent( mOldStyle.xmlData(), false, &myMessage, &errorLine, &errorColumn );
830     mLayer->importNamedStyle( doc, myMessage );
831     syncToLayer();
832   }
833 }
834 
urlClicked(const QUrl & url)835 void QgsVectorLayerProperties::urlClicked( const QUrl &url )
836 {
837   QFileInfo file( url.toLocalFile() );
838   if ( file.exists() && !file.isDir() )
839     QgsGui::instance()->nativePlatformInterface()->openFileExplorerAndSelectFile( url.toLocalFile() );
840   else
841     QDesktopServices::openUrl( url );
842 }
843 
pbnQueryBuilder_clicked()844 void QgsVectorLayerProperties::pbnQueryBuilder_clicked()
845 {
846   // launch the query builder
847   QgsQueryBuilder *qb = new QgsQueryBuilder( mLayer, this );
848 
849   // Set the sql in the query builder to the same in the prop dialog
850   // (in case the user has already changed it)
851   qb->setSql( txtSubsetSQL->text() );
852   // Open the query builder
853   if ( qb->exec() )
854   {
855     // if the sql is changed, update it in the prop subset text box
856     txtSubsetSQL->setText( qb->sql() );
857     //TODO If the sql is changed in the prop dialog, the layer extent should be recalculated
858 
859     // The datasource for the layer needs to be updated with the new sql since this gets
860     // saved to the project file. This should happen at the map layer level...
861 
862   }
863   // delete the query builder object
864   delete qb;
865 }
866 
pbnIndex_clicked()867 void QgsVectorLayerProperties::pbnIndex_clicked()
868 {
869   QgsVectorDataProvider *pr = mLayer->dataProvider();
870   if ( pr )
871   {
872     setCursor( Qt::WaitCursor );
873     bool errval = pr->createSpatialIndex();
874     setCursor( Qt::ArrowCursor );
875     if ( errval )
876     {
877       pbnIndex->setEnabled( false );
878       pbnIndex->setText( tr( "Spatial Index Exists" ) );
879       QMessageBox::information( this, tr( "Spatial Index" ), tr( "Creation of spatial index successful" ) );
880     }
881     else
882     {
883       QMessageBox::warning( this, tr( "Spatial Index" ), tr( "Creation of spatial index failed" ) );
884     }
885   }
886 }
887 
htmlMetadata()888 QString QgsVectorLayerProperties::htmlMetadata()
889 {
890   return mLayer->htmlMetadata();
891 }
892 
mLayerOrigNameLineEdit_textEdited(const QString & text)893 void QgsVectorLayerProperties::mLayerOrigNameLineEdit_textEdited( const QString &text )
894 {
895   txtDisplayName->setText( mLayer->formatLayerName( text ) );
896 }
897 
mCrsSelector_crsChanged(const QgsCoordinateReferenceSystem & crs)898 void QgsVectorLayerProperties::mCrsSelector_crsChanged( const QgsCoordinateReferenceSystem &crs )
899 {
900   QgsDatumTransformDialog::run( crs, QgsProject::instance()->crs(), this, mCanvas, tr( "Select Transformation for the vector layer" ) );
901   mLayer->setCrs( crs );
902   mMetadataFilled = false;
903   mMetadataWidget->crsChanged();
904 }
905 
loadDefaultStyle_clicked()906 void QgsVectorLayerProperties::loadDefaultStyle_clicked()
907 {
908   QString msg;
909   bool defaultLoadedFlag = false;
910 
911   const QgsVectorDataProvider *provider = mLayer->dataProvider();
912   if ( !provider )
913     return;
914   if ( provider->isSaveAndLoadStyleToDatabaseSupported() )
915   {
916     QMessageBox askToUser;
917     askToUser.setText( tr( "Load default style from: " ) );
918     askToUser.setIcon( QMessageBox::Question );
919     askToUser.addButton( tr( "Cancel" ), QMessageBox::RejectRole );
920     askToUser.addButton( tr( "Local Database" ), QMessageBox::NoRole );
921     askToUser.addButton( tr( "Datasource Database" ), QMessageBox::YesRole );
922 
923     switch ( askToUser.exec() )
924     {
925       case 0:
926         return;
927       case 2:
928         msg = mLayer->loadNamedStyle( mLayer->styleURI(), defaultLoadedFlag );
929         if ( !defaultLoadedFlag )
930         {
931           //something went wrong - let them know why
932           QMessageBox::information( this, tr( "Default Style" ), msg );
933         }
934         if ( msg.compare( tr( "Loaded from Provider" ) ) )
935         {
936           QMessageBox::information( this, tr( "Default Style" ),
937                                     tr( "No default style was found for this layer." ) );
938         }
939         else
940         {
941           syncToLayer();
942         }
943 
944         return;
945       default:
946         break;
947     }
948   }
949 
950   QString myMessage = mLayer->loadNamedStyle( mLayer->styleURI(), defaultLoadedFlag, true );
951 //  QString myMessage = layer->loadDefaultStyle( defaultLoadedFlag );
952   //reset if the default style was loaded OK only
953   if ( defaultLoadedFlag )
954   {
955     // all worked OK so no need to inform user
956     syncToLayer();
957   }
958   else
959   {
960     //something went wrong - let them know why
961     QMessageBox::information( this, tr( "Default Style" ), myMessage );
962   }
963 }
964 
saveDefaultStyle_clicked()965 void QgsVectorLayerProperties::saveDefaultStyle_clicked()
966 {
967   apply();
968   QString errorMsg;
969   const QgsVectorDataProvider *provider = mLayer->dataProvider();
970   if ( !provider )
971     return;
972   if ( provider->isSaveAndLoadStyleToDatabaseSupported() )
973   {
974     QMessageBox askToUser;
975     askToUser.setText( tr( "Save default style to: " ) );
976     askToUser.setIcon( QMessageBox::Question );
977     askToUser.addButton( tr( "Cancel" ), QMessageBox::RejectRole );
978     askToUser.addButton( tr( "Local Database" ), QMessageBox::NoRole );
979     askToUser.addButton( tr( "Datasource Database" ), QMessageBox::YesRole );
980 
981     switch ( askToUser.exec() )
982     {
983       case 0:
984         return;
985       case 2:
986         mLayer->saveStyleToDatabase( QString(), QString(), true, QString(), errorMsg );
987         if ( errorMsg.isNull() )
988         {
989           return;
990         }
991         break;
992       default:
993         break;
994     }
995   }
996 
997   bool defaultSavedFlag = false;
998   errorMsg = mLayer->saveDefaultStyle( defaultSavedFlag );
999   if ( !defaultSavedFlag )
1000   {
1001     QMessageBox::warning( this, tr( "Default Style" ), errorMsg );
1002   }
1003 }
1004 
loadMetadata()1005 void QgsVectorLayerProperties::loadMetadata()
1006 {
1007   QgsSettings myQSettings;  // where we keep last used filter in persistent state
1008   QString myLastUsedDir = myQSettings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
1009 
1010   QString myFileName = QFileDialog::getOpenFileName( this, tr( "Load Layer Metadata from Metadata File" ), myLastUsedDir,
1011                        tr( "QGIS Layer Metadata File" ) + " (*.qmd)" );
1012   if ( myFileName.isNull() )
1013   {
1014     return;
1015   }
1016 
1017   QString myMessage;
1018   bool defaultLoadedFlag = false;
1019   myMessage = mLayer->loadNamedMetadata( myFileName, defaultLoadedFlag );
1020 
1021   //reset if the default style was loaded OK only
1022   if ( defaultLoadedFlag )
1023   {
1024     mMetadataWidget->setMetadata( &mLayer->metadata() );
1025   }
1026   else
1027   {
1028     //let the user know what went wrong
1029     QMessageBox::warning( this, tr( "Load Metadata" ), myMessage );
1030   }
1031 
1032   QFileInfo myFI( myFileName );
1033   QString myPath = myFI.path();
1034   myQSettings.setValue( QStringLiteral( "style/lastStyleDir" ), myPath );
1035 
1036   activateWindow(); // set focus back to properties dialog
1037 }
1038 
saveMetadataAs()1039 void QgsVectorLayerProperties::saveMetadataAs()
1040 {
1041   QgsSettings myQSettings;  // where we keep last used filter in persistent state
1042   QString myLastUsedDir = myQSettings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
1043 
1044   QString myOutputFileName = QFileDialog::getSaveFileName( this, tr( "Save Layer Metadata as QMD" ),
1045                              myLastUsedDir, tr( "QMD File" ) + " (*.qmd)" );
1046   if ( myOutputFileName.isNull() ) //dialog canceled
1047   {
1048     return;
1049   }
1050 
1051   mMetadataWidget->acceptMetadata();
1052 
1053   //ensure the user never omitted the extension from the file name
1054   if ( !myOutputFileName.endsWith( QgsMapLayer::extensionPropertyType( QgsMapLayer::Metadata ), Qt::CaseInsensitive ) )
1055   {
1056     myOutputFileName += QgsMapLayer::extensionPropertyType( QgsMapLayer::Metadata );
1057   }
1058 
1059   QString myMessage;
1060   bool defaultLoadedFlag = false;
1061   myMessage = mLayer->saveNamedMetadata( myOutputFileName, defaultLoadedFlag );
1062 
1063   //reset if the default style was loaded OK only
1064   if ( defaultLoadedFlag )
1065   {
1066     syncToLayer();
1067   }
1068   else
1069   {
1070     //let the user know what went wrong
1071     QMessageBox::information( this, tr( "Save Metadata" ), myMessage );
1072   }
1073 
1074   QFileInfo myFI( myOutputFileName );
1075   QString myPath = myFI.path();
1076   // Persist last used dir
1077   myQSettings.setValue( QStringLiteral( "style/lastStyleDir" ), myPath );
1078 }
1079 
saveDefaultMetadata()1080 void QgsVectorLayerProperties::saveDefaultMetadata()
1081 {
1082   mMetadataWidget->acceptMetadata();
1083 
1084   bool defaultSavedFlag = false;
1085   QString errorMsg = mLayer->saveDefaultMetadata( defaultSavedFlag );
1086   if ( !defaultSavedFlag )
1087   {
1088     QMessageBox::warning( this, tr( "Default Metadata" ), errorMsg );
1089   }
1090 }
1091 
loadDefaultMetadata()1092 void QgsVectorLayerProperties::loadDefaultMetadata()
1093 {
1094   bool defaultLoadedFlag = false;
1095   QString myMessage = mLayer->loadNamedMetadata( mLayer->metadataUri(), defaultLoadedFlag );
1096   //reset if the default metadata was loaded OK only
1097   if ( defaultLoadedFlag )
1098   {
1099     mMetadataWidget->setMetadata( &mLayer->metadata() );
1100   }
1101   else
1102   {
1103     QMessageBox::information( this, tr( "Default Metadata" ), myMessage );
1104   }
1105 }
1106 
1107 
saveStyleAs()1108 void QgsVectorLayerProperties::saveStyleAs()
1109 {
1110   if ( !mLayer->dataProvider() )
1111     return;
1112   QgsVectorLayerSaveStyleDialog dlg( mLayer );
1113   QgsSettings settings;
1114 
1115   if ( dlg.exec() )
1116   {
1117     apply();
1118 
1119     bool defaultLoadedFlag = false;
1120 
1121     StyleType type = dlg.currentStyleType();
1122     switch ( type )
1123     {
1124       case QML:
1125       case SLD:
1126       {
1127         QString message;
1128         QString filePath = dlg.outputFilePath();
1129         if ( type == QML )
1130           message = mLayer->saveNamedStyle( filePath, defaultLoadedFlag, dlg.styleCategories() );
1131         else
1132           message = mLayer->saveSldStyle( filePath, defaultLoadedFlag );
1133 
1134         //reset if the default style was loaded OK only
1135         if ( defaultLoadedFlag )
1136         {
1137           syncToLayer();
1138         }
1139         else
1140         {
1141           //let the user know what went wrong
1142           QMessageBox::information( this, tr( "Save Style" ), message );
1143         }
1144 
1145         break;
1146       }
1147       case DB:
1148       {
1149         QString infoWindowTitle = QObject::tr( "Save style to DB (%1)" ).arg( mLayer->providerType() );
1150         QString msgError;
1151 
1152         QgsVectorLayerSaveStyleDialog::SaveToDbSettings dbSettings = dlg.saveToDbSettings();
1153 
1154         mLayer->saveStyleToDatabase( dbSettings.name, dbSettings.description, dbSettings.isDefault, dbSettings.uiFileContent, msgError );
1155 
1156         int messageTimeout = QgsSettings().value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
1157         if ( !msgError.isNull() )
1158         {
1159           mMessageBar->pushMessage( infoWindowTitle, msgError, Qgis::Warning, messageTimeout );
1160         }
1161         else
1162         {
1163           mMessageBar->pushMessage( infoWindowTitle, tr( "Style saved" ), Qgis::Info, messageTimeout );
1164         }
1165         break;
1166       }
1167     }
1168   }
1169 }
1170 
saveMultipleStylesAs()1171 void QgsVectorLayerProperties::saveMultipleStylesAs()
1172 {
1173   QgsVectorLayerSaveStyleDialog dlg( mLayer );
1174   dlg.setSaveOnlyCurrentStyle( false );
1175   QgsSettings settings;
1176 
1177   if ( dlg.exec() )
1178   {
1179     apply();
1180 
1181     // Store the original style, that we can restore at the end
1182     const QString originalStyle { mLayer->styleManager()->currentStyle() };
1183     const QListWidget *stylesWidget { dlg.stylesWidget() };
1184 
1185     // Collect selected (checked) styles for export/save
1186     QStringList stylesSelected;
1187     for ( int i = 0; i < stylesWidget->count(); i++ )
1188     {
1189       if ( stylesWidget->item( i )->checkState() == Qt::CheckState::Checked )
1190       {
1191         stylesSelected.push_back( stylesWidget->item( i )->text() );
1192       }
1193     }
1194 
1195     if ( ! stylesSelected.isEmpty() )
1196     {
1197       int styleIndex = 0;
1198       for ( const QString &styleName : qgis::as_const( stylesSelected ) )
1199       {
1200         bool defaultLoadedFlag = false;
1201 
1202         StyleType type = dlg.currentStyleType();
1203         mLayer->styleManager()->setCurrentStyle( styleName );
1204         switch ( type )
1205         {
1206           case QML:
1207           case SLD:
1208           {
1209             QString message;
1210             const QString filePath { dlg.outputFilePath() };
1211             QString safePath { filePath };
1212             if ( styleIndex > 0 && stylesSelected.count( ) > 1 )
1213             {
1214               int i = 1;
1215               while ( QFile::exists( safePath ) )
1216               {
1217                 const QFileInfo fi { filePath };
1218                 safePath = QString( filePath ).replace( '.' + fi.completeSuffix(), QStringLiteral( "_%1.%2" )
1219                                                         .arg( QString::number( i ) )
1220                                                         .arg( fi.completeSuffix() ) );
1221                 i++;
1222               }
1223             }
1224             if ( type == QML )
1225               message = mLayer->saveNamedStyle( safePath, defaultLoadedFlag, dlg.styleCategories() );
1226             else
1227               message = mLayer->saveSldStyle( safePath, defaultLoadedFlag );
1228 
1229             //reset if the default style was loaded OK only
1230             if ( defaultLoadedFlag )
1231             {
1232               syncToLayer();
1233             }
1234             else
1235             {
1236               //let the user know what went wrong
1237               QMessageBox::information( this, tr( "Save Style" ), message );
1238             }
1239 
1240             break;
1241           }
1242           case DB:
1243           {
1244             QString infoWindowTitle = QObject::tr( "Save style '%1' to DB (%2)" )
1245                                       .arg( styleName )
1246                                       .arg( mLayer->providerType() );
1247             QString msgError;
1248 
1249             QgsVectorLayerSaveStyleDialog::SaveToDbSettings dbSettings = dlg.saveToDbSettings();
1250 
1251             // If a name is defined, we add _1 etc. else we use the style name
1252             QString name { dbSettings.name };
1253             if ( name.isEmpty() )
1254             {
1255               name = styleName;
1256             }
1257             else
1258             {
1259               QStringList ids, names, descriptions;
1260               mLayer->listStylesInDatabase( ids, names, descriptions, msgError );
1261               int i = 1;
1262               while ( names.contains( name ) )
1263               {
1264                 name = QStringLiteral( "%1 %2" ).arg( name ).arg( QString::number( i ) );
1265                 i++;
1266               }
1267             }
1268             mLayer->saveStyleToDatabase( name, dbSettings.description, dbSettings.isDefault, dbSettings.uiFileContent, msgError );
1269 
1270             const int timeout = QgsSettings().value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
1271             if ( !msgError.isNull() )
1272             {
1273               mMessageBar->pushMessage( infoWindowTitle, msgError, Qgis::Warning, timeout );
1274             }
1275             else
1276             {
1277               mMessageBar->pushMessage( infoWindowTitle, tr( "Style '%1' saved" ).arg( styleName ),
1278                                         Qgis::Info, timeout );
1279             }
1280             break;
1281           }
1282         }
1283         styleIndex ++;
1284       }
1285       // Restore original style
1286       mLayer->styleManager()->setCurrentStyle( originalStyle );
1287     }
1288   } // Nothing selected!
1289 }
1290 
aboutToShowStyleMenu()1291 void QgsVectorLayerProperties::aboutToShowStyleMenu()
1292 {
1293   // this should be unified with QgsRasterLayerProperties::aboutToShowStyleMenu()
1294   QMenu *m = qobject_cast<QMenu *>( sender() );
1295 
1296   QgsMapLayerStyleGuiUtils::instance()->removesExtraMenuSeparators( m );
1297   // re-add style manager actions!
1298   m->addSeparator();
1299   QgsMapLayerStyleGuiUtils::instance()->addStyleManagerActions( m, mLayer );
1300 }
1301 
loadStyle()1302 void QgsVectorLayerProperties::loadStyle()
1303 {
1304   QgsSettings settings;  // where we keep last used filter in persistent state
1305 
1306   QString errorMsg;
1307   QStringList ids, names, descriptions;
1308 
1309   //get the list of styles in the db
1310   int sectionLimit = mLayer->listStylesInDatabase( ids, names, descriptions, errorMsg );
1311   QgsMapLayerLoadStyleDialog dlg( mLayer );
1312   dlg.initializeLists( ids, names, descriptions, sectionLimit );
1313 
1314   if ( dlg.exec() )
1315   {
1316     mOldStyle = mLayer->styleManager()->style( mLayer->styleManager()->currentStyle() );
1317     QgsMapLayer::StyleCategories categories = dlg.styleCategories();
1318     StyleType type = dlg.currentStyleType();
1319     switch ( type )
1320     {
1321       case QML:
1322       case SLD:
1323       {
1324         QString message;
1325         bool defaultLoadedFlag = false;
1326         QString filePath = dlg.filePath();
1327         if ( type == SLD )
1328         {
1329           message = mLayer->loadSldStyle( filePath, defaultLoadedFlag );
1330         }
1331         else
1332         {
1333           message = mLayer->loadNamedStyle( filePath, defaultLoadedFlag, true, categories );
1334         }
1335         //reset if the default style was loaded OK only
1336         if ( defaultLoadedFlag )
1337         {
1338           syncToLayer();
1339         }
1340         else
1341         {
1342           //let the user know what went wrong
1343           QMessageBox::warning( this, tr( "Load Style" ), message );
1344         }
1345         break;
1346       }
1347       case DB:
1348       {
1349         QString selectedStyleId = dlg.selectedStyleId();
1350 
1351         QString qmlStyle = mLayer->getStyleFromDatabase( selectedStyleId, errorMsg );
1352         if ( !errorMsg.isNull() )
1353         {
1354           QMessageBox::warning( this, tr( "Load Styles from Database" ), errorMsg );
1355           return;
1356         }
1357 
1358         QDomDocument myDocument( QStringLiteral( "qgis" ) );
1359         myDocument.setContent( qmlStyle );
1360 
1361         if ( mLayer->importNamedStyle( myDocument, errorMsg, categories ) )
1362         {
1363           syncToLayer();
1364         }
1365         else
1366         {
1367           QMessageBox::warning( this, tr( "Load Styles from Database" ),
1368                                 tr( "The retrieved style is not a valid named style. Error message: %1" )
1369                                 .arg( errorMsg ) );
1370         }
1371         break;
1372       }
1373     }
1374     activateWindow(); // set focus back to properties dialog
1375   }
1376 }
1377 
mButtonAddJoin_clicked()1378 void QgsVectorLayerProperties::mButtonAddJoin_clicked()
1379 {
1380   if ( !mLayer )
1381     return;
1382 
1383   QList<QgsMapLayer *> joinedLayers;
1384   const QList< QgsVectorLayerJoinInfo > &joins = mLayer->vectorJoins();
1385   joinedLayers.reserve( joins.size() );
1386   for ( int i = 0; i < joins.size(); ++i )
1387   {
1388     joinedLayers.append( joins[i].joinLayer() );
1389   }
1390 
1391   QgsJoinDialog d( mLayer, joinedLayers );
1392   if ( d.exec() == QDialog::Accepted )
1393   {
1394     QgsVectorLayerJoinInfo info = d.joinInfo();
1395     //create attribute index if possible
1396     if ( d.createAttributeIndex() )
1397     {
1398       QgsVectorLayer *joinLayer = info.joinLayer();
1399       if ( joinLayer )
1400       {
1401         joinLayer->dataProvider()->createAttributeIndex( joinLayer->fields().indexFromName( info.joinFieldName() ) );
1402       }
1403     }
1404     mLayer->addJoin( info );
1405     addJoinToTreeWidget( info );
1406     setPbnQueryBuilderEnabled();
1407     mSourceFieldsPropertiesDialog->init();
1408     mAttributesFormPropertiesDialog->init();
1409   }
1410 }
1411 
mButtonEditJoin_clicked()1412 void QgsVectorLayerProperties::mButtonEditJoin_clicked()
1413 {
1414   QTreeWidgetItem *currentJoinItem = mJoinTreeWidget->currentItem();
1415   mJoinTreeWidget_itemDoubleClicked( currentJoinItem, 0 );
1416 }
1417 
mJoinTreeWidget_itemDoubleClicked(QTreeWidgetItem * item,int)1418 void QgsVectorLayerProperties::mJoinTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int )
1419 {
1420   if ( !mLayer || !item )
1421   {
1422     return;
1423   }
1424 
1425   QList<QgsMapLayer *> joinedLayers;
1426   QString joinLayerId = item->data( 0, Qt::UserRole ).toString();
1427   const QList< QgsVectorLayerJoinInfo > &joins = mLayer->vectorJoins();
1428   int j = -1;
1429   for ( int i = 0; i < joins.size(); ++i )
1430   {
1431     QgsVectorLayer *joinLayer = joins[i].joinLayer();
1432     if ( !joinLayer )
1433       continue;  // invalid join (unresolved join layer)
1434 
1435     if ( joinLayer->id() == joinLayerId )
1436     {
1437       j = i;
1438     }
1439     else
1440     {
1441       // remove already joined layers from possible list to be displayed in dialog
1442       joinedLayers.append( joinLayer );
1443     }
1444   }
1445   if ( j == -1 )
1446   {
1447     return;
1448   }
1449 
1450   QgsJoinDialog d( mLayer, joinedLayers );
1451   d.setWindowTitle( tr( "Edit Vector Join" ) );
1452   d.setJoinInfo( joins[j] );
1453 
1454   if ( d.exec() == QDialog::Accepted )
1455   {
1456     QgsVectorLayerJoinInfo info = d.joinInfo();
1457 
1458     // remove old join
1459     mLayer->removeJoin( joinLayerId );
1460     int idx = mJoinTreeWidget->indexOfTopLevelItem( item );
1461     mJoinTreeWidget->takeTopLevelItem( idx );
1462 
1463     // add the new edited
1464 
1465     //create attribute index if possible
1466     if ( d.createAttributeIndex() )
1467     {
1468       QgsVectorLayer *joinLayer = info.joinLayer();
1469       if ( joinLayer )
1470       {
1471         joinLayer->dataProvider()->createAttributeIndex( joinLayer->fields().indexFromName( info.joinFieldName() ) );
1472       }
1473     }
1474     mLayer->addJoin( info );
1475     addJoinToTreeWidget( info, idx );
1476 
1477     setPbnQueryBuilderEnabled();
1478     mSourceFieldsPropertiesDialog->init();
1479     mAttributesFormPropertiesDialog->init();
1480   }
1481 }
1482 
addJoinToTreeWidget(const QgsVectorLayerJoinInfo & join,const int insertIndex)1483 void QgsVectorLayerProperties::addJoinToTreeWidget( const QgsVectorLayerJoinInfo &join, const int insertIndex )
1484 {
1485   QTreeWidgetItem *joinItem = new QTreeWidgetItem();
1486   joinItem->setFlags( Qt::ItemIsEnabled );
1487 
1488   QgsVectorLayer *joinLayer = join.joinLayer();
1489   if ( !mLayer || !joinLayer )
1490   {
1491     return;
1492   }
1493 
1494   joinItem->setText( 0, tr( "Join layer" ) );
1495   if ( mLayer->auxiliaryLayer() && mLayer->auxiliaryLayer()->id() == join.joinLayerId() )
1496   {
1497     return;
1498   }
1499 
1500   joinItem->setText( 1, joinLayer->name() );
1501 
1502   QFont f = joinItem->font( 0 );
1503   f.setBold( true );
1504   joinItem->setFont( 0, f );
1505   joinItem->setFont( 1, f );
1506 
1507   joinItem->setData( 0, Qt::UserRole, join.joinLayerId() );
1508 
1509   QTreeWidgetItem *childJoinField = new QTreeWidgetItem();
1510   childJoinField->setText( 0, tr( "Join field" ) );
1511   childJoinField->setText( 1, join.joinFieldName() );
1512   childJoinField->setFlags( Qt::ItemIsEnabled );
1513   joinItem->addChild( childJoinField );
1514 
1515   QTreeWidgetItem *childTargetField = new QTreeWidgetItem();
1516   childTargetField->setText( 0, tr( "Target field" ) );
1517   childTargetField->setText( 1, join.targetFieldName() );
1518   joinItem->addChild( childTargetField );
1519 
1520   QTreeWidgetItem *childMemCache = new QTreeWidgetItem();
1521   childMemCache->setText( 0, tr( "Cache join layer in virtual memory" ) );
1522   if ( join.isUsingMemoryCache() )
1523     childMemCache->setText( 1, QChar( 0x2714 ) );
1524   joinItem->addChild( childMemCache );
1525 
1526   QTreeWidgetItem *childDynForm = new QTreeWidgetItem();
1527   childDynForm->setText( 0, tr( "Dynamic form" ) );
1528   if ( join.isDynamicFormEnabled() )
1529     childDynForm->setText( 1, QChar( 0x2714 ) );
1530   joinItem->addChild( childDynForm );
1531 
1532   QTreeWidgetItem *childEditable = new QTreeWidgetItem();
1533   childEditable->setText( 0, tr( "Editable join layer" ) );
1534   if ( join.isEditable() )
1535     childEditable->setText( 1, QChar( 0x2714 ) );
1536   joinItem->addChild( childEditable );
1537 
1538   QTreeWidgetItem *childUpsert = new QTreeWidgetItem();
1539   childUpsert->setText( 0, tr( "Upsert on edit" ) );
1540   if ( join.hasUpsertOnEdit() )
1541     childUpsert->setText( 1, QChar( 0x2714 ) );
1542   joinItem->addChild( childUpsert );
1543 
1544   QTreeWidgetItem *childCascade = new QTreeWidgetItem();
1545   childCascade->setText( 0, tr( "Delete cascade" ) );
1546   if ( join.hasCascadedDelete() )
1547     childCascade->setText( 1, QChar( 0x2714 ) );
1548   joinItem->addChild( childCascade );
1549 
1550   QTreeWidgetItem *childPrefix = new QTreeWidgetItem();
1551   childPrefix->setText( 0, tr( "Custom field name prefix" ) );
1552   childPrefix->setText( 1, join.prefix() );
1553   joinItem->addChild( childPrefix );
1554 
1555   QTreeWidgetItem *childFields = new QTreeWidgetItem();
1556   childFields->setText( 0, tr( "Joined fields" ) );
1557   const QStringList *list = join.joinFieldNamesSubset();
1558   if ( list )
1559     childFields->setText( 1, QString::number( list->count() ) );
1560   else
1561     childFields->setText( 1, tr( "all" ) );
1562   joinItem->addChild( childFields );
1563 
1564   if ( insertIndex >= 0 )
1565     mJoinTreeWidget->insertTopLevelItem( insertIndex, joinItem );
1566   else
1567     mJoinTreeWidget->addTopLevelItem( joinItem );
1568 
1569   mJoinTreeWidget->setCurrentItem( joinItem );
1570   mJoinTreeWidget->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
1571 }
1572 
createExpressionContext() const1573 QgsExpressionContext QgsVectorLayerProperties::createExpressionContext() const
1574 {
1575   return mContext;
1576 }
1577 
openPanel(QgsPanelWidget * panel)1578 void QgsVectorLayerProperties::openPanel( QgsPanelWidget *panel )
1579 {
1580   QDialog *dlg = new QDialog();
1581   QString key = QStringLiteral( "/UI/paneldialog/%1" ).arg( panel->panelTitle() );
1582   QgsSettings settings;
1583   dlg->restoreGeometry( settings.value( key ).toByteArray() );
1584   dlg->setWindowTitle( panel->panelTitle() );
1585   dlg->setLayout( new QVBoxLayout() );
1586   dlg->layout()->addWidget( panel );
1587   QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok );
1588   connect( buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept );
1589   dlg->layout()->addWidget( buttonBox );
1590   dlg->exec();
1591   settings.setValue( key, dlg->saveGeometry() );
1592   panel->acceptPanel();
1593 }
1594 
mButtonRemoveJoin_clicked()1595 void QgsVectorLayerProperties::mButtonRemoveJoin_clicked()
1596 {
1597   QTreeWidgetItem *currentJoinItem = mJoinTreeWidget->currentItem();
1598   if ( !mLayer || !currentJoinItem )
1599   {
1600     return;
1601   }
1602 
1603   mLayer->removeJoin( currentJoinItem->data( 0, Qt::UserRole ).toString() );
1604   mJoinTreeWidget->takeTopLevelItem( mJoinTreeWidget->indexOfTopLevelItem( currentJoinItem ) );
1605   setPbnQueryBuilderEnabled();
1606   mSourceFieldsPropertiesDialog->init();
1607   mAttributesFormPropertiesDialog->init();
1608 }
1609 
1610 
mButtonAddWmsDimension_clicked()1611 void QgsVectorLayerProperties::mButtonAddWmsDimension_clicked()
1612 {
1613   if ( !mLayer )
1614     return;
1615 
1616   // get wms dimensions name
1617   QStringList alreadyDefinedDimensions;
1618   const QList<QgsVectorLayerServerProperties::WmsDimensionInfo> &dims = mLayer->serverProperties()->wmsDimensions();
1619   for ( const QgsVectorLayerServerProperties::WmsDimensionInfo &dim : dims )
1620   {
1621     alreadyDefinedDimensions << dim.name;
1622   }
1623 
1624   QgsWmsDimensionDialog d( mLayer, alreadyDefinedDimensions );
1625   if ( d.exec() == QDialog::Accepted )
1626   {
1627     QgsVectorLayerServerProperties::WmsDimensionInfo info = d.info();
1628     // save dimension
1629     mLayer->serverProperties()->addWmsDimension( info );
1630     addWmsDimensionInfoToTreeWidget( info );
1631   }
1632 }
1633 
mButtonEditWmsDimension_clicked()1634 void QgsVectorLayerProperties::mButtonEditWmsDimension_clicked()
1635 {
1636   QTreeWidgetItem *currentWmsDimensionItem = mWmsDimensionsTreeWidget->currentItem();
1637   mWmsDimensionsTreeWidget_itemDoubleClicked( currentWmsDimensionItem, 0 );
1638 }
1639 
mWmsDimensionsTreeWidget_itemDoubleClicked(QTreeWidgetItem * item,int)1640 void QgsVectorLayerProperties::mWmsDimensionsTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int )
1641 {
1642   if ( !mLayer || !item )
1643   {
1644     return;
1645   }
1646 
1647   QString wmsDimName = item->data( 0, Qt::UserRole ).toString();
1648   const QList<QgsVectorLayerServerProperties::WmsDimensionInfo> &dims = mLayer->serverProperties()->wmsDimensions();
1649   QStringList alreadyDefinedDimensions;
1650   int j = -1;
1651   for ( int i = 0; i < dims.size(); ++i )
1652   {
1653     QString dimName = dims[i].name;
1654     if ( dimName == wmsDimName )
1655     {
1656       j = i;
1657     }
1658     else
1659     {
1660       alreadyDefinedDimensions << dimName;
1661     }
1662   }
1663   if ( j == -1 )
1664   {
1665     return;
1666   }
1667 
1668   QgsWmsDimensionDialog d( mLayer, alreadyDefinedDimensions );
1669   d.setWindowTitle( tr( "Edit WMS Dimension" ) );
1670   d.setInfo( dims[j] );
1671 
1672   if ( d.exec() == QDialog::Accepted )
1673   {
1674     QgsVectorLayerServerProperties::WmsDimensionInfo info = d.info();
1675 
1676     // remove old
1677     mLayer->serverProperties()->removeWmsDimension( wmsDimName );
1678     int idx = mWmsDimensionsTreeWidget->indexOfTopLevelItem( item );
1679     mWmsDimensionsTreeWidget->takeTopLevelItem( idx );
1680 
1681     // save new
1682     mLayer->serverProperties()->addWmsDimension( info );
1683     addWmsDimensionInfoToTreeWidget( info, idx );
1684   }
1685 }
1686 
addWmsDimensionInfoToTreeWidget(const QgsVectorLayerServerProperties::WmsDimensionInfo & wmsDim,const int insertIndex)1687 void QgsVectorLayerProperties::addWmsDimensionInfoToTreeWidget( const QgsVectorLayerServerProperties::WmsDimensionInfo &wmsDim, const int insertIndex )
1688 {
1689   QTreeWidgetItem *wmsDimensionItem = new QTreeWidgetItem();
1690   wmsDimensionItem->setFlags( Qt::ItemIsEnabled );
1691 
1692   wmsDimensionItem->setText( 0, tr( "Dimension" ) );
1693   wmsDimensionItem->setText( 1, wmsDim.name );
1694 
1695   QFont f = wmsDimensionItem->font( 0 );
1696   f.setBold( true );
1697   wmsDimensionItem->setFont( 0, f );
1698   wmsDimensionItem->setFont( 1, f );
1699 
1700   wmsDimensionItem->setData( 0, Qt::UserRole, wmsDim.name );
1701 
1702   QTreeWidgetItem *childWmsDimensionField = new QTreeWidgetItem();
1703   childWmsDimensionField->setText( 0, tr( "Field" ) );
1704   childWmsDimensionField->setText( 1, wmsDim.fieldName );
1705   childWmsDimensionField->setFlags( Qt::ItemIsEnabled );
1706   wmsDimensionItem->addChild( childWmsDimensionField );
1707 
1708   QTreeWidgetItem *childWmsDimensionEndField = new QTreeWidgetItem();
1709   childWmsDimensionEndField->setText( 0, tr( "End field" ) );
1710   childWmsDimensionEndField->setText( 1, wmsDim.endFieldName );
1711   childWmsDimensionEndField->setFlags( Qt::ItemIsEnabled );
1712   wmsDimensionItem->addChild( childWmsDimensionEndField );
1713 
1714   QTreeWidgetItem *childWmsDimensionUnits = new QTreeWidgetItem();
1715   childWmsDimensionUnits->setText( 0, tr( "Units" ) );
1716   childWmsDimensionUnits->setText( 1, wmsDim.units );
1717   childWmsDimensionUnits->setFlags( Qt::ItemIsEnabled );
1718   wmsDimensionItem->addChild( childWmsDimensionUnits );
1719 
1720   QTreeWidgetItem *childWmsDimensionUnitSymbol = new QTreeWidgetItem();
1721   childWmsDimensionUnitSymbol->setText( 0, tr( "Unit symbol" ) );
1722   childWmsDimensionUnitSymbol->setText( 1, wmsDim.unitSymbol );
1723   childWmsDimensionUnitSymbol->setFlags( Qt::ItemIsEnabled );
1724   wmsDimensionItem->addChild( childWmsDimensionUnitSymbol );
1725 
1726   QTreeWidgetItem *childWmsDimensionDefaultValue = new QTreeWidgetItem();
1727   childWmsDimensionDefaultValue->setText( 0, tr( "Default display" ) );
1728   childWmsDimensionDefaultValue->setText( 1, QgsVectorLayerServerProperties::wmsDimensionDefaultDisplayLabels()[wmsDim.defaultDisplayType] );
1729   childWmsDimensionDefaultValue->setFlags( Qt::ItemIsEnabled );
1730   wmsDimensionItem->addChild( childWmsDimensionDefaultValue );
1731 
1732   QTreeWidgetItem *childWmsDimensionRefValue = new QTreeWidgetItem();
1733   childWmsDimensionRefValue->setText( 0, tr( "Reference value" ) );
1734   childWmsDimensionRefValue->setText( 1, wmsDim.referenceValue.toString() );
1735   childWmsDimensionRefValue->setFlags( Qt::ItemIsEnabled );
1736   wmsDimensionItem->addChild( childWmsDimensionRefValue );
1737 
1738   if ( insertIndex >= 0 )
1739     mWmsDimensionsTreeWidget->insertTopLevelItem( insertIndex, wmsDimensionItem );
1740   else
1741     mWmsDimensionsTreeWidget->addTopLevelItem( wmsDimensionItem );
1742 
1743   mWmsDimensionsTreeWidget->setCurrentItem( wmsDimensionItem );
1744   mWmsDimensionsTreeWidget->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
1745 }
1746 
mButtonRemoveWmsDimension_clicked()1747 void QgsVectorLayerProperties::mButtonRemoveWmsDimension_clicked()
1748 {
1749   QTreeWidgetItem *currentWmsDimensionItem = mWmsDimensionsTreeWidget->currentItem();
1750   if ( !mLayer || !currentWmsDimensionItem )
1751   {
1752     return;
1753   }
1754 
1755   mLayer->serverProperties()->removeWmsDimension( currentWmsDimensionItem->data( 0, Qt::UserRole ).toString() );
1756   mWmsDimensionsTreeWidget->takeTopLevelItem( mWmsDimensionsTreeWidget->indexOfTopLevelItem( currentWmsDimensionItem ) );
1757 }
1758 
1759 
updateSymbologyPage()1760 void QgsVectorLayerProperties::updateSymbologyPage()
1761 {
1762 
1763   //find out the type of renderer in the vectorlayer, create a dialog with these settings and add it to the form
1764   delete mRendererDialog;
1765   mRendererDialog = nullptr;
1766 
1767   if ( mLayer->renderer() )
1768   {
1769     mRendererDialog = new QgsRendererPropertiesDialog( mLayer, QgsStyle::defaultStyle(), true, this );
1770     mRendererDialog->setDockMode( false );
1771     QgsSymbolWidgetContext context;
1772     context.setMapCanvas( mCanvas );
1773     context.setMessageBar( mMessageBar );
1774     mRendererDialog->setContext( context );
1775     connect( mRendererDialog, &QgsRendererPropertiesDialog::showPanel, this, &QgsVectorLayerProperties::openPanel );
1776     connect( mRendererDialog, &QgsRendererPropertiesDialog::layerVariablesChanged, this, &QgsVectorLayerProperties::updateVariableEditor );
1777     connect( mRendererDialog, &QgsRendererPropertiesDialog::widgetChanged, this,  [ = ] { updateAuxiliaryStoragePage(); } );
1778   }
1779   else
1780   {
1781     mOptsPage_Style->setEnabled( false ); // hide symbology item
1782   }
1783 
1784   if ( mRendererDialog )
1785   {
1786     mRendererDialog->layout()->setContentsMargins( 0, 0, 0, 0 );
1787     widgetStackRenderers->addWidget( mRendererDialog );
1788     widgetStackRenderers->setCurrentWidget( mRendererDialog );
1789     widgetStackRenderers->currentWidget()->layout()->setContentsMargins( 0, 0, 0, 0 );
1790   }
1791 }
1792 
setPbnQueryBuilderEnabled()1793 void QgsVectorLayerProperties::setPbnQueryBuilderEnabled()
1794 {
1795   pbnQueryBuilder->setEnabled( mLayer &&
1796                                mLayer->dataProvider() &&
1797                                mLayer->dataProvider()->supportsSubsetString() &&
1798                                !mLayer->isEditable() );
1799 
1800   if ( mLayer && mLayer->isEditable() )
1801   {
1802     pbnQueryBuilder->setToolTip( tr( "Stop editing mode to enable this." ) );
1803   }
1804 
1805 }
1806 
pbnUpdateExtents_clicked()1807 void QgsVectorLayerProperties::pbnUpdateExtents_clicked()
1808 {
1809   mLayer->updateExtents( true ); // force update whatever options activated
1810   mMetadataFilled = false;
1811 }
1812 
optionsStackedWidget_CurrentChanged(int index)1813 void QgsVectorLayerProperties::optionsStackedWidget_CurrentChanged( int index )
1814 {
1815   QgsOptionsDialogBase::optionsStackedWidget_CurrentChanged( index );
1816 
1817   bool isMetadataPanel = ( index == mOptStackedWidget->indexOf( mOptsPage_Metadata ) );
1818   mBtnStyle->setVisible( ! isMetadataPanel );
1819   mBtnMetadata->setVisible( isMetadataPanel );
1820 
1821   if ( index == mOptStackedWidget->indexOf( mOptsPage_Information ) && ! mMetadataFilled )
1822   {
1823     //set the metadata contents (which can be expensive)
1824     teMetadataViewer->clear();
1825     teMetadataViewer->setHtml( htmlMetadata() );
1826     mMetadataFilled = true;
1827   }
1828 
1829   resizeAlltabs( index );
1830 }
1831 
mSimplifyDrawingGroupBox_toggled(bool checked)1832 void QgsVectorLayerProperties::mSimplifyDrawingGroupBox_toggled( bool checked )
1833 {
1834   const QgsVectorDataProvider *provider = mLayer->dataProvider();
1835   if ( !( provider && ( provider->capabilities() & QgsVectorDataProvider::SimplifyGeometries ) != 0 ) )
1836   {
1837     mSimplifyDrawingAtProvider->setEnabled( false );
1838   }
1839   else
1840   {
1841     mSimplifyDrawingAtProvider->setEnabled( checked );
1842   }
1843 }
1844 
updateVariableEditor()1845 void QgsVectorLayerProperties::updateVariableEditor()
1846 {
1847   QgsExpressionContext context;
1848   mVariableEditor->setContext( &context );
1849   mVariableEditor->context()->appendScope( QgsExpressionContextUtils::globalScope() );
1850   mVariableEditor->context()->appendScope( QgsExpressionContextUtils::projectScope( QgsProject::instance() ) );
1851   mVariableEditor->context()->appendScope( QgsExpressionContextUtils::layerScope( mLayer ) );
1852   mVariableEditor->reloadContext();
1853   mVariableEditor->setEditableScopeIndex( 2 );
1854 }
1855 
showHelp()1856 void QgsVectorLayerProperties::showHelp()
1857 {
1858   const QVariant helpPage = mOptionsStackedWidget->currentWidget()->property( "helpPage" );
1859 
1860   if ( helpPage.isValid() )
1861   {
1862     QgsHelp::openHelp( helpPage.toString() );
1863   }
1864   else
1865   {
1866     QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html" ) );
1867   }
1868 }
1869 
updateAuxiliaryStoragePage()1870 void QgsVectorLayerProperties::updateAuxiliaryStoragePage()
1871 {
1872   const QgsAuxiliaryLayer *alayer = mLayer->auxiliaryLayer();
1873 
1874   if ( alayer )
1875   {
1876     // set widgets to enable state
1877     mAuxiliaryStorageInformationGrpBox->setEnabled( true );
1878     mAuxiliaryStorageFieldsGrpBox->setEnabled( true );
1879 
1880     // update key
1881     mAuxiliaryStorageKeyLineEdit->setText( alayer->joinInfo().targetFieldName() );
1882 
1883     // update feature count
1884     long features = alayer->featureCount();
1885     mAuxiliaryStorageFeaturesLineEdit->setText( QString::number( features ) );
1886 
1887     // update actions
1888     mAuxiliaryLayerActionClear->setEnabled( true );
1889     mAuxiliaryLayerActionDelete->setEnabled( true );
1890     mAuxiliaryLayerActionExport->setEnabled( true );
1891     mAuxiliaryLayerActionNew->setEnabled( false );
1892 
1893     const QgsAuxiliaryLayer *alayer = mLayer->auxiliaryLayer();
1894     if ( alayer )
1895     {
1896       const int fields = alayer->auxiliaryFields().count();
1897       mAuxiliaryStorageFieldsLineEdit->setText( QString::number( fields ) );
1898 
1899       // add fields
1900       mAuxiliaryStorageFieldsTree->clear();
1901       for ( const QgsField &field : alayer->auxiliaryFields() )
1902       {
1903         const QgsPropertyDefinition prop = QgsAuxiliaryLayer::propertyDefinitionFromField( field );
1904         QTreeWidgetItem *item = new QTreeWidgetItem();
1905 
1906         item->setText( 0, prop.origin() );
1907         item->setText( 1, prop.name() );
1908         item->setText( 2, prop.comment() );
1909         item->setText( 3, field.typeName() );
1910         item->setText( 4, field.name() );
1911 
1912         mAuxiliaryStorageFieldsTree->addTopLevelItem( item );
1913       }
1914     }
1915   }
1916   else
1917   {
1918     mAuxiliaryStorageInformationGrpBox->setEnabled( false );
1919     mAuxiliaryStorageFieldsGrpBox->setEnabled( false );
1920 
1921     mAuxiliaryLayerActionClear->setEnabled( false );
1922     mAuxiliaryLayerActionDelete->setEnabled( false );
1923     mAuxiliaryLayerActionExport->setEnabled( false );
1924     mAuxiliaryLayerActionNew->setEnabled( true );
1925 
1926     mAuxiliaryStorageFieldsTree->clear();
1927     mAuxiliaryStorageKeyLineEdit->setText( QString() );
1928     mAuxiliaryStorageFieldsLineEdit->setText( QString() );
1929     mAuxiliaryStorageFeaturesLineEdit->setText( QString() );
1930   }
1931 }
1932 
onAuxiliaryLayerNew()1933 void QgsVectorLayerProperties::onAuxiliaryLayerNew()
1934 {
1935   QgsAuxiliaryLayer *alayer = mLayer->auxiliaryLayer();
1936 
1937   if ( alayer )
1938     return;
1939 
1940   QgsNewAuxiliaryLayerDialog dlg( mLayer, this );
1941   if ( dlg.exec() == QDialog::Accepted )
1942   {
1943     updateAuxiliaryStoragePage();
1944   }
1945 }
1946 
onAuxiliaryLayerClear()1947 void QgsVectorLayerProperties::onAuxiliaryLayerClear()
1948 {
1949   QgsAuxiliaryLayer *alayer = mLayer->auxiliaryLayer();
1950 
1951   if ( !alayer )
1952     return;
1953 
1954   const QString msg = tr( "Are you sure you want to clear auxiliary data for %1?" ).arg( mLayer->name() );
1955   QMessageBox::StandardButton reply;
1956   reply = QMessageBox::question( this, "Clear Auxiliary Data", msg, QMessageBox::Yes | QMessageBox::No );
1957 
1958   if ( reply == QMessageBox::Yes )
1959   {
1960     QApplication::setOverrideCursor( Qt::WaitCursor );
1961     alayer->clear();
1962     QApplication::restoreOverrideCursor();
1963     updateAuxiliaryStoragePage();
1964     mLayer->triggerRepaint();
1965   }
1966 }
1967 
onAuxiliaryLayerDelete()1968 void QgsVectorLayerProperties::onAuxiliaryLayerDelete()
1969 {
1970   QgsAuxiliaryLayer *alayer = mLayer->auxiliaryLayer();
1971   if ( !alayer )
1972     return;
1973 
1974   const QString msg = tr( "Are you sure you want to delete auxiliary storage for %1?" ).arg( mLayer->name() );
1975   QMessageBox::StandardButton reply;
1976   reply = QMessageBox::question( this, "Delete Auxiliary Storage", msg, QMessageBox::Yes | QMessageBox::No );
1977 
1978   if ( reply == QMessageBox::Yes )
1979   {
1980     QApplication::setOverrideCursor( Qt::WaitCursor );
1981     QgsDataSourceUri uri( alayer->source() );
1982 
1983     // delete each attribute to correctly update layer settings and data
1984     // defined buttons
1985     while ( alayer->auxiliaryFields().size() > 0 )
1986     {
1987       QgsField aField = alayer->auxiliaryFields()[0];
1988       deleteAuxiliaryField( alayer->fields().indexOf( aField.name() ) );
1989     }
1990 
1991     mLayer->setAuxiliaryLayer(); // remove auxiliary layer
1992     QgsAuxiliaryStorage::deleteTable( uri );
1993     QApplication::restoreOverrideCursor();
1994     updateAuxiliaryStoragePage();
1995     mLayer->triggerRepaint();
1996   }
1997 }
1998 
onAuxiliaryLayerDeleteField()1999 void QgsVectorLayerProperties::onAuxiliaryLayerDeleteField()
2000 {
2001   QgsAuxiliaryLayer *alayer = mLayer->auxiliaryLayer();
2002   if ( !alayer )
2003     return;
2004 
2005   QList<QTreeWidgetItem *> items = mAuxiliaryStorageFieldsTree->selectedItems();
2006   if ( items.count() < 1 )
2007     return;
2008 
2009   // get auxiliary field name and index from item
2010   const QTreeWidgetItem *item = items[0];
2011   QgsPropertyDefinition def;
2012   def.setOrigin( item->text( 0 ) );
2013   def.setName( item->text( 1 ) );
2014   def.setComment( item->text( 2 ) );
2015 
2016   const QString fieldName = QgsAuxiliaryLayer::nameFromProperty( def );
2017 
2018   const int index = mLayer->auxiliaryLayer()->fields().indexOf( fieldName );
2019   if ( index < 0 )
2020     return;
2021 
2022   // should be only 1 field
2023   const QString msg = tr( "Are you sure you want to delete auxiliary field %1 for %2?" ).arg( item->text( 1 ), item->text( 0 ) );
2024 
2025   QMessageBox::StandardButton reply;
2026   const QString title = QObject::tr( "Delete Auxiliary Field" );
2027   reply = QMessageBox::question( this, title, msg, QMessageBox::Yes | QMessageBox::No );
2028 
2029   if ( reply == QMessageBox::Yes )
2030   {
2031     QApplication::setOverrideCursor( Qt::WaitCursor );
2032     deleteAuxiliaryField( index );
2033     mLayer->triggerRepaint();
2034     QApplication::restoreOverrideCursor();
2035   }
2036 }
2037 
onAuxiliaryLayerAddField()2038 void QgsVectorLayerProperties::onAuxiliaryLayerAddField()
2039 {
2040   QgsAuxiliaryLayer *alayer = mLayer->auxiliaryLayer();
2041   if ( !alayer )
2042     return;
2043 
2044   QgsNewAuxiliaryFieldDialog dlg( QgsPropertyDefinition(), mLayer, false );
2045   if ( dlg.exec() == QDialog::Accepted )
2046   {
2047     updateAuxiliaryStoragePage();
2048   }
2049 }
2050 
deleteAuxiliaryField(int index)2051 void QgsVectorLayerProperties::deleteAuxiliaryField( int index )
2052 {
2053   if ( !mLayer->auxiliaryLayer() )
2054     return;
2055 
2056   int key = mLayer->auxiliaryLayer()->propertyFromIndex( index );
2057   QgsPropertyDefinition def = mLayer->auxiliaryLayer()->propertyDefinitionFromIndex( index );
2058 
2059   if ( mLayer->auxiliaryLayer()->deleteAttribute( index ) )
2060   {
2061     mLayer->updateFields();
2062 
2063     // immediately deactivate data defined button
2064     if ( key >= 0 && def.origin().compare( "labeling", Qt::CaseInsensitive ) == 0
2065          && labelingDialog
2066          && labelingDialog->labelingGui() )
2067     {
2068       labelingDialog->labelingGui()->deactivateField( static_cast<QgsPalLayerSettings::Property>( key ) );
2069     }
2070 
2071     updateAuxiliaryStoragePage();
2072     mSourceFieldsPropertiesDialog->init();
2073   }
2074   else
2075   {
2076     const QString title = QObject::tr( "Delete Auxiliary Field" );
2077     const int timeout = QgsSettings().value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
2078     const QString errors = mLayer->auxiliaryLayer()->commitErrors().join( QLatin1String( "\n  " ) );
2079     const QString msg = QObject::tr( "Unable to remove auxiliary field (%1)" ).arg( errors );
2080     mMessageBar->pushMessage( title, msg, Qgis::Warning, timeout );
2081   }
2082 }
2083