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
- const QString basepath = datasource.left ( datasource.lastIndexOf (' /' ) );
121
- mFileBase [name].append ( basepath );
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
-
369
-
370
- for ( int i = 0 ; i < mLayerList ->rowCount (); i++ )
371
- {
372
- int idx = mLayerList ->item ( i, 0 )->data ( Qt::UserRole ).toInt ();
373
- QDomNode &node = const_cast <QDomNode &>( mLayers [ idx ] );
374
-
375
- const QString name = mLayerList ->item ( i, 0 )->text ();
376
- QTableWidgetItem *item = mLayerList ->item ( i, 4 );
377
- QString datasource = item->text ();
378
- const QString basepath = datasource.left ( datasource.lastIndexOf (' /' ) );
379
- bool changed = false ;
380
-
381
- if ( mFileBase [ name ].size () == 1 )
382
- {
383
- if ( mFileBase [ name ][0 ] != basepath && !baseChange.contains ( mFileBase [ name ][0 ] ) )
384
- {
385
- baseChange[ mFileBase [ name ][0 ] ] = basepath;
386
- changed = true ;
387
- }
388
- }
389
- else if ( mFileBase [ name ].size () > 1 )
390
- {
391
- if ( mFileBase [ name ].indexOf ( basepath ) == -1 )
392
- {
393
- const QList<QString> fileBases = mFileBase [ name ];
394
- for ( QString fileBase : fileBases )
395
- {
396
- if ( !baseChange.contains ( fileBase ) )
397
- {
398
- baseChange[ fileBase ] = basepath;
399
- changed = true ;
400
- }
401
- }
402
- }
403
- }
404
- if ( !changed && baseChange.contains ( basepath ) )
405
- datasource = datasource.replace ( basepath, baseChange[basepath] );
406
-
407
-
408
- bool dataSourceChanged { false };
409
- const QString layerId { node.namedItem ( QStringLiteral ( " id" ) ).toElement ().text () };
410
- const QString provider { node.namedItem ( QStringLiteral ( " provider" ) ).toElement ().text () };
411
-
412
- // Try first to change the datasource of the existing layers, this will
413
- // maintain the current status (checked/unchecked) and group
414
- if ( QgsProject::instance ()->mapLayer ( layerId ) )
415
- {
416
- QgsDataProvider::ProviderOptions options;
417
- QgsMapLayer *mapLayer = QgsProject::instance ()->mapLayer ( layerId );
418
- if ( mapLayer )
419
- {
420
- mapLayer->setDataSource ( datasource, name, provider, options );
421
- dataSourceChanged = mapLayer->isValid ();
422
- }
423
- }
424
-
425
- // If the data source was changed successfully, remove the bad layer from the dialog
426
- // otherwise, try to set the new datasource in the XML node and reload the layer,
427
- // finally marks with red all remaining bad layers.
428
- if ( dataSourceChanged )
429
- {
430
- mLayerList ->removeRow ( i-- );
431
- }
432
- else
433
- {
434
- node.namedItem ( QStringLiteral ( " datasource" ) ).toElement ().firstChild ().toText ().setData ( datasource );
435
- if ( QgsProject::instance ()->readLayer ( node ) )
436
- {
437
- mLayerList ->removeRow ( i-- );
438
- }
439
- else
440
- {
441
- item->setForeground ( QBrush ( Qt::red ) );
442
- if ( mFileBase [ name ].size () == 1 )
443
- mFileBase [ name ][0 ] = basepath ;
444
- else if ( mFileBase [ name ].size () > 1 )
445
- mFileBase [ name ].append ( basepath );
446
- }
447
- }
448
- }
449
-
450
- // Final cleanup: remove any bad layer (it should not be any btw)
451
- if ( mLayerList ->rowCount () == 0 )
452
- {
453
- QList<QgsMapLayer *> toRemove;
454
- const auto mapLayers = QgsProject::instance ()->mapLayers ();
455
- for ( const auto &l : mapLayers )
456
- {
457
- if ( ! l->isValid () )
458
- toRemove << l;
459
- }
460
- QgsProject::instance ()->removeMapLayers ( toRemove );
461
- accept ();
462
- }
463
-
464
- QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( false );
465
-
466
- }
467
-
468
- void QgsHandleBadLayers::accept ()
469
- {
470
-
471
- if ( mLayerList ->rowCount () > 0 &&
472
- QMessageBox::warning ( this ,
473
- tr ( " Unhandled layer will be lost." ),
474
- tr ( " There are still %n unhandled layer(s). If they are not fixed, they will be disabled/deactivated until the project is opened again." ,
475
- " unhandled layers" ,
476
- mLayerList ->rowCount () ),
477
- QMessageBox::Ok | QMessageBox::Cancel,
478
- QMessageBox::Cancel ) == QMessageBox::Cancel )
479
- {
480
- return ;
481
- }
482
- QList<QgsMapLayer *> toRemove;
483
- for ( const auto &l : QgsProject::instance ()->mapLayers ( ) )
484
- {
485
- if ( ! l->isValid () )
486
- toRemove << l;
487
- }
488
- QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( true );
489
- QgsProject::instance ()->removeMapLayers ( toRemove );
490
- QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( false );
491
- mLayerList ->clear ();
492
-
493
- QDialog::accept ();
494
- }
495
-
496
- int QgsHandleBadLayers::layerCount ()
497
- {
498
- return mLayerList ->rowCount ();
499
- }
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
+ const QString basepath = datasource.left ( datasource.lastIndexOf (' /' ) );
121
+ mFileBase [name].append ( basepath );
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
+
369
+
370
+ for ( int i = 0 ; i < mLayerList ->rowCount (); i++ )
371
+ {
372
+ int idx = mLayerList ->item ( i, 0 )->data ( Qt::UserRole ).toInt ();
373
+ QDomNode &node = const_cast <QDomNode &>( mLayers [ idx ] );
374
+
375
+ const QString name = mLayerList ->item ( i, 0 )->text ();
376
+ QTableWidgetItem *item = mLayerList ->item ( i, 4 );
377
+ QString datasource = item->text ();
378
+ const QString basepath = datasource.left ( datasource.lastIndexOf (' /' ) );
379
+ bool changed = false ;
380
+
381
+ if ( mFileBase [ name ].size () == 1 )
382
+ {
383
+ if ( mFileBase [ name ][0 ] != basepath && !baseChange.contains ( mFileBase [ name ][0 ] ) )
384
+ {
385
+ baseChange[ mFileBase [ name ][0 ] ] = basepath;
386
+ changed = true ;
387
+ }
388
+ }
389
+ else if ( mFileBase [ name ].size () > 1 )
390
+ {
391
+ if ( mFileBase [ name ].indexOf ( basepath ) == -1 )
392
+ {
393
+ const QList<QString> fileBases = mFileBase [ name ];
394
+ for ( QString fileBase : fileBases )
395
+ {
396
+ if ( !baseChange.contains ( fileBase ) )
397
+ {
398
+ baseChange[ fileBase ] = basepath;
399
+ changed = true ;
400
+ }
401
+ }
402
+ }
403
+ }
404
+ if ( !changed && baseChange.contains ( basepath ) )
405
+ datasource = datasource.replace ( basepath, baseChange[basepath] );
406
+
407
+
408
+ bool dataSourceChanged { false };
409
+ const QString layerId { node.namedItem ( QStringLiteral ( " id" ) ).toElement ().text () };
410
+ const QString provider { node.namedItem ( QStringLiteral ( " provider" ) ).toElement ().text () };
411
+
412
+ // Try first to change the datasource of the existing layers, this will
413
+ // maintain the current status (checked/unchecked) and group
414
+ if ( QgsProject::instance ()->mapLayer ( layerId ) )
415
+ {
416
+ QgsDataProvider::ProviderOptions options;
417
+ QgsMapLayer *mapLayer = QgsProject::instance ()->mapLayer ( layerId );
418
+ if ( mapLayer )
419
+ {
420
+ mapLayer->setDataSource ( datasource, name, provider, options );
421
+ dataSourceChanged = mapLayer->isValid ();
422
+ }
423
+ }
424
+
425
+ // If the data source was changed successfully, remove the bad layer from the dialog
426
+ // otherwise, try to set the new datasource in the XML node and reload the layer,
427
+ // finally marks with red all remaining bad layers.
428
+ if ( dataSourceChanged )
429
+ {
430
+ mLayerList ->removeRow ( i-- );
431
+ }
432
+ else
433
+ {
434
+ node.namedItem ( QStringLiteral ( " datasource" ) ).toElement ().firstChild ().toText ().setData ( datasource );
435
+ if ( QgsProject::instance ()->readLayer ( node ) )
436
+ {
437
+ mLayerList ->removeRow ( i-- );
438
+ }
439
+ else
440
+ {
441
+ item->setForeground ( QBrush ( Qt::red ) );
442
+ if ( mFileBase [ name ].size () == 1 )
443
+ mFileBase [ name ][0 ] = basepath ;
444
+ else if ( mFileBase [ name ].size () > 1 )
445
+ mFileBase [ name ].append ( basepath );
446
+ }
447
+ }
448
+ }
449
+
450
+ // Final cleanup: remove any bad layer (it should not be any btw)
451
+ if ( mLayerList ->rowCount () == 0 )
452
+ {
453
+ QList<QgsMapLayer *> toRemove;
454
+ const auto mapLayers = QgsProject::instance ()->mapLayers ();
455
+ for ( const auto &l : mapLayers )
456
+ {
457
+ if ( ! l->isValid () )
458
+ toRemove << l;
459
+ }
460
+ QgsProject::instance ()->removeMapLayers ( toRemove );
461
+ accept ();
462
+ }
463
+
464
+ QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( false );
465
+
466
+ }
467
+
468
+ void QgsHandleBadLayers::accept ()
469
+ {
470
+
471
+ if ( mLayerList ->rowCount () > 0 &&
472
+ QMessageBox::warning ( this ,
473
+ tr ( " Unhandled layer will be lost." ),
474
+ tr ( " There are still %n unhandled layer(s). If they are not fixed, they will be disabled/deactivated until the project is opened again." ,
475
+ " unhandled layers" ,
476
+ mLayerList ->rowCount () ),
477
+ QMessageBox::Ok | QMessageBox::Cancel,
478
+ QMessageBox::Cancel ) == QMessageBox::Cancel )
479
+ {
480
+ return ;
481
+ }
482
+ QList<QgsMapLayer *> toRemove;
483
+ for ( const auto &l : QgsProject::instance ()->mapLayers ( ) )
484
+ {
485
+ if ( ! l->isValid () )
486
+ toRemove << l;
487
+ }
488
+ QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( true );
489
+ QgsProject::instance ()->removeMapLayers ( toRemove );
490
+ QgsProject::instance ()->layerTreeRegistryBridge ()->setEnabled ( false );
491
+ mLayerList ->clear ();
492
+
493
+ QDialog::accept ();
494
+ }
495
+
496
+ int QgsHandleBadLayers::layerCount ()
497
+ {
498
+ return mLayerList ->rowCount ();
499
+ }
0 commit comments