mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-08 00:05:09 -04:00
1090 lines
37 KiB
C++
1090 lines
37 KiB
C++
/***************************************************************************
|
|
* qgsprojectionselector.cpp *
|
|
* Copyright (C) 2005 by Tim Sutton *
|
|
* tim@linfiniti.com *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
***************************************************************************/
|
|
#include "qgsprojectionselectiontreewidget.h"
|
|
|
|
//standard includes
|
|
#include <sqlite3.h>
|
|
|
|
//qgis includes
|
|
#include "qgis.h" //magic numbers here
|
|
#include "qgsapplication.h"
|
|
#include "qgslogger.h"
|
|
#include "qgscoordinatereferencesystem.h"
|
|
#include "qgsmessagelog.h"
|
|
#include "qgssettings.h"
|
|
#include "qgsrectangle.h"
|
|
#include "qgsrubberband.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsmaptoolpan.h"
|
|
#include "qgsvertexmarker.h"
|
|
|
|
//qt includes
|
|
#include <QFileInfo>
|
|
#include <QHeaderView>
|
|
#include <QResizeEvent>
|
|
#include <QMessageBox>
|
|
|
|
QgsProjectionSelectionTreeWidget::QgsProjectionSelectionTreeWidget( QWidget *parent )
|
|
: QWidget( parent )
|
|
{
|
|
setupUi( this );
|
|
|
|
leSearch->setShowSearchIcon( true );
|
|
|
|
connect( lstCoordinateSystems, &QTreeWidget::itemDoubleClicked, this, &QgsProjectionSelectionTreeWidget::lstCoordinateSystems_itemDoubleClicked );
|
|
connect( lstRecent, &QTreeWidget::itemDoubleClicked, this, &QgsProjectionSelectionTreeWidget::lstRecent_itemDoubleClicked );
|
|
connect( lstCoordinateSystems, &QTreeWidget::currentItemChanged, this, &QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged );
|
|
connect( lstRecent, &QTreeWidget::currentItemChanged, this, &QgsProjectionSelectionTreeWidget::lstRecent_currentItemChanged );
|
|
connect( cbxHideDeprecated, &QCheckBox::stateChanged, this, &QgsProjectionSelectionTreeWidget::updateFilter );
|
|
connect( leSearch, &QgsFilterLineEdit::textChanged, this, &QgsProjectionSelectionTreeWidget::updateFilter );
|
|
|
|
mPreviewBand = new QgsRubberBand( mAreaCanvas, QgsWkbTypes::PolygonGeometry );
|
|
mPreviewBand->setWidth( 4 );
|
|
|
|
mPreviewBand2 = new QgsRubberBand( mAreaCanvas, QgsWkbTypes::PolygonGeometry );
|
|
mPreviewBand2->setWidth( 4 );
|
|
QColor rectColor = QColor( 185, 84, 210, 60 );
|
|
mPreviewBand2->setColor( rectColor );
|
|
|
|
mVertexMarker = new QgsVertexMarker( mAreaCanvas );
|
|
mVertexMarker->setIconType( QgsVertexMarker::ICON_CROSS );
|
|
mVertexMarker->setColor( QColor( 185, 84, 210 ) );
|
|
mVertexMarker->setPenWidth( 3 );
|
|
|
|
QgsCoordinateReferenceSystem srs( 4326, QgsCoordinateReferenceSystem::EpsgCrsId );
|
|
mAreaCanvas->setDestinationCrs( srs );
|
|
|
|
QString layerPath = QgsApplication::pkgDataPath() + QStringLiteral( "/resources/data/world_map.shp" );
|
|
mLayers << new QgsVectorLayer( layerPath );
|
|
mAreaCanvas->setLayers( mLayers );
|
|
mAreaCanvas->setMapTool( new QgsMapToolPan( mAreaCanvas ) );
|
|
mAreaCanvas->setPreviewJobsEnabled( true );
|
|
mAreaCanvas->setVisible( mShowMap );
|
|
|
|
if ( QDialog *dlg = qobject_cast<QDialog *>( parent ) )
|
|
{
|
|
// mark selected projection for push to front if parent dialog is accepted
|
|
connect( dlg, &QDialog::accepted, this, &QgsProjectionSelectionTreeWidget::pushProjectionToFront );
|
|
}
|
|
|
|
// Get the full path name to the sqlite3 spatial reference database.
|
|
mSrsDatabaseFileName = QgsApplication::srsDatabaseFilePath();
|
|
|
|
lstCoordinateSystems->header()->setSectionResizeMode( AuthidColumn, QHeaderView::Stretch );
|
|
lstCoordinateSystems->header()->resizeSection( QgisCrsIdColumn, 0 );
|
|
lstCoordinateSystems->header()->setSectionResizeMode( QgisCrsIdColumn, QHeaderView::Fixed );
|
|
|
|
// Hide (internal) ID column
|
|
lstCoordinateSystems->setColumnHidden( QgisCrsIdColumn, true );
|
|
|
|
lstRecent->header()->setSectionResizeMode( AuthidColumn, QHeaderView::Stretch );
|
|
lstRecent->header()->resizeSection( QgisCrsIdColumn, 0 );
|
|
lstRecent->header()->setSectionResizeMode( QgisCrsIdColumn, QHeaderView::Fixed );
|
|
|
|
// Hide (internal) ID column
|
|
lstRecent->setColumnHidden( QgisCrsIdColumn, true );
|
|
|
|
mRecentProjections = QgsCoordinateReferenceSystem::recentProjections();
|
|
|
|
mCheckBoxNoProjection->setHidden( true );
|
|
connect( mCheckBoxNoProjection, &QCheckBox::toggled, this, &QgsProjectionSelectionTreeWidget::crsSelected );
|
|
connect( mCheckBoxNoProjection, &QCheckBox::toggled, mFrameProjections, &QFrame::setDisabled );
|
|
}
|
|
|
|
QgsProjectionSelectionTreeWidget::~QgsProjectionSelectionTreeWidget()
|
|
{
|
|
qDeleteAll( mLayers );
|
|
delete mPreviewBand;
|
|
delete mPreviewBand2;
|
|
delete mVertexMarker;
|
|
|
|
if ( !mPushProjectionToFront )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Push current projection to front, only if set
|
|
long crsId = selectedCrsId();
|
|
if ( crsId == 0 )
|
|
return;
|
|
|
|
// Save persistent list of projects
|
|
mRecentProjections.removeAll( QString::number( crsId ) );
|
|
mRecentProjections.prepend( QString::number( crsId ) );
|
|
// Prune size of list
|
|
while ( mRecentProjections.size() > 8 )
|
|
{
|
|
mRecentProjections.removeLast();
|
|
}
|
|
|
|
// Save to file *** Should be removed sometims in the future ***
|
|
QgsSettings settings;
|
|
settings.setValue( QStringLiteral( "/UI/recentProjections" ), mRecentProjections );
|
|
|
|
// Convert to EPSG and proj4, and save those values also
|
|
|
|
QStringList projectionsProj4;
|
|
QStringList projectionsAuthId;
|
|
for ( int i = 0; i < mRecentProjections.size(); i++ )
|
|
{
|
|
// Create a crs from the crsId
|
|
QgsCoordinateReferenceSystem crs( mRecentProjections.at( i ).toLong(), QgsCoordinateReferenceSystem::InternalCrsId );
|
|
if ( ! crs.isValid() )
|
|
{
|
|
// No? Skip this entry
|
|
continue;
|
|
}
|
|
projectionsProj4 << crs.toProj4();
|
|
projectionsAuthId << crs.authid();
|
|
}
|
|
settings.setValue( QStringLiteral( "/UI/recentProjectionsProj4" ), projectionsProj4 );
|
|
settings.setValue( QStringLiteral( "/UI/recentProjectionsAuthId" ), projectionsAuthId );
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::resizeEvent( QResizeEvent *event )
|
|
{
|
|
lstCoordinateSystems->header()->resizeSection( NameColumn, event->size().width() - 240 );
|
|
lstCoordinateSystems->header()->resizeSection( AuthidColumn, 240 );
|
|
lstCoordinateSystems->header()->resizeSection( QgisCrsIdColumn, 0 );
|
|
|
|
lstRecent->header()->resizeSection( NameColumn, event->size().width() - 240 );
|
|
lstRecent->header()->resizeSection( AuthidColumn, 240 );
|
|
lstRecent->header()->resizeSection( QgisCrsIdColumn, 0 );
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::showEvent( QShowEvent *event )
|
|
{
|
|
// ensure the projection list view is actually populated
|
|
// before we show this widget
|
|
loadCrsList( &mCrsFilter );
|
|
loadUserCrsList( &mCrsFilter );
|
|
|
|
if ( !mRecentProjListDone )
|
|
{
|
|
for ( int i = mRecentProjections.size() - 1; i >= 0; i-- )
|
|
insertRecent( mRecentProjections.at( i ).toLong() );
|
|
mRecentProjListDone = true;
|
|
}
|
|
|
|
// apply deferred selection
|
|
applySelection();
|
|
|
|
emit initialized();
|
|
|
|
// Pass up the inheritance hierarchy
|
|
QWidget::showEvent( event );
|
|
}
|
|
|
|
QString QgsProjectionSelectionTreeWidget::ogcWmsCrsFilterAsSqlExpression( QSet<QString> *crsFilter )
|
|
{
|
|
QString sqlExpression = QStringLiteral( "1" ); // it's "SQL" for "true"
|
|
QMap<QString, QStringList> authParts;
|
|
|
|
if ( !crsFilter )
|
|
return sqlExpression;
|
|
|
|
/*
|
|
Ref: WMS 1.3.0, section 6.7.3 "Layer CRS":
|
|
|
|
Every Layer CRS has an identifier that is a character string. Two types of
|
|
Layer CRS identifiers are permitted: "label" and "URL" identifiers:
|
|
|
|
Label: The identifier includes a namespace prefix, a colon, a numeric or
|
|
string code, and in some instances a comma followed by additional
|
|
parameters. This International Standard defines three namespaces:
|
|
CRS, EpsgCrsId and AUTO2 [...]
|
|
|
|
URL: The identifier is a fully-qualified Uniform Resource Locator that
|
|
references a publicly-accessible file containing a definition of the CRS
|
|
that is compliant with ISO 19111.
|
|
*/
|
|
|
|
// iterate through all incoming CRSs
|
|
|
|
Q_FOREACH ( const QString &auth_id, *crsFilter )
|
|
{
|
|
QStringList parts = auth_id.split( ':' );
|
|
|
|
if ( parts.size() < 2 )
|
|
continue;
|
|
|
|
authParts[ parts.at( 0 ).toUpper()].append( parts.at( 1 ).toUpper() );
|
|
}
|
|
|
|
if ( authParts.isEmpty() )
|
|
return sqlExpression;
|
|
|
|
if ( !authParts.isEmpty() )
|
|
{
|
|
QString prefix = QStringLiteral( " AND (" );
|
|
for ( auto it = authParts.constBegin(); it != authParts.constEnd(); ++it )
|
|
{
|
|
sqlExpression += QStringLiteral( "%1(upper(auth_name)='%2' AND upper(auth_id) IN ('%3'))" )
|
|
.arg( prefix,
|
|
it.key(),
|
|
it.value().join( QStringLiteral( "','" ) ) );
|
|
prefix = QStringLiteral( " OR " );
|
|
}
|
|
sqlExpression += ')';
|
|
}
|
|
|
|
QgsDebugMsgLevel( "exiting with '" + sqlExpression + "'.", 4 );
|
|
|
|
return sqlExpression;
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::applySelection( int column, QString value )
|
|
{
|
|
if ( !mProjListDone || !mUserProjListDone )
|
|
{
|
|
// defer selection until loaded
|
|
mSearchColumn = column;
|
|
mSearchValue = value;
|
|
return;
|
|
}
|
|
|
|
if ( column == QgsProjectionSelectionTreeWidget::None )
|
|
{
|
|
// invoked deferred selection
|
|
column = mSearchColumn;
|
|
value = mSearchValue;
|
|
|
|
mSearchColumn = QgsProjectionSelectionTreeWidget::None;
|
|
mSearchValue.clear();
|
|
}
|
|
|
|
if ( column == QgsProjectionSelectionTreeWidget::None )
|
|
return;
|
|
|
|
QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( value, Qt::MatchExactly | Qt::MatchRecursive, column );
|
|
if ( !nodes.isEmpty() )
|
|
{
|
|
QgsDebugMsgLevel( QString( "found %1,%2" ).arg( column ).arg( value ), 4 );
|
|
lstCoordinateSystems->setCurrentItem( nodes.first() );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsgLevel( QString( "nothing found for %1,%2" ).arg( column ).arg( value ), 4 );
|
|
// deselect the selected item to avoid confusing the user
|
|
lstCoordinateSystems->clearSelection();
|
|
lstRecent->clearSelection();
|
|
teProjection->clear();
|
|
teSelected->clear();
|
|
}
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::insertRecent( long crsId )
|
|
{
|
|
if ( !mProjListDone || !mUserProjListDone )
|
|
return;
|
|
|
|
QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( QString::number( crsId ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
|
|
if ( nodes.isEmpty() )
|
|
return;
|
|
|
|
lstRecent->insertTopLevelItem( 0, new QTreeWidgetItem( lstRecent, QStringList()
|
|
<< nodes.first()->text( NameColumn )
|
|
<< nodes.first()->text( AuthidColumn )
|
|
<< nodes.first()->text( QgisCrsIdColumn ) ) );
|
|
}
|
|
|
|
//note this line just returns the projection name!
|
|
QString QgsProjectionSelectionTreeWidget::selectedName()
|
|
{
|
|
// return the selected wkt name from the list view
|
|
QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
|
|
return lvi ? lvi->text( NameColumn ) : QString();
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::setCrs( const QgsCoordinateReferenceSystem &crs )
|
|
{
|
|
if ( !crs.isValid() )
|
|
{
|
|
mCheckBoxNoProjection->setChecked( true );
|
|
}
|
|
else
|
|
{
|
|
mCheckBoxNoProjection->setChecked( false );
|
|
applySelection( AuthidColumn, crs.authid() );
|
|
}
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::setPreviewRect( const QgsRectangle &rect )
|
|
{
|
|
mPreviewRect = rect;
|
|
mPreviewBand2->setToGeometry( QgsGeometry::fromRect( mPreviewRect ), nullptr );
|
|
mPreviewBand2->show();
|
|
mVertexMarker->setCenter( rect.center() );
|
|
mVertexMarker->show();
|
|
}
|
|
|
|
QgsRectangle QgsProjectionSelectionTreeWidget::previewRect() const
|
|
{
|
|
return mPreviewRect;
|
|
}
|
|
|
|
// Returns the whole proj4 string for the selected projection node
|
|
QString QgsProjectionSelectionTreeWidget::selectedProj4String()
|
|
{
|
|
// Only return the projection if there is a node in the tree
|
|
// selected that has an srid. This prevents error if the user
|
|
// selects a top-level node rather than an actual coordinate
|
|
// system
|
|
//
|
|
// Get the selected node
|
|
QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
|
|
if ( !item || item->text( QgisCrsIdColumn ).isEmpty() )
|
|
return QString();
|
|
|
|
QString srsId = item->text( QgisCrsIdColumn );
|
|
|
|
QgsDebugMsgLevel( "srsId = " + srsId, 4 );
|
|
QgsDebugMsgLevel( "USER_CRS_START_ID = " + QString::number( USER_CRS_START_ID ), 4 );
|
|
|
|
//
|
|
// Determine if this is a user projection or a system on
|
|
// user projection defs all have srs_id >= 100000
|
|
//
|
|
QString databaseFileName;
|
|
if ( srsId.toLong() >= USER_CRS_START_ID )
|
|
{
|
|
databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
|
|
if ( !QFileInfo::exists( databaseFileName ) ) //its unlikely that this condition will ever be reached
|
|
return QString();
|
|
}
|
|
else //must be a system projection then
|
|
{
|
|
databaseFileName = mSrsDatabaseFileName;
|
|
}
|
|
|
|
QgsDebugMsgLevel( "db = " + databaseFileName, 4 );
|
|
|
|
sqlite3 *database = nullptr;
|
|
int rc = sqlite3_open_v2( databaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
|
|
if ( rc )
|
|
{
|
|
showDBMissingWarning( databaseFileName );
|
|
return QString();
|
|
}
|
|
|
|
// prepare the sql statement
|
|
const char *tail = nullptr;
|
|
sqlite3_stmt *stmt = nullptr;
|
|
QString sql = QStringLiteral( "select parameters from tbl_srs where srs_id=%1" ).arg( srsId );
|
|
|
|
QgsDebugMsgLevel( "Selection sql: " + sql, 4 );
|
|
|
|
rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
|
|
// XXX Need to free memory from the error msg if one is set
|
|
QString projString;
|
|
if ( rc == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
|
|
{
|
|
projString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
|
|
}
|
|
|
|
// close the statement
|
|
sqlite3_finalize( stmt );
|
|
// close the database
|
|
sqlite3_close( database );
|
|
|
|
Q_ASSERT( !projString.isEmpty() );
|
|
|
|
return projString;
|
|
}
|
|
|
|
QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString &expression ) const
|
|
{
|
|
// Only return the attribute if there is a node in the tree
|
|
// selected that has an srs_id. This prevents error if the user
|
|
// selects a top-level node rather than an actual coordinate
|
|
// system
|
|
//
|
|
// Get the selected node and make sure it is a srs andx
|
|
// not a top-level projection node
|
|
QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
|
|
if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() )
|
|
return QString();
|
|
|
|
//
|
|
// Determine if this is a user projection or a system on
|
|
// user projection defs all have srs_id >= 100000
|
|
//
|
|
QString databaseFileName;
|
|
if ( lvi->text( QgisCrsIdColumn ).toLong() >= USER_CRS_START_ID )
|
|
{
|
|
databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
|
|
if ( !QFileInfo::exists( databaseFileName ) )
|
|
{
|
|
return QString();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
databaseFileName = mSrsDatabaseFileName;
|
|
}
|
|
|
|
//
|
|
// set up the database
|
|
// XXX We could probabaly hold the database open for the life of this object,
|
|
// assuming that it will never be used anywhere else. Given the low overhead,
|
|
// opening it each time seems to be a reasonable approach at this time.
|
|
sqlite3 *database = nullptr;
|
|
int rc = sqlite3_open_v2( databaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
|
|
if ( rc )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Resource Location Error" ), tr( "Error reading database file from: \n %1\n"
|
|
"Because of this the projection selector will not work…" ).arg( databaseFileName ),
|
|
Qgis::Critical );
|
|
return QString();
|
|
}
|
|
|
|
// prepare the sql statement
|
|
const char *tail = nullptr;
|
|
sqlite3_stmt *stmt = nullptr;
|
|
QString sql = QStringLiteral( "select %1 from tbl_srs where srs_id=%2" )
|
|
.arg( expression,
|
|
lvi->text( QgisCrsIdColumn ) );
|
|
|
|
QgsDebugMsgLevel( QString( "Finding selected attribute using : %1" ).arg( sql ), 4 );
|
|
rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
|
|
// XXX Need to free memory from the error msg if one is set
|
|
QString attributeValue;
|
|
if ( rc == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
|
|
{
|
|
// get the first row of the result set
|
|
attributeValue = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
|
|
}
|
|
|
|
// close the statement
|
|
sqlite3_finalize( stmt );
|
|
// close the database
|
|
sqlite3_close( database );
|
|
|
|
// return the srs
|
|
return attributeValue;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem QgsProjectionSelectionTreeWidget::crs() const
|
|
{
|
|
if ( mCheckBoxNoProjection->isChecked() )
|
|
return QgsCoordinateReferenceSystem();
|
|
|
|
int srid = getSelectedExpression( QStringLiteral( "srs_id" ) ).toLong();
|
|
if ( srid >= USER_CRS_START_ID )
|
|
return QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "USER:%1" ).arg( srid ) );
|
|
else
|
|
return QgsCoordinateReferenceSystem::fromOgcWmsCrs( getSelectedExpression( QStringLiteral( "upper(auth_name||':'||auth_id)" ) ) );
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::setShowNoProjection( bool show )
|
|
{
|
|
mCheckBoxNoProjection->setHidden( !show );
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::setShowBoundsMap( bool show )
|
|
{
|
|
mShowMap = show;
|
|
mAreaCanvas->setVisible( show );
|
|
|
|
}
|
|
|
|
bool QgsProjectionSelectionTreeWidget::showNoProjection() const
|
|
{
|
|
return !mCheckBoxNoProjection->isHidden();
|
|
}
|
|
|
|
bool QgsProjectionSelectionTreeWidget::showBoundsMap() const
|
|
{
|
|
return mShowMap;
|
|
}
|
|
|
|
bool QgsProjectionSelectionTreeWidget::hasValidSelection() const
|
|
{
|
|
QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
|
|
if ( mCheckBoxNoProjection->isChecked() )
|
|
return true;
|
|
else
|
|
return item && !item->text( QgisCrsIdColumn ).isEmpty();
|
|
}
|
|
|
|
long QgsProjectionSelectionTreeWidget::selectedCrsId()
|
|
{
|
|
QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
|
|
|
|
if ( item && !item->text( QgisCrsIdColumn ).isEmpty() )
|
|
return lstCoordinateSystems->currentItem()->text( QgisCrsIdColumn ).toLong();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
void QgsProjectionSelectionTreeWidget::setOgcWmsCrsFilter( const QSet<QString> &crsFilter )
|
|
{
|
|
mCrsFilter = crsFilter;
|
|
mProjListDone = false;
|
|
mUserProjListDone = false;
|
|
lstCoordinateSystems->clear();
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::loadUserCrsList( QSet<QString> *crsFilter )
|
|
{
|
|
if ( mUserProjListDone )
|
|
return;
|
|
|
|
QgsDebugMsgLevel( "Fetching user projection list...", 4 );
|
|
|
|
// convert our Coordinate Reference System filter into the SQL expression
|
|
QString sqlFilter = ogcWmsCrsFilterAsSqlExpression( crsFilter );
|
|
|
|
// User defined coordinate system node
|
|
// Make in an italic font to distinguish them from real projections
|
|
mUserProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "User Defined Coordinate Systems" ) ) );
|
|
|
|
QFont fontTemp = mUserProjList->font( 0 );
|
|
fontTemp.setItalic( true );
|
|
fontTemp.setBold( true );
|
|
mUserProjList->setFont( 0, fontTemp );
|
|
mUserProjList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) ) );
|
|
|
|
//determine where the user proj database lives for this user. If none is found an empty
|
|
//now only will be shown
|
|
QString databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
|
|
// first we look for ~/.qgis/qgis.db
|
|
// if it doesn't exist we copy it in from the global resources dir
|
|
|
|
//return straight away if the user has not created any custom projections
|
|
if ( !QFileInfo::exists( databaseFileName ) )
|
|
{
|
|
QgsDebugMsg( "Users qgis.db not found...skipping" );
|
|
mUserProjListDone = true;
|
|
return;
|
|
}
|
|
|
|
sqlite3 *database = nullptr;
|
|
const char *tail = nullptr;
|
|
sqlite3_stmt *stmt = nullptr;
|
|
//check the db is available
|
|
int result = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
|
|
if ( result )
|
|
{
|
|
// XXX This will likely never happen since on open, sqlite creates the
|
|
// database if it does not exist. But we checked earlier for its existence
|
|
// and aborted in that case. This is because we may be running from read only
|
|
// media such as live cd and don't want to force trying to create a db.
|
|
showDBMissingWarning( databaseFileName );
|
|
return;
|
|
}
|
|
|
|
// Set up the query to retrieve the projection information needed to populate the list
|
|
QString sql = QStringLiteral( "select description, srs_id from vw_srs where %1" ).arg( sqlFilter );
|
|
|
|
result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
|
|
// XXX Need to free memory from the error msg if one is set
|
|
if ( result == SQLITE_OK )
|
|
{
|
|
QTreeWidgetItem *newItem = nullptr;
|
|
while ( sqlite3_step( stmt ) == SQLITE_ROW )
|
|
{
|
|
newItem = new QTreeWidgetItem( mUserProjList, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
|
|
// EpsgCrsId for user projections is not always defined in some dbases.
|
|
// It's also not written from customprojections dialog.
|
|
// display the epsg (field 2) in the second column of the list view
|
|
// newItem->setText( EPSG_COLUMN, QString::fromUtf8(( char * )sqlite3_column_text( stmt, 2 ) ) );
|
|
// display the qgis srs_id (field 1) in the third column of the list view
|
|
newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
|
|
newItem->setText( AuthidColumn, QStringLiteral( "USER:%1" ).arg( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ).toInt() ) );
|
|
}
|
|
}
|
|
// close the sqlite3 statement
|
|
sqlite3_finalize( stmt );
|
|
sqlite3_close( database );
|
|
|
|
mUserProjListDone = true;
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::loadCrsList( QSet<QString> *crsFilter )
|
|
{
|
|
if ( mProjListDone )
|
|
return;
|
|
|
|
// convert our Coordinate Reference System filter into the SQL expression
|
|
QString sqlFilter = ogcWmsCrsFilterAsSqlExpression( crsFilter );
|
|
|
|
// Create the top-level nodes for the list view of projections
|
|
// Make in an italic font to distinguish them from real projections
|
|
//
|
|
// Geographic coordinate system node
|
|
mGeoList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Geographic Coordinate Systems" ) ) );
|
|
|
|
QFont fontTemp = mGeoList->font( 0 );
|
|
fontTemp.setItalic( true );
|
|
fontTemp.setBold( true );
|
|
mGeoList->setFont( 0, fontTemp );
|
|
mGeoList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconProjectionEnabled.svg" ) ) );
|
|
|
|
// Projected coordinate system node
|
|
mProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Projected Coordinate Systems" ) ) );
|
|
|
|
fontTemp = mProjList->font( 0 );
|
|
fontTemp.setItalic( true );
|
|
fontTemp.setBold( true );
|
|
mProjList->setFont( 0, fontTemp );
|
|
mProjList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/transformed.svg" ) ) );
|
|
|
|
//bail out in case the projections db does not exist
|
|
//this is necessary in case the pc is running linux with a
|
|
//read only filesystem because otherwise sqlite will try
|
|
//to create the db file on the fly
|
|
|
|
if ( !QFileInfo::exists( mSrsDatabaseFileName ) )
|
|
{
|
|
mProjListDone = true;
|
|
return;
|
|
}
|
|
|
|
// open the database containing the spatial reference data
|
|
sqlite3 *database = nullptr;
|
|
int rc = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
|
|
if ( rc )
|
|
{
|
|
// XXX This will likely never happen since on open, sqlite creates the
|
|
// database if it does not exist.
|
|
showDBMissingWarning( mSrsDatabaseFileName );
|
|
return;
|
|
}
|
|
|
|
const char *tail = nullptr;
|
|
sqlite3_stmt *stmt = nullptr;
|
|
// Set up the query to retrieve the projection information needed to populate the list
|
|
//note I am giving the full field names for clarity here and in case someone
|
|
//changes the underlying view TS
|
|
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" )
|
|
.arg( sqlFilter );
|
|
|
|
rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
|
|
// XXX Need to free memory from the error msg if one is set
|
|
if ( rc == SQLITE_OK )
|
|
{
|
|
QTreeWidgetItem *newItem = nullptr;
|
|
// Cache some stuff to speed up creating of the list of projected
|
|
// spatial reference systems
|
|
QString previousSrsType;
|
|
QTreeWidgetItem *previousSrsTypeNode = nullptr;
|
|
|
|
while ( sqlite3_step( stmt ) == SQLITE_ROW )
|
|
{
|
|
// check to see if the srs is geographic
|
|
int isGeo = sqlite3_column_int( stmt, 3 );
|
|
if ( isGeo )
|
|
{
|
|
// this is a geographic coordinate system
|
|
// Add it to the tree (field 0)
|
|
newItem = new QTreeWidgetItem( mGeoList, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
|
|
|
|
// display the authority name (field 2) in the second column of the list view
|
|
newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
|
|
|
|
// display the qgis srs_id (field 1) in the third column of the list view
|
|
newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
|
|
}
|
|
else
|
|
{
|
|
// This is a projected srs
|
|
QTreeWidgetItem *node = nullptr;
|
|
QString srsType = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 4 ) );
|
|
// Find the node for this type and add the projection to it
|
|
// If the node doesn't exist, create it
|
|
if ( srsType == previousSrsType )
|
|
{
|
|
node = previousSrsTypeNode;
|
|
}
|
|
else
|
|
{
|
|
// Different from last one, need to search
|
|
QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( srsType, Qt::MatchExactly | Qt::MatchRecursive, NameColumn );
|
|
if ( nodes.isEmpty() )
|
|
{
|
|
// the node doesn't exist -- create it
|
|
// Make in an italic font to distinguish them from real projections
|
|
node = new QTreeWidgetItem( mProjList, QStringList( srsType ) );
|
|
QFont fontTemp = node->font( 0 );
|
|
fontTemp.setItalic( true );
|
|
node->setFont( 0, fontTemp );
|
|
}
|
|
else
|
|
{
|
|
node = nodes.first();
|
|
}
|
|
// Update the cache.
|
|
previousSrsType = srsType;
|
|
previousSrsTypeNode = node;
|
|
}
|
|
// add the item, setting the projection name in the first column of the list view
|
|
newItem = new QTreeWidgetItem( node, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
|
|
// display the authority id (field 2) in the second column of the list view
|
|
newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
|
|
// display the qgis srs_id (field 1) in the third column of the list view
|
|
newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
|
|
// expand also parent node
|
|
newItem->parent()->setExpanded( true );
|
|
}
|
|
|
|
// display the qgis deprecated in the user data of the item
|
|
newItem->setData( 0, RoleDeprecated, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 6 ) ) );
|
|
newItem->setHidden( cbxHideDeprecated->isChecked() );
|
|
}
|
|
mProjList->setExpanded( true );
|
|
}
|
|
|
|
// close the sqlite3 statement
|
|
sqlite3_finalize( stmt );
|
|
// close the database
|
|
sqlite3_close( database );
|
|
|
|
mProjListDone = true;
|
|
}
|
|
|
|
// New coordinate system selected from the list
|
|
void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
|
|
{
|
|
QgsDebugMsgLevel( "Entered.", 4 );
|
|
|
|
if ( !current )
|
|
{
|
|
QgsDebugMsgLevel( "no current item", 4 );
|
|
return;
|
|
}
|
|
|
|
lstCoordinateSystems->scrollToItem( current );
|
|
|
|
// If the item has children, it's not an end node in the tree, and
|
|
// hence is just a grouping thingy, not an actual CRS.
|
|
if ( current->childCount() == 0 )
|
|
{
|
|
// Found a real CRS
|
|
emit crsSelected();
|
|
|
|
teSelected->setText( selectedName() );
|
|
updateBoundsPreview();
|
|
|
|
QList<QTreeWidgetItem *> nodes = lstRecent->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly, QgisCrsIdColumn );
|
|
if ( !nodes.isEmpty() )
|
|
{
|
|
QgsDebugMsgLevel( QString( "found srs %1 in recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
|
|
lstRecent->setCurrentItem( nodes.first() );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsgLevel( QString( "srs %1 not recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
|
|
lstRecent->clearSelection();
|
|
lstCoordinateSystems->setFocus( Qt::OtherFocusReason );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not an CRS - remove the highlight so the user doesn't get too confused
|
|
current->setSelected( false );
|
|
teProjection->clear();
|
|
teSelected->clear();
|
|
lstRecent->clearSelection();
|
|
}
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_itemDoubleClicked( QTreeWidgetItem *current, int column )
|
|
{
|
|
Q_UNUSED( column );
|
|
|
|
QgsDebugMsgLevel( "Entered.", 4 );
|
|
|
|
if ( !current )
|
|
{
|
|
QgsDebugMsgLevel( "no current item", 4 );
|
|
return;
|
|
}
|
|
|
|
// If the item has children, it's not an end node in the tree, and
|
|
// hence is just a grouping thingy, not an actual CRS.
|
|
if ( current->childCount() == 0 )
|
|
emit projectionDoubleClicked();
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::lstRecent_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
|
|
{
|
|
QgsDebugMsgLevel( "Entered.", 4 );
|
|
|
|
if ( !current )
|
|
{
|
|
QgsDebugMsgLevel( "no current item", 4 );
|
|
return;
|
|
}
|
|
|
|
lstRecent->scrollToItem( current );
|
|
|
|
QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
|
|
if ( !nodes.isEmpty() )
|
|
lstCoordinateSystems->setCurrentItem( nodes.first() );
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::lstRecent_itemDoubleClicked( QTreeWidgetItem *current, int column )
|
|
{
|
|
Q_UNUSED( column );
|
|
|
|
QgsDebugMsgLevel( "Entered.", 4 );
|
|
|
|
if ( !current )
|
|
{
|
|
QgsDebugMsgLevel( "no current item", 4 );
|
|
return;
|
|
}
|
|
|
|
QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
|
|
if ( !nodes.isEmpty() )
|
|
emit projectionDoubleClicked();
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::updateFilter()
|
|
{
|
|
QString filterTxtCopy = leSearch->text();
|
|
filterTxtCopy.replace( QRegExp( "\\s+" ), QStringLiteral( ".*" ) );
|
|
QRegExp re( filterTxtCopy, Qt::CaseInsensitive );
|
|
|
|
const bool hideDeprecated = cbxHideDeprecated->isChecked();
|
|
|
|
auto filterTreeWidget = [ = ]( QTreeWidget * tree )
|
|
{
|
|
QTreeWidgetItemIterator itr( tree );
|
|
while ( *itr )
|
|
{
|
|
if ( ( *itr )->childCount() == 0 ) // it's an end node aka a projection
|
|
{
|
|
if ( hideDeprecated && ( *itr )->data( 0, RoleDeprecated ).toBool() )
|
|
{
|
|
( *itr )->setHidden( true );
|
|
if ( ( *itr )->isSelected() )
|
|
{
|
|
( *itr )->setSelected( false );
|
|
teProjection->clear();
|
|
teSelected->clear();
|
|
}
|
|
}
|
|
else if ( ( *itr )->text( NameColumn ).contains( re )
|
|
|| ( *itr )->text( AuthidColumn ).contains( re )
|
|
)
|
|
{
|
|
( *itr )->setHidden( false );
|
|
QTreeWidgetItem *parent = ( *itr )->parent();
|
|
while ( parent )
|
|
{
|
|
parent->setExpanded( true );
|
|
parent->setHidden( false );
|
|
parent = parent->parent();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
( *itr )->setHidden( true );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
( *itr )->setHidden( true );
|
|
}
|
|
++itr;
|
|
}
|
|
};
|
|
|
|
// filter recent crs's
|
|
filterTreeWidget( lstRecent );
|
|
|
|
// filter crs's
|
|
filterTreeWidget( lstCoordinateSystems );
|
|
}
|
|
|
|
|
|
void QgsProjectionSelectionTreeWidget::pushProjectionToFront()
|
|
{
|
|
// set flag to push selected projection to front in destructor
|
|
mPushProjectionToFront = true;
|
|
}
|
|
|
|
|
|
long QgsProjectionSelectionTreeWidget::getLargestCrsIdMatch( const QString &sql )
|
|
{
|
|
long srsId = 0;
|
|
|
|
//
|
|
// Now perform the actual search
|
|
//
|
|
|
|
sqlite3 *database = nullptr;
|
|
const char *tail = nullptr;
|
|
sqlite3_stmt *stmt = nullptr;
|
|
int result;
|
|
|
|
// first we search the users db as any srsid there will be definition be greater than in sys db
|
|
|
|
//check the db is available
|
|
QString databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
|
|
if ( QFileInfo::exists( databaseFileName ) ) //only bother trying to open if the file exists
|
|
{
|
|
result = sqlite3_open_v2( databaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
|
|
if ( result )
|
|
{
|
|
// XXX This will likely never happen since on open, sqlite creates the
|
|
// database if it does not exist. But we checked earlier for its existence
|
|
// and aborted in that case. This is because we may be running from read only
|
|
// media such as live cd and don't want to force trying to create a db.
|
|
showDBMissingWarning( databaseFileName );
|
|
return 0;
|
|
}
|
|
|
|
result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
|
|
// XXX Need to free memory from the error msg if one is set
|
|
if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
|
|
{
|
|
QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
|
|
srsId = srsIdString.toLong();
|
|
// close the sqlite3 statement
|
|
sqlite3_finalize( stmt );
|
|
sqlite3_close( database );
|
|
return srsId;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//only bother looking in srs.db if it wasn't found above
|
|
result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
|
|
if ( result )
|
|
{
|
|
QgsDebugMsg( QString( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
|
|
//no need for assert because user db may not have been created yet
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
|
|
// XXX Need to free memory from the error msg if one is set
|
|
if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
|
|
{
|
|
QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
|
|
srsId = srsIdString.toLong();
|
|
}
|
|
|
|
// close the sqlite3 statement
|
|
sqlite3_finalize( stmt );
|
|
sqlite3_close( database );
|
|
|
|
return srsId;
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::updateBoundsPreview()
|
|
{
|
|
QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
|
|
if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() )
|
|
return;
|
|
|
|
QgsCoordinateReferenceSystem currentCrs = crs();
|
|
if ( !currentCrs.isValid() )
|
|
return;
|
|
|
|
QgsRectangle rect = currentCrs.bounds();
|
|
if ( !qgsDoubleNear( rect.area(), 0.0 ) )
|
|
{
|
|
QgsGeometry geom;
|
|
if ( rect.xMinimum() > rect.xMaximum() )
|
|
{
|
|
QgsRectangle rect1 = QgsRectangle( -180, rect.yMinimum(), rect.xMaximum(), rect.yMaximum() );
|
|
QgsRectangle rect2 = QgsRectangle( rect.xMinimum(), rect.yMinimum(), 180, rect.yMaximum() );
|
|
geom = QgsGeometry::fromRect( rect1 );
|
|
geom.addPart( QgsGeometry::fromRect( rect2 ) );
|
|
}
|
|
else
|
|
{
|
|
geom = QgsGeometry::fromRect( rect );
|
|
}
|
|
mPreviewBand->setToGeometry( geom, nullptr );
|
|
mPreviewBand->setColor( QColor( 255, 0, 0, 65 ) );
|
|
QgsRectangle extent = geom.boundingBox();
|
|
extent.scale( 1.1 );
|
|
mAreaCanvas->setExtent( extent );
|
|
mAreaCanvas->refresh();
|
|
mPreviewBand->show();
|
|
QString extentString = tr( "Extent: %1, %2, %3, %4" )
|
|
.arg( rect.xMinimum(), 0, 'f', 2 )
|
|
.arg( rect.yMinimum(), 0, 'f', 2 )
|
|
.arg( rect.xMaximum(), 0, 'f', 2 )
|
|
.arg( rect.yMaximum(), 0, 'f', 2 );
|
|
QString proj4String = tr( "Proj4: %1" ).arg( selectedProj4String() );
|
|
teProjection->setText( extentString + "\n" + proj4String );
|
|
}
|
|
else
|
|
{
|
|
mPreviewBand->hide();
|
|
mAreaCanvas->zoomToFullExtent();
|
|
QString extentString = tr( "Extent: Extent not known" );
|
|
QString proj4String = tr( "Proj4: %1" ).arg( selectedProj4String() );
|
|
teProjection->setText( extentString + "\n" + proj4String );
|
|
}
|
|
}
|
|
|
|
QStringList QgsProjectionSelectionTreeWidget::authorities()
|
|
{
|
|
sqlite3 *database = nullptr;
|
|
const char *tail = nullptr;
|
|
sqlite3_stmt *stmt = nullptr;
|
|
|
|
int result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
|
|
if ( result )
|
|
{
|
|
QgsDebugMsg( QString( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
|
|
//no need for assert because user db may not have been created yet
|
|
return QStringList();
|
|
}
|
|
|
|
QString sql = QStringLiteral( "select distinct auth_name from tbl_srs" );
|
|
result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
|
|
|
|
QStringList authorities;
|
|
if ( result == SQLITE_OK )
|
|
{
|
|
while ( sqlite3_step( stmt ) == SQLITE_ROW )
|
|
{
|
|
authorities << QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
|
|
}
|
|
|
|
}
|
|
|
|
// close the sqlite3 statement
|
|
sqlite3_finalize( stmt );
|
|
sqlite3_close( database );
|
|
|
|
return authorities;
|
|
}
|
|
|
|
QString QgsProjectionSelectionTreeWidget::sqlSafeString( const QString &theSQL ) const
|
|
{
|
|
QString retval = theSQL;
|
|
retval.replace( '\\', QLatin1String( "\\\\" ) );
|
|
retval.replace( '\"', QLatin1String( "\\\"" ) );
|
|
retval.replace( '\'', QLatin1String( "\\'" ) );
|
|
retval.replace( '%', QLatin1String( "\\%" ) );
|
|
return retval;
|
|
}
|
|
|
|
void QgsProjectionSelectionTreeWidget::showDBMissingWarning( const QString &fileName )
|
|
{
|
|
|
|
QMessageBox::critical( this, tr( "Resource Location Error" ),
|
|
tr( "Error reading database file from: \n %1\n"
|
|
"Because of this the projection selector will not work…" )
|
|
.arg( fileName ) );
|
|
}
|