1
- /* **************************************************************************
2
- qgshandlebadlayers.cpp - description
3
- -------------------
4
- begin : Sat 5 Mar 2011
5
- copyright : (C) 2011 by Juergen E. Fischer, norBIT GmbH
6
- email : jef at norbit dot de
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 " qgshandlebadlayers.h"
19
- #include " qgisapp.h"
20
- #include " qgsauthconfigselect.h"
21
- #include " qgsdataprovider.h"
22
- #include " qgsguiutils.h"
23
- #include " qgsdatasourceuri.h"
24
- #include " qgslogger.h"
25
- #include " qgsrasterlayer.h"
26
- #include " qgsproviderregistry.h"
27
- #include " qgsmessagebar.h"
28
- #include " qgssettings.h"
29
- #include " qgslayertreeregistrybridge.h"
30
- #include " qgsapplication.h"
31
-
32
- #include < QDomDocument>
33
- #include < QDomElement>
34
- #include < QFileDialog>
35
- #include < QPushButton>
36
- #include < QToolButton>
37
- #include < QMessageBox>
38
- #include < QDialogButtonBox>
39
- #include < QUrl>
40
-
41
- void QgsHandleBadLayersHandler::handleBadLayers ( const QList<QDomNode> &layers )
42
- {
43
- QApplication::setOverrideCursor ( Qt::ArrowCursor );
44
- QgsHandleBadLayers *dialog = new QgsHandleBadLayers ( layers );
45
-
46
- dialog->buttonBox ->button ( QDialogButtonBox::Ignore )->setToolTip ( tr ( " Import all unavailable layers unmodified (you can fix them later)." ) );
47
- dialog->buttonBox ->button ( QDialogButtonBox::Ignore )->setText ( tr ( " Keep Unavailable Layers" ) );
48
- dialog->buttonBox ->button ( QDialogButtonBox::Discard )->setToolTip ( tr ( " Remove all unavailable layers from the project" ) );
49
- dialog->buttonBox ->button ( QDialogButtonBox::Discard )->setText ( tr ( " Remove Unavailable Layers" ) );
50
- dialog->buttonBox ->button ( QDialogButtonBox::Discard )->setIcon ( QgsApplication::getThemeIcon ( QStringLiteral ( " /mActionDeleteSelected.svg" ) ) );
51
-
52
- if ( dialog->layerCount () < layers.size () )
53
- QgisApp::instance ()->messageBar ()->pushMessage (
54
- tr ( " Handle unavailable layers" ),
55
- tr ( " %1 of %2 unavailable layers were not fixable." )
56
- .arg ( layers.size () - dialog->layerCount () )
57
- .arg ( layers.size () ),
58
- Qgis::Warning, QgisApp::instance ()->messageTimeout () );
59
-
60
- if ( dialog->layerCount () > 0 )
61
- {
62
- if ( dialog->exec () == dialog->Accepted )
63
- {
64
- emit layersChanged ();
65
- }
66
- }
67
-
68
- delete dialog;
69
- QApplication::restoreOverrideCursor ();
70
- }
71
-
72
-
73
- QgsHandleBadLayers::QgsHandleBadLayers ( const QList<QDomNode> &layers )
74
- : QDialog( QgisApp::instance() )
75
- , mLayers( layers )
76
- {
77
- setupUi ( this );
78
-
79
- mVectorFileFilter = QgsProviderRegistry::instance ()->fileVectorFilters ();
80
- mRasterFileFilter = QgsProviderRegistry::instance ()->fileRasterFilters ();
81
-
82
- mBrowseButton = new QPushButton ( tr ( " Browse" ) );
83
- buttonBox->addButton ( mBrowseButton , QDialogButtonBox::ActionRole );
84
- mBrowseButton ->setDisabled ( true );
85
- mApplyButton = new QPushButton ( tr ( " Apply Changes" ) );
86
- mApplyButton ->setToolTip ( tr ( " Apply fixes to unavailable layers (remaining unavailable layers will be removed from the project)." ) );
87
- buttonBox->addButton ( mApplyButton , QDialogButtonBox::ActionRole );
88
-
89
- connect ( mLayerList , &QTableWidget::itemSelectionChanged, this , &QgsHandleBadLayers::selectionChanged );
90
- connect ( mBrowseButton , &QAbstractButton::clicked, this , &QgsHandleBadLayers::browseClicked );
91
- connect ( mApplyButton , &QAbstractButton::clicked, this , &QgsHandleBadLayers::apply );
92
- connect ( buttonBox->button ( QDialogButtonBox::Ignore ), &QPushButton::clicked, this , &QgsHandleBadLayers::reject );
93
- connect ( buttonBox->button ( QDialogButtonBox::Discard ), &QPushButton::clicked, this , &QgsHandleBadLayers::accept );
94
-
95
- mLayerList ->clear ();
96
- mLayerList ->setSortingEnabled ( true );
97
- mLayerList ->setSelectionBehavior ( QAbstractItemView::SelectRows );
98
- mLayerList ->setColumnCount ( 5 );
99
- mLayerList ->setColumnWidth ( 3 , 75 );
100
-
101
- mLayerList ->setHorizontalHeaderLabels ( QStringList ()
102
- << tr ( " Layer name" )
103
- << tr ( " Type" )
104
- << tr ( " Provider" )
105
- << tr ( " Auth config" )
106
- << tr ( " Datasource" )
107
- );
108
-
109
- int j = 0 ;
110
- for ( int i = 0 ; i < mLayers .size (); i++ )
111
- {
112
- const QDomNode &node = mLayers [i];
113
-
114
- QString name = node.namedItem ( QStringLiteral ( " layername" ) ).toElement ().text ();
115
- QString type = node.toElement ().attribute ( QStringLiteral ( " type" ) );
116
- QString datasource = node.namedItem ( QStringLiteral ( " datasource" ) ).toElement ().text ();
117
- QString provider = node.namedItem ( QStringLiteral ( " provider" ) ).toElement ().text ();
118
- QString vectorProvider = type == QLatin1String ( " vector" ) ? provider : tr ( " none" );
119
- bool providerFileBased = ( QgsProviderRegistry::instance ()->providerCapabilities ( provider ) & QgsDataProvider::File ) != 0 ;
120
-
121
- QgsDebugMsg ( QStringLiteral ( " name=%1 type=%2 provider=%3 datasource='%4'" )
122
- .arg ( name,
123
- type,
124
- vectorProvider,
125
- datasource ) );
126
-
127
- mLayerList ->setRowCount ( j + 1 );
128
-
129
- QTableWidgetItem *item = nullptr ;
130
-
131
- item = new QTableWidgetItem ( name );
132
- item->setData ( Qt::UserRole + 0 , i );
133
- item->setFlags ( item->flags () & ~Qt::ItemIsEditable );
134
- mLayerList ->setItem ( j, 0 , item );
135
-
136
- item = new QTableWidgetItem ( type );
137
- item->setData ( Qt::UserRole + 0 , providerFileBased );
138
- item->setFlags ( item->flags () & ~Qt::ItemIsEditable );
139
- mLayerList ->setItem ( j, 1 , item );
140
-
141
- item = new QTableWidgetItem ( vectorProvider );
142
- item->setFlags ( item->flags () & ~Qt::ItemIsEditable );
143
- mLayerList ->setItem ( j, 2 , item );
144
-
145
- if ( QgsAuthConfigUriEdit::hasConfigId ( datasource ) )
146
- {
147
- QToolButton *btn = new QToolButton ( this );
148
- btn->setMaximumWidth ( 75 );
149
- btn->setMinimumHeight ( 24 );
150
- btn->setText ( tr ( " Edit" ) );
151
- btn->setProperty ( " row" , j );
152
- connect ( btn, &QAbstractButton::clicked, this , &QgsHandleBadLayers::editAuthCfg );
153
- mLayerList ->setCellWidget ( j, 3 , btn );
154
- }
155
- else
156
- {
157
- item = new QTableWidgetItem ( QString () );
158
- mLayerList ->setItem ( j, 3 , item );
159
- }
160
-
161
- item = new QTableWidgetItem ( datasource );
162
- mLayerList ->setItem ( j, 4 , item );
163
-
164
- j++;
165
- }
166
-
167
- // mLayerList->resizeColumnsToContents();
168
- }
169
-
170
- void QgsHandleBadLayers::selectionChanged ()
171
- {
172
-
173
- mRows .clear ();
174
-
175
- Q_FOREACH ( QTableWidgetItem *item, mLayerList ->selectedItems () )
176
- {
177
- if ( item->column () != 0 )
178
- continue ;
179
-
180
- bool providerFileBased = mLayerList ->item ( item->row (), 1 )->data ( Qt::UserRole + 0 ).toBool ();
181
- if ( !providerFileBased )
182
- continue ;
183
-
184
- mRows << item->row ();
185
- }
186
-
187
- mBrowseButton ->setEnabled ( !mRows .isEmpty () );
188
- }
189
-
190
- QString QgsHandleBadLayers::filename ( int row )
191
- {
192
- QString type = mLayerList ->item ( row, 1 )->text ();
193
- QString provider = mLayerList ->item ( row, 2 )->text ();
194
- QString datasource = mLayerList ->item ( row, 4 )->text ();
195
-
196
- if ( type == QLatin1String ( " vector" ) )
197
- {
198
- const QVariantMap parts = QgsProviderRegistry::instance ()->decodeUri ( provider, datasource );
199
- // if parts is empty then provider doesn't handle this method!
200
- return parts.empty () ? datasource : parts.value ( QStringLiteral ( " path" ) ).toString ();
201
- }
202
- else
203
- {
204
- return datasource;
205
- }
206
- }
207
-
208
- void QgsHandleBadLayers::setFilename ( int row, const QString &filename )
209
- {
210
- if ( !QFileInfo::exists ( filename ) )
211
- return ;
212
-
213
- QString type = mLayerList ->item ( row, 1 )->text ();
214
- QString provider = mLayerList ->item ( row, 2 )->text ();
215
- QTableWidgetItem *item = mLayerList ->item ( row, 4 );
216
-
217
- QString datasource = item->text ();
218
-
219
- if ( type == QLatin1String ( " vector" ) )
220
- {
221
- if ( provider == QLatin1String ( " spatialite" ) )
222
- {
223
- QgsDataSourceUri uri ( datasource );
224
- uri.setDatabase ( filename );
225
- datasource = uri.uri ();
226
- }
227
- else if ( provider == QLatin1String ( " ogr" ) )
228
- {
229
- QStringList theURIParts = datasource.split ( ' |' );
230
- theURIParts[0 ] = filename;
231
- datasource = theURIParts.join ( QStringLiteral ( " |" ) );
232
- }
233
- else if ( provider == QLatin1String ( " delimitedtext" ) )
234
- {
235
- QUrl uriSource = QUrl::fromEncoded ( datasource.toLatin1 () );
236
- QUrl uriDest = QUrl::fromLocalFile ( filename );
237
- uriDest.setQueryItems ( uriSource.queryItems () );
238
- datasource = QString::fromLatin1 ( uriDest.toEncoded () );
239
- }
240
- }
241
- else
242
- {
243
- datasource = filename;
244
- }
245
-
246
- item->setText ( datasource );
247
- }
248
-
249
- void QgsHandleBadLayers::browseClicked ()
250
- {
251
-
252
- if ( mRows .size () == 1 )
253
- {
254
- int row = mRows .at ( 0 );
255
- QString type = mLayerList ->item ( row, 1 )->text ();
256
-
257
- QString memoryQualifier, fileFilter;
258
- if ( type == QLatin1String ( " vector" ) )
259
- {
260
- memoryQualifier = QStringLiteral ( " lastVectorFileFilter" );
261
- fileFilter = mVectorFileFilter ;
262
- }
263
- else
264
- {
265
- memoryQualifier = QStringLiteral ( " lastRasterFileFilter" );
266
- fileFilter = mRasterFileFilter ;
267
- }
268
-
269
- QString fn = filename ( row );
270
- if ( fn.isNull () )
271
- return ;
272
-
273
- QStringList selectedFiles;
274
- QString enc;
275
- QString title = tr ( " Select File to Replace '%1'" ).arg ( fn );
276
-
277
- QgsGuiUtils::openFilesRememberingFilter ( memoryQualifier, fileFilter, selectedFiles, enc, title );
278
- if ( selectedFiles.size () != 1 )
279
- {
280
- QMessageBox::information ( this , title, tr ( " Please select exactly one file." ) );
281
- return ;
282
- }
283
-
284
- setFilename ( row, selectedFiles[0 ] );
285
- }
286
- else if ( mRows .size () > 1 )
287
- {
288
- QString title = tr ( " Select New Directory of Selected Files" );
289
-
290
- QgsSettings settings;
291
- QString lastDir = settings.value ( QStringLiteral ( " UI/missingDirectory" ), QDir::homePath () ).toString ();
292
- QString selectedFolder = QFileDialog::getExistingDirectory ( this , title, lastDir );
293
- if ( selectedFolder.isEmpty () )
294
- {
295
- return ;
296
- }
297
-
298
- QDir dir ( selectedFolder );
299
- if ( !dir.exists () )
300
- {
301
- return ;
302
- }
303
-
304
- Q_FOREACH ( int row, mRows )
305
- {
306
- bool providerFileBased = mLayerList ->item ( row, 1 )->data ( Qt::UserRole + 0 ).toBool ();
307
- if ( !providerFileBased )
308
- continue ;
309
-
310
- QString fn = filename ( row );
311
- if ( fn.isEmpty () )
312
- continue ;
313
-
314
- QFileInfo fi ( fn );
315
- fi.setFile ( dir, fi.fileName () );
316
- if ( !fi.exists () )
317
- continue ;
318
-
319
- setFilename ( row, fi.absoluteFilePath () );
320
- }
321
- }
322
- }
323
-
324
- void QgsHandleBadLayers::editAuthCfg ()
325
- {
326
- QToolButton *btn = qobject_cast<QToolButton *>( sender () );
327
- int row = -1 ;
328
- for ( int i = 0 ; i < mLayerList ->rowCount (); i++ )
329
- {
330
- if ( mLayerList ->cellWidget ( i, 3 ) == btn )
331
- {
332
- row = i;
333
- break ;
334
- }
335
- }
336
-
337
- if ( row == -1 )
338
- return ;
339
-
340
- QString provider = mLayerList ->item ( row, 2 )->text ();
341
- if ( provider == QLatin1String ( " none" ) )
342
- provider.clear ();
343
-
344
- QString prevuri = mLayerList ->item ( row, 4 )->text ();
345
-
346
- QgsAuthConfigUriEdit *dlg = new QgsAuthConfigUriEdit ( this , prevuri, provider );
347
- dlg->setWindowModality ( Qt::WindowModal );
348
- dlg->resize ( 500 , 500 );
349
- if ( dlg->exec () )
350
- {
351
- QString newuri ( dlg->dataSourceUri () );
352
- if ( newuri != prevuri )
353
- {
354
- mLayerList ->item ( row, 4 )->setText ( newuri );
355
- }
356
- }
357
- dlg->deleteLater ();
358
- }
359
-
360
- void QgsHandleBadLayers::apply ()
361
- {
362
- QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( true );
363
- buttonBox->button ( QDialogButtonBox::Ignore )->setEnabled ( false );
364
-
365
- for ( int i = 0 ; i < mLayerList ->rowCount (); i++ )
366
- {
367
- int idx = mLayerList ->item ( i, 0 )->data ( Qt::UserRole ).toInt ();
368
- QDomNode &node = const_cast <QDomNode &>( mLayers [ idx ] );
369
-
370
- QTableWidgetItem *item = mLayerList ->item ( i, 4 );
371
- QString datasource = item->text ();
372
-
373
- bool dataSourceChanged { false };
374
- const QString layerId { node.namedItem ( QStringLiteral ( " id" ) ).toElement ().text () };
375
- const QString provider { node.namedItem ( QStringLiteral ( " provider" ) ).toElement ().text () };
376
- const QString name { mLayerList ->item ( i, 0 )->text () };
377
-
378
- // Try first to change the datasource of the existing layers, this will
379
- // maintain the current status (checked/unchecked) and group
380
- if ( QgsProject::instance ()->mapLayer ( layerId ) )
381
- {
382
- QgsDataProvider::ProviderOptions options;
383
- QgsMapLayer *mapLayer = QgsProject::instance ()->mapLayer ( layerId );
384
- if ( mapLayer )
385
- {
386
- mapLayer->setDataSource ( datasource, name, provider, options );
387
- dataSourceChanged = mapLayer->isValid ();
388
- }
389
- }
390
-
391
- // If the data source was changed successfully, remove the bad layer from the dialog
392
- // otherwise, try to set the new datasource in the XML node and reload the layer,
393
- // finally marks with red all remaining bad layers.
394
- if ( dataSourceChanged )
395
- {
396
- mLayerList ->removeRow ( i-- );
397
- }
398
- else
399
- {
400
- node.namedItem ( QStringLiteral ( " datasource" ) ).toElement ().firstChild ().toText ().setData ( datasource );
401
- if ( QgsProject::instance ()->readLayer ( node ) )
402
- {
403
- mLayerList ->removeRow ( i-- );
404
- }
405
- else
406
- {
407
- item->setForeground ( QBrush ( Qt::red ) );
408
- }
409
- }
410
- }
411
-
412
- // Final cleanup: remove any bad layer (it should not be any btw)
413
- if ( mLayerList ->rowCount () == 0 )
414
- {
415
- QList<QgsMapLayer *> toRemove;
416
- const auto mapLayers = QgsProject::instance ()->mapLayers ();
417
- for ( const auto &l : mapLayers )
418
- {
419
- if ( ! l->isValid () )
420
- toRemove << l;
421
- }
422
- QgsProject::instance ()->removeMapLayers ( toRemove );
423
- accept ();
424
- }
425
-
426
- QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( false );
427
-
428
- }
429
-
430
- void QgsHandleBadLayers::accept ()
431
- {
432
-
433
- if ( mLayerList ->rowCount () > 0 &&
434
- QMessageBox::warning ( this ,
435
- tr ( " Unhandled layer will be lost." ),
436
- tr ( " There are still %n unhandled layer(s). If they are not fixed, they will be disabled/deactivated until the project is opened again." ,
437
- " unhandled layers" ,
438
- mLayerList ->rowCount () ),
439
- QMessageBox::Ok | QMessageBox::Cancel,
440
- QMessageBox::Cancel ) == QMessageBox::Cancel )
441
- {
442
- return ;
443
- }
444
- QList<QgsMapLayer *> toRemove;
445
- for ( const auto &l : QgsProject::instance ()->mapLayers ( ) )
446
- {
447
- if ( ! l->isValid () )
448
- toRemove << l;
449
- }
450
- QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( true );
451
- QgsProject::instance ()->removeMapLayers ( toRemove );
452
- QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( false );
453
- mLayerList ->clear ();
454
-
455
- QDialog::accept ();
456
- }
457
-
458
- int QgsHandleBadLayers::layerCount ()
459
- {
460
- return mLayerList ->rowCount ();
461
- }
1
+ /* **************************************************************************
2
+ qgshandlebadlayers.cpp - description
3
+ -------------------
4
+ begin : Sat 5 Mar 2011
5
+ copyright : (C) 2011 by Juergen E. Fischer, norBIT GmbH
6
+ email : jef at norbit dot de
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 " qgshandlebadlayers.h"
19
+ #include " qgisapp.h"
20
+ #include " qgsauthconfigselect.h"
21
+ #include " qgsdataprovider.h"
22
+ #include " qgsguiutils.h"
23
+ #include " qgsdatasourceuri.h"
24
+ #include " qgslogger.h"
25
+ #include " qgsrasterlayer.h"
26
+ #include " qgsproviderregistry.h"
27
+ #include " qgsmessagebar.h"
28
+ #include " qgssettings.h"
29
+ #include " qgslayertreeregistrybridge.h"
30
+ #include " qgsapplication.h"
31
+
32
+ #include < QDomDocument>
33
+ #include < QDomElement>
34
+ #include < QFileDialog>
35
+ #include < QPushButton>
36
+ #include < QToolButton>
37
+ #include < QMessageBox>
38
+ #include < QDialogButtonBox>
39
+ #include < QUrl>
40
+
41
+ void QgsHandleBadLayersHandler::handleBadLayers ( const QList<QDomNode> &layers )
42
+ {
43
+ QApplication::setOverrideCursor ( Qt::ArrowCursor );
44
+ QgsHandleBadLayers *dialog = new QgsHandleBadLayers ( layers );
45
+
46
+ dialog->buttonBox ->button ( QDialogButtonBox::Ignore )->setToolTip ( tr ( " Import all unavailable layers unmodified (you can fix them later)." ) );
47
+ dialog->buttonBox ->button ( QDialogButtonBox::Ignore )->setText ( tr ( " Keep Unavailable Layers" ) );
48
+ dialog->buttonBox ->button ( QDialogButtonBox::Discard )->setToolTip ( tr ( " Remove all unavailable layers from the project" ) );
49
+ dialog->buttonBox ->button ( QDialogButtonBox::Discard )->setText ( tr ( " Remove Unavailable Layers" ) );
50
+ dialog->buttonBox ->button ( QDialogButtonBox::Discard )->setIcon ( QgsApplication::getThemeIcon ( QStringLiteral ( " /mActionDeleteSelected.svg" ) ) );
51
+
52
+ if ( dialog->layerCount () < layers.size () )
53
+ QgisApp::instance ()->messageBar ()->pushMessage (
54
+ tr ( " Handle unavailable layers" ),
55
+ tr ( " %1 of %2 unavailable layers were not fixable." )
56
+ .arg ( layers.size () - dialog->layerCount () )
57
+ .arg ( layers.size () ),
58
+ Qgis::Warning, QgisApp::instance ()->messageTimeout () );
59
+
60
+ if ( dialog->layerCount () > 0 )
61
+ {
62
+ if ( dialog->exec () == dialog->Accepted )
63
+ {
64
+ emit layersChanged ();
65
+ }
66
+ }
67
+
68
+ delete dialog;
69
+ QApplication::restoreOverrideCursor ();
70
+ }
71
+
72
+
73
+ QgsHandleBadLayers::QgsHandleBadLayers ( const QList<QDomNode> &layers )
74
+ : QDialog( QgisApp::instance() )
75
+ , mLayers( layers )
76
+ {
77
+ setupUi ( this );
78
+
79
+ mVectorFileFilter = QgsProviderRegistry::instance ()->fileVectorFilters ();
80
+ mRasterFileFilter = QgsProviderRegistry::instance ()->fileRasterFilters ();
81
+
82
+ mBrowseButton = new QPushButton ( tr ( " Browse" ) );
83
+ buttonBox->addButton ( mBrowseButton , QDialogButtonBox::ActionRole );
84
+ mBrowseButton ->setDisabled ( true );
85
+ mApplyButton = new QPushButton ( tr ( " Apply Changes" ) );
86
+ mApplyButton ->setToolTip ( tr ( " Apply fixes to unavailable layers (remaining unavailable layers will be removed from the project)." ) );
87
+ buttonBox->addButton ( mApplyButton , QDialogButtonBox::ActionRole );
88
+
89
+ connect ( mLayerList , &QTableWidget::itemSelectionChanged, this , &QgsHandleBadLayers::selectionChanged );
90
+ connect ( mBrowseButton , &QAbstractButton::clicked, this , &QgsHandleBadLayers::browseClicked );
91
+ connect ( mApplyButton , &QAbstractButton::clicked, this , &QgsHandleBadLayers::apply );
92
+ connect ( buttonBox->button ( QDialogButtonBox::Ignore ), &QPushButton::clicked, this , &QgsHandleBadLayers::reject );
93
+ connect ( buttonBox->button ( QDialogButtonBox::Discard ), &QPushButton::clicked, this , &QgsHandleBadLayers::accept );
94
+
95
+ mLayerList ->clear ();
96
+ mLayerList ->setSortingEnabled ( true );
97
+ mLayerList ->setSelectionBehavior ( QAbstractItemView::SelectRows );
98
+ mLayerList ->setColumnCount ( 5 );
99
+ mLayerList ->setColumnWidth ( 3 , 75 );
100
+
101
+ mLayerList ->setHorizontalHeaderLabels ( QStringList ()
102
+ << tr ( " Layer name" )
103
+ << tr ( " Type" )
104
+ << tr ( " Provider" )
105
+ << tr ( " Auth config" )
106
+ << tr ( " Datasource" )
107
+ );
108
+
109
+ int j = 0 ;
110
+ for ( int i = 0 ; i < mLayers .size (); i++ )
111
+ {
112
+ const QDomNode &node = mLayers [i];
113
+
114
+ QString name = node.namedItem ( QStringLiteral ( " layername" ) ).toElement ().text ();
115
+ QString type = node.toElement ().attribute ( QStringLiteral ( " type" ) );
116
+ QString datasource = node.namedItem ( QStringLiteral ( " datasource" ) ).toElement ().text ();
117
+ QString provider = node.namedItem ( QStringLiteral ( " provider" ) ).toElement ().text ();
118
+ QString vectorProvider = type == QLatin1String ( " vector" ) ? provider : tr ( " none" );
119
+ bool providerFileBased = ( QgsProviderRegistry::instance ()->providerCapabilities ( provider ) & QgsDataProvider::File ) != 0 ;
120
+
121
+ mFileBase [name].append ( const datasource.left ( datasource.lastIndexOf (' /' ) ) );
122
+
123
+ QgsDebugMsg ( QStringLiteral ( " name=%1 type=%2 provider=%3 datasource='%4'" )
124
+ .arg ( name,
125
+ type,
126
+ vectorProvider,
127
+ datasource ) );
128
+
129
+ mLayerList ->setRowCount ( j + 1 );
130
+
131
+ QTableWidgetItem *item = nullptr ;
132
+
133
+ item = new QTableWidgetItem ( name );
134
+ item->setData ( Qt::UserRole + 0 , i );
135
+ item->setFlags ( item->flags () & ~Qt::ItemIsEditable );
136
+ mLayerList ->setItem ( j, 0 , item );
137
+
138
+ item = new QTableWidgetItem ( type );
139
+ item->setData ( Qt::UserRole + 0 , providerFileBased );
140
+ item->setFlags ( item->flags () & ~Qt::ItemIsEditable );
141
+ mLayerList ->setItem ( j, 1 , item );
142
+
143
+ item = new QTableWidgetItem ( vectorProvider );
144
+ item->setFlags ( item->flags () & ~Qt::ItemIsEditable );
145
+ mLayerList ->setItem ( j, 2 , item );
146
+
147
+ if ( QgsAuthConfigUriEdit::hasConfigId ( datasource ) )
148
+ {
149
+ QToolButton *btn = new QToolButton ( this );
150
+ btn->setMaximumWidth ( 75 );
151
+ btn->setMinimumHeight ( 24 );
152
+ btn->setText ( tr ( " Edit" ) );
153
+ btn->setProperty ( " row" , j );
154
+ connect ( btn, &QAbstractButton::clicked, this , &QgsHandleBadLayers::editAuthCfg );
155
+ mLayerList ->setCellWidget ( j, 3 , btn );
156
+ }
157
+ else
158
+ {
159
+ item = new QTableWidgetItem ( QString () );
160
+ mLayerList ->setItem ( j, 3 , item );
161
+ }
162
+
163
+ item = new QTableWidgetItem ( datasource );
164
+ mLayerList ->setItem ( j, 4 , item );
165
+
166
+ j++;
167
+ }
168
+
169
+ // mLayerList->resizeColumnsToContents();
170
+ }
171
+
172
+ void QgsHandleBadLayers::selectionChanged ()
173
+ {
174
+
175
+ mRows .clear ();
176
+
177
+ Q_FOREACH ( QTableWidgetItem *item, mLayerList ->selectedItems () )
178
+ {
179
+ if ( item->column () != 0 )
180
+ continue ;
181
+
182
+ bool providerFileBased = mLayerList ->item ( item->row (), 1 )->data ( Qt::UserRole + 0 ).toBool ();
183
+ if ( !providerFileBased )
184
+ continue ;
185
+
186
+ mRows << item->row ();
187
+ }
188
+
189
+ mBrowseButton ->setEnabled ( !mRows .isEmpty () );
190
+ }
191
+
192
+ QString QgsHandleBadLayers::filename ( int row )
193
+ {
194
+ QString type = mLayerList ->item ( row, 1 )->text ();
195
+ QString provider = mLayerList ->item ( row, 2 )->text ();
196
+ QString datasource = mLayerList ->item ( row, 4 )->text ();
197
+
198
+ if ( type == QLatin1String ( " vector" ) )
199
+ {
200
+ const QVariantMap parts = QgsProviderRegistry::instance ()->decodeUri ( provider, datasource );
201
+ // if parts is empty then provider doesn't handle this method!
202
+ return parts.empty () ? datasource : parts.value ( QStringLiteral ( " path" ) ).toString ();
203
+ }
204
+ else
205
+ {
206
+ return datasource;
207
+ }
208
+ }
209
+
210
+ void QgsHandleBadLayers::setFilename ( int row, const QString &filename )
211
+ {
212
+ if ( !QFileInfo::exists ( filename ) )
213
+ return ;
214
+
215
+ QString type = mLayerList ->item ( row, 1 )->text ();
216
+ QString provider = mLayerList ->item ( row, 2 )->text ();
217
+ QTableWidgetItem *item = mLayerList ->item ( row, 4 );
218
+
219
+ QString datasource = item->text ();
220
+
221
+ if ( type == QLatin1String ( " vector" ) )
222
+ {
223
+ if ( provider == QLatin1String ( " spatialite" ) )
224
+ {
225
+ QgsDataSourceUri uri ( datasource );
226
+ uri.setDatabase ( filename );
227
+ datasource = uri.uri ();
228
+ }
229
+ else if ( provider == QLatin1String ( " ogr" ) )
230
+ {
231
+ QStringList theURIParts = datasource.split ( ' |' );
232
+ theURIParts[0 ] = filename;
233
+ datasource = theURIParts.join ( QStringLiteral ( " |" ) );
234
+ }
235
+ else if ( provider == QLatin1String ( " delimitedtext" ) )
236
+ {
237
+ QUrl uriSource = QUrl::fromEncoded ( datasource.toLatin1 () );
238
+ QUrl uriDest = QUrl::fromLocalFile ( filename );
239
+ uriDest.setQueryItems ( uriSource.queryItems () );
240
+ datasource = QString::fromLatin1 ( uriDest.toEncoded () );
241
+ }
242
+ }
243
+ else
244
+ {
245
+ datasource = filename;
246
+ }
247
+
248
+ item->setText ( datasource );
249
+ }
250
+
251
+ void QgsHandleBadLayers::browseClicked ()
252
+ {
253
+
254
+ if ( mRows .size () == 1 )
255
+ {
256
+ int row = mRows .at ( 0 );
257
+ QString type = mLayerList ->item ( row, 1 )->text ();
258
+
259
+ QString memoryQualifier, fileFilter;
260
+ if ( type == QLatin1String ( " vector" ) )
261
+ {
262
+ memoryQualifier = QStringLiteral ( " lastVectorFileFilter" );
263
+ fileFilter = mVectorFileFilter ;
264
+ }
265
+ else
266
+ {
267
+ memoryQualifier = QStringLiteral ( " lastRasterFileFilter" );
268
+ fileFilter = mRasterFileFilter ;
269
+ }
270
+
271
+ QString fn = filename ( row );
272
+ if ( fn.isNull () )
273
+ return ;
274
+
275
+ QStringList selectedFiles;
276
+ QString enc;
277
+ QString title = tr ( " Select File to Replace '%1'" ).arg ( fn );
278
+
279
+ QgsGuiUtils::openFilesRememberingFilter ( memoryQualifier, fileFilter, selectedFiles, enc, title );
280
+ if ( selectedFiles.size () != 1 )
281
+ {
282
+ QMessageBox::information ( this , title, tr ( " Please select exactly one file." ) );
283
+ return ;
284
+ }
285
+
286
+ setFilename ( row, selectedFiles[0 ] );
287
+ }
288
+ else if ( mRows .size () > 1 )
289
+ {
290
+ QString title = tr ( " Select New Directory of Selected Files" );
291
+
292
+ QgsSettings settings;
293
+ QString lastDir = settings.value ( QStringLiteral ( " UI/missingDirectory" ), QDir::homePath () ).toString ();
294
+ QString selectedFolder = QFileDialog::getExistingDirectory ( this , title, lastDir );
295
+ if ( selectedFolder.isEmpty () )
296
+ {
297
+ return ;
298
+ }
299
+
300
+ QDir dir ( selectedFolder );
301
+ if ( !dir.exists () )
302
+ {
303
+ return ;
304
+ }
305
+
306
+ Q_FOREACH ( int row, mRows )
307
+ {
308
+ bool providerFileBased = mLayerList ->item ( row, 1 )->data ( Qt::UserRole + 0 ).toBool ();
309
+ if ( !providerFileBased )
310
+ continue ;
311
+
312
+ QString fn = filename ( row );
313
+ if ( fn.isEmpty () )
314
+ continue ;
315
+
316
+ QFileInfo fi ( fn );
317
+ fi.setFile ( dir, fi.fileName () );
318
+ if ( !fi.exists () )
319
+ continue ;
320
+
321
+ setFilename ( row, fi.absoluteFilePath () );
322
+ }
323
+ }
324
+ }
325
+
326
+ void QgsHandleBadLayers::editAuthCfg ()
327
+ {
328
+ QToolButton *btn = qobject_cast<QToolButton *>( sender () );
329
+ int row = -1 ;
330
+ for ( int i = 0 ; i < mLayerList ->rowCount (); i++ )
331
+ {
332
+ if ( mLayerList ->cellWidget ( i, 3 ) == btn )
333
+ {
334
+ row = i;
335
+ break ;
336
+ }
337
+ }
338
+
339
+ if ( row == -1 )
340
+ return ;
341
+
342
+ QString provider = mLayerList ->item ( row, 2 )->text ();
343
+ if ( provider == QLatin1String ( " none" ) )
344
+ provider.clear ();
345
+
346
+ QString prevuri = mLayerList ->item ( row, 4 )->text ();
347
+
348
+ QgsAuthConfigUriEdit *dlg = new QgsAuthConfigUriEdit ( this , prevuri, provider );
349
+ dlg->setWindowModality ( Qt::WindowModal );
350
+ dlg->resize ( 500 , 500 );
351
+ if ( dlg->exec () )
352
+ {
353
+ QString newuri ( dlg->dataSourceUri () );
354
+ if ( newuri != prevuri )
355
+ {
356
+ mLayerList ->item ( row, 4 )->setText ( newuri );
357
+ }
358
+ }
359
+ dlg->deleteLater ();
360
+ }
361
+
362
+ void QgsHandleBadLayers::apply ()
363
+ {
364
+ QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( true );
365
+ buttonBox->button ( QDialogButtonBox::Ignore )->setEnabled ( false );
366
+ QHash<QString, QString> baseChange;
367
+
368
+ for ( int i = 0 ; i < mLayerList ->rowCount (); i++ )
369
+ {
370
+ int idx = mLayerList ->item ( i, 0 )->data ( Qt::UserRole ).toInt ();
371
+ QDomNode &node = const_cast <QDomNode &>( mLayers [ idx ] );
372
+
373
+ QTableWidgetItem *item = mLayerList ->item ( i, 4 );
374
+ QString datasource = item->text ();
375
+ const QString basepath = datasource.left ( datasource.lastIndexOf (' /' ) )
376
+ bool changed = false ;
377
+
378
+ if ( mFileBase [ name ].size () == 1 )
379
+ {
380
+ if ( mFileBase [ name ][0 ] != basepath && !baseChange.contains ( mFileBase [ name ][0 ] ) )
381
+ {
382
+ baseChange[ mFileBase [ name ][0 ] ] = basepath;
383
+ changed = true ;
384
+ }
385
+ }
386
+ else if ( mFileBase [ name ].size () > 1 )
387
+ {
388
+ if ( mFileBase [ name ].indexOf ( basepath ) == -1 )
389
+ {
390
+ const QList fileBases = mFileBase [ name ];
391
+ for ( QString fileBase : fileBases )
392
+ {
393
+ if ( !baseChange.contains ( fileBase ) )
394
+ {
395
+ baseChange[ fileBase ] = basepath;
396
+ changed = true ;
397
+ }
398
+ }
399
+ }
400
+ }
401
+ if ( !changed && baseChange.contains ( basepath ) )
402
+ datasource = datasource.replace ( basepath, baseChange ( basepath ) );
403
+
404
+
405
+ bool dataSourceChanged { false };
406
+ const QString layerId { node.namedItem ( QStringLiteral ( " id" ) ).toElement ().text () };
407
+ const QString provider { node.namedItem ( QStringLiteral ( " provider" ) ).toElement ().text () };
408
+ const QString name { mLayerList ->item ( i, 0 )->text () };
409
+
410
+ // Try first to change the datasource of the existing layers, this will
411
+ // maintain the current status (checked/unchecked) and group
412
+ if ( QgsProject::instance ()->mapLayer ( layerId ) )
413
+ {
414
+ QgsDataProvider::ProviderOptions options;
415
+ QgsMapLayer *mapLayer = QgsProject::instance ()->mapLayer ( layerId );
416
+ if ( mapLayer )
417
+ {
418
+ mapLayer->setDataSource ( datasource, name, provider, options );
419
+ dataSourceChanged = mapLayer->isValid ();
420
+ }
421
+ }
422
+
423
+ // If the data source was changed successfully, remove the bad layer from the dialog
424
+ // otherwise, try to set the new datasource in the XML node and reload the layer,
425
+ // finally marks with red all remaining bad layers.
426
+ if ( dataSourceChanged )
427
+ {
428
+ mLayerList ->removeRow ( i-- );
429
+ }
430
+ else
431
+ {
432
+ node.namedItem ( QStringLiteral ( " datasource" ) ).toElement ().firstChild ().toText ().setData ( datasource );
433
+ if ( QgsProject::instance ()->readLayer ( node ) )
434
+ {
435
+ mLayerList ->removeRow ( i-- );
436
+ }
437
+ else
438
+ {
439
+ item->setForeground ( QBrush ( Qt::red ) );
440
+ }
441
+ }
442
+ }
443
+
444
+ // Final cleanup: remove any bad layer (it should not be any btw)
445
+ if ( mLayerList ->rowCount () == 0 )
446
+ {
447
+ QList<QgsMapLayer *> toRemove;
448
+ const auto mapLayers = QgsProject::instance ()->mapLayers ();
449
+ for ( const auto &l : mapLayers )
450
+ {
451
+ if ( ! l->isValid () )
452
+ toRemove << l;
453
+ }
454
+ QgsProject::instance ()->removeMapLayers ( toRemove );
455
+ accept ();
456
+ }
457
+
458
+ QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( false );
459
+
460
+ }
461
+
462
+ void QgsHandleBadLayers::accept ()
463
+ {
464
+
465
+ if ( mLayerList ->rowCount () > 0 &&
466
+ QMessageBox::warning ( this ,
467
+ tr ( " Unhandled layer will be lost." ),
468
+ tr ( " There are still %n unhandled layer(s). If they are not fixed, they will be disabled/deactivated until the project is opened again." ,
469
+ " unhandled layers" ,
470
+ mLayerList ->rowCount () ),
471
+ QMessageBox::Ok | QMessageBox::Cancel,
472
+ QMessageBox::Cancel ) == QMessageBox::Cancel )
473
+ {
474
+ return ;
475
+ }
476
+ QList<QgsMapLayer *> toRemove;
477
+ for ( const auto &l : QgsProject::instance ()->mapLayers ( ) )
478
+ {
479
+ if ( ! l->isValid () )
480
+ toRemove << l;
481
+ }
482
+ QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( true );
483
+ QgsProject::instance ()->removeMapLayers ( toRemove );
484
+ QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( false );
485
+ mLayerList ->clear ();
486
+
487
+ QDialog::accept ();
488
+ }
489
+
490
+ int QgsHandleBadLayers::layerCount ()
491
+ {
492
+ return mLayerList ->rowCount ();
493
+ }
0 commit comments