QGIS/src/app/qgsmapcanvasdockwidget.cpp
Nyall Dawson 86d9492372 Remove context-unaware QgsCoordinateTransform constructors from Python bindings
This forces Python code and plugins to become datum transform
aware, and given that upgrading python code is easy (just
add QgsProject.instance() as a new argument to the constructor)
it's relatively painless to force this on PyQGIS users.

Also fix upgrade the easy QgsCoordinateTransform c++ constructors
where the project is available, or where using QgsProject::instance()
is safe to do.

For others, just avoid the deprecated warnings until we can
get access to the correct project instance where the transform
is being constructed.
2017-12-15 14:13:22 +10:00

552 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/***************************************************************************
qgsmapcanvasdockwidget.cpp
--------------------------
begin : February 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot 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 "qgsmapcanvasdockwidget.h"
#include "qgsmapcanvas.h"
#include "qgsexception.h"
#include "qgsprojectionselectiondialog.h"
#include "qgsscalecombobox.h"
#include "qgsdoublespinbox.h"
#include "qgssettings.h"
#include "qgsmaptoolpan.h"
#include "qgsmapthemecollection.h"
#include "qgsproject.h"
#include "qgsmapthemes.h"
#include "qgslayertreeview.h"
#include "qgslayertreeviewdefaultactions.h"
#include "qgisapp.h"
#include "qgsvertexmarker.h"
#include "qgsrubberband.h"
#include <QMessageBox>
#include <QMenu>
#include <QToolBar>
#include <QToolButton>
QgsMapCanvasDockWidget::QgsMapCanvasDockWidget( const QString &name, QWidget *parent )
: QgsDockWidget( parent )
{
setupUi( this );
setAttribute( Qt::WA_DeleteOnClose );
mContents->layout()->setContentsMargins( 0, 0, 0, 0 );
mContents->layout()->setMargin( 0 );
static_cast< QVBoxLayout * >( mContents->layout() )->setSpacing( 0 );
setWindowTitle( name );
mToolbar->setIconSize( QgisApp::instance()->iconSize( true ) );
mMapCanvas = new QgsMapCanvas( this );
mXyMarker = new QgsVertexMarker( mMapCanvas );
mXyMarker->setIconType( QgsVertexMarker::ICON_CIRCLE );
mXyMarker->setIconSize( 6 );
mXyMarker->setColor( QColor( 30, 30, 30, 225 ) );
mXyMarker->setFillColor( QColor( 255, 255, 255, 225 ) );
mExtentRubberBand = new QgsRubberBand( mMapCanvas, QgsWkbTypes::PolygonGeometry );
mExtentRubberBand->setStrokeColor( Qt::red );
mExtentRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 225 ) );
mExtentRubberBand->setFillColor( Qt::transparent );
mPanTool = new QgsMapToolPan( mMapCanvas );
mMapCanvas->setMapTool( mPanTool );
mMainWidget->setLayout( new QVBoxLayout() );
mMainWidget->layout()->setContentsMargins( 0, 0, 0, 0 );
mMainWidget->layout()->setMargin( 0 );
mMainWidget->layout()->addWidget( mMapCanvas );
mMenu = new QMenu();
connect( mMenu, &QMenu::aboutToShow, this, &QgsMapCanvasDockWidget::menuAboutToShow );
QToolButton *btnMapThemes = new QToolButton;
btnMapThemes->setAutoRaise( true );
btnMapThemes->setToolTip( tr( "Set View Theme" ) );
btnMapThemes->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowAllLayers.svg" ) ) );
btnMapThemes->setPopupMode( QToolButton::InstantPopup );
btnMapThemes->setMenu( mMenu );
mToolbar->addWidget( btnMapThemes );
QMenu *settingsMenu = new QMenu();
QToolButton *settingsButton = new QToolButton();
settingsButton->setAutoRaise( true );
settingsButton->setToolTip( tr( "View Settings" ) );
settingsButton->setMenu( settingsMenu );
settingsButton->setPopupMode( QToolButton::InstantPopup );
settingsButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapSettings.svg" ) ) );
mToolbar->addWidget( settingsButton );
connect( mActionSetCrs, &QAction::triggered, this, &QgsMapCanvasDockWidget::setMapCrs );
connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsMapCanvasDockWidget::mapCrsChanged );
connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsMapCanvasDockWidget::updateExtentRect );
connect( mActionZoomFullExtent, &QAction::triggered, mMapCanvas, &QgsMapCanvas::zoomToFullExtent );
connect( mActionZoomToLayer, &QAction::triggered, mMapCanvas, [ = ] { QgisApp::instance()->layerTreeView()->defaultActions()->zoomToLayer( mMapCanvas ); } );
connect( mActionZoomToSelected, &QAction::triggered, mMapCanvas, [ = ] { mMapCanvas->zoomToSelected(); } );
mapCrsChanged();
QgsMapSettingsAction *settingsAction = new QgsMapSettingsAction( settingsMenu );
settingsMenu->addAction( settingsAction );
settingsMenu->addSeparator();
settingsMenu->addAction( mActionShowAnnotations );
settingsMenu->addAction( mActionShowCursor );
settingsMenu->addAction( mActionShowExtent );
settingsMenu->addAction( mActionShowLabels );
settingsMenu->addSeparator();
settingsMenu->addAction( mActionSetCrs );
settingsMenu->addAction( mActionRename );
connect( settingsMenu, &QMenu::aboutToShow, this, &QgsMapCanvasDockWidget::settingsMenuAboutToShow );
connect( mActionRename, &QAction::triggered, this, &QgsMapCanvasDockWidget::renameTriggered );
mActionShowAnnotations->setChecked( mMapCanvas->annotationsVisible() );
connect( mActionShowAnnotations, &QAction::toggled, this, [ = ]( bool checked ) { mMapCanvas->setAnnotationsVisible( checked ); } );
mActionShowCursor->setChecked( true );
connect( mActionShowCursor, &QAction::toggled, this, [ = ]( bool checked ) { mXyMarker->setVisible( checked ); } );
mActionShowExtent->setChecked( false );
connect( mActionShowExtent, &QAction::toggled, this, [ = ]( bool checked ) { mExtentRubberBand->setVisible( checked ); updateExtentRect(); } );
mActionShowLabels->setChecked( true );
connect( mActionShowLabels, &QAction::toggled, this, &QgsMapCanvasDockWidget::showLabels );
mSyncExtentCheckBox = settingsAction->syncExtentCheckBox();
mScaleCombo = settingsAction->scaleCombo();
mRotationEdit = settingsAction->rotationSpinBox();
mMagnificationEdit = settingsAction->magnifierSpinBox();
mSyncScaleCheckBox = settingsAction->syncScaleCheckBox();
mScaleFactorWidget = settingsAction->scaleFactorSpinBox();
connect( mSyncExtentCheckBox, &QCheckBox::toggled, this, [ = ]
{
syncViewCenter( mMainCanvas );
} );
connect( mScaleCombo, &QgsScaleComboBox::scaleChanged, this, [ = ]( double scale )
{
if ( !mBlockScaleUpdate )
{
mBlockScaleUpdate = true;
mMapCanvas->zoomScale( scale );
mBlockScaleUpdate = false;
}
} );
connect( mMapCanvas, &QgsMapCanvas::scaleChanged, this, [ = ]( double scale )
{
if ( !mBlockScaleUpdate )
{
mBlockScaleUpdate = true;
mScaleCombo->setScale( scale );
mBlockScaleUpdate = false;
}
} );
connect( mRotationEdit, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( double value )
{
if ( !mBlockRotationUpdate )
{
mBlockRotationUpdate = true;
mMapCanvas->setRotation( value );
mMapCanvas->refresh();
mBlockRotationUpdate = false;
}
} );
connect( mMapCanvas, &QgsMapCanvas::rotationChanged, this, [ = ]( double rotation )
{
if ( !mBlockRotationUpdate )
{
mBlockRotationUpdate = true;
mRotationEdit->setValue( rotation );
mBlockRotationUpdate = false;
}
} );
connect( mMagnificationEdit, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( double value )
{
if ( !mBlockMagnificationUpdate )
{
mBlockMagnificationUpdate = true;
mMapCanvas->setMagnificationFactor( value / 100 );
mMapCanvas->refresh();
mBlockMagnificationUpdate = false;
}
} );
connect( mMapCanvas, &QgsMapCanvas::magnificationChanged, this, [ = ]( double factor )
{
if ( !mBlockMagnificationUpdate )
{
mBlockMagnificationUpdate = true;
mMagnificationEdit->setValue( factor * 100 );
mBlockMagnificationUpdate = false;
}
} );
connect( mScaleFactorWidget, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsMapCanvasDockWidget::mapScaleChanged );
connect( mSyncScaleCheckBox, &QCheckBox::toggled, this, [ = ]( bool checked )
{
if ( checked )
mapScaleChanged();
}
);
mResizeTimer.setSingleShot( true );
connect( &mResizeTimer, &QTimer::timeout, this, [ = ]
{
mBlockExtentSync = false;
if ( mSyncExtentCheckBox->isChecked() )
syncViewCenter( mMainCanvas );
} );
}
void QgsMapCanvasDockWidget::setMainCanvas( QgsMapCanvas *canvas )
{
if ( mMainCanvas )
{
disconnect( mMainCanvas, &QgsMapCanvas::xyCoordinates, this, &QgsMapCanvasDockWidget::syncMarker );
disconnect( mMainCanvas, &QgsMapCanvas::scaleChanged, this, &QgsMapCanvasDockWidget::mapScaleChanged );
disconnect( mMainCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::mapExtentChanged );
disconnect( mMainCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::updateExtentRect );
disconnect( mMainCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsMapCanvasDockWidget::updateExtentRect );
}
mMainCanvas = canvas;
connect( mMainCanvas, &QgsMapCanvas::xyCoordinates, this, &QgsMapCanvasDockWidget::syncMarker );
connect( mMainCanvas, &QgsMapCanvas::scaleChanged, this, &QgsMapCanvasDockWidget::mapScaleChanged );
connect( mMainCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::mapExtentChanged );
connect( mMapCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::mapExtentChanged, Qt::UniqueConnection );
connect( mMainCanvas, &QgsMapCanvas::extentsChanged, this, &QgsMapCanvasDockWidget::updateExtentRect );
connect( mMainCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsMapCanvasDockWidget::updateExtentRect );
updateExtentRect();
}
QgsMapCanvas *QgsMapCanvasDockWidget::mapCanvas()
{
return mMapCanvas;
}
void QgsMapCanvasDockWidget::setViewCenterSynchronized( bool enabled )
{
mSyncExtentCheckBox->setChecked( enabled );
}
bool QgsMapCanvasDockWidget::isViewCenterSynchronized() const
{
return mSyncExtentCheckBox->isChecked();
}
void QgsMapCanvasDockWidget::setCursorMarkerVisible( bool visible )
{
mActionShowCursor->setChecked( visible );
}
bool QgsMapCanvasDockWidget::isCursorMarkerVisible() const
{
return mXyMarker->isVisible();
}
void QgsMapCanvasDockWidget::setMainCanvasExtentVisible( bool visible )
{
mActionShowExtent->setChecked( visible );
}
bool QgsMapCanvasDockWidget::isMainCanvasExtentVisible() const
{
return mExtentRubberBand->isVisible();
}
void QgsMapCanvasDockWidget::setScaleFactor( double factor )
{
mScaleFactorWidget->setValue( factor );
}
void QgsMapCanvasDockWidget::setViewScaleSynchronized( bool enabled )
{
mSyncScaleCheckBox->setChecked( enabled );
}
bool QgsMapCanvasDockWidget::isViewScaleSynchronized() const
{
return mSyncScaleCheckBox->isChecked();
}
void QgsMapCanvasDockWidget::setLabelsVisible( bool enabled )
{
mActionShowLabels->setChecked( enabled );
}
bool QgsMapCanvasDockWidget::labelsVisible() const
{
return mActionShowLabels->isChecked();
}
double QgsMapCanvasDockWidget::scaleFactor() const
{
return mScaleFactorWidget->value();
}
void QgsMapCanvasDockWidget::resizeEvent( QResizeEvent * )
{
mBlockExtentSync = true;
mResizeTimer.start( 500 );
}
void QgsMapCanvasDockWidget::setMapCrs()
{
QgsProjectionSelectionDialog dlg;
dlg.setShowNoProjection( true );
dlg.setCrs( mMapCanvas->mapSettings().destinationCrs() );
if ( dlg.exec() )
{
mMapCanvas->setDestinationCrs( dlg.crs() );
}
}
void QgsMapCanvasDockWidget::syncViewCenter( QgsMapCanvas *sourceCanvas )
{
// avoid infinite recursion
mBlockExtentSync = true;
QgsMapCanvas *destCanvas = sourceCanvas == mMapCanvas ? mMainCanvas : mMapCanvas;
// reproject extent
QgsCoordinateTransform ct( sourceCanvas->mapSettings().destinationCrs(),
destCanvas->mapSettings().destinationCrs(), QgsProject::instance() );
try
{
destCanvas->setCenter( ct.transform( sourceCanvas->center() ) );
}
catch ( QgsCsException & )
{
destCanvas->setCenter( sourceCanvas->center() );
}
destCanvas->refresh();
mBlockExtentSync = false;
}
void QgsMapCanvasDockWidget::mapExtentChanged()
{
if ( mBlockExtentSync )
return;
QgsMapCanvas *sourceCanvas = qobject_cast< QgsMapCanvas * >( sender() );
if ( !sourceCanvas )
return;
if ( sourceCanvas == mMapCanvas && mSyncScaleCheckBox->isChecked() )
{
double newScaleFactor = mMainCanvas->scale() / mMapCanvas->scale();
mScaleFactorWidget->setValue( newScaleFactor );
}
if ( mSyncExtentCheckBox->isChecked() )
syncViewCenter( sourceCanvas );
}
void QgsMapCanvasDockWidget::mapCrsChanged()
{
mActionSetCrs->setText( trUtf8( "Change Map CRS (%1)…" ).arg( mMapCanvas->mapSettings().destinationCrs().isValid() ?
mMapCanvas->mapSettings().destinationCrs().authid() :
tr( "No projection" ) ) );
}
void QgsMapCanvasDockWidget::menuAboutToShow()
{
qDeleteAll( mMenuPresetActions );
mMenuPresetActions.clear();
QString currentTheme = mMapCanvas->theme();
QAction *actionFollowMain = new QAction( tr( "(default)" ), mMenu );
actionFollowMain->setCheckable( true );
if ( currentTheme.isEmpty() || !QgsProject::instance()->mapThemeCollection()->hasMapTheme( currentTheme ) )
{
actionFollowMain->setChecked( true );
}
connect( actionFollowMain, &QAction::triggered, this, [ = ]
{
mMapCanvas->setTheme( QString() );
mMapCanvas->refresh();
} );
mMenuPresetActions.append( actionFollowMain );
Q_FOREACH ( const QString &grpName, QgsProject::instance()->mapThemeCollection()->mapThemes() )
{
QAction *a = new QAction( grpName, mMenu );
a->setCheckable( true );
if ( grpName == currentTheme )
{
a->setChecked( true );
}
connect( a, &QAction::triggered, this, [a, this]
{
mMapCanvas->setTheme( a->text() );
mMapCanvas->refresh();
} );
mMenuPresetActions.append( a );
}
mMenu->addActions( mMenuPresetActions );
}
void QgsMapCanvasDockWidget::settingsMenuAboutToShow()
{
whileBlocking( mActionShowAnnotations )->setChecked( mMapCanvas->annotationsVisible() );
}
void QgsMapCanvasDockWidget::syncMarker( const QgsPointXY &p )
{
if ( !mXyMarker->isVisible() )
return;
// reproject point
QgsCoordinateTransform ct( mMainCanvas->mapSettings().destinationCrs(),
mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() );
QgsPointXY t = p;
try
{
t = ct.transform( p );
}
catch ( QgsCsException & )
{}
mXyMarker->setCenter( t );
}
void QgsMapCanvasDockWidget::mapScaleChanged()
{
if ( !mSyncScaleCheckBox->isChecked() )
return;
double newScale = mMainCanvas->scale() / mScaleFactorWidget->value();
bool prev = mBlockExtentSync;
mBlockExtentSync = true;
mMapCanvas->zoomScale( newScale );
mBlockExtentSync = prev;
}
void QgsMapCanvasDockWidget::updateExtentRect()
{
if ( !mExtentRubberBand->isVisible() )
return;
QPolygonF mainCanvasPoly = mMainCanvas->mapSettings().visiblePolygon();
// close polygon
mainCanvasPoly << mainCanvasPoly.at( 0 );
QgsGeometry g = QgsGeometry::fromQPolygonF( mainCanvasPoly );
if ( mMainCanvas->mapSettings().destinationCrs() !=
mMapCanvas->mapSettings().destinationCrs() )
{
// reproject extent
QgsCoordinateTransform ct( mMainCanvas->mapSettings().destinationCrs(),
mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() );
g = g.densifyByCount( 5 );
try
{
g.transform( ct );
}
catch ( QgsCsException & )
{
}
}
mExtentRubberBand->setToGeometry( g, nullptr );
}
void QgsMapCanvasDockWidget::showLabels( bool show )
{
QgsMapSettings::Flags flags = mMapCanvas->mapSettings().flags();
if ( show )
flags = flags | QgsMapSettings::DrawLabeling;
else
flags = flags & ~QgsMapSettings::DrawLabeling;
mMapCanvas->setMapSettingsFlags( flags );
}
QgsMapSettingsAction::QgsMapSettingsAction( QWidget *parent )
: QWidgetAction( parent )
{
QGridLayout *gLayout = new QGridLayout();
gLayout->setContentsMargins( 3, 2, 3, 2 );
mSyncExtentCheckBox = new QCheckBox( tr( "Synchronize View Center with Main Map" ) );
gLayout->addWidget( mSyncExtentCheckBox, 0, 0, 1, 2 );
QLabel *label = new QLabel( tr( "Scale" ) );
gLayout->addWidget( label, 1, 0 );
mScaleCombo = new QgsScaleComboBox();
gLayout->addWidget( mScaleCombo, 1, 1 );
mRotationWidget = new QgsDoubleSpinBox();
mRotationWidget->setClearValue( 0.0 );
mRotationWidget->setKeyboardTracking( false );
mRotationWidget->setMaximumWidth( 120 );
mRotationWidget->setDecimals( 1 );
mRotationWidget->setRange( -180.0, 180.0 );
mRotationWidget->setWrapping( true );
mRotationWidget->setSingleStep( 5.0 );
mRotationWidget->setSuffix( trUtf8( " °" ) );
mRotationWidget->setToolTip( tr( "Current clockwise map rotation in degrees" ) );
label = new QLabel( tr( "Rotation" ) );
gLayout->addWidget( label, 2, 0 );
gLayout->addWidget( mRotationWidget, 2, 1 );
QgsSettings settings;
int minimumFactor = 100 * QgsGuiUtils::CANVAS_MAGNIFICATION_MIN;
int maximumFactor = 100 * QgsGuiUtils::CANVAS_MAGNIFICATION_MAX;
int defaultFactor = 100 * settings.value( QStringLiteral( "/qgis/magnifier_factor_default" ), 1.0 ).toDouble();
mMagnifierWidget = new QgsDoubleSpinBox();
mMagnifierWidget->setSuffix( QStringLiteral( "%" ) );
mMagnifierWidget->setKeyboardTracking( false );
mMagnifierWidget->setDecimals( 0 );
mMagnifierWidget->setRange( minimumFactor, maximumFactor );
mMagnifierWidget->setWrapping( false );
mMagnifierWidget->setSingleStep( 50 );
mMagnifierWidget->setToolTip( tr( "Magnifier level" ) );
mMagnifierWidget->setClearValueMode( QgsDoubleSpinBox::CustomValue );
mMagnifierWidget->setClearValue( defaultFactor );
mMagnifierWidget->setValue( defaultFactor );
label = new QLabel( tr( "Magnification" ) );
gLayout->addWidget( label, 3, 0 );
gLayout->addWidget( mMagnifierWidget, 3, 1 );
mSyncScaleCheckBox = new QCheckBox( tr( "Synchronize Scale" ) );
gLayout->addWidget( mSyncScaleCheckBox, 4, 0, 1, 2 );
mScaleFactorWidget = new QgsDoubleSpinBox();
mScaleFactorWidget->setSuffix( trUtf8( "×" ) );
mScaleFactorWidget->setDecimals( 2 );
mScaleFactorWidget->setRange( 0.01, 100000 );
mScaleFactorWidget->setWrapping( false );
mScaleFactorWidget->setSingleStep( 0.1 );
mScaleFactorWidget->setToolTip( tr( "Multiplication factor for main canvas scale to view scale" ) );
mScaleFactorWidget->setClearValueMode( QgsDoubleSpinBox::CustomValue );
mScaleFactorWidget->setClearValue( 1.0 );
mScaleFactorWidget->setValue( 1.0 );
mScaleFactorWidget->setEnabled( false );
connect( mSyncScaleCheckBox, &QCheckBox::toggled, mScaleFactorWidget, &QgsDoubleSpinBox::setEnabled );
label = new QLabel( tr( "Scale Factor" ) );
gLayout->addWidget( label, 5, 0 );
gLayout->addWidget( mScaleFactorWidget, 5, 1 );
QWidget *w = new QWidget();
w->setLayout( gLayout );
setDefaultWidget( w );
}