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