QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgsprojectionselectiontreewidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * qgsprojectionselector.cpp *
3  * Copyright (C) 2005 by Tim Sutton *
5  * *
6  * This program is free software; you can redistribute it and/or modify *
7  * it under the terms of the GNU General Public License as published by *
8  * the Free Software Foundation; either version 2 of the License, or *
9  * (at your option) any later version. *
10  ***************************************************************************/
12 
13 //standard includes
14 #include <sqlite3.h>
15 
16 //qgis includes
17 #include "qgis.h" //magic numbers here
18 #include "qgsapplication.h"
19 #include "qgslogger.h"
21 #include "qgsmessagelog.h"
22 #include "qgssettings.h"
23 #include "qgsrectangle.h"
25 
26 //qt includes
27 #include <QFileInfo>
28 #include <QHeaderView>
29 #include <QResizeEvent>
30 #include <QMessageBox>
31 
33  : QWidget( parent )
34 {
35  setupUi( this );
36 
37  QFont f = teProjection->font();
38  f.setPointSize( f.pointSize() - 2 );
39  teProjection->setFont( f );
40 
41  leSearch->setShowSearchIcon( true );
42 
43  connect( lstCoordinateSystems, &QTreeWidget::itemDoubleClicked, this, &QgsProjectionSelectionTreeWidget::lstCoordinateSystems_itemDoubleClicked );
44  connect( lstRecent, &QTreeWidget::itemDoubleClicked, this, &QgsProjectionSelectionTreeWidget::lstRecent_itemDoubleClicked );
45  connect( lstCoordinateSystems, &QTreeWidget::currentItemChanged, this, &QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged );
46  connect( lstRecent, &QTreeWidget::currentItemChanged, this, &QgsProjectionSelectionTreeWidget::lstRecent_currentItemChanged );
47  connect( cbxHideDeprecated, &QCheckBox::stateChanged, this, &QgsProjectionSelectionTreeWidget::updateFilter );
48  connect( leSearch, &QgsFilterLineEdit::textChanged, this, &QgsProjectionSelectionTreeWidget::updateFilter );
49 
50  mAreaCanvas->setVisible( mShowMap );
51 
52  if ( QDialog *dlg = qobject_cast<QDialog *>( parent ) )
53  {
54  // mark selected projection for push to front if parent dialog is accepted
55  connect( dlg, &QDialog::accepted, this, &QgsProjectionSelectionTreeWidget::pushProjectionToFront );
56  }
57 
58  // Get the full path name to the sqlite3 spatial reference database.
59  mSrsDatabaseFileName = QgsApplication::srsDatabaseFilePath();
60 
61  lstCoordinateSystems->header()->setSectionResizeMode( AuthidColumn, QHeaderView::Stretch );
62  lstCoordinateSystems->header()->resizeSection( QgisCrsIdColumn, 0 );
63  lstCoordinateSystems->header()->setSectionResizeMode( QgisCrsIdColumn, QHeaderView::Fixed );
64 
65  // Hide (internal) ID column
66  lstCoordinateSystems->setColumnHidden( QgisCrsIdColumn, true );
67 
68  lstRecent->header()->setSectionResizeMode( AuthidColumn, QHeaderView::Stretch );
69  lstRecent->header()->resizeSection( QgisCrsIdColumn, 0 );
70  lstRecent->header()->setSectionResizeMode( QgisCrsIdColumn, QHeaderView::Fixed );
71 
72  // Hide (internal) ID column
73  lstRecent->setColumnHidden( QgisCrsIdColumn, true );
74 
76 
77  mCheckBoxNoProjection->setHidden( true );
78  mCheckBoxNoProjection->setEnabled( false );
79  connect( mCheckBoxNoProjection, &QCheckBox::toggled, this, [ = ]
80  {
81  if ( !mBlockSignals )
82  {
83  emit crsSelected();
85  }
86  } );
87  connect( mCheckBoxNoProjection, &QCheckBox::toggled, this, [ = ]( bool checked )
88  {
89  if ( mCheckBoxNoProjection->isEnabled() )
90  {
91  mFrameProjections->setDisabled( checked );
92  }
93  } );
94 
95  QgsSettings settings;
96  mSplitter->restoreState( settings.value( QStringLiteral( "Windows/ProjectionSelector/splitterState" ) ).toByteArray() );
97 }
98 
100 {
101  if ( !mPushProjectionToFront )
102  {
103  return;
104  }
105 
106  // Push current projection to front, only if set
107  long crsId = selectedCrsId();
108  if ( crsId == 0 )
109  return;
110 
111  QgsSettings settings;
112  settings.setValue( QStringLiteral( "Windows/ProjectionSelector/splitterState" ), mSplitter->saveState() );
113 
115 }
116 
118 {
119  lstCoordinateSystems->header()->resizeSection( NameColumn, event->size().width() - 240 );
120  lstCoordinateSystems->header()->resizeSection( AuthidColumn, 240 );
121  lstCoordinateSystems->header()->resizeSection( QgisCrsIdColumn, 0 );
122 
123  lstRecent->header()->resizeSection( NameColumn, event->size().width() - 240 );
124  lstRecent->header()->resizeSection( AuthidColumn, 240 );
125  lstRecent->header()->resizeSection( QgisCrsIdColumn, 0 );
126 }
127 
129 {
130  if ( mInitialized )
131  return;
132 
133  // ensure the projection list view is actually populated
134  // before we show this widget
135  loadCrsList( &mCrsFilter );
136  loadUserCrsList( &mCrsFilter );
137 
138  if ( !mRecentProjListDone )
139  {
140  for ( const QgsCoordinateReferenceSystem &crs : qgis::as_const( mRecentProjections ) )
141  insertRecent( crs );
142  mRecentProjListDone = true;
143  }
144 
145  // apply deferred selection
146  mBlockSignals = true; // we've already emitted the signal, when the deferred crs was first set
147  applySelection();
148  mBlockSignals = false;
149 
150  emit initialized();
151 
152  // Pass up the inheritance hierarchy
153  QWidget::showEvent( event );
154  mInitialized = true;
155 }
156 
157 QString QgsProjectionSelectionTreeWidget::ogcWmsCrsFilterAsSqlExpression( QSet<QString> *crsFilter )
158 {
159  QString sqlExpression = QStringLiteral( "1" ); // it's "SQL" for "true"
160  QMap<QString, QStringList> authParts;
161 
162  if ( !crsFilter )
163  return sqlExpression;
164 
165  /*
166  Ref: WMS 1.3.0, section 6.7.3 "Layer CRS":
167 
168  Every Layer CRS has an identifier that is a character string. Two types of
169  Layer CRS identifiers are permitted: "label" and "URL" identifiers:
170 
171  Label: The identifier includes a namespace prefix, a colon, a numeric or
172  string code, and in some instances a comma followed by additional
173  parameters. This International Standard defines three namespaces:
174  CRS, EpsgCrsId and AUTO2 [...]
175 
176  URL: The identifier is a fully-qualified Uniform Resource Locator that
177  references a publicly-accessible file containing a definition of the CRS
178  that is compliant with ISO 19111.
179  */
180 
181  // iterate through all incoming CRSs
182 
183  const auto authIds { *crsFilter };
184  for ( const QString &auth_id : authIds )
185  {
186  QStringList parts = auth_id.split( ':' );
187 
188  if ( parts.size() < 2 )
189  continue;
190 
191  authParts[ parts.at( 0 ).toUpper()].append( parts.at( 1 ).toUpper() );
192  }
193 
194  if ( authParts.isEmpty() )
195  return sqlExpression;
196 
197  if ( !authParts.isEmpty() )
198  {
199  QString prefix = QStringLiteral( " AND (" );
200  for ( auto it = authParts.constBegin(); it != authParts.constEnd(); ++it )
201  {
202  sqlExpression += QStringLiteral( "%1(upper(auth_name)='%2' AND upper(auth_id) IN ('%3'))" )
203  .arg( prefix,
204  it.key(),
205  it.value().join( QLatin1String( "','" ) ) );
206  prefix = QStringLiteral( " OR " );
207  }
208  sqlExpression += ')';
209  }
210 
211  QgsDebugMsgLevel( "exiting with '" + sqlExpression + "'.", 4 );
212 
213  return sqlExpression;
214 }
215 
216 void QgsProjectionSelectionTreeWidget::applySelection( int column, QString value )
217 {
218  if ( !mProjListDone || !mUserProjListDone )
219  {
220  // defer selection until loaded
221  mSearchColumn = column;
222  mSearchValue = value;
223  return;
224  }
225 
226  if ( column == QgsProjectionSelectionTreeWidget::None )
227  {
228  // invoked deferred selection
229  column = mSearchColumn;
230  value = mSearchValue;
231 
232  mSearchColumn = QgsProjectionSelectionTreeWidget::None;
233  mSearchValue.clear();
234  }
235 
236  if ( column == QgsProjectionSelectionTreeWidget::None )
237  return;
238 
239  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( value, Qt::MatchExactly | Qt::MatchRecursive, column );
240  if ( !nodes.isEmpty() )
241  {
242  QgsDebugMsgLevel( QStringLiteral( "found %1,%2" ).arg( column ).arg( value ), 4 );
243  lstCoordinateSystems->setCurrentItem( nodes.first() );
244  }
245  else
246  {
247  QgsDebugMsgLevel( QStringLiteral( "nothing found for %1,%2" ).arg( column ).arg( value ), 4 );
248  // deselect the selected item to avoid confusing the user
249  lstCoordinateSystems->clearSelection();
250  lstRecent->clearSelection();
251  teProjection->clear();
252  }
253 }
254 
255 void QgsProjectionSelectionTreeWidget::insertRecent( const QgsCoordinateReferenceSystem &crs )
256 {
257  if ( !mProjListDone || !mUserProjListDone )
258  return;
259 
260  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( QString::number( crs.srsid() ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
261  if ( nodes.isEmpty() )
262  return;
263 
264  lstRecent->insertTopLevelItem( 0, new QTreeWidgetItem( lstRecent, QStringList()
265  << nodes.first()->text( NameColumn )
266  << nodes.first()->text( AuthidColumn )
267  << nodes.first()->text( QgisCrsIdColumn ) ) );
268 }
269 
270 //note this line just returns the projection name!
271 QString QgsProjectionSelectionTreeWidget::selectedName()
272 {
273  // return the selected wkt name from the list view
274  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
275  return lvi ? lvi->text( NameColumn ) : QString();
276 }
277 
279 {
280  if ( !crs.isValid() )
281  {
282  mCheckBoxNoProjection->setChecked( true );
283  }
284  else
285  {
286  bool changed = false;
287  if ( !mInitialized )
288  {
289  changed = mDeferredLoadCrs != crs;
290  mDeferredLoadCrs = crs;
291  }
292  mBlockSignals = true;
293  mCheckBoxNoProjection->setChecked( false );
294  mBlockSignals = false;
295 
296  if ( !crs.authid().isEmpty() )
297  applySelection( AuthidColumn, crs.authid() );
298  else
299  loadUnknownCrs( crs );
300  if ( changed )
301  {
302  emit crsSelected();
304  }
305  }
306 }
307 
309 {
310  mAreaCanvas->setCanvasRect( rect );
311 }
312 
314 {
315  return mAreaCanvas->canvasRect();
316 }
317 
318 QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString &expression ) const
319 {
320  // Only return the attribute if there is a node in the tree
321  // selected that has an srs_id. This prevents error if the user
322  // selects a top-level node rather than an actual coordinate
323  // system
324  //
325  // Get the selected node and make sure it is a srs andx
326  // not a top-level projection node
327  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
328  if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() )
329  return QString();
330 
331  //
332  // Determine if this is a user projection or a system on
333  // user projection defs all have srs_id >= 100000
334  //
335  QString databaseFileName;
336  if ( lvi->text( QgisCrsIdColumn ).toLong() >= USER_CRS_START_ID )
337  {
338  databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
339  if ( !QFileInfo::exists( databaseFileName ) )
340  {
341  return QString();
342  }
343  }
344  else
345  {
346  databaseFileName = mSrsDatabaseFileName;
347  }
348 
349  //
350  // set up the database
351  // XXX We could probably hold the database open for the life of this object,
352  // assuming that it will never be used anywhere else. Given the low overhead,
353  // opening it each time seems to be a reasonable approach at this time.
354  sqlite3 *database = nullptr;
355  int rc = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
356  if ( rc )
357  {
358  QgsMessageLog::logMessage( tr( "Resource Location Error" ), tr( "Error reading database file from: \n %1\n"
359  "Because of this the projection selector will not work…" ).arg( databaseFileName ),
360  Qgis::Critical );
361  return QString();
362  }
363 
364  // prepare the sql statement
365  const char *tail = nullptr;
366  sqlite3_stmt *stmt = nullptr;
367  QString sql = QStringLiteral( "select %1 from tbl_srs where srs_id=%2" )
368  .arg( expression,
369  lvi->text( QgisCrsIdColumn ) );
370 
371  QgsDebugMsgLevel( QStringLiteral( "Finding selected attribute using : %1" ).arg( sql ), 4 );
372  rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
373  // XXX Need to free memory from the error msg if one is set
374  QString attributeValue;
375  if ( rc == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
376  {
377  // get the first row of the result set
378  attributeValue = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
379  }
380 
381  // close the statement
382  sqlite3_finalize( stmt );
383  // close the database
384  sqlite3_close( database );
385 
386  // return the srs
387  return attributeValue;
388 }
389 
391 {
392  if ( mCheckBoxNoProjection->isEnabled() && mCheckBoxNoProjection->isChecked() )
394 
395  if ( !mInitialized && mDeferredLoadCrs.isValid() )
396  return mDeferredLoadCrs;
397 
398  const QString srsIdString = getSelectedExpression( QStringLiteral( "srs_id" ) );
399  if ( !srsIdString.isEmpty() )
400  {
401  int srid = srsIdString.toLong();
402  if ( srid >= USER_CRS_START_ID )
403  return QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "USER:%1" ).arg( srid ) );
404  else
405  return QgsCoordinateReferenceSystem::fromOgcWmsCrs( getSelectedExpression( QStringLiteral( "upper(auth_name||':'||auth_id)" ) ) );
406  }
407  else
408  {
409  // custom CRS
410  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
411  if ( lvi && lvi->data( 0, RoleWkt ).isValid() )
412  return QgsCoordinateReferenceSystem::fromWkt( lvi->data( 0, RoleWkt ).toString() );
413  else if ( lvi && lvi->data( 0, RoleProj ).isValid() )
414  return QgsCoordinateReferenceSystem::fromProj( lvi->data( 0, RoleProj ).toString() );
415  else
417  }
418 }
419 
421 {
422  mCheckBoxNoProjection->setVisible( show );
423  mCheckBoxNoProjection->setEnabled( show );
424  if ( show )
425  {
426  mFrameProjections->setDisabled( mCheckBoxNoProjection->isChecked() );
427  }
428 }
429 
431 {
432  mShowMap = show;
433  mAreaCanvas->setVisible( show );
434 }
435 
437 {
438  return !mCheckBoxNoProjection->isHidden();
439 }
440 
442 {
443  mCheckBoxNoProjection->setText( text );
444 }
445 
447 {
448  return mShowMap;
449 }
450 
452 {
453  QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
454  if ( mCheckBoxNoProjection->isChecked() )
455  return true;
456  else if ( !mInitialized && mDeferredLoadCrs.isValid() )
457  return true;
458  else
459  return item && ( !item->text( QgisCrsIdColumn ).isEmpty() || item->data( 0, RoleWkt ).isValid() );
460 }
461 
462 long QgsProjectionSelectionTreeWidget::selectedCrsId()
463 {
464  QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
465 
466  if ( item && !item->text( QgisCrsIdColumn ).isEmpty() )
467  return lstCoordinateSystems->currentItem()->text( QgisCrsIdColumn ).toLong();
468  else
469  return 0;
470 }
471 
472 
473 void QgsProjectionSelectionTreeWidget::setOgcWmsCrsFilter( const QSet<QString> &crsFilter )
474 {
475  mCrsFilter = crsFilter;
476  mProjListDone = false;
477  mUserProjListDone = false;
478  lstCoordinateSystems->clear();
479 }
480 
481 void QgsProjectionSelectionTreeWidget::loadUserCrsList( QSet<QString> *crsFilter )
482 {
483  if ( mUserProjListDone )
484  return;
485 
486  QgsDebugMsgLevel( QStringLiteral( "Fetching user projection list..." ), 4 );
487 
488  // User defined coordinate system node
489  // Make in an italic font to distinguish them from real projections
490  mUserProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "User Defined Coordinate Systems" ) ) );
491  mUserProjList->setFlags( mUserProjList->flags() & ~Qt::ItemIsSelectable );
492 
493  QFont fontTemp = mUserProjList->font( 0 );
494  fontTemp.setItalic( true );
495  fontTemp.setBold( true );
496  mUserProjList->setFont( 0, fontTemp );
497  mUserProjList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) ) );
498 
499  const QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> userCrsList = QgsApplication::coordinateReferenceSystemRegistry()->userCrsList();
500  for ( const QgsCoordinateReferenceSystemRegistry::UserCrsDetails &details : userCrsList )
501  {
502  const QString authid = QStringLiteral( "USER:%1" ).arg( details.id );
503  if ( crsFilter && !crsFilter->isEmpty() && !crsFilter->contains( authid ) && !crsFilter->contains( authid.toLower() ) )
504  continue;
505 
506  QTreeWidgetItem *newItem = new QTreeWidgetItem( mUserProjList, QStringList() << details.name );
507  newItem->setText( QgisCrsIdColumn, QString::number( details.id ) );
508  newItem->setText( AuthidColumn, authid );
509  }
510 
511  mUserProjListDone = true;
512 }
513 
514 void QgsProjectionSelectionTreeWidget::loadCrsList( QSet<QString> *crsFilter )
515 {
516  if ( mProjListDone )
517  return;
518 
519  // convert our Coordinate Reference System filter into the SQL expression
520  QString sqlFilter = ogcWmsCrsFilterAsSqlExpression( crsFilter );
521 
522  // Create the top-level nodes for the list view of projections
523  // Make in an italic font to distinguish them from real projections
524  //
525  // Geographic coordinate system node
526  mGeoList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Geographic Coordinate Systems" ) ) );
527  mGeoList->setFlags( mGeoList->flags() & ~Qt::ItemIsSelectable );
528 
529  QFont fontTemp = mGeoList->font( 0 );
530  fontTemp.setItalic( true );
531  fontTemp.setBold( true );
532  mGeoList->setFont( 0, fontTemp );
533  mGeoList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconProjectionEnabled.svg" ) ) );
534 
535  // Projected coordinate system node
536  mProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Projected Coordinate Systems" ) ) );
537  mProjList->setFlags( mProjList->flags() & ~Qt::ItemIsSelectable );
538 
539  fontTemp = mProjList->font( 0 );
540  fontTemp.setItalic( true );
541  fontTemp.setBold( true );
542  mProjList->setFont( 0, fontTemp );
543  mProjList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/transformed.svg" ) ) );
544 
545  //bail out in case the projections db does not exist
546  //this is necessary in case the pc is running linux with a
547  //read only filesystem because otherwise sqlite will try
548  //to create the db file on the fly
549 
550  if ( !QFileInfo::exists( mSrsDatabaseFileName ) )
551  {
552  mProjListDone = true;
553  return;
554  }
555 
556  // open the database containing the spatial reference data
557  sqlite3 *database = nullptr;
558  int rc = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
559  if ( rc )
560  {
561  // XXX This will likely never happen since on open, sqlite creates the
562  // database if it does not exist.
563  showDBMissingWarning( mSrsDatabaseFileName );
564  return;
565  }
566 
567  const char *tail = nullptr;
568  sqlite3_stmt *stmt = nullptr;
569  // Set up the query to retrieve the projection information needed to populate the list
570  //note I am giving the full field names for clarity here and in case someone
571  //changes the underlying view TS
572  QString sql = QStringLiteral( "select description, srs_id, upper(auth_name||':'||auth_id), is_geo, name, parameters, deprecated from vw_srs where %1 order by name,description" )
573  .arg( sqlFilter );
574 
575  rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
576  // XXX Need to free memory from the error msg if one is set
577  if ( rc == SQLITE_OK )
578  {
579  QTreeWidgetItem *newItem = nullptr;
580  // Cache some stuff to speed up creating of the list of projected
581  // spatial reference systems
582  QString previousSrsType;
583  QTreeWidgetItem *previousSrsTypeNode = nullptr;
584 
585  while ( sqlite3_step( stmt ) == SQLITE_ROW )
586  {
587  // check to see if the srs is geographic
588  int isGeo = sqlite3_column_int( stmt, 3 );
589  if ( isGeo )
590  {
591  // this is a geographic coordinate system
592  // Add it to the tree (field 0)
593  newItem = new QTreeWidgetItem( mGeoList, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
594 
595  // display the authority name (field 2) in the second column of the list view
596  newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
597 
598  // display the qgis srs_id (field 1) in the third column of the list view
599  newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
600  }
601  else
602  {
603  // This is a projected srs
604  QTreeWidgetItem *node = nullptr;
605  QString srsType = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 4 ) );
606  if ( srsType.isEmpty() )
607  srsType = tr( "Other" );
608 
609  // Find the node for this type and add the projection to it
610  // If the node doesn't exist, create it
611  if ( srsType == previousSrsType )
612  {
613  node = previousSrsTypeNode;
614  }
615  else
616  {
617  // Different from last one, need to search
618  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( srsType, Qt::MatchExactly | Qt::MatchRecursive, NameColumn );
619  if ( nodes.isEmpty() )
620  {
621  // the node doesn't exist -- create it
622  // Make in an italic font to distinguish them from real projections
623  node = new QTreeWidgetItem( mProjList, QStringList( srsType ) );
624  node->setFlags( node->flags() & ~Qt::ItemIsSelectable );
625 
626  QFont fontTemp = node->font( 0 );
627  fontTemp.setItalic( true );
628  node->setFont( 0, fontTemp );
629  }
630  else
631  {
632  node = nodes.first();
633  }
634  // Update the cache.
635  previousSrsType = srsType;
636  previousSrsTypeNode = node;
637  }
638  // add the item, setting the projection name in the first column of the list view
639  newItem = new QTreeWidgetItem( node, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
640  // display the authority id (field 2) in the second column of the list view
641  newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
642  // display the qgis srs_id (field 1) in the third column of the list view
643  newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
644  // expand also parent node
645  newItem->parent()->setExpanded( true );
646  }
647 
648  // display the qgis deprecated in the user data of the item
649  newItem->setData( 0, RoleDeprecated, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 6 ) ) );
650  newItem->setHidden( cbxHideDeprecated->isChecked() );
651  }
652  mProjList->setExpanded( true );
653  }
654 
655  // close the sqlite3 statement
656  sqlite3_finalize( stmt );
657  // close the database
658  sqlite3_close( database );
659 
660  mProjListDone = true;
661 }
662 
663 void QgsProjectionSelectionTreeWidget::loadUnknownCrs( const QgsCoordinateReferenceSystem &crs )
664 {
665  if ( !mUnknownList )
666  {
667  mUnknownList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Custom Coordinate Systems" ) ) );
668  mUnknownList->setFlags( mUnknownList->flags() & ~Qt::ItemIsSelectable );
669 
670  QFont fontTemp = mUnknownList->font( 0 );
671  fontTemp.setItalic( true );
672  fontTemp.setBold( true );
673  mUnknownList->setFont( 0, fontTemp );
674  mUnknownList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) ) );
675  }
676 
677  QTreeWidgetItem *newItem = new QTreeWidgetItem( mUnknownList, QStringList( crs.description().isEmpty() ? QObject::tr( "Custom CRS" ) : crs.description() ) );
678  newItem->setData( 0, RoleWkt, crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) );
679  newItem->setData( 0, RoleProj, crs.toProj() );
680 
681  lstCoordinateSystems->setCurrentItem( newItem );
682 }
683 
684 // New coordinate system selected from the list
685 void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
686 {
687  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
688 
689  if ( !current )
690  {
691  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
692  return;
693  }
694 
695  lstCoordinateSystems->scrollToItem( current );
696 
697  // If the item has children, it's not an end node in the tree, and
698  // hence is just a grouping thingy, not an actual CRS.
699  if ( current->childCount() == 0 )
700  {
701  // Found a real CRS
702  if ( !mBlockSignals )
703  {
704  emit crsSelected();
705  emit hasValidSelectionChanged( true );
706  }
707 
708  updateBoundsPreview();
709 
710  const QString crsId = current->text( QgisCrsIdColumn );
711  if ( !crsId.isEmpty() )
712  {
713  QList<QTreeWidgetItem *> nodes = lstRecent->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly, QgisCrsIdColumn );
714  if ( !nodes.isEmpty() )
715  {
716  QgsDebugMsgLevel( QStringLiteral( "found srs %1 in recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
717  lstRecent->setCurrentItem( nodes.first() );
718  }
719  else
720  {
721  QgsDebugMsgLevel( QStringLiteral( "srs %1 not recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
722  lstRecent->clearSelection();
723  lstCoordinateSystems->setFocus( Qt::OtherFocusReason );
724  }
725  }
726  else
727  {
728  lstRecent->clearSelection();
729  lstCoordinateSystems->setFocus( Qt::OtherFocusReason );
730  }
731  }
732  else
733  {
734  // Not a CRS - remove the highlight so the user doesn't get too confused
735  current->setSelected( false );
736  teProjection->clear();
737  lstRecent->clearSelection();
738  emit hasValidSelectionChanged( false );
739  }
740 }
741 
742 void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_itemDoubleClicked( QTreeWidgetItem *current, int column )
743 {
744  Q_UNUSED( column )
745 
746  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
747 
748  if ( !current )
749  {
750  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
751  return;
752  }
753 
754  // If the item has children, it's not an end node in the tree, and
755  // hence is just a grouping thingy, not an actual CRS.
756  if ( current->childCount() == 0 )
758 }
759 
760 void QgsProjectionSelectionTreeWidget::lstRecent_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
761 {
762  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
763 
764  if ( !current )
765  {
766  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
767  return;
768  }
769 
770  lstRecent->scrollToItem( current );
771 
772  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
773  if ( !nodes.isEmpty() )
774  lstCoordinateSystems->setCurrentItem( nodes.first() );
775 }
776 
777 void QgsProjectionSelectionTreeWidget::lstRecent_itemDoubleClicked( QTreeWidgetItem *current, int column )
778 {
779  Q_UNUSED( column )
780 
781  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
782 
783  if ( !current )
784  {
785  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
786  return;
787  }
788 
789  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
790  if ( !nodes.isEmpty() )
792 }
793 
794 void QgsProjectionSelectionTreeWidget::updateFilter()
795 {
796  QString filterTxtCopy = leSearch->text();
797  filterTxtCopy.replace( QRegExp( "\\s+" ), QStringLiteral( ".*" ) );
798  QRegExp re( filterTxtCopy, Qt::CaseInsensitive );
799 
800  const bool hideDeprecated = cbxHideDeprecated->isChecked();
801 
802  auto filterTreeWidget = [ = ]( QTreeWidget * tree )
803  {
804  QTreeWidgetItemIterator itr( tree );
805  while ( *itr )
806  {
807  if ( ( *itr )->childCount() == 0 ) // it's an end node aka a projection
808  {
809  if ( hideDeprecated && ( *itr )->data( 0, RoleDeprecated ).toBool() )
810  {
811  ( *itr )->setHidden( true );
812  if ( ( *itr )->isSelected() )
813  {
814  ( *itr )->setSelected( false );
815  teProjection->clear();
816  }
817  }
818  else if ( ( *itr )->text( NameColumn ).contains( re )
819  || ( *itr )->text( AuthidColumn ).contains( re )
820  )
821  {
822  ( *itr )->setHidden( false );
823  QTreeWidgetItem *parent = ( *itr )->parent();
824  while ( parent )
825  {
826  parent->setExpanded( true );
827  parent->setHidden( false );
828  parent = parent->parent();
829  }
830  }
831  else
832  {
833  ( *itr )->setHidden( true );
834  }
835  }
836  else
837  {
838  ( *itr )->setHidden( true );
839  }
840  ++itr;
841  }
842  };
843 
844  // filter recent crs's
845  filterTreeWidget( lstRecent );
846 
847  // filter crs's
848  filterTreeWidget( lstCoordinateSystems );
849 }
850 
851 
853 {
854  // set flag to push selected projection to front in destructor
855  mPushProjectionToFront = true;
856 }
857 
858 
859 long QgsProjectionSelectionTreeWidget::getLargestCrsIdMatch( const QString &sql )
860 {
861  long srsId = 0;
862 
863  //
864  // Now perform the actual search
865  //
866 
867  sqlite3 *database = nullptr;
868  const char *tail = nullptr;
869  sqlite3_stmt *stmt = nullptr;
870  int result;
871 
872  // first we search the users db as any srsid there will be definition be greater than in sys db
873 
874  //check the db is available
875  QString databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
876  if ( QFileInfo::exists( databaseFileName ) ) //only bother trying to open if the file exists
877  {
878  result = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
879  if ( result )
880  {
881  // XXX This will likely never happen since on open, sqlite creates the
882  // database if it does not exist. But we checked earlier for its existence
883  // and aborted in that case. This is because we may be running from read only
884  // media such as live cd and don't want to force trying to create a db.
885  showDBMissingWarning( databaseFileName );
886  return 0;
887  }
888 
889  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
890  // XXX Need to free memory from the error msg if one is set
891  if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
892  {
893  QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
894  srsId = srsIdString.toLong();
895  // close the sqlite3 statement
896  sqlite3_finalize( stmt );
897  sqlite3_close( database );
898  return srsId;
899  }
900  }
901  else
902  {
903  //only bother looking in srs.db if it wasn't found above
904  result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
905  if ( result )
906  {
907  QgsDebugMsg( QStringLiteral( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
908  //no need for assert because user db may not have been created yet
909  return 0;
910  }
911  }
912 
913  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
914  // XXX Need to free memory from the error msg if one is set
915  if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
916  {
917  QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
918  srsId = srsIdString.toLong();
919  }
920 
921  // close the sqlite3 statement
922  sqlite3_finalize( stmt );
923  sqlite3_close( database );
924 
925  return srsId;
926 }
927 
928 void QgsProjectionSelectionTreeWidget::updateBoundsPreview()
929 {
930  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
931  if ( !lvi || ( lvi->text( QgisCrsIdColumn ).isEmpty() && !lvi->data( 0, RoleWkt ).isValid() ) )
932  return;
933 
934  QgsCoordinateReferenceSystem currentCrs = crs();
935  if ( !currentCrs.isValid() )
936  return;
937 
938  QgsRectangle rect = currentCrs.bounds();
939  QString extentString = tr( "Extent not known" );
940  mAreaCanvas->setPreviewRect( rect );
941  if ( !qgsDoubleNear( rect.area(), 0.0 ) )
942  {
943  extentString = QStringLiteral( "%1, %2, %3, %4" )
944  .arg( rect.xMinimum(), 0, 'f', 2 )
945  .arg( rect.yMinimum(), 0, 'f', 2 )
946  .arg( rect.xMaximum(), 0, 'f', 2 )
947  .arg( rect.yMaximum(), 0, 'f', 2 );
948  }
949 
950  const QString extentHtml = QStringLiteral( "<dt><b>%1</b></dt><dd>%2</dd>" ).arg( tr( "Extent" ), extentString );
951  const QString wktString = tr( "<dt><b>%1</b></dt><dd><code>%2</code></dd>" ).arg( tr( "WKT" ), currentCrs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, true ).replace( '\n', QLatin1String( "<br>" ) ).replace( ' ', QLatin1String( "&nbsp;" ) ) );
952  const QString proj4String = tr( "<dt><b>%1</b></dt><dd><code>%2</code></dd>" ).arg( tr( "Proj4" ), currentCrs.toProj() );
953 
954 #ifdef Q_OS_WIN
955  const int smallerPointSize = std::max( font().pointSize() - 1, 8 ); // bit less on windows, due to poor rendering of small point sizes
956 #else
957  const int smallerPointSize = std::max( font().pointSize() - 2, 6 );
958 #endif
959 
960  teProjection->setText( QStringLiteral( "<div style=\"font-size: %1pt\"><h3>%2</h3><dl>" ).arg( smallerPointSize ).arg( selectedName() ) + wktString + proj4String + extentHtml + QStringLiteral( "</dl></div>" ) );
961 }
962 
963 QStringList QgsProjectionSelectionTreeWidget::authorities()
964 {
965  sqlite3 *database = nullptr;
966  const char *tail = nullptr;
967  sqlite3_stmt *stmt = nullptr;
968 
969  int result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
970  if ( result )
971  {
972  QgsDebugMsg( QStringLiteral( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
973  //no need for assert because user db may not have been created yet
974  return QStringList();
975  }
976 
977  QString sql = QStringLiteral( "select distinct auth_name from tbl_srs" );
978  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
979 
980  QStringList authorities;
981  if ( result == SQLITE_OK )
982  {
983  while ( sqlite3_step( stmt ) == SQLITE_ROW )
984  {
985  authorities << QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
986  }
987 
988  }
989 
990  // close the sqlite3 statement
991  sqlite3_finalize( stmt );
992  sqlite3_close( database );
993 
994  return authorities;
995 }
996 
997 QString QgsProjectionSelectionTreeWidget::sqlSafeString( const QString &theSQL ) const
998 {
999  QString retval = theSQL;
1000  retval.replace( '\\', QLatin1String( "\\\\" ) );
1001  retval.replace( '\"', QLatin1String( "\\\"" ) );
1002  retval.replace( '\'', QLatin1String( "\\'" ) );
1003  retval.replace( '%', QLatin1String( "\\%" ) );
1004  return retval;
1005 }
1006 
1007 void QgsProjectionSelectionTreeWidget::showDBMissingWarning( const QString &fileName )
1008 {
1009 
1010  QMessageBox::critical( this, tr( "Resource Location Error" ),
1011  tr( "Error reading database file from: \n %1\n"
1012  "Because of this the projection selector will not work…" )
1013  .arg( fileName ) );
1014 }
@ Critical
Definition: qgis.h:92
static QgsCoordinateReferenceSystemRegistry * coordinateReferenceSystemRegistry()
Returns the application's coordinate reference system (CRS) registry, which handles known CRS definit...
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QList< QgsCoordinateReferenceSystemRegistry::UserCrsDetails > userCrsList() const
Returns a list containing the details of all registered custom (user-defined) CRSes.
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
QString toProj() const
Returns a Proj string representation of this CRS.
static void pushRecentCoordinateReferenceSystem(const QgsCoordinateReferenceSystem &crs)
Pushes a recently used CRS to the top of the recent CRS list.
QString description() const
Returns the descriptive name of the CRS, e.g., "WGS 84" or "GDA 94 / Vicgrid94".
static QgsCoordinateReferenceSystem fromProj(const QString &proj)
Creates a CRS from a proj style formatted string.
static QList< QgsCoordinateReferenceSystem > recentCoordinateReferenceSystems()
Returns a list of recently used CRS.
QString authid() const
Returns the authority identifier for the CRS.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
long srsid() const
Returns the internal CRS ID, if available.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
void resizeEvent(QResizeEvent *event) override
void setPreviewRect(const QgsRectangle &rect)
Sets the initial "preview" rectangle for the bounds overview map.
void setShowBoundsMap(bool show)
Sets whether to show the bounds preview map.
void crsSelected()
Emitted when a projection is selected in the widget.
void pushProjectionToFront()
Marks the current selected projection for push to front of recent projections list.
QgsCoordinateReferenceSystem crs() const
Returns the CRS currently selected in the widget.
bool showNoProjection() const
Returns whether the "no/invalid" projection option is shown.
void setShowNoProjection(bool show)
Sets whether a "no/invalid" projection option should be shown.
bool showBoundsMap() const
Returns whether the bounds preview map is shown.
QgsRectangle previewRect() const
The initial "preview" rectangle for the bounds overview map.
QgsProjectionSelectionTreeWidget(QWidget *parent=nullptr)
Constructor for QgsProjectionSelectionTreeWidget.
void projectionDoubleClicked()
Emitted when a projection is double clicked in the list.
bool hasValidSelection() const
Returns true if the current selection in the widget is a valid choice.
void setOgcWmsCrsFilter(const QSet< QString > &crsFilter)
filters this widget by the given CRSs
void setNotSetText(const QString &text)
Sets the text to show for the not set option.
void initialized()
Notifies others that the widget is now fully initialized, including deferred selection of projection.
void hasValidSelectionChanged(bool isValid)
Emitted when the selection in the tree is changed from a valid selection to an invalid selection,...
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the initial crs to show within the dialog.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
double area() const SIP_HOLDGIL
Returns the area of the rectangle.
Definition: qgsrectangle.h:218
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/....
Definition: qgis.h:746
struct sqlite3 sqlite3
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs