QGIS API Documentation  3.23.0-Master (22c16f2067)
qgsprojectionselectionwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsprojectionselectionwidget.cpp
3  --------------------------------------
4  Date : 05.01.2015
5  Copyright : (C) 2015 Denis Rouzaud
6  Email : [email protected]
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include <QHBoxLayout>
17 
19 #include "qgsapplication.h"
21 #include "qgsproject.h"
22 #include "qgssettings.h"
25 #include "qgsdatums.h"
26 
28  : QWidget( parent )
29 {
30  mCrsComboBox = new QgsHighlightableComboBox( this );
31  mCrsComboBox->addItem( tr( "invalid projection" ), QgsProjectionSelectionWidget::CurrentCrs );
32  mCrsComboBox->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred );
33 
34  const int labelMargin = static_cast< int >( std::round( mCrsComboBox->fontMetrics().horizontalAdvance( 'X' ) ) );
35  QHBoxLayout *layout = new QHBoxLayout();
36  layout->setContentsMargins( 0, 0, 0, 0 );
37  layout->setSpacing( 0 );
38  setLayout( layout );
39 
40  mProjectCrs = QgsProject::instance()->crs();
41  addProjectCrsOption();
42 
43  const QgsSettings settings;
44  mDefaultCrs = QgsCoordinateReferenceSystem( settings.value( QStringLiteral( "/projections/defaultProjectCrs" ), geoEpsgCrsAuthId(), QgsSettings::App ).toString() );
45  if ( mDefaultCrs.authid() != mProjectCrs.authid() )
46  {
47  //only show default CRS option if it's different to the project CRS, avoids
48  //needlessly cluttering the widget
49  addDefaultCrsOption();
50  }
51 
52  addRecentCrs();
53 
54  layout->addWidget( mCrsComboBox, 1 );
55 
56  // bit of fiddlyness here -- we want the initial spacing to only be visible
57  // when the warning label is shown, so it's embedded inside mWarningLabel
58  // instead of outside it
59  mWarningLabelContainer = new QWidget();
60  QHBoxLayout *warningLayout = new QHBoxLayout();
61  warningLayout->setContentsMargins( 0, 0, 0, 0 );
62  mWarningLabel = new QLabel();
63  const QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "mIconWarning.svg" ) );
64  const int size = static_cast< int >( std::max( 24.0, mCrsComboBox->minimumSize().height() * 0.5 ) );
65  mWarningLabel->setPixmap( icon.pixmap( icon.actualSize( QSize( size, size ) ) ) );
66  warningLayout->insertSpacing( 0, labelMargin / 2 );
67  warningLayout->insertWidget( 1, mWarningLabel );
68  mWarningLabelContainer->setLayout( warningLayout );
69  layout->addWidget( mWarningLabelContainer );
70  mWarningLabelContainer->hide();
71 
72  layout->addSpacing( labelMargin / 2 );
73 
74  mButton = new QToolButton( this );
75  mButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionSetProjection.svg" ) ) );
76  mButton->setToolTip( tr( "Select CRS" ) );
77  layout->addWidget( mButton );
78 
79  setFocusPolicy( Qt::StrongFocus );
80  setFocusProxy( mButton );
81  setAcceptDrops( true );
82 
83  connect( mButton, &QToolButton::clicked, this, &QgsProjectionSelectionWidget::selectCrs );
84  connect( mCrsComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsProjectionSelectionWidget::comboIndexChanged );
85 
87  {
88  mCrs.updateDefinition();
89  mLayerCrs.updateDefinition();
90  mProjectCrs.updateDefinition();
91  mDefaultCrs.updateDefinition();
92  } );
93 }
94 
96 {
97  switch ( static_cast< CrsOption >( mCrsComboBox->currentData().toInt() ) )
98  {
100  return mLayerCrs;
102  return mProjectCrs;
104  return mDefaultCrs;
106  return mCrs;
108  {
109  const long srsid = mCrsComboBox->currentData( Qt::UserRole + 1 ).toLongLong();
111  return crs;
112  }
115  }
116  return mCrs;
117 }
118 
120 {
121  const int optionIndex = mCrsComboBox->findData( option );
122 
123  if ( visible && optionIndex < 0 )
124  {
125  //add missing CRS option
126  switch ( option )
127  {
129  {
130  setLayerCrs( mLayerCrs );
131  return;
132  }
134  {
135  addProjectCrsOption();
136  return;
137  }
139  {
140  addDefaultCrsOption();
141  return;
142  }
144  {
145  addCurrentCrsOption();
146  return;
147  }
149  //recently used CRS option cannot be readded
150  return;
152  {
153  addNotSetOption();
154 
155  if ( optionVisible( CurrentCrs ) && !mCrs.isValid() )
156  {
157  // hide invalid option if not set option is shown
158  setOptionVisible( CurrentCrs, false );
159  }
160 
161  return;
162  }
163  }
164  }
165  else if ( !visible && optionIndex >= 0 )
166  {
167  //remove CRS option
168  mCrsComboBox->removeItem( optionIndex );
169 
170  if ( option == CrsNotSet )
171  {
172  setOptionVisible( CurrentCrs, true );
173  }
174  }
175 }
176 
178 {
179  mNotSetText = text;
180  const int optionIndex = mCrsComboBox->findData( CrsNotSet );
181  if ( optionIndex >= 0 )
182  {
183  mCrsComboBox->setItemText( optionIndex, mNotSetText );
184  }
185 }
186 
187 void QgsProjectionSelectionWidget::setMessage( const QString &text )
188 {
189  mMessage = text;
190 }
191 
193 {
194  const int optionIndex = mCrsComboBox->findData( option );
195  return optionIndex >= 0;
196 }
197 
199 {
200  //find out crs id of current proj4 string
201  QgsProjectionSelectionDialog dlg( this );
202  if ( !mMessage.isEmpty() )
203  dlg.setMessage( mMessage );
204  dlg.setCrs( mCrs );
205 
206  if ( !mNotSetText.isEmpty() )
207  dlg.setNotSetText( mNotSetText );
208 
209  if ( optionVisible( QgsProjectionSelectionWidget::CrsOption::CrsNotSet ) )
210  {
211  dlg.setShowNoProjection( true );
212  }
214 
215  if ( dlg.exec() )
216  {
217  mCrsComboBox->blockSignals( true );
218  mCrsComboBox->setCurrentIndex( mCrsComboBox->findData( QgsProjectionSelectionWidget::CurrentCrs ) );
219  mCrsComboBox->blockSignals( false );
220  const QgsCoordinateReferenceSystem crs = dlg.crs();
221  setCrs( crs );
222  emit crsChanged( crs );
223  }
224  else
225  {
226  QApplication::restoreOverrideCursor();
227  }
228 }
229 
230 void QgsProjectionSelectionWidget::dragEnterEvent( QDragEnterEvent *event )
231 {
232  if ( !( event->possibleActions() & Qt::CopyAction ) )
233  {
234  event->ignore();
235  return;
236  }
237 
238  if ( mapLayerFromMimeData( event->mimeData() ) )
239  {
240  // dragged an acceptable layer, phew
241  event->setDropAction( Qt::CopyAction );
242  event->accept();
243  mCrsComboBox->setHighlighted( true );
244  update();
245  }
246  else
247  {
248  event->ignore();
249  }
250 }
251 
252 void QgsProjectionSelectionWidget::dragLeaveEvent( QDragLeaveEvent *event )
253 {
254  if ( mCrsComboBox->isHighlighted() )
255  {
256  event->accept();
257  mCrsComboBox->setHighlighted( false );
258  update();
259  }
260  else
261  {
262  event->ignore();
263  }
264 }
265 
267 {
268  if ( !( event->possibleActions() & Qt::CopyAction ) )
269  {
270  event->ignore();
271  return;
272  }
273 
274  if ( QgsMapLayer *layer = mapLayerFromMimeData( event->mimeData() ) )
275  {
276  // dropped a map layer
277  setFocus( Qt::MouseFocusReason );
278  event->setDropAction( Qt::CopyAction );
279  event->accept();
280 
281  if ( layer->crs().isValid() )
282  setCrs( layer->crs() );
283  }
284  else
285  {
286  event->ignore();
287  }
288  mCrsComboBox->setHighlighted( false );
289  update();
290 }
291 
293 {
294  return mSourceEnsemble;
295 }
296 
297 void QgsProjectionSelectionWidget::setSourceEnsemble( const QString &ensemble )
298 {
299  if ( mSourceEnsemble == ensemble )
300  return;
301 
302  mSourceEnsemble = ensemble;
303  updateWarning();
304 }
305 
307 {
308  return mShowAccuracyWarnings;
309 }
310 
312 {
313  mShowAccuracyWarnings = show;
314  if ( !mShowAccuracyWarnings )
315  mWarningLabelContainer->hide();
316  else
317  updateWarning();
318 }
319 
320 void QgsProjectionSelectionWidget::addNotSetOption()
321 {
322  mCrsComboBox->insertItem( 0, mNotSetText, QgsProjectionSelectionWidget::CrsNotSet );
323  if ( !mCrs.isValid() )
324  whileBlocking( mCrsComboBox )->setCurrentIndex( 0 );
325 }
326 
327 void QgsProjectionSelectionWidget::comboIndexChanged( int idx )
328 {
329  switch ( static_cast< CrsOption >( mCrsComboBox->itemData( idx ).toInt() ) )
330  {
332  emit crsChanged( mLayerCrs );
333  break;
335  emit crsChanged( mProjectCrs );
336  break;
338  emit crsChanged( mCrs );
339  break;
341  emit crsChanged( mDefaultCrs );
342  break;
344  {
345  const long srsid = mCrsComboBox->itemData( idx, Qt::UserRole + 1 ).toLongLong();
347  emit crsChanged( crs );
348  break;
349  }
351  emit cleared();
353  break;
354  }
355  updateTooltip();
356 }
357 
358 void QgsProjectionSelectionWidget::updateWarning()
359 {
360  if ( !mShowAccuracyWarnings )
361  {
362  if ( mWarningLabelContainer->isVisible() )
363  mWarningLabelContainer->hide();
364  return;
365  }
366 
367  try
368  {
369  const double crsAccuracyWarningThreshold = QgsSettings().value( QStringLiteral( "/projections/crsAccuracyWarningThreshold" ), 0.0, QgsSettings::App ).toDouble();
370 
371  const QgsDatumEnsemble ensemble = crs().datumEnsemble();
372  if ( !ensemble.isValid() || ensemble.name() == mSourceEnsemble || ( ensemble.accuracy() > 0 && ensemble.accuracy() < crsAccuracyWarningThreshold ) )
373  {
374  mWarningLabelContainer->hide();
375  }
376  else
377  {
378  mWarningLabelContainer->show();
379 
380  QString warning = QStringLiteral( "<p>" );
381 
382  QString id;
383  if ( !ensemble.code().isEmpty() )
384  id = QStringLiteral( "<i>%1</i> (%2:%3)" ).arg( ensemble.name(), ensemble.authority(), ensemble.code() );
385  else
386  id = QStringLiteral( "<i>%</i>”" ).arg( ensemble.name() );
387 
388  if ( ensemble.accuracy() > 0 )
389  {
390  warning = tr( "The selected CRS is based on %1, which has a limited accuracy of <b>at best %2 meters</b>." ).arg( id ).arg( ensemble.accuracy() );
391  }
392  else
393  {
394  warning = tr( "The selected CRS is based on %1, which has a limited accuracy." ).arg( id );
395  }
396  warning += QStringLiteral( "</p><p>" ) + tr( "Use an alternative CRS if accurate positioning is required." ) + QStringLiteral( "</p>" );
397 
398  const QList< QgsDatumEnsembleMember > members = ensemble.members();
399  if ( !members.isEmpty() )
400  {
401  warning += QStringLiteral( "<p>" ) + tr( "%1 consists of the datums:" ).arg( ensemble.name() ) + QStringLiteral( "</p><ul>" );
402 
403  for ( const QgsDatumEnsembleMember &member : members )
404  {
405  if ( !member.code().isEmpty() )
406  id = QStringLiteral( "%1 (%2:%3)" ).arg( member.name(), member.authority(), member.code() );
407  else
408  id = member.name();
409  warning += QStringLiteral( "<li>%1</li>" ).arg( id );
410  }
411 
412  warning += QLatin1String( "</ul>" );
413  }
414 
415  mWarningLabel->setToolTip( warning );
416  }
417  }
418  catch ( QgsNotSupportedException & )
419  {
420  mWarningLabelContainer->hide();
421  }
422 }
423 
425 {
426  if ( crs.isValid() )
427  {
430  mCrsComboBox->setItemText( mCrsComboBox->findData( QgsProjectionSelectionWidget::CurrentCrs ),
431  crsOptionText( crs ) );
432  mCrsComboBox->blockSignals( true );
433  mCrsComboBox->setCurrentIndex( mCrsComboBox->findData( QgsProjectionSelectionWidget::CurrentCrs ) );
434  mCrsComboBox->blockSignals( false );
435  }
436  else
437  {
438  const int crsNotSetIndex = mCrsComboBox->findData( QgsProjectionSelectionWidget::CrsNotSet );
439  if ( crsNotSetIndex >= 0 )
440  {
441  mCrsComboBox->blockSignals( true );
442  mCrsComboBox->setCurrentIndex( crsNotSetIndex );
443  mCrsComboBox->blockSignals( false );
444  }
445  else
446  {
447  mCrsComboBox->setItemText( mCrsComboBox->findData( QgsProjectionSelectionWidget::CurrentCrs ),
448  crsOptionText( crs ) );
449  }
450  }
451  if ( mCrs != crs )
452  {
453  mCrs = crs;
454  emit crsChanged( crs );
455  }
456  updateTooltip();
457 }
458 
460 {
461  const int layerItemIndex = mCrsComboBox->findData( QgsProjectionSelectionWidget::LayerCrs );
462  if ( crs.isValid() )
463  {
464  if ( layerItemIndex > -1 )
465  {
466  mCrsComboBox->setItemText( layerItemIndex, tr( "Layer CRS: %1" ).arg( crs.userFriendlyIdentifier() ) );
467  }
468  else
469  {
470  mCrsComboBox->insertItem( firstRecentCrsIndex(), tr( "Layer CRS: %1" ).arg( crs.userFriendlyIdentifier() ), QgsProjectionSelectionWidget::LayerCrs );
471  }
472  }
473  else
474  {
475  if ( layerItemIndex > -1 )
476  {
477  mCrsComboBox->removeItem( layerItemIndex );
478  }
479  }
480  mLayerCrs = crs;
481 }
482 
483 void QgsProjectionSelectionWidget::addProjectCrsOption()
484 {
485  if ( mProjectCrs.isValid() )
486  {
487  mCrsComboBox->addItem( tr( "Project CRS: %1" ).arg( mProjectCrs.userFriendlyIdentifier() ), QgsProjectionSelectionWidget::ProjectCrs );
488  }
489 }
490 
491 void QgsProjectionSelectionWidget::addDefaultCrsOption()
492 {
493  mCrsComboBox->addItem( tr( "Default CRS: %1" ).arg( mDefaultCrs.userFriendlyIdentifier() ), QgsProjectionSelectionWidget::DefaultCrs );
494 }
495 
496 void QgsProjectionSelectionWidget::addCurrentCrsOption()
497 {
498  const int index = optionVisible( CrsNotSet ) ? 1 : 0;
499  mCrsComboBox->insertItem( index, crsOptionText( mCrs ), QgsProjectionSelectionWidget::CurrentCrs );
500 
501 }
502 
504 {
505  if ( crs.isValid() )
506  return crs.userFriendlyIdentifier();
507  else
508  return tr( "invalid projection" );
509 }
510 
511 void QgsProjectionSelectionWidget::addRecentCrs()
512 {
513  const QList< QgsCoordinateReferenceSystem> recentProjections = QgsCoordinateReferenceSystem::recentCoordinateReferenceSystems();
514  for ( const QgsCoordinateReferenceSystem &crs : recentProjections )
515  {
516  const long srsid = crs.srsid();
517 
518  //check if already shown
519  if ( crsIsShown( srsid ) )
520  {
521  continue;
522  }
523 
524  if ( crs.isValid() )
525  {
527  mCrsComboBox->setItemData( mCrsComboBox->count() - 1, QVariant( ( long long )srsid ), Qt::UserRole + 1 );
528  }
529  }
530 }
531 
532 bool QgsProjectionSelectionWidget::crsIsShown( const long srsid ) const
533 {
534  return srsid == mLayerCrs.srsid() || srsid == mDefaultCrs.srsid() || srsid == mProjectCrs.srsid();
535 }
536 
537 int QgsProjectionSelectionWidget::firstRecentCrsIndex() const
538 {
539  for ( int i = 0; i < mCrsComboBox->count(); ++i )
540  {
541  if ( static_cast< CrsOption >( mCrsComboBox->itemData( i ).toInt() ) == RecentCrs )
542  {
543  return i;
544  }
545  }
546  return -1;
547 }
548 
549 void QgsProjectionSelectionWidget::updateTooltip()
550 {
552  if ( c.isValid() )
553  setToolTip( c.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, true ) );
554  else
555  setToolTip( QString() );
556  updateWarning();
557 }
558 
559 QgsMapLayer *QgsProjectionSelectionWidget::mapLayerFromMimeData( const QMimeData *data ) const
560 {
562  for ( const QgsMimeDataUtils::Uri &u : uriList )
563  {
564  // is this uri from the current project?
565  if ( QgsMapLayer *layer = u.mapLayer() )
566  {
567  return layer;
568  }
569  }
570  return nullptr;
571 }
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsCoordinateReferenceSystemRegistry * coordinateReferenceSystemRegistry()
Returns the application's coordinate reference system (CRS) registry, which handles known CRS definit...
void userCrsChanged(const QString &id)
Emitted whenever an existing user CRS definition is changed.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QgsDatumEnsemble datumEnsemble() const SIP_THROW(QgsNotSupportedException)
Attempts to retrieve datum ensemble details from the CRS.
void updateDefinition()
Updates the definition and parameters of the coordinate reference system to their latest values.
static QList< QgsCoordinateReferenceSystem > recentCoordinateReferenceSystems()
Returns a list of recently used CRS.
QString userFriendlyIdentifier(IdentifierType type=MediumString) const
Returns a user friendly identifier for the 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 fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
long srsid() const
Returns the internal CRS ID, if available.
Contains information about a member of a datum ensemble.
Definition: qgsdatums.h:35
Contains information about a datum ensemble.
Definition: qgsdatums.h:95
QString code() const
Identification code, e.g.
Definition: qgsdatums.h:122
QString authority() const
Authority name, e.g.
Definition: qgsdatums.h:117
bool isValid() const
Returns true if the datum ensemble is a valid object, or false if it is a null/invalid object.
Definition: qgsdatums.h:102
QList< QgsDatumEnsembleMember > members() const
Contains a list of members of the ensemble.
Definition: qgsdatums.h:137
QString name() const
Display name of datum ensemble.
Definition: qgsdatums.h:107
double accuracy() const
Positional accuracy (in meters).
Definition: qgsdatums.h:112
A QComboBox subclass with the ability to "highlight" the edges of the widget.
void setHighlighted(bool highlighted)
Sets whether the combo box is currently highlighted.
bool isHighlighted() const
Returns true if the combo box is currently highlighted.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QList< QgsMimeDataUtils::Uri > UriList
static UriList decodeUriList(const QMimeData *data)
Custom exception class which is raised when an operation is not supported.
Definition: qgsexception.h:118
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:468
QgsCoordinateReferenceSystem crs
Definition: qgsproject.h:106
A generic dialog to prompt the user for a Coordinate Reference System.
void setNotSetText(const QString &text)
Sets the text to show for the not set option.
void setShowNoProjection(bool show)
Sets whether a "no/invalid" projection option should be shown.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the initial crs to show within the dialog.
void setMessage(const QString &message)
Sets a message to show in the dialog.
QgsCoordinateReferenceSystem crs() const
Returns the CRS currently selected in the widget.
void setRequireValidSelection()
Sets the dialog to require a valid selection only, preventing users from accepting the dialog if no s...
bool showAccuracyWarnings() const
Returns true if the widget will show a warning to users when they select a CRS which has low accuracy...
void cleared()
Emitted when the not set option is selected.
void selectCrs()
Opens the dialog for selecting a new CRS.
void crsChanged(const QgsCoordinateReferenceSystem &)
Emitted when the selected CRS is changed.
CrsOption
Predefined CRS options shown in widget.
@ CrsNotSet
Not set (hidden by default)
@ ProjectCrs
Current project CRS (if OTF reprojection enabled)
@ CurrentCrs
Current user selected CRS.
QgsProjectionSelectionWidget(QWidget *parent=nullptr)
Constructor for QgsProjectionSelectionWidget.
bool optionVisible(CrsOption option) const
Returns whether the specified CRS option is visible in the widget.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the current CRS for the widget.
void setNotSetText(const QString &text)
Sets the text to show for the not set option.
void setLayerCrs(const QgsCoordinateReferenceSystem &crs)
Sets the layer CRS for the widget.
void setOptionVisible(CrsOption option, bool visible)
Sets whether a predefined CRS option should be shown in the widget.
QString sourceEnsemble() const
Returns the original source ensemble datum name.
QgsCoordinateReferenceSystem crs() const
Returns the currently selected CRS for the widget.
void dragEnterEvent(QDragEnterEvent *event) override
void setMessage(const QString &text)
Sets a message to show in the dialog.
static QString crsOptionText(const QgsCoordinateReferenceSystem &crs)
Returns display text for the specified crs.
void dragLeaveEvent(QDragLeaveEvent *event) override
void setShowAccuracyWarnings(bool show)
Sets whether the widget will show warnings to users when they select a CRS which has low accuracy.
void setSourceEnsemble(const QString &ensemble)
Sets the original source ensemble datum name.
void dropEvent(QDropEvent *event) override
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:1430
CONSTLATIN1STRING geoEpsgCrsAuthId()
Geographic coord sys from EPSG authority.
Definition: qgis.h:1899
const QgsCoordinateReferenceSystem & crs