QGIS API Documentation  3.0.2-Girona (307d082)
qgsmetadatawidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmetadatawidget.h - description
3  -------------------
4  begin : 17/05/2017
5  copyright : (C) 2017 by Etienne Trimaille
6  email : etienne at kartoza.com
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 <QtWidgets>
19 #include <QIcon>
20 #include <QPushButton>
21 #include <QComboBox>
22 #include <QString>
23 #include <QInputDialog>
24 #include <QStringListModel>
25 
26 #include "qgsbox3d.h"
27 #include "qgsmetadatawidget.h"
28 #include "qgslogger.h"
30 #include "qgsapplication.h"
31 #include "qgsmapcanvas.h"
32 
34  : QWidget( parent ),
35  mLayer( layer )
36 {
37  setupUi( this );
38  if ( mLayer )
39  {
40  mMetadata = mLayer->metadata();
41  }
42  tabWidget->setCurrentIndex( 0 );
43 
44  // Disable the encoding
45  encodingFrame->setHidden( true );
46 
47  // Default categories, we want them translated, so we are not using a CSV.
48  mDefaultCategories << tr( "Farming" ) << tr( "Climatology Meteorology Atmosphere" ) << tr( "Location" ) << tr( "Intelligence Military" ) << tr( "Transportation" ) << tr( "Structure" ) << tr( "Boundaries" );
49  mDefaultCategories << tr( "Inland Waters" ) << tr( "Planning Cadastre" ) << tr( "Geoscientific Information" ) << tr( "Elevation" ) << tr( "Health" ) << tr( "Biota" ) << tr( "Oceans" ) << tr( "Environment" );
50  mDefaultCategories << tr( "Utilities Communication" ) << tr( "Economy" ) << tr( "Society" ) << tr( "Imagery Base Maps Earth Cover" );
51  mDefaultCategoriesModel = new QStringListModel( mDefaultCategories );
52  mDefaultCategoriesModel->sort( 0 ); // Sorting using translations
53  listDefaultCategories->setModel( mDefaultCategoriesModel );
54 
55  // Categories
56  mCategoriesModel = new QStringListModel( listCategories );
57  listCategories->setModel( mCategoriesModel );
58 
59  // Rights
60  mRightsModel = new QStringListModel( listRights );
61  listRights->setModel( mRightsModel );
62 
63  // Setup the constraints view
64  mConstraintsModel = new QStandardItemModel( tabConstraints );
65  mConstraintsModel->setColumnCount( 2 );
66  QStringList constraintheaders;
67  constraintheaders << tr( "Type" ) << tr( "Constraint" );
68  mConstraintsModel->setHorizontalHeaderLabels( constraintheaders );
69  tabConstraints->setModel( mConstraintsModel );
70  tabConstraints->setItemDelegate( new ConstraintItemDelegate( this ) );
71 
72  // Extent
73  dateTimeFrom->setAllowNull( true );
74  dateTimeTo->setAllowNull( true );
75 
76  // Setup the link view
77  mLinksModel = new QStandardItemModel( tabLinks );
78  mLinksModel->setColumnCount( 7 );
79  QStringList headers = QStringList();
80  headers << tr( "Name" ) << tr( "Type" ) << tr( "URL" ) << tr( "Description" ) << tr( "Format" ) << tr( "MIME" ) << tr( "Size" );
81  mLinksModel->setHorizontalHeaderLabels( headers );
82  tabLinks->setModel( mLinksModel );
83  tabLinks->setItemDelegate( new LinkItemDelegate( this ) );
84 
85  // History
86  mHistoryModel = new QStringListModel( listHistory );
87  listHistory->setModel( mHistoryModel );
88 
89  // Connect signals and slots
90  connect( tabWidget, &QTabWidget::currentChanged, this, &QgsMetadataWidget::updatePanel );
91  connect( btnAutoSource, &QPushButton::clicked, this, &QgsMetadataWidget::fillSourceFromLayer );
92  connect( btnAddVocabulary, &QPushButton::clicked, this, &QgsMetadataWidget::addVocabulary );
93  connect( btnRemoveVocabulary, &QPushButton::clicked, this, &QgsMetadataWidget::removeSelectedVocabulary );
94  connect( btnAddRight, &QPushButton::clicked, this, &QgsMetadataWidget::addRight );
95  connect( btnRemoveRight, &QPushButton::clicked, this, &QgsMetadataWidget::removeSelectedRight );
96  connect( btnAddLicence, &QPushButton::clicked, this, &QgsMetadataWidget::addLicence );
97  connect( btnRemoveLicence, &QPushButton::clicked, this, &QgsMetadataWidget::removeSelectedLicence );
98  connect( btnAddConstraint, &QPushButton::clicked, this, &QgsMetadataWidget::addConstraint );
99  connect( btnRemoveConstraint, &QPushButton::clicked, this, &QgsMetadataWidget::removeSelectedConstraint );
100  connect( btnSetCrsFromLayer, &QPushButton::clicked, this, &QgsMetadataWidget::fillCrsFromLayer );
101  connect( btnSetCrsFromProvider, &QPushButton::clicked, this, &QgsMetadataWidget::fillCrsFromProvider );
102  connect( btnAddAddress, &QPushButton::clicked, this, &QgsMetadataWidget::addAddress );
103  connect( btnRemoveAddress, &QPushButton::clicked, this, &QgsMetadataWidget::removeSelectedAddress );
104  connect( btnAddLink, &QPushButton::clicked, this, &QgsMetadataWidget::addLink );
105  connect( btnRemoveLink, &QPushButton::clicked, this, &QgsMetadataWidget::removeSelectedLink );
106  connect( btnAddHistory, &QPushButton::clicked, this, &QgsMetadataWidget::addHistory );
107  connect( btnRemoveHistory, &QPushButton::clicked, this, &QgsMetadataWidget::removeSelectedHistory );
108  connect( btnNewCategory, &QPushButton::clicked, this, &QgsMetadataWidget::addNewCategory );
109  connect( btnAddDefaultCategory, &QPushButton::clicked, this, &QgsMetadataWidget::addDefaultCategories );
110  connect( btnRemoveCategory, &QPushButton::clicked, this, &QgsMetadataWidget::removeSelectedCategories );
111 
112  fillComboBox();
113  if ( !mLayer )
114  {
115  btnAutoSource->setEnabled( false );
116  btnAutoEncoding->setEnabled( false );
117  btnSetCrsFromLayer->setEnabled( false );
118  }
119  setMetadata( mMetadata );
120 }
121 
123 {
124  mMetadata = layerMetadata;
125  setPropertiesFromLayer();
126 }
127 
129 {
130  QgsLayerMetadata md;
131  saveMetadata( md );
132  return md;
133 }
134 
135 void QgsMetadataWidget::fillSourceFromLayer()
136 {
137  if ( mLayer )
138  {
139  lineEditIdentifier->setText( mLayer->publicSource() );
140  }
141 }
142 
143 void QgsMetadataWidget::addVocabulary()
144 {
145  int row = tabKeywords->rowCount();
146  tabKeywords->setRowCount( row + 1 );
147  QTableWidgetItem *pCell = nullptr;
148 
149  // Vocabulary
150  pCell = new QTableWidgetItem( QString( tr( "undefined %1" ) ).arg( row + 1 ) );
151  tabKeywords->setItem( row, 0, pCell );
152 
153  // Keywords
154  pCell = new QTableWidgetItem();
155  tabKeywords->setItem( row, 1, pCell );
156 }
157 
158 void QgsMetadataWidget::removeSelectedVocabulary()
159 {
160  QItemSelectionModel *selectionModel = tabKeywords->selectionModel();
161  const QModelIndexList selectedRows = selectionModel->selectedRows();
162  for ( int i = 0; i < selectedRows.size() ; i++ )
163  {
164  tabKeywords->model()->removeRow( selectedRows[i].row() );
165  }
166 }
167 
168 void QgsMetadataWidget::addLicence()
169 {
170  QString newLicence = QInputDialog::getItem( this, tr( "New Licence" ), tr( "New Licence" ), parseLicenses(), 0, true );
171  if ( tabLicenses->findItems( newLicence, Qt::MatchExactly ).isEmpty() )
172  {
173  int row = tabLicenses->rowCount();
174  tabLicenses->setRowCount( row + 1 );
175  QTableWidgetItem *pCell = new QTableWidgetItem( newLicence );
176  tabLicenses->setItem( row, 0, pCell );
177  }
178 }
179 
180 void QgsMetadataWidget::removeSelectedLicence()
181 {
182  QItemSelectionModel *selectionModel = tabLicenses->selectionModel();
183  const QModelIndexList selectedRows = selectionModel->selectedRows();
184  for ( int i = 0; i < selectedRows.size() ; i++ )
185  {
186  tabLicenses->model()->removeRow( selectedRows[i].row() );
187  }
188 }
189 
190 void QgsMetadataWidget::addRight()
191 {
192  QString newRight = QInputDialog::getText( this, tr( "New Right" ), tr( "New Right" ) );
193  QStringList existingRights = mRightsModel->stringList();
194  if ( ! existingRights.contains( newRight ) )
195  {
196  existingRights.append( newRight );
197  mRightsModel->setStringList( existingRights );
198  }
199 }
200 
201 void QgsMetadataWidget::removeSelectedRight()
202 {
203  QItemSelectionModel *selection = listRights->selectionModel();
204  if ( selection->hasSelection() )
205  {
206  QModelIndex indexElementSelectionne = selection->currentIndex();
207 
208  QVariant item = mRightsModel->data( indexElementSelectionne, Qt::DisplayRole );
209  QStringList list = mRightsModel->stringList();
210  list.removeOne( item.toString() );
211  mRightsModel->setStringList( list );
212  }
213 }
214 
215 void QgsMetadataWidget::addConstraint()
216 {
217  int row = mConstraintsModel->rowCount();
218  mConstraintsModel->setItem( row, 0, new QStandardItem( QString( tr( "undefined %1" ) ).arg( row + 1 ) ) );
219  mConstraintsModel->setItem( row, 1, new QStandardItem( QString( tr( "undefined %1" ) ).arg( row + 1 ) ) );
220 }
221 
222 void QgsMetadataWidget::removeSelectedConstraint()
223 {
224  const QModelIndexList selectedRows = tabConstraints->selectionModel()->selectedRows();
225  mConstraintsModel->removeRow( selectedRows[0].row() );
226 }
227 
229 {
230  if ( ( mCrs.isValid() ) && ( mLayer ) )
231  {
232  lblCurrentCrs->setText( tr( "CRS: %1 - %2" ).arg( mCrs.authid(), mCrs.description() ) );
233  spatialExtentSelector->setEnabled( true );
234  spatialExtentSelector->setOutputCrs( mCrs );
235 
236  if ( mCrs == mLayer->crs() && mCrs == mLayer->dataProvider()->crs() )
237  {
238  lblCurrentCrsStatus->setText( tr( "Same as layer properties and provider." ) );
239  }
240  else if ( mCrs == mLayer->crs() && mCrs != mLayer->dataProvider()->crs() )
241  {
242  lblCurrentCrsStatus->setText( tr( "Same as layer properties but different than the provider." ) );
243  }
244  else if ( mCrs != mLayer->crs() && mCrs == mLayer->dataProvider()->crs() )
245  {
246  lblCurrentCrsStatus->setText( tr( "Same as the provider but different than the layer properties." ) );
247  }
248  else
249  {
250  lblCurrentCrsStatus->setText( tr( "Does not match either layer properties or the provider." ) );
251  }
252  }
253  else
254  {
255  lblCurrentCrs->setText( tr( "CRS: Not set." ) );
256  lblCurrentCrsStatus->setText( QString() );
257  spatialExtentSelector->setEnabled( false );
258  }
259 }
260 
261 void QgsMetadataWidget::addAddress()
262 {
263  int row = tabAddresses->rowCount();
264  tabAddresses->setRowCount( row + 1 );
265  QTableWidgetItem *pCell = nullptr;
266 
267  // Type
268  pCell = new QTableWidgetItem( QString( tr( "postal" ) ) );
269  tabAddresses->setItem( row, 0, pCell );
270 
271  // Address
272  tabAddresses->setItem( row, 1, new QTableWidgetItem() );
273 
274  // postal code
275  tabAddresses->setItem( row, 2, new QTableWidgetItem() );
276 
277  // City
278  tabAddresses->setItem( row, 3, new QTableWidgetItem() );
279 
280  // Admin area
281  tabAddresses->setItem( row, 4, new QTableWidgetItem() );
282 
283  // Country
284  tabAddresses->setItem( row, 5, new QTableWidgetItem() );
285 }
286 
287 void QgsMetadataWidget::removeSelectedAddress()
288 {
289  QItemSelectionModel *selectionModel = tabAddresses->selectionModel();
290  const QModelIndexList selectedRows = selectionModel->selectedRows();
291  for ( int i = 0; i < selectedRows.size() ; i++ )
292  {
293  tabAddresses->model()->removeRow( selectedRows[i].row() );
294  }
295 }
296 
297 void QgsMetadataWidget::fillCrsFromLayer()
298 {
299  mCrs = mLayer->crs();
300  crsChanged();
301 }
302 
303 void QgsMetadataWidget::fillCrsFromProvider()
304 {
305  mCrs = mLayer->dataProvider()->crs();
306  crsChanged();
307 }
308 
309 void QgsMetadataWidget::addLink()
310 {
311  int row = mLinksModel->rowCount();
312  mLinksModel->setItem( row, 0, new QStandardItem( QString( tr( "undefined %1" ) ).arg( row + 1 ) ) );
313  mLinksModel->setItem( row, 1, new QStandardItem() );
314  mLinksModel->setItem( row, 2, new QStandardItem() );
315  mLinksModel->setItem( row, 3, new QStandardItem() );
316  mLinksModel->setItem( row, 4, new QStandardItem() );
317  mLinksModel->setItem( row, 5, new QStandardItem() );
318  mLinksModel->setItem( row, 6, new QStandardItem() );
319 }
320 
321 void QgsMetadataWidget::removeSelectedLink()
322 {
323  const QModelIndexList selectedRows = tabLinks->selectionModel()->selectedRows();
324  mLinksModel->removeRow( selectedRows[0].row() );
325 }
326 
327 void QgsMetadataWidget::addHistory()
328 {
329  QString newHistory = QInputDialog::getText( this, tr( "New History" ), tr( "New History" ) );
330  QStringList existingHistory = mHistoryModel->stringList();
331  if ( ! existingHistory.contains( newHistory ) )
332  {
333  existingHistory.append( newHistory );
334  mHistoryModel->setStringList( existingHistory );
335  }
336 }
337 
338 void QgsMetadataWidget::removeSelectedHistory()
339 {
340  QItemSelectionModel *selection = listHistory->selectionModel();
341  if ( selection->hasSelection() )
342  {
343  QModelIndex indexElementSelectionne = selection->currentIndex();
344 
345  QVariant item = mHistoryModel->data( indexElementSelectionne, Qt::DisplayRole );
346  QStringList list = mHistoryModel->stringList();
347  list.removeOne( item.toString() );
348  mHistoryModel->setStringList( list );
349  }
350 }
351 
352 void QgsMetadataWidget::fillComboBox()
353 {
354  // Set default values in type combobox
355  // It is advised to use the ISO 19115 MD_ScopeCode values. E.g. 'dataset' or 'series'.
356  // http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml
357  comboType->setEditable( true );
358  comboType->clear();
359  QMap<QString, QString> types = parseTypes();
360  const QStringList &keys = types.keys();
361  int i = 0;
362  for ( const QString &type : keys )
363  {
364  comboType->insertItem( i, type );
365  comboType->setItemData( i, types.value( type ), Qt::ToolTipRole );
366  i++;
367  }
368 
369  // Set default values in language combobox
370  // It is advised to use the ISO 639.2 or ISO 3166 specifications, e.g. 'ENG' or 'SPA',
371  comboLanguage->setEditable( true );
372  comboLanguage->clear();
373  QMap<QString, QString> countries = parseLanguages();
374  const QStringList &k = countries.keys();
375  i = 0;
376  for ( const QString &countryCode : k )
377  {
378  comboLanguage->insertItem( i, countryCode );
379  comboLanguage->setItemData( i, countries.value( countryCode ), Qt::ToolTipRole );
380  i++;
381  }
382 }
383 
384 void QgsMetadataWidget::setPropertiesFromLayer()
385 {
386  // Parent ID
387  lineEditParentId->setText( mMetadata.parentIdentifier() );
388 
389  // Identifier
390  if ( ! mMetadata.identifier().isEmpty() )
391  {
392  lineEditIdentifier->setText( mMetadata.identifier() );
393  }
394 
395  // Title
396  if ( ! mMetadata.title().isEmpty() )
397  {
398  lineEditTitle->setText( mMetadata.title() );
399  }
400 
401  // Type
402  if ( ! mMetadata.type().isEmpty() )
403  {
404  if ( comboType->findText( mMetadata.type() ) == -1 )
405  {
406  comboType->addItem( mMetadata.type() );
407  }
408  comboType->setCurrentIndex( comboType->findText( mMetadata.type() ) );
409  }
410 
411  // Language
412  if ( ! mMetadata.language().isEmpty() )
413  {
414  if ( comboLanguage->findText( mMetadata.language() ) == -1 )
415  {
416  comboLanguage->addItem( mMetadata.language() );
417  }
418  comboLanguage->setCurrentIndex( comboLanguage->findText( mMetadata.language() ) );
419  }
420 
421  // Abstract
422  textEditAbstract->setPlainText( mMetadata.abstract() );
423 
424  // Categories
425  mCategoriesModel->setStringList( mMetadata.categories() );
426 
427  // Keywords
428  tabKeywords->setRowCount( 0 );
429  QMapIterator<QString, QStringList> i( mMetadata.keywords() );
430  while ( i.hasNext() )
431  {
432  i.next();
433  addVocabulary();
434  int currentRow = tabKeywords->rowCount() - 1;
435  tabKeywords->item( currentRow, 0 )->setText( i.key() );
436  tabKeywords->item( currentRow, 1 )->setText( i.value().join( QStringLiteral( "," ) ) );
437  }
438 
439  // Fees
440  lineEditFees->setText( mMetadata.fees() );
441 
442  // Licenses
443  tabLicenses->setRowCount( 0 );
444  const QStringList &licenses = mMetadata.licenses();
445  for ( const QString &licence : licenses )
446  {
447  int currentRow = tabLicenses->rowCount();
448  tabLicenses->setRowCount( currentRow + 1 );
449  QTableWidgetItem *pCell = tabLicenses->item( currentRow, 0 );
450  if ( !pCell )
451  {
452  pCell = new QTableWidgetItem;
453  tabLicenses->setItem( currentRow, 0, pCell );
454  }
455  pCell->setText( licence );
456  }
457 
458  // Rights
459  mRightsModel->setStringList( mMetadata.rights() );
460 
461  // Constraints
462  const QList<QgsLayerMetadata::Constraint> &constraints = mMetadata.constraints();
463  for ( const QgsLayerMetadata::Constraint &constraint : constraints )
464  {
465  int row = mConstraintsModel->rowCount();
466  mConstraintsModel->setItem( row, 0, new QStandardItem( constraint.type ) );
467  mConstraintsModel->setItem( row, 1, new QStandardItem( constraint.constraint ) );
468  }
469 
470  // CRS
471  mCrs = mMetadata.crs();
472  crsChanged();
473 
474  // Spatial extent
475  const QList<QgsLayerMetadata::SpatialExtent> &spatialExtents = mMetadata.extent().spatialExtents();
476  if ( ! spatialExtents.isEmpty() )
477  {
478  // Even if it's a list, it's supposed to store the same extent in different CRS.
479  spatialExtentSelector->setOutputCrs( spatialExtents.at( 0 ).extentCrs );
480  spatialExtentSelector->setOriginalExtent( spatialExtents.at( 0 ).bounds.toRectangle(), spatialExtents.at( 0 ).extentCrs );
481  spatialExtentSelector->setOutputExtentFromOriginal();
482  spinBoxZMaximum->setValue( spatialExtents.at( 0 ).bounds.zMaximum() );
483  spinBoxZMinimum->setValue( spatialExtents.at( 0 ).bounds.zMinimum() );
484  }
485 
486  // Temporal extent
487  const QList<QgsDateTimeRange> &temporalExtents = mMetadata.extent().temporalExtents();
488  if ( ! temporalExtents.isEmpty() )
489  {
490  // Even if it's a list, it seems we use only one for now (cf discussion with Tom)
491  dateTimeFrom->setDateTime( temporalExtents.at( 0 ).begin() );
492  dateTimeTo->setDateTime( temporalExtents.at( 0 ).end() );
493  }
494  else
495  {
496  dateTimeFrom->clear();
497  dateTimeTo->clear();
498  }
499 
500  // Contacts
501  const QList<QgsLayerMetadata::Contact> &contacts = mMetadata.contacts();
502  if ( ! contacts.isEmpty() )
503  {
504  // Only one contact supported in the UI for now
505  const QgsLayerMetadata::Contact &contact = contacts.at( 0 );
506  lineEditContactName->setText( contact.name );
507  lineEditContactEmail->setText( contact.email );
508  lineEditContactFax->setText( contact.fax );
509  lineEditContactOrganization->setText( contact.organization );
510  lineEditContactPosition->setText( contact.position );
511  lineEditContactVoice->setText( contact.voice );
512  if ( comboContactRole->findText( contact.role ) == -1 )
513  {
514  comboContactRole->addItem( contact.role );
515  }
516  comboContactRole->setCurrentIndex( comboContactRole->findText( contact.role ) );
517  tabAddresses->setRowCount( 0 );
518  const QList<QgsLayerMetadata::Address> &addresses = contact.addresses;
519  for ( const QgsLayerMetadata::Address &address : addresses )
520  {
521  int currentRow = tabAddresses->rowCount();
522  tabAddresses->setRowCount( currentRow + 1 );
523  tabAddresses->setItem( currentRow, 0, new QTableWidgetItem( address.type ) );
524  tabAddresses->setItem( currentRow, 1, new QTableWidgetItem( address.address ) );
525  tabAddresses->setItem( currentRow, 2, new QTableWidgetItem( address.postalCode ) );
526  tabAddresses->setItem( currentRow, 3, new QTableWidgetItem( address.city ) );
527  tabAddresses->setItem( currentRow, 4, new QTableWidgetItem( address.administrativeArea ) );
528  tabAddresses->setItem( currentRow, 5, new QTableWidgetItem( address.country ) );
529  }
530  }
531 
532  // Links
533  const QList<QgsLayerMetadata::Link> &links = mMetadata.links();
534  mLinksModel->setRowCount( 0 );
535  for ( const QgsLayerMetadata::Link &link : links )
536  {
537  int row = mLinksModel->rowCount();
538  mLinksModel->setItem( row, 0, new QStandardItem( link.name ) );
539  mLinksModel->setItem( row, 1, new QStandardItem( link.type ) );
540  mLinksModel->setItem( row, 2, new QStandardItem( link.url ) );
541  mLinksModel->setItem( row, 3, new QStandardItem( link.description ) );
542  mLinksModel->setItem( row, 4, new QStandardItem( link.format ) );
543  mLinksModel->setItem( row, 5, new QStandardItem( link.mimeType ) );
544  mLinksModel->setItem( row, 6, new QStandardItem( link.size ) );
545  }
546 
547  // History
548  mHistoryModel->setStringList( mMetadata.history() );
549 }
550 
552 {
553  layerMetadata.setParentIdentifier( lineEditParentId->text() );
554  layerMetadata.setIdentifier( lineEditIdentifier->text() );
555  layerMetadata.setTitle( lineEditTitle->text() );
556  layerMetadata.setType( comboType->currentText() );
557  layerMetadata.setLanguage( comboLanguage->currentText() );
558  layerMetadata.setAbstract( textEditAbstract->toPlainText() );
559 
560  // Keywords, it will save categories too.
561  syncFromCategoriesTabToKeywordsTab();
562  QMap<QString, QStringList> keywords;
563  for ( int i = 0; i < tabKeywords->rowCount() ; i++ )
564  {
565  keywords.insert( tabKeywords->item( i, 0 )->text(), tabKeywords->item( i, 1 )->text().split( ',' ) );
566  }
567  layerMetadata.setKeywords( keywords );
568 
569  // Fees
570  layerMetadata.setFees( lineEditFees->text() );
571 
572  // Licenses
573  QStringList licenses;
574  for ( int i = 0; i < tabLicenses->rowCount() ; i++ )
575  {
576  licenses.append( tabLicenses->item( i, 0 )->text() );
577  }
578  layerMetadata.setLicenses( licenses );
579 
580  // Rights
581  layerMetadata.setRights( mRightsModel->stringList() );
582 
583  // Constraints
584  QList<QgsLayerMetadata::Constraint> constraints;
585  for ( int row = 0; row < mConstraintsModel->rowCount() ; row++ )
586  {
588  constraint.type = mConstraintsModel->item( row, 0 )->text();
589  constraint.constraint = mConstraintsModel->item( row, 1 )->text();
590  constraints.append( constraint );
591  }
592  layerMetadata.setConstraints( constraints );
593 
594  // CRS
595  if ( mCrs.isValid() )
596  {
597  layerMetadata.setCrs( mCrs );
598  }
599 
600  // Extent
602  spatialExtent.bounds = QgsBox3d( spatialExtentSelector->outputExtent() );
603  spatialExtent.bounds.setZMinimum( spinBoxZMinimum->value() );
604  spatialExtent.bounds.setZMaximum( spinBoxZMaximum->value() );
605  spatialExtent.extentCrs = spatialExtentSelector->outputCrs();
606  QList<QgsLayerMetadata::SpatialExtent> spatialExtents;
607  spatialExtents.append( spatialExtent );
608  QList<QgsDateTimeRange> temporalExtents;
609  temporalExtents.append( QgsDateTimeRange( dateTimeFrom->dateTime(), dateTimeTo->dateTime() ) );
611  extent.setSpatialExtents( spatialExtents );
612  extent.setTemporalExtents( temporalExtents );
613  layerMetadata.setExtent( extent );
614 
615  // Contacts, only one contact supported in the UI for now.
616  // We don't want to lost data if more than one contact, so we update only the first one.
617  QList<QgsLayerMetadata::Contact> contacts = mMetadata.contacts();
618  if ( contacts.size() > 0 )
619  contacts.removeFirst();
621  contact.email = lineEditContactEmail->text();
622  contact.position = lineEditContactPosition->text();
623  contact.fax = lineEditContactFax->text();
624  contact.voice = lineEditContactVoice->text();
625  contact.name = lineEditContactName->text();
626  contact.organization = lineEditContactOrganization->text();
627  contact.role = comboContactRole->currentText();
628  QList<QgsLayerMetadata::Address> addresses;
629  for ( int i = 0; i < tabAddresses->rowCount() ; i++ )
630  {
632  address.type = tabAddresses->item( i, 0 )->text();
633  address.address = tabAddresses->item( i, 1 )->text();
634  address.postalCode = tabAddresses->item( i, 2 )->text();
635  address.city = tabAddresses->item( i, 3 )->text();
636  address.administrativeArea = tabAddresses->item( i, 4 )->text();
637  address.country = tabAddresses->item( i, 5 )->text();
638  addresses.append( address );
639  }
640  contact.addresses = addresses;
641  contacts.insert( 0, contact );
642  layerMetadata.setContacts( contacts );
643 
644  // Links
645  QList<QgsLayerMetadata::Link> links;
646  for ( int row = 0; row < mLinksModel->rowCount() ; row++ )
647  {
649  link.name = mLinksModel->item( row, 0 )->text();
650  link.type = mLinksModel->item( row, 1 )->text();
651  link.url = mLinksModel->item( row, 2 )->text();
652  link.description = mLinksModel->item( row, 3 )->text();
653  link.format = mLinksModel->item( row, 4 )->text();
654  link.mimeType = mLinksModel->item( row, 5 )->text();
655  link.size = mLinksModel->item( row, 6 )->text();
656  links.append( link );
657  }
658  layerMetadata.setLinks( links );
659 
660  // History
661  layerMetadata.setHistory( mHistoryModel->stringList() );
662 }
663 
665 {
667  saveMetadata( metadata );
668  QgsNativeMetadataValidator validator;
669  QList<QgsMetadataValidator::ValidationResult> validationResults;
670  bool results = validator.validate( metadata, validationResults );
671 
672  QString errors;
673  if ( !results )
674  {
675  for ( const QgsMetadataValidator::ValidationResult &result : qgis::as_const( validationResults ) )
676  {
677  errors += QLatin1String( "<b>" ) % result.section;
678  if ( ! result.identifier.isNull() )
679  {
680  errors += QLatin1String( " " ) % QVariant( result.identifier.toInt() + 1 ).toString();
681  }
682  errors += QLatin1String( "</b>: " ) % result.note % QLatin1String( "<br />" );
683  }
684  }
685  else
686  {
687  errors = QString( tr( "Ok, it seems valid according to the QGIS Schema." ) );
688  }
689 
690  QString myStyle = QgsApplication::reportStyleSheet();
691  myStyle.append( QStringLiteral( "body { margin: 10px; }\n " ) );
692  resultsCheckMetadata->clear();
693  resultsCheckMetadata->document()->setDefaultStyleSheet( myStyle );
694  resultsCheckMetadata->setHtml( errors );
695 
696  return results;
697 }
698 
699 QMap<QString, QString> QgsMetadataWidget::parseLanguages()
700 {
701  QMap<QString, QString> countries;
702  countries.insert( QString(), QString() ); // We add an empty line, because it's not compulsory.
703 
704  QString path = QDir( QgsApplication::metadataPath() ).absoluteFilePath( QStringLiteral( "language_codes_ISO_639.csv" ) );
705  QFile file( path );
706  if ( !file.open( QIODevice::ReadOnly ) )
707  {
708  QgsDebugMsg( QString( "Error while opening the CSV file: %1, %2 " ).arg( path, file.errorString() ) );
709  return countries;
710  }
711 
712  // Skip the first line of the CSV
713  file.readLine();
714  while ( !file.atEnd() )
715  {
716  QByteArray line = file.readLine();
717  QList<QByteArray> items = line.split( ',' );
718  countries.insert( QString( items.at( 0 ).constData() ).trimmed(), QString( items.at( 1 ).constData() ).trimmed() );
719  }
720  file.close();
721 
722  path = QDir( QgsApplication::metadataPath() ).absoluteFilePath( QStringLiteral( "country_code_ISO_3166.csv" ) );
723  QFile secondFile( path );
724  if ( !secondFile.open( QIODevice::ReadOnly ) )
725  {
726  QgsDebugMsg( QString( "Error while opening the CSV file: %1, %2 " ).arg( path, file.errorString() ) );
727  return countries;
728  }
729 
730  // Skip the first line of the CSV
731  secondFile.readLine();
732  while ( !secondFile.atEnd() )
733  {
734  QByteArray line = secondFile.readLine();
735  QList<QByteArray> items = line.split( ',' );
736  countries.insert( QString( items.at( 2 ).constData() ).trimmed(), QString( items.at( 0 ).constData() ).trimmed() );
737  }
738  secondFile.close();
739  return countries;
740 }
741 
743 {
744  QStringList wordList;
745  wordList.append( QString() ); // We add an empty line, because it's not compulsory.
746 
747  QString path = QDir( QgsApplication::metadataPath() ).absoluteFilePath( QStringLiteral( "licenses.csv" ) );
748  QFile file( path );
749  if ( !file.open( QIODevice::ReadOnly ) )
750  {
751  QgsDebugMsg( QString( "Error while opening the CSV file: %1, %2 " ).arg( path, file.errorString() ) );
752  return wordList;
753  }
754 
755  // Skip the first line of the CSV
756  file.readLine();
757  while ( !file.atEnd() )
758  {
759  QByteArray line = file.readLine();
760  wordList.append( line.split( ',' ).at( 0 ).trimmed() );
761  }
762  file.close();
763  return wordList;
764 }
765 
767 {
768  QStringList wordList;
769  wordList.append( QString() ); // We add an empty line, because it's not compulsory.
770 
771  QString path = QDir( QgsApplication::metadataPath() ).absoluteFilePath( QStringLiteral( "LinkPropertyLookupTable.csv" ) );
772  QFile file( path );
773  if ( !file.open( QIODevice::ReadOnly ) )
774  {
775  QgsDebugMsg( QString( "Error while opening the CSV file: %1, %2 " ).arg( path, file.errorString() ) );
776  return wordList;
777  }
778 
779  // Skip the first line of the CSV
780  file.readLine();
781  while ( !file.atEnd() )
782  {
783  QByteArray line = file.readLine();
784  wordList.append( line.split( ',' ).at( 0 ).trimmed() );
785  }
786  file.close();
787  return wordList;
788 }
789 
791 {
792  QStringList wordList;
793  wordList.append( QString() ); // We add an empty line, because it's not compulsory.
794 
795  QString path = QDir( QgsApplication::metadataPath() ).absoluteFilePath( QStringLiteral( "mime.csv" ) );
796  QFile file( path );
797  if ( !file.open( QIODevice::ReadOnly ) )
798  {
799  QgsDebugMsg( QString( "Error while opening the CSV file: %1, %2 " ).arg( path, file.errorString() ) );
800  return wordList;
801  }
802 
803  while ( !file.atEnd() )
804  {
805  QByteArray line = file.readLine();
806  wordList.append( line.split( ',' ).at( 0 ).trimmed() );
807  }
808  file.close();
809  return wordList;
810 }
811 
812 QMap<QString, QString> QgsMetadataWidget::parseTypes()
813 {
814  QMap<QString, QString> types;
815  types.insert( QString(), QString() ); // We add an empty line, because it's not compulsory.
816  QString path = QDir( QgsApplication::metadataPath() ).absoluteFilePath( QStringLiteral( "md_scope_codes.csv" ) );
817  QFile file( path );
818  if ( !file.open( QIODevice::ReadOnly ) )
819  {
820  QgsDebugMsg( QString( "Error while opening the CSV file: %1, %2 " ).arg( path, file.errorString() ) );
821  return types;
822  }
823 
824  types.insert( QString(), QString() ); // We add an empty line, because it's not compulsory.
825  while ( !file.atEnd() )
826  {
827  QByteArray line = file.readLine();
828  QList<QByteArray> items = line.split( ';' );
829  types.insert( items.at( 0 ).constData(), items.at( 1 ).constData() );
830  }
831  file.close();
832  return types;
833 }
834 
836 {
837  if ( canvas )
838  spatialExtentSelector->setCurrentExtent( canvas->extent(), canvas->mapSettings().destinationCrs() );
839 }
840 
842 {
843  saveMetadata( mMetadata );
844  if ( mLayer )
845  {
846  // Save layer metadata properties
847  mLayer->setMetadata( mMetadata );
848  }
849 }
850 
851 void QgsMetadataWidget::syncFromCategoriesTabToKeywordsTab()
852 {
853  if ( mCategoriesModel->rowCount() > 0 )
854  {
855  QList<QTableWidgetItem *> categories = tabKeywords->findItems( QStringLiteral( "gmd:topicCategory" ), Qt::MatchExactly );
856  int row;
857  if ( !categories.isEmpty() )
858  {
859  row = categories.at( 0 )->row();
860  }
861  else
862  {
863  // Create a new line with 'gmd:topicCategory'
864  addVocabulary();
865  row = tabKeywords->rowCount() - 1;
866  tabKeywords->item( row, 0 )->setText( QStringLiteral( "gmd:topicCategory" ) );
867  }
868  tabKeywords->item( row, 1 )->setText( mCategoriesModel->stringList().join( QStringLiteral( "," ) ) );
869  }
870 }
871 
872 void QgsMetadataWidget::updatePanel()
873 {
874  int index = tabWidget->currentIndex();
875  QString currentTabText = tabWidget->widget( index )->objectName();
876  if ( currentTabText == QStringLiteral( "tabCategoriesDialog" ) )
877  {
878  // Categories tab
879  // We need to take keywords and insert them into the list
880  QList<QTableWidgetItem *> categories = tabKeywords->findItems( QStringLiteral( "gmd:topicCategory" ), Qt::MatchExactly );
881  if ( !categories.isEmpty() )
882  {
883  int row = categories.at( 0 )->row();
884  mCategoriesModel->setStringList( tabKeywords->item( row, 1 )->text().split( ',' ) );
885  }
886  else
887  {
888  mCategoriesModel->setStringList( QStringList() );
889  }
890  }
891  else if ( currentTabText == QStringLiteral( "tabKeywordsDialog" ) )
892  {
893  // Keywords tab
894  // We need to take categories and insert them into the table
895  syncFromCategoriesTabToKeywordsTab();
896  }
897  else if ( currentTabText == QStringLiteral( "tabValidationDialog" ) )
898  {
899  checkMetadata();
900  }
901 }
902 
903 void QgsMetadataWidget::addNewCategory()
904 {
905  bool ok;
906  QString text = QInputDialog::getText( this, tr( "New Category" ),
907  tr( "New Category:" ), QLineEdit::Normal,
908  QString(), &ok );
909  if ( ok && !text.isEmpty() )
910  {
911  QStringList list = mCategoriesModel->stringList();
912  if ( ! list.contains( text ) )
913  {
914  list.append( text );
915  mCategoriesModel->setStringList( list );
916  mCategoriesModel->sort( 0 );
917  }
918  }
919 }
920 
921 void QgsMetadataWidget::addDefaultCategories()
922 {
923  const QModelIndexList selectedIndexes = listDefaultCategories->selectionModel()->selectedIndexes();
924  QStringList defaultCategoriesList = mDefaultCategoriesModel->stringList();
925  QStringList selectedCategories = mCategoriesModel->stringList();
926 
927  for ( const QModelIndex &selection : selectedIndexes )
928  {
929  QVariant item = mDefaultCategoriesModel->data( selection, Qt::DisplayRole );
930  defaultCategoriesList.removeOne( item.toString() );
931 
932  selectedCategories.append( item.toString() );
933  }
934 
935  mDefaultCategoriesModel->setStringList( defaultCategoriesList );
936  mCategoriesModel->setStringList( selectedCategories );
937  mCategoriesModel->sort( 0 );
938 }
939 
940 void QgsMetadataWidget::removeSelectedCategories()
941 {
942  const QModelIndexList selectedIndexes = listCategories->selectionModel()->selectedIndexes();
943  QStringList categories = mCategoriesModel->stringList();
944  QStringList defaultList = mDefaultCategoriesModel->stringList();
945 
946  for ( const QModelIndex &selection : selectedIndexes )
947  {
948  QVariant item = mCategoriesModel->data( selection, Qt::DisplayRole );
949  categories.removeOne( item.toString() );
950 
951  if ( mDefaultCategories.contains( item.toString() ) )
952  {
953  defaultList.append( item.toString() );
954  }
955  }
956  mCategoriesModel->setStringList( categories );
957 
958  mDefaultCategoriesModel->setStringList( defaultList );
959  mDefaultCategoriesModel->sort( 0 );
960 }
961 
963  : QStyledItemDelegate( parent )
964 {
965 
966 }
967 
968 QWidget *LinkItemDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
969 {
970  if ( index.column() == 1 )
971  {
972  // Link type
973  QComboBox *typeEditor = new QComboBox( parent );
974  typeEditor->setEditable( true );
975  QStringListModel *model = new QStringListModel( parent );
976  model->setStringList( QgsMetadataWidget::parseLinkTypes() );
977  typeEditor->setModel( model );
978  return typeEditor;
979  }
980  else if ( index.column() == 5 )
981  {
982  // MIME
983  QComboBox *mimeEditor = new QComboBox( parent );
984  mimeEditor->setEditable( true );
985  QStringListModel *model = new QStringListModel( parent );
986  model->setStringList( QgsMetadataWidget::parseMimeTypes() );
987  mimeEditor->setModel( model );
988  return mimeEditor;
989  }
990 
991  return QStyledItemDelegate::createEditor( parent, option, index );
992 }
993 
995  : QStyledItemDelegate( parent )
996 {
997 
998 }
999 
1000 QWidget *ConstraintItemDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
1001 {
1002  if ( index.column() == 0 )
1003  {
1004  // Constraint type
1005  QComboBox *typeEditor = new QComboBox( parent );
1006  typeEditor->setEditable( true );
1007  QStringList types;
1008  types << QStringLiteral( "access" ) << QStringLiteral( "use" ) << QStringLiteral( "other" );
1009  QStringListModel *model = new QStringListModel( parent );
1010  model->setStringList( types );
1011  typeEditor->setModel( model );
1012  return typeEditor;
1013  }
1014 
1015  return QStyledItemDelegate::createEditor( parent, option, index );
1016 }
Contains the parameters describing a metadata validation failure.
Base class for all map layer types.
Definition: qgsmaplayer.h:56
QString parentIdentifier() const
A reference, URI, URL or some other mechanism to identify the parent resource that this resource is a...
QString name
Name of contact.
A validator for the native QGIS metadata schema definition.
void setKeywords(const KeywordMap &keywords)
Sets the keywords map, which is a set of descriptive keywords associated with the resource...
virtual QgsDataProvider * dataProvider()
Returns the layer&#39;s data provider.
QString role
Role of contact.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void setMapCanvas(QgsMapCanvas *canvas)
Sets a map canvas associated with the widget.
QStringList rights() const
Returns a list of attribution or copyright strings associated with the resource.
QString position
Position/title of contact.
QString title() const
Returns the human readable name of the resource, typically displayed in search results.
QString abstract() const
Returns a free-form description of the resource.
QString organization
Organization contact belongs to/represents.
QString fax
Facsimile telephone.
void setLicenses(const QStringList &licenses)
Sets a list of licenses associated with the resource.
QList< QgsDateTimeRange > temporalExtents() const
Temporal extents of the resource.
QgsLayerMetadata metadata
Definition: qgsmaplayer.h:62
static QString reportStyleSheet()
get a standard css style sheet for reports.
A 3-dimensional box composed of x, y, z coordinates.
Definition: qgsbox3d.h:35
QString identifier() const
A reference, URI, URL or some other mechanism to identify the resource.
\@cond PRIVATE
void saveMetadata(QgsLayerMetadata &layerMetadata)
Save all fields in a QgsLayerMetadata object.
const QgsLayerMetadata::Extent & extent() const
Returns the spatial and temporal extents associated with the resource.
void setExtent(const QgsLayerMetadata::Extent &extent)
Sets the spatial and temporal extents associated with the resource.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:74
QgsCoordinateReferenceSystem extentCrs
Coordinate reference system for spatial extent.
QgsCoordinateReferenceSystem destinationCrs() const
returns CRS of destination coordinate reference system
QString type
Constraint type.
void setLinks(const QgsLayerMetadata::LinkList &links)
Sets the list of online resources associated with the resource.
QgsLayerMetadata::ContactList contacts() const
Returns a list of contact persons or entities associated with the resource.
Metadata address structure.
Metadata constraint structure.
static QMap< QString, QString > parseTypes()
Returns a list of types available by default in the wizard.
QgsLayerMetadata metadata()
Returns a QgsLayerMetadata object representing the current state of the widget.
virtual void setMetadata(const QgsLayerMetadata &metadata)
Sets the layer&#39;s metadata store.
QgsLayerMetadata()=default
Constructor for QgsLayerMetadata.
virtual QgsCoordinateReferenceSystem crs() const =0
Returns the coordinate system for the data source.
void setConstraints(const QgsLayerMetadata::ConstraintList &constraints)
Sets the list of constraints associated with using the resource.
QString language() const
Returns the human language associated with the resource.
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
QStringList history() const
Returns a freeform description of the history or lineage of the resource.
QString description() const
Returns the descriptive name of the CRS, e.g., "WGS 84" or "GDA 94 / Vicgrid94".
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Create a special editor with a QCombobox in the constraint view.
QgsCoordinateReferenceSystem crs() const
Returns the layer&#39;s spatial reference system.
LinkItemDelegate(QObject *parent=nullptr)
LinkItemDelegate constructor.
QString postalCode
Postal (or ZIP) code.
QString publicSource() const
Gets a version of the internal layer definition that has sensitive bits removed (for example...
bool validate(const QgsLayerMetadata &metadata, QList< QgsMetadataValidator::ValidationResult > &results) const override
Validates a metadata object, and returns true if the metadata is considered valid.
void setFees(const QString &fees)
Sets the fees associated with using the resource.
Metadata extent structure.
QString type() const
Returns the nature of the resource.
QString email
Electronic mail address.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the coordinate reference system for the layer&#39;s metadata.
static QStringList parseLicenses()
Returns a list of licences available by default in the wizard.
void acceptMetadata()
Saves the metadata to the layer.
void setRights(const QStringList &rights)
Sets a list of rights (attribution or copyright strings) associated with the resource.
void setZMinimum(double z)
Sets the minimum z value.
Definition: qgsbox3d.cpp:59
KeywordMap keywords() const
Returns the keywords map, which is a set of descriptive keywords associated with the resource...
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Create a special editor with a QCombobox in the link view.
A structured metadata store for a map layer.
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
QString voice
Voice telephone.
void setSpatialExtents(const QList< QgsLayerMetadata::SpatialExtent > &extents)
Sets the spatial extents of the resource.
QgsMetadataWidget(QWidget *parent, QgsMapLayer *layer=nullptr)
Constructor for the wizard.
static QMap< QString, QString > parseLanguages()
Returns a list of languages available by default in the wizard.
QgsLayerMetadata::LinkList links() const
Returns a list of online resources associated with the resource.
static QStringList parseMimeTypes()
Returns a list of MIME types available by default in the wizard.
void crsChanged()
If the CRS is updated.
void setContacts(const QgsLayerMetadata::ContactList &contacts)
Sets the list of contacts or entities associated with the resource.
void setAbstract(const QString &abstract)
Sets a free-form abstract (description) of the resource.
Metadata contact structure.
QgsLayerMetadata::ConstraintList constraints() const
Returns a list of constraints associated with using the resource.
QgsBox3d bounds
Geospatial extent of the resource.
void setHistory(const QStringList &history)
Sets the freeform description of the history or lineage of the resource.
QgsCoordinateReferenceSystem crs() const
Returns the coordinate reference system described by the layer&#39;s metadata.
Special delegate for the constraint view in the metadata wizard.
void setMetadata(const QgsLayerMetadata &metadata)
Sets the metadata to display in the widget.
void setParentIdentifier(const QString &parentIdentifier)
Sets a reference, URI, URL or some other mechanism to identify the parent resource that this resource...
QString city
City or locality name.
QStringList licenses() const
Returns a list of licenses associated with the resource (examples: http://opendefinition.org/licenses/).
QList< QgsLayerMetadata::SpatialExtent > spatialExtents() const
Spatial extents of the resource.
void setLanguage(const QString &language)
Sets the human language associated with the resource.
QString fees() const
Returns any fees associated with using the resource.
QList< QgsLayerMetadata::Address > addresses
List of addresses associated with this contact.
QStringList categories() const
Returns categories of the resource.
QString constraint
Free-form constraint string.
void setTitle(const QString &title)
Sets the human readable title (name) of the resource, typically displayed in search results...
Metadata spatial extent structure.
void setType(const QString &type)
Sets the type (nature) of the resource.
static QString metadataPath()
Returns the path to the metadata directory.
QString address
Free-form physical address component, e.g.
ConstraintItemDelegate(QObject *parent=nullptr)
ConstraintItemDelegate constructor.
static QStringList parseLinkTypes()
Returns a list of link types available by default in the wizard.
QString authid() const
Returns the authority identifier for the CRS.
void setIdentifier(const QString &identifier)
Sets the reference, URI, URL or some other mechanism to identify the resource.
QString administrativeArea
Administrative area (state, province/territory, etc.).
bool checkMetadata()
Check if values in the wizard are correct.
QString country
Free-form country string.
QString type
Type of address, e.g.
void setTemporalExtents(const QList< QgsDateTimeRange > &extents)
Sets the temporal extents of the resource.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
void setZMaximum(double z)
Sets the maximum z value.
Definition: qgsbox3d.cpp:64