QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsmapcanvas.cpp
Go to the documentation of this file.
1/***************************************************************************
2qgsmapcanvas.cpp - description
3------------------ -
4begin : Sun Jun 30 2002
5copyright : (C) 2002 by Gary E.Sherman
6email : sherman at mrcc.com
7***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include <cmath>
19
20#include <QtGlobal>
21#include <QApplication>
22#include <QCursor>
23#include <QDir>
24#include <QFile>
25#include <QGraphicsItem>
26#include <QGraphicsScene>
27#include <QGraphicsView>
28#include <QKeyEvent>
29#include <QPainter>
30#include <QPaintEvent>
31#include <QPixmap>
32#include <QRect>
33#include <QTextStream>
34#include <QResizeEvent>
35#include <QScreen>
36#include <QString>
37#include <QStringList>
38#include <QWheelEvent>
39#include <QWindow>
40#include <QMenu>
41#include <QClipboard>
42#include <QVariantAnimation>
43#include <QPropertyAnimation>
44
45#include "qgis.h"
46#include "qgssettings.h"
48#include "qgsapplication.h"
49#include "qgsexception.h"
50#include "qgsfeatureiterator.h"
51#include "qgslogger.h"
52#include "qgsmapcanvas.h"
53#include "qgsmapcanvasmap.h"
55#include "qgsmaplayer.h"
56#include "qgsmapmouseevent.h"
57#include "qgsmaptoolpan.h"
58#include "qgsmaptopixel.h"
59#include "qgsmaprenderercache.h"
61#include "qgsmaprendererjob.h"
64#include "qgsmapsettingsutils.h"
65#include "qgsmessagelog.h"
66#include "qgsproject.h"
67#include "qgsrubberband.h"
68#include "qgsvectorlayer.h"
72#include "qgssvgcache.h"
73#include "qgsimagecache.h"
75#include "qgsmimedatautils.h"
81#include "qgsruntimeprofiler.h"
83#include "qgsannotationlayer.h"
86#include "qgslabelingresults.h"
87#include "qgsmaplayerutils.h"
91#include "qgssymbollayerutils.h"
92#include "qgsvectortilelayer.h"
93#include "qgsscreenhelper.h"
94
100//TODO QGIS 4.0 - remove
102{
103 public:
104
108 CanvasProperties() = default;
109
111 bool mouseButtonDown{ false };
112
115
118
120 bool panSelectorDown{ false };
121};
122
123
124
126 : QGraphicsView( parent )
127 , mCanvasProperties( new CanvasProperties )
128 , mExpressionContextScope( tr( "Map Canvas" ) )
129{
130 mScene = new QGraphicsScene();
131 setScene( mScene );
132 setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
133 setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
134 setMouseTracking( true );
135 setFocusPolicy( Qt::StrongFocus );
136
137 mScreenHelper = new QgsScreenHelper( this );
138 connect( mScreenHelper, &QgsScreenHelper::screenDpiChanged, this, &QgsMapCanvas::updateDevicePixelFromScreen );
139
140 mResizeTimer = new QTimer( this );
141 mResizeTimer->setSingleShot( true );
142 connect( mResizeTimer, &QTimer::timeout, this, &QgsMapCanvas::refresh );
143
144 mRefreshTimer = new QTimer( this );
145 mRefreshTimer->setSingleShot( true );
146 connect( mRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::refreshMap );
147
148 // create map canvas item which will show the map
149 mMap = new QgsMapCanvasMap( this );
150
151 // project handling
156
157 connect( QgsProject::instance()->mainAnnotationLayer(), &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
158 connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsMapCanvas::mapThemeChanged );
159 connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsMapCanvas::mapThemeRenamed );
160 connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemesChanged, this, &QgsMapCanvas::projectThemesChanged );
161
162 {
163 QgsScopedRuntimeProfile profile( "Map settings initialization" );
167 mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
169 this, [ = ]
170 {
171 mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
172 refresh();
173 } );
176 this, [ = ]
177 {
178 mSettings.setTransformContext( QgsProject::instance()->transformContext() );
180 refresh();
181 } );
182
184 {
187 if ( mSettings.destinationCrs() != crs )
188 {
189 // user crs has changed definition, refresh the map
190 setDestinationCrs( crs );
191 }
192 } );
193 }
194
195 // refresh canvas when a remote svg/image has finished downloading
198 // refresh canvas when project color scheme is changed -- if layers use project colors, they need to be redrawn
200
201 //segmentation parameters
202 QgsSettings settings;
203 double segmentationTolerance = settings.value( QStringLiteral( "qgis/segmentationTolerance" ), "0.01745" ).toDouble();
204 QgsAbstractGeometry::SegmentationToleranceType toleranceType = settings.enumValue( QStringLiteral( "qgis/segmentationToleranceType" ), QgsAbstractGeometry::MaximumAngle );
205 mSettings.setSegmentationTolerance( segmentationTolerance );
206 mSettings.setSegmentationToleranceType( toleranceType );
207
208 mWheelZoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
209
210 QSize s = viewport()->size();
211 mSettings.setOutputSize( s );
212
214
215 setSceneRect( 0, 0, s.width(), s.height() );
216 mScene->setSceneRect( QRectF( 0, 0, s.width(), s.height() ) );
217
218 moveCanvasContents( true );
219
220 connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsMapCanvas::mapUpdateTimeout );
221 mMapUpdateTimer.setInterval( 250 );
222
223#ifdef Q_OS_WIN
224 // Enable touch event on Windows.
225 // Qt on Windows needs to be told it can take touch events or else it ignores them.
226 grabGesture( Qt::PinchGesture );
227 grabGesture( Qt::TapAndHoldGesture );
228 viewport()->setAttribute( Qt::WA_AcceptTouchEvents );
229#endif
230
231 mPreviewEffect = new QgsPreviewEffect( this );
232 viewport()->setGraphicsEffect( mPreviewEffect );
233
234 mZoomCursor = QgsApplication::getThemeCursor( QgsApplication::Cursor::ZoomIn );
235
236 connect( &mAutoRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::autoRefreshTriggered );
237
239
240 setInteractive( false );
241
242 // make sure we have the same default in QgsMapSettings and the scene's background brush
243 // (by default map settings has white bg color, scene background brush is black)
244 setCanvasColor( mSettings.backgroundColor() );
245
246 setTemporalRange( mSettings.temporalRange() );
247 refresh();
248}
249
250
252{
253 if ( mMapTool )
254 {
255 mMapTool->deactivate();
256 disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
257 mMapTool = nullptr;
258 }
259
260 // we also clear the canvas pointer for all child map tools. We're now in a partially destroyed state and it's
261 // no longer safe for map tools to try to cleanup things in the canvas during their destruction (such as removing
262 // associated canvas items)
263 // NOTE -- it may be better to just delete the map tool children here upfront?
264 const QList< QgsMapTool * > tools = findChildren< QgsMapTool *>();
265 for ( QgsMapTool *tool : tools )
266 {
267 tool->mCanvas = nullptr;
268 }
269
270 mLastNonZoomMapTool = nullptr;
271
272 cancelJobs();
273
274 // delete canvas items prior to deleting the canvas
275 // because they might try to update canvas when it's
276 // already being destructed, ends with segfault
277 qDeleteAll( mScene->items() );
278
279 mScene->deleteLater(); // crashes in python tests on windows
280
281 delete mCache;
282}
283
284
286{
287 // rendering job may still end up writing into canvas map item
288 // so kill it before deleting canvas items
289 if ( mJob )
290 {
291 whileBlocking( mJob )->cancel();
292 delete mJob;
293 mJob = nullptr;
294 }
295
296 for ( auto previewJob = mPreviewJobs.constBegin(); previewJob != mPreviewJobs.constEnd(); ++previewJob )
297 {
298 if ( *previewJob )
299 {
300 whileBlocking( *previewJob )->cancel();
301 delete *previewJob;
302 }
303 }
304 mPreviewJobs.clear();
305}
306
307void QgsMapCanvas::setMagnificationFactor( double factor, const QgsPointXY *center )
308{
309 // do not go higher or lower than min max magnification ratio
310 double magnifierMin = QgsGuiUtils::CANVAS_MAGNIFICATION_MIN;
311 double magnifierMax = QgsGuiUtils::CANVAS_MAGNIFICATION_MAX;
312 factor = std::clamp( factor, magnifierMin, magnifierMax );
313
314 // the magnifier widget is in integer percent
315 if ( !qgsDoubleNear( factor, mSettings.magnificationFactor(), 0.01 ) )
316 {
317 mSettings.setMagnificationFactor( factor, center );
318 refresh();
319 emit magnificationChanged( factor );
320 }
321}
322
324{
325 return mSettings.magnificationFactor();
326}
327
329{
332}
333
335{
337}
338
340{
342}
343
345{
346 QList<QgsMapLayer *> layers = mapSettings().layers();
347 if ( index >= 0 && index < layers.size() )
348 return layers[index];
349 else
350 return nullptr;
351}
352
353QgsMapLayer *QgsMapCanvas::layer( const QString &id )
354{
355 // first check for layers from canvas map settings
356 const QList<QgsMapLayer *> layers = mapSettings().layers();
357 for ( QgsMapLayer *layer : layers )
358 {
359 if ( layer && layer->id() == id )
360 return layer;
361 }
362
363 // else fallback to searching project layers
364 // TODO: allow a specific project to be associated with a canvas!
365 return QgsProject::instance()->mapLayer( id );
366}
367
369{
370 if ( mCurrentLayer == layer )
371 return;
372
373 mCurrentLayer = layer;
375}
376
378{
379 return mapSettings().scale();
380}
381
383{
384 return nullptr != mJob;
385} // isDrawing
386
387// return the current coordinate transform based on the extents and
388// device size
390{
391 return &mapSettings().mapToPixel();
392}
393
394void QgsMapCanvas::setLayers( const QList<QgsMapLayer *> &layers )
395{
396 // following a theme => request denied!
397 if ( !mTheme.isEmpty() )
398 return;
399
400 setLayersPrivate( layers );
401}
402
403void QgsMapCanvas::setLayersPrivate( const QList<QgsMapLayer *> &layers )
404{
405 QList<QgsMapLayer *> oldLayers = mSettings.layers();
406
407 // update only if needed
408 if ( layers == oldLayers )
409 return;
410
411 const auto constOldLayers = oldLayers;
412 for ( QgsMapLayer *layer : constOldLayers )
413 {
414 disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
415 disconnect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
416 switch ( layer->type() )
417 {
419 {
420 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
422 disconnect( vlayer, &QgsVectorLayer::rendererChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
423 break;
424 }
425
427 {
428 QgsVectorTileLayer *vtlayer = qobject_cast<QgsVectorTileLayer *>( layer );
430 break;
431 }
432
439 break;
440 }
441 }
442
443 mSettings.setLayers( layers );
444
445 const auto constLayers = layers;
446 for ( QgsMapLayer *layer : constLayers )
447 {
448 if ( !layer )
449 continue;
450 connect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
451 connect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
452
453 switch ( layer->type() )
454 {
456 {
457 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
459 connect( vlayer, &QgsVectorLayer::rendererChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
460 break;
461 }
462
464 {
465 QgsVectorTileLayer *vtlayer = qobject_cast<QgsVectorTileLayer *>( layer );
467 break;
468 }
469
476 break;
477 }
478 }
479
480 QgsDebugMsgLevel( QStringLiteral( "Layers have changed, refreshing" ), 2 );
481 emit layersChanged();
482
483 updateAutoRefreshTimer();
484 refresh();
485}
486
487
489{
490 return mSettings;
491}
492
494{
495 if ( mSettings.destinationCrs() == crs )
496 return;
497
498 // try to reproject current extent to the new one
499 QgsRectangle rect;
500 if ( !mSettings.visibleExtent().isEmpty() )
501 {
502 const QgsCoordinateTransform transform( mSettings.destinationCrs(), crs, QgsProject::instance(),
505 try
506 {
507 rect = transform.transformBoundingBox( mSettings.visibleExtent() );
508 }
509 catch ( QgsCsException &e )
510 {
511 Q_UNUSED( e )
512 QgsDebugMsg( QStringLiteral( "Transform error caught: %1" ).arg( e.what() ) );
513 }
514 }
515
516 if ( !rect.isEmpty() )
517 {
518 // we will be manually calling updateCanvasItemPositions() later, AFTER setting the updating the mSettings destination CRS, and we don't
519 // want to do that twice!
520 mBlockItemPositionUpdates++;
521 setExtent( rect );
522 mBlockItemPositionUpdates--;
523 }
524
525 mSettings.setDestinationCrs( crs );
526 updateScale();
528
529 QgsDebugMsgLevel( QStringLiteral( "refreshing after destination CRS changed" ), 2 );
530 refresh();
531
533}
534
536{
537 if ( mController )
539 if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast< QgsTemporalNavigationObject * >( mController ) )
540 {
541 disconnect( temporalNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsMapCanvas::temporalControllerModeChanged );
542
543 // clear any existing animation settings from map settings. We don't do this on every render, as a 3rd party plugin
544 // might be in control of these!
545 mSettings.setFrameRate( -1 );
546 mSettings.setCurrentFrame( -1 );
547 }
548
549 mController = controller;
551 if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast< QgsTemporalNavigationObject * >( mController ) )
552 connect( temporalNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsMapCanvas::temporalControllerModeChanged );
553}
554
555void QgsMapCanvas::temporalControllerModeChanged()
556{
557 if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast< QgsTemporalNavigationObject * >( mController ) )
558 {
559 switch ( temporalNavigationObject->navigationMode() )
560 {
562 mSettings.setFrameRate( temporalNavigationObject->framesPerSecond() );
563 mSettings.setCurrentFrame( temporalNavigationObject->currentFrameNumber() );
564 break;
565
568 // clear any existing animation settings from map settings. We don't do this on every render, as a 3rd party plugin
569 // might be in control of these!
570 mSettings.setFrameRate( -1 );
571 mSettings.setCurrentFrame( -1 );
572 break;
573 }
574 }
575}
576
578{
579 return mController;
580}
581
582void QgsMapCanvas::setMapSettingsFlags( Qgis::MapSettingsFlags flags )
583{
584 mSettings.setFlags( flags );
585 clearCache();
586 refresh();
587}
588
589const QgsLabelingResults *QgsMapCanvas::labelingResults( bool allowOutdatedResults ) const
590{
591 if ( !allowOutdatedResults && mLabelingResultsOutdated )
592 return nullptr;
593
594 return mLabelingResults.get();
595}
596
597const QgsRenderedItemResults *QgsMapCanvas::renderedItemResults( bool allowOutdatedResults ) const
598{
599 if ( !allowOutdatedResults && mRenderedItemResultsOutdated )
600 return nullptr;
601
602 return mRenderedItemResults.get();
603}
604
606{
607 if ( enabled == isCachingEnabled() )
608 return;
609
610 if ( mJob && mJob->isActive() )
611 {
612 // wait for the current rendering to finish, before touching the cache
613 mJob->waitForFinished();
614 }
615
616 if ( enabled )
617 {
618 mCache = new QgsMapRendererCache;
619 }
620 else
621 {
622 delete mCache;
623 mCache = nullptr;
624 }
625 mPreviousRenderedItemResults.reset();
626}
627
629{
630 return nullptr != mCache;
631}
632
634{
635 if ( mCache )
636 mCache->clear();
637
638 if ( mPreviousRenderedItemResults )
639 mPreviousRenderedItemResults.reset();
640 if ( mRenderedItemResults )
641 mRenderedItemResults.reset();
642}
643
645{
646 mUseParallelRendering = enabled;
647}
648
650{
651 return mUseParallelRendering;
652}
653
654void QgsMapCanvas::setMapUpdateInterval( int timeMilliseconds )
655{
656 mMapUpdateTimer.setInterval( timeMilliseconds );
657}
658
660{
661 return mMapUpdateTimer.interval();
662}
663
664
666{
667 return mCurrentLayer;
668}
669
671{
672 QgsExpressionContextScope *s = new QgsExpressionContextScope( QObject::tr( "Map Canvas" ) );
673 s->setVariable( QStringLiteral( "canvas_cursor_point" ), QgsGeometry::fromPointXY( cursorPoint() ), true );
674 return s;
675}
676
678{
679 //build the expression context
680 QgsExpressionContext expressionContext;
681 expressionContext << QgsExpressionContextUtils::globalScope()
685 if ( QgsExpressionContextScopeGenerator *generator = dynamic_cast< QgsExpressionContextScopeGenerator * >( mController ) )
686 {
687 expressionContext << generator->createExpressionContextScope();
688 }
689 expressionContext << defaultExpressionContextScope()
690 << new QgsExpressionContextScope( mExpressionContextScope );
691 return expressionContext;
692}
693
695{
696 if ( !mSettings.hasValidSettings() )
697 {
698 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh - invalid settings -> nothing to do" ), 2 );
699 return;
700 }
701
702 if ( !mRenderFlag || mFrozen )
703 {
704 QgsDebugMsgLevel( QStringLiteral( "CANVAS render flag off" ), 2 );
705 return;
706 }
707
708 if ( mRefreshScheduled )
709 {
710 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh already scheduled" ), 2 );
711 return;
712 }
713
714 mRefreshScheduled = true;
715
716 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh scheduling" ), 2 );
717
718 // schedule a refresh
719 mRefreshTimer->start( 1 );
720
721 mLabelingResultsOutdated = true;
722 mRenderedItemResultsOutdated = true;
723}
724
725void QgsMapCanvas::refreshMap()
726{
727 Q_ASSERT( mRefreshScheduled );
728
729 QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh!" ), 3 );
730
731 stopRendering(); // if any...
732 stopPreviewJobs();
733
735
736 // if using the temporal controller in animation mode, get the frame settings from that
737 if ( QgsTemporalNavigationObject *temporalNavigationObject = dynamic_cast < QgsTemporalNavigationObject * >( mController ) )
738 {
739 switch ( temporalNavigationObject->navigationMode() )
740 {
742 mSettings.setFrameRate( temporalNavigationObject->framesPerSecond() );
743 mSettings.setCurrentFrame( temporalNavigationObject->currentFrameNumber() );
744 break;
745
748 break;
749 }
750 }
751
752 mSettings.setPathResolver( QgsProject::instance()->pathResolver() );
753
754 if ( !mTheme.isEmpty() )
755 {
756 // IMPORTANT: we MUST set the layer style overrides here! (At the time of writing this
757 // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
758 // current state of the style. If we had stored the style overrides earlier (such as in
759 // mapThemeChanged slot) then this xml could be out of date...
760 // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
761 // just return the style name, we can instead set the overrides in mapThemeChanged and not here
762 mSettings.setLayerStyleOverrides( QgsProject::instance()->mapThemeCollection()->mapThemeStyleOverrides( mTheme ) );
763 }
764
765 // render main annotation layer above all other layers
766 QgsMapSettings renderSettings = mSettings;
767 QList<QgsMapLayer *> allLayers = renderSettings.layers();
768 allLayers.insert( 0, QgsProject::instance()->mainAnnotationLayer() );
769 renderSettings.setLayers( allLayers );
770
771 // create the renderer job
772 Q_ASSERT( !mJob );
773 mJobCanceled = false;
774 if ( mUseParallelRendering )
775 mJob = new QgsMapRendererParallelJob( renderSettings );
776 else
777 mJob = new QgsMapRendererSequentialJob( renderSettings );
778
779 connect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
780 mJob->setCache( mCache );
781 mJob->setLayerRenderingTimeHints( mLastLayerRenderTime );
782
783 mJob->start();
784
785 // from now on we can accept refresh requests again
786 // this must be reset only after the job has been started, because
787 // some providers (yes, it's you WCS and AMS!) during preparation
788 // do network requests and start an internal event loop, which may
789 // end up calling refresh() and would schedule another refresh,
790 // deleting the one we have just started.
791 mRefreshScheduled = false;
792
793 mMapUpdateTimer.start();
794
795 emit renderStarting();
796}
797
798void QgsMapCanvas::mapThemeChanged( const QString &theme )
799{
800 if ( theme == mTheme )
801 {
802 // set the canvas layers to match the new layers contained in the map theme
803 // NOTE: we do this when the theme layers change and not when we are refreshing the map
804 // as setLayers() sets up necessary connections to handle changes to the layers
805 setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
806 // IMPORTANT: we don't set the layer style overrides here! (At the time of writing this
807 // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
808 // current state of the style. If changes were made to the style then this xml
809 // snapshot goes out of sync...
810 // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
811 // just return the style name, we can instead set the overrides here and not in refreshMap()
812
813 clearCache();
814 refresh();
815 }
816}
817
818void QgsMapCanvas::mapThemeRenamed( const QString &theme, const QString &newTheme )
819{
820 if ( mTheme.isEmpty() || theme != mTheme )
821 {
822 return;
823 }
824
825 setTheme( newTheme );
826 refresh();
827}
828
829void QgsMapCanvas::rendererJobFinished()
830{
831 QgsDebugMsgLevel( QStringLiteral( "CANVAS finish! %1" ).arg( !mJobCanceled ), 2 );
832
833 mMapUpdateTimer.stop();
834
835 notifyRendererErrors( mJob->errors() );
836
837 if ( !mJobCanceled )
838 {
839 // take labeling results before emitting renderComplete, so labeling map tools
840 // connected to signal work with correct results
841 if ( !mJob->usedCachedLabels() )
842 {
843 mLabelingResults.reset( mJob->takeLabelingResults() );
844 }
845 mLabelingResultsOutdated = false;
846
847 std::unique_ptr< QgsRenderedItemResults > renderedItemResults( mJob->takeRenderedItemResults() );
848 // if a layer was redrawn from the cached version, we should copy any existing rendered item results from that layer
849 if ( mRenderedItemResults )
850 {
851 renderedItemResults->transferResults( mRenderedItemResults.get(), mJob->layersRedrawnFromCache() );
852 }
853 if ( mPreviousRenderedItemResults )
854 {
855 // also transfer any results from previous renders which happened before this
856 renderedItemResults->transferResults( mPreviousRenderedItemResults.get(), mJob->layersRedrawnFromCache() );
857 }
858
859 if ( mCache && !mPreviousRenderedItemResults )
860 mPreviousRenderedItemResults = std::make_unique< QgsRenderedItemResults >( mJob->mapSettings().extent() );
861
862 if ( mRenderedItemResults && mPreviousRenderedItemResults )
863 {
864 // for other layers which ARE present in the most recent rendered item results BUT were not part of this render, we
865 // store the results in a temporary store in case they are later switched back on and the layer's image is taken
866 // from the cache
867 mPreviousRenderedItemResults->transferResults( mRenderedItemResults.get() );
868 }
869 if ( mPreviousRenderedItemResults )
870 {
871 mPreviousRenderedItemResults->eraseResultsFromLayers( mJob->mapSettings().layerIds() );
872 }
873
874 mRenderedItemResults = std::move( renderedItemResults );
875 mRenderedItemResultsOutdated = false;
876
877 QImage img = mJob->renderedImage();
878
879 // emit renderComplete to get our decorations drawn
880 QPainter p( &img );
881 emit renderComplete( &p );
882
884 {
885 QString logMsg = tr( "Canvas refresh: %1 ms" ).arg( mJob->renderingTime() );
886 QgsMessageLog::logMessage( logMsg, tr( "Rendering" ) );
887 }
888
889 if ( mDrawRenderingStats )
890 {
891 int w = img.width(), h = img.height();
892 QFont fnt = p.font();
893 fnt.setBold( true );
894 p.setFont( fnt );
895 int lh = p.fontMetrics().height() * 2;
896 QRect r( 0, h - lh, w, lh );
897 p.setPen( Qt::NoPen );
898 p.setBrush( QColor( 0, 0, 0, 110 ) );
899 p.drawRect( r );
900 p.setPen( Qt::white );
901 QString msg = QStringLiteral( "%1 :: %2 ms" ).arg( mUseParallelRendering ? QStringLiteral( "PARALLEL" ) : QStringLiteral( "SEQUENTIAL" ) ).arg( mJob->renderingTime() );
902 p.drawText( r, msg, QTextOption( Qt::AlignCenter ) );
903 }
904
905 p.end();
906
907 mMap->setContent( img, imageRect( img, mSettings ) );
908
909 mLastLayerRenderTime.clear();
910 const auto times = mJob->perLayerRenderingTime();
911 for ( auto it = times.constBegin(); it != times.constEnd(); ++it )
912 {
913 mLastLayerRenderTime.insert( it.key()->id(), it.value() );
914 }
915 if ( mUsePreviewJobs && !mRefreshAfterJob )
916 startPreviewJobs();
917 }
918 else
919 {
920 mRefreshAfterJob = false;
921 }
922
923 // now we are in a slot called from mJob - do not delete it immediately
924 // so the class is still valid when the execution returns to the class
925 mJob->deleteLater();
926 mJob = nullptr;
927
928 emit mapCanvasRefreshed();
929
930 if ( mRefreshAfterJob )
931 {
932 mRefreshAfterJob = false;
933 clearTemporalCache();
934 clearElevationCache();
935 refresh();
936 }
937}
938
939void QgsMapCanvas::previewJobFinished()
940{
941 QgsMapRendererQImageJob *job = qobject_cast<QgsMapRendererQImageJob *>( sender() );
942 Q_ASSERT( job );
943
944 if ( mMap )
945 {
946 mMap->addPreviewImage( job->renderedImage(), job->mapSettings().extent() );
947 mPreviewJobs.removeAll( job );
948
949 int number = job->property( "number" ).toInt();
950 if ( number < 8 )
951 {
952 startPreviewJob( number + 1 );
953 }
954
955 delete job;
956 }
957}
958
959QgsRectangle QgsMapCanvas::imageRect( const QImage &img, const QgsMapSettings &mapSettings )
960{
961 // This is a hack to pass QgsMapCanvasItem::setRect what it
962 // expects (encoding of position and size of the item)
963 const QgsMapToPixel &m2p = mapSettings.mapToPixel();
964 QgsPointXY topLeft = m2p.toMapCoordinates( 0, 0 );
965#ifdef QGISDEBUG
966 // do not assert this, since it might lead to crashes when changing screen while rendering
967 if ( img.devicePixelRatio() != mapSettings.devicePixelRatio() )
968 {
969 QgsLogger::warning( QStringLiteral( "The renderer map has a wrong device pixel ratio" ) );
970 }
971#endif
972 double res = m2p.mapUnitsPerPixel() / img.devicePixelRatioF();
973 QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + img.width()*res, topLeft.y() - img.height()*res );
974 return rect;
975}
976
978{
979 return mUsePreviewJobs;
980}
981
983{
984 mUsePreviewJobs = enabled;
985}
986
987void QgsMapCanvas::setCustomDropHandlers( const QVector<QPointer<QgsCustomDropHandler> > &handlers )
988{
989 mDropHandlers = handlers;
990}
991
992void QgsMapCanvas::clearTemporalCache()
993{
994 if ( mCache )
995 {
996 bool invalidateLabels = false;
997 const QList<QgsMapLayer *> layerList = mapSettings().layers();
998 for ( QgsMapLayer *layer : layerList )
999 {
1000 bool alreadyInvalidatedThisLayer = false;
1001 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
1002 {
1003 if ( vl->renderer() && QgsSymbolLayerUtils::rendererFrameRate( vl->renderer() ) > -1 )
1004 {
1005 // layer has an animated symbol assigned, so we have to redraw it regardless of whether
1006 // or not it has temporal settings
1007 mCache->invalidateCacheForLayer( layer );
1008 alreadyInvalidatedThisLayer = true;
1009 // we can't shortcut and "continue" here, as we still need to check whether the layer
1010 // will cause label invalidation using the logic below
1011 }
1012 }
1013
1015 {
1016 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
1017 {
1018 if ( vl->labelsEnabled() || vl->diagramsEnabled() )
1019 invalidateLabels = true;
1020 }
1021
1023 continue;
1024
1025 if ( !alreadyInvalidatedThisLayer )
1026 mCache->invalidateCacheForLayer( layer );
1027 }
1028 }
1029
1030 if ( invalidateLabels )
1031 {
1032 mCache->clearCacheImage( QStringLiteral( "_labels_" ) );
1033 mCache->clearCacheImage( QStringLiteral( "_preview_labels_" ) );
1034 }
1035 }
1036}
1037
1038void QgsMapCanvas::clearElevationCache()
1039{
1040 if ( mCache )
1041 {
1042 bool invalidateLabels = false;
1043 const QList<QgsMapLayer *> layerList = mapSettings().layers();
1044 for ( QgsMapLayer *layer : layerList )
1045 {
1047 {
1048 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
1049 {
1050 if ( vl->labelsEnabled() || vl->diagramsEnabled() )
1051 invalidateLabels = true;
1052 }
1053
1055 continue;
1056
1057 mCache->invalidateCacheForLayer( layer );
1058 }
1059 }
1060
1061 if ( invalidateLabels )
1062 {
1063 mCache->clearCacheImage( QStringLiteral( "_labels_" ) );
1064 mCache->clearCacheImage( QStringLiteral( "_preview_labels_" ) );
1065 }
1066 }
1067}
1068
1069void QgsMapCanvas::showContextMenu( QgsMapMouseEvent *event )
1070{
1071 const QgsPointXY mapPoint = event->originalMapPoint();
1072
1073 QMenu menu;
1074
1075 QMenu *copyCoordinateMenu = new QMenu( tr( "Copy Coordinate" ), &menu );
1076 copyCoordinateMenu->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ) );
1077
1078 auto addCoordinateFormat = [ &, this]( const QString identifier, const QgsCoordinateReferenceSystem & crs )
1079 {
1080 const QgsCoordinateTransform ct( mSettings.destinationCrs(), crs, mSettings.transformContext() );
1081 try
1082 {
1083 const QgsPointXY transformedPoint = ct.transform( mapPoint );
1084
1085 // calculate precision based on visible map extent -- if user is zoomed in, we get better precision!
1086 int displayPrecision = 0;
1087 try
1088 {
1089 QgsCoordinateTransform extentTransform = ct;
1090 extentTransform.setBallparkTransformsAreAppropriate( true );
1091 QgsRectangle extentReproj = extentTransform.transformBoundingBox( extent() );
1092 const double mapUnitsPerPixel = ( extentReproj.width() / width() + extentReproj.height() / height() ) * 0.5;
1093 if ( mapUnitsPerPixel > 10 )
1094 displayPrecision = 0;
1095 else if ( mapUnitsPerPixel > 1 )
1096 displayPrecision = 1;
1097 else if ( mapUnitsPerPixel > 0.1 )
1098 displayPrecision = 2;
1099 else if ( mapUnitsPerPixel > 0.01 )
1100 displayPrecision = 3;
1101 else if ( mapUnitsPerPixel > 0.001 )
1102 displayPrecision = 4;
1103 else if ( mapUnitsPerPixel > 0.0001 )
1104 displayPrecision = 5;
1105 else if ( mapUnitsPerPixel > 0.00001 )
1106 displayPrecision = 6;
1107 else if ( mapUnitsPerPixel > 0.000001 )
1108 displayPrecision = 7;
1109 else if ( mapUnitsPerPixel > 0.0000001 )
1110 displayPrecision = 8;
1111 else
1112 displayPrecision = 9;
1113 }
1114 catch ( QgsCsException & )
1115 {
1116 displayPrecision = crs.mapUnits() == QgsUnitTypes::DistanceDegrees ? 5 : 3;
1117 }
1118
1119 const QList< Qgis::CrsAxisDirection > axisList = crs.axisOrdering();
1120 QString firstSuffix;
1121 QString secondSuffix;
1122 if ( axisList.size() >= 2 )
1123 {
1126 }
1127
1128 QString firstNumber;
1129 QString secondNumber;
1131 {
1132 firstNumber = QString::number( transformedPoint.y(), 'f', displayPrecision );
1133 secondNumber = QString::number( transformedPoint.x(), 'f', displayPrecision );
1134 }
1135 else
1136 {
1137 firstNumber = QString::number( transformedPoint.x(), 'f', displayPrecision );
1138 secondNumber = QString::number( transformedPoint.y(), 'f', displayPrecision );
1139 }
1140
1141 QAction *copyCoordinateAction = new QAction( QStringLiteral( "%5 (%1%2, %3%4)" ).arg(
1142 firstNumber, firstSuffix, secondNumber, secondSuffix, identifier ), &menu );
1143
1144 connect( copyCoordinateAction, &QAction::triggered, this, [firstNumber, secondNumber, transformedPoint]
1145 {
1146 QClipboard *clipboard = QApplication::clipboard();
1147
1148 const QString coordinates = firstNumber + ',' + secondNumber;
1149
1150 //if we are on x11 system put text into selection ready for middle button pasting
1151 if ( clipboard->supportsSelection() )
1152 {
1153 clipboard->setText( coordinates, QClipboard::Selection );
1154 }
1155 clipboard->setText( coordinates, QClipboard::Clipboard );
1156
1157 } );
1158 copyCoordinateMenu->addAction( copyCoordinateAction );
1159 }
1160 catch ( QgsCsException & )
1161 {
1162
1163 }
1164 };
1165
1166 addCoordinateFormat( tr( "Map CRS — %1" ).arg( mSettings.destinationCrs().userFriendlyIdentifier( QgsCoordinateReferenceSystem::MediumString ) ), mSettings.destinationCrs() );
1167 QgsCoordinateReferenceSystem wgs84( QStringLiteral( "EPSG:4326" ) );
1168 if ( mSettings.destinationCrs() != wgs84 )
1169 addCoordinateFormat( wgs84.userFriendlyIdentifier( QgsCoordinateReferenceSystem::MediumString ), wgs84 );
1170
1171 QgsSettings settings;
1172 const QString customCrsString = settings.value( QStringLiteral( "qgis/custom_coordinate_crs" ) ).toString();
1173 if ( !customCrsString.isEmpty() )
1174 {
1175 QgsCoordinateReferenceSystem customCrs( customCrsString );
1176 if ( customCrs != mSettings.destinationCrs() && customCrs != QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) )
1177 {
1178 addCoordinateFormat( customCrs.userFriendlyIdentifier( QgsCoordinateReferenceSystem::MediumString ), customCrs );
1179 }
1180 }
1181 copyCoordinateMenu->addSeparator();
1182 QAction *setCustomCrsAction = new QAction( tr( "Set Custom CRS…" ), &menu );
1183 connect( setCustomCrsAction, &QAction::triggered, this, [ = ]
1184 {
1185 QgsProjectionSelectionDialog selector( this );
1186 selector.setCrs( QgsCoordinateReferenceSystem( customCrsString ) );
1187 if ( selector.exec() )
1188 {
1189 QgsSettings().setValue( QStringLiteral( "qgis/custom_coordinate_crs" ), selector.crs().authid().isEmpty() ? selector.crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) : selector.crs().authid() );
1190 }
1191 } );
1192 copyCoordinateMenu->addAction( setCustomCrsAction );
1193
1194 menu.addMenu( copyCoordinateMenu );
1195
1196 if ( mMapTool )
1197 if ( !mapTool()->populateContextMenuWithEvent( &menu, event ) )
1198 mMapTool->populateContextMenu( &menu );
1199
1200 emit contextMenuAboutToShow( &menu, event );
1201
1202 menu.exec( event->globalPos() );
1203}
1204
1205void QgsMapCanvas::notifyRendererErrors( const QgsMapRendererJob::Errors &errors )
1206{
1207 const QDateTime currentTime = QDateTime::currentDateTime();
1208
1209 // remove errors too old
1210 for ( const QgsMapRendererJob::Error &error : errors )
1211 {
1212 const QString errorKey = error.layerID + ':' + error.message;
1213 if ( mRendererErrors.contains( errorKey ) )
1214 {
1215 const QDateTime sameErrorTime = mRendererErrors.value( errorKey );
1216
1217 if ( sameErrorTime.secsTo( currentTime ) < 60 )
1218 continue;
1219 }
1220
1221 mRendererErrors[errorKey] = currentTime;
1222
1223 if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( error.layerID ) )
1224 emit renderErrorOccurred( error.message, layer );
1225 }
1226}
1227
1228void QgsMapCanvas::updateDevicePixelFromScreen()
1229{
1230 mSettings.setDevicePixelRatio( devicePixelRatio() );
1231 // TODO: QGIS 4 -> always respect screen dpi
1233 {
1234 if ( window()->windowHandle() )
1235 {
1236 mSettings.setOutputDpi( window()->windowHandle()->screen()->physicalDotsPerInch() );
1237 mSettings.setDpiTarget( window()->windowHandle()->screen()->physicalDotsPerInch() );
1238 }
1239 }
1240 else
1241 {
1242 // Fallback: compatibility with QGIS <= 3.20; always assume low dpi screens
1243 mSettings.setOutputDpi( window()->windowHandle()->screen()->logicalDotsPerInch() );
1244 mSettings.setDpiTarget( window()->windowHandle()->screen()->logicalDotsPerInch() );
1245 }
1246 refresh();
1247}
1248
1249void QgsMapCanvas::setTemporalRange( const QgsDateTimeRange &dateTimeRange )
1250{
1251 if ( temporalRange() == dateTimeRange )
1252 return;
1253
1254 mSettings.setTemporalRange( dateTimeRange );
1255 mSettings.setIsTemporal( dateTimeRange.begin().isValid() || dateTimeRange.end().isValid() );
1256
1257 emit temporalRangeChanged();
1258
1259 // we need to discard any previously cached images which have temporal properties enabled, so that these will be updated when
1260 // the canvas is redrawn
1261 if ( !mJob )
1262 clearTemporalCache();
1263
1264 autoRefreshTriggered();
1265}
1266
1267const QgsDateTimeRange &QgsMapCanvas::temporalRange() const
1268{
1269 return mSettings.temporalRange();
1270}
1271
1273{
1274 mInteractionBlockers.append( blocker );
1275}
1276
1278{
1279 mInteractionBlockers.removeAll( blocker );
1280}
1281
1283{
1284 for ( const QgsMapCanvasInteractionBlocker *block : mInteractionBlockers )
1285 {
1286 if ( block->blockCanvasInteraction( interaction ) )
1287 return false;
1288 }
1289 return true;
1290}
1291
1292void QgsMapCanvas::mapUpdateTimeout()
1293{
1294 if ( mJob )
1295 {
1296 const QImage &img = mJob->renderedImage();
1297 mMap->setContent( img, imageRect( img, mSettings ) );
1298 }
1299}
1300
1302{
1303 if ( mJob )
1304 {
1305 QgsDebugMsgLevel( QStringLiteral( "CANVAS stop rendering!" ), 2 );
1306 mJobCanceled = true;
1307 disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
1308 connect( mJob, &QgsMapRendererQImageJob::finished, mJob, &QgsMapRendererQImageJob::deleteLater );
1309 mJob->cancelWithoutBlocking();
1310 mJob = nullptr;
1311 emit mapRefreshCanceled();
1312 }
1313 stopPreviewJobs();
1314}
1315
1316//the format defaults to "PNG" if not specified
1317void QgsMapCanvas::saveAsImage( const QString &fileName, QPixmap *theQPixmap, const QString &format )
1318{
1319 QPainter painter;
1320 QImage image;
1321
1322 //
1323 //check if the optional QPaintDevice was supplied
1324 //
1325 if ( theQPixmap )
1326 {
1327 image = theQPixmap->toImage();
1328 painter.begin( &image );
1329
1330 // render
1331 QgsMapRendererCustomPainterJob job( mSettings, &painter );
1332 job.start();
1333 job.waitForFinished();
1334 emit renderComplete( &painter );
1335 }
1336 else //use the map view
1337 {
1338 image = mMap->contentImage().copy();
1339 painter.begin( &image );
1340 }
1341
1342 // draw annotations
1343 QStyleOptionGraphicsItem option;
1344 option.initFrom( this );
1345 QGraphicsItem *item = nullptr;
1346 QListIterator<QGraphicsItem *> i( items() );
1347 i.toBack();
1348 while ( i.hasPrevious() )
1349 {
1350 item = i.previous();
1351
1352 if ( !( item && dynamic_cast< QgsMapCanvasAnnotationItem * >( item ) ) )
1353 {
1354 continue;
1355 }
1356
1357 QgsScopedQPainterState painterState( &painter );
1358
1359 QPointF itemScenePos = item->scenePos();
1360 painter.translate( itemScenePos.x(), itemScenePos.y() );
1361
1362 item->paint( &painter, &option );
1363 }
1364
1365 painter.end();
1366 image.save( fileName, format.toLocal8Bit().data() );
1367
1368 QFileInfo myInfo = QFileInfo( fileName );
1369
1370 // build the world file name
1371 QString outputSuffix = myInfo.suffix();
1372 QString myWorldFileName = myInfo.absolutePath() + '/' + myInfo.completeBaseName() + '.'
1373 + outputSuffix.at( 0 ) + outputSuffix.at( myInfo.suffix().size() - 1 ) + 'w';
1374 QFile myWorldFile( myWorldFileName );
1375 if ( !myWorldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) //don't use QIODevice::Text
1376 {
1377 return;
1378 }
1379 QTextStream myStream( &myWorldFile );
1381}
1382
1384{
1385 return mapSettings().visibleExtent();
1386}
1387
1389{
1390 return QgsMapLayerUtils::combinedExtent( mSettings.layers(), mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
1391}
1392
1394{
1396 QgsCoordinateTransform ct( extent.crs(), mapSettings().destinationCrs(), mProject ? mProject->transformContext() : QgsProject::instance()->transformContext() );
1398 QgsRectangle rect;
1399 try
1400 {
1401 rect = ct.transformBoundingBox( extent );
1402 }
1403 catch ( QgsCsException & )
1404 {
1405 rect = mapSettings().fullExtent();
1406 }
1407
1408 return rect;
1409}
1410
1411void QgsMapCanvas::setExtent( const QgsRectangle &r, bool magnified )
1412{
1413 QgsRectangle current = extent();
1414
1415 if ( ( r == current ) && magnified )
1416 return;
1417
1418 if ( r.isEmpty() )
1419 {
1420 if ( !mSettings.hasValidSettings() )
1421 {
1422 // we can't even just move the map center
1423 QgsDebugMsgLevel( QStringLiteral( "Empty extent - ignoring" ), 2 );
1424 return;
1425 }
1426
1427 // ### QGIS 3: do not allow empty extent - require users to call setCenter() explicitly
1428 QgsDebugMsgLevel( QStringLiteral( "Empty extent - keeping old scale with new center!" ), 2 );
1429 setCenter( r.center() );
1430 }
1431 else
1432 {
1433 // If scale is locked we need to maintain the current scale, so we
1434 // - magnify and recenter the map
1435 // - restore locked scale
1436 if ( mScaleLocked && magnified )
1437 {
1438 ScaleRestorer restorer( this );
1439 const double ratio { extent().width() / extent().height() };
1440 const double factor { r.width() / r.height() > ratio ? extent().width() / r.width() : extent().height() / r.height() };
1441 const double scaleFactor { std::clamp( mSettings.magnificationFactor() * factor, QgsGuiUtils::CANVAS_MAGNIFICATION_MIN, QgsGuiUtils::CANVAS_MAGNIFICATION_MAX ) };
1442 const QgsPointXY newCenter { r.center() };
1443 mSettings.setMagnificationFactor( scaleFactor, &newCenter );
1444 emit magnificationChanged( scaleFactor );
1445 }
1446 else
1447 {
1448 mSettings.setExtent( r, magnified );
1449 }
1450 }
1451 emit extentsChanged();
1452 updateScale();
1453
1454 //clear all extent items after current index
1455 for ( int i = mLastExtent.size() - 1; i > mLastExtentIndex; i-- )
1456 {
1457 mLastExtent.removeAt( i );
1458 }
1459
1460 if ( !mLastExtent.isEmpty() && mLastExtent.last() != mSettings.extent() )
1461 {
1462 mLastExtent.append( mSettings.extent() );
1463 }
1464
1465 // adjust history to no more than 100
1466 if ( mLastExtent.size() > 100 )
1467 {
1468 mLastExtent.removeAt( 0 );
1469 }
1470
1471 // the last item is the current extent
1472 mLastExtentIndex = mLastExtent.size() - 1;
1473
1474 // update controls' enabled state
1475 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1476 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1477}
1478
1480{
1481 QgsRectangle canvasExtent = extent;
1482 if ( extent.crs() != mapSettings().destinationCrs() )
1483 {
1484 QgsCoordinateTransform ct( extent.crs(), mapSettings().destinationCrs(), QgsProject::instance() );
1486 canvasExtent = ct.transformBoundingBox( extent );
1487
1488 if ( canvasExtent.isEmpty() )
1489 {
1490 return false;
1491 }
1492 }
1493
1494 setExtent( canvasExtent, true );
1495 return true;
1496}
1497
1499{
1500 const QgsRectangle r = mapSettings().extent();
1501 const double xMin = center.x() - r.width() / 2.0;
1502 const double yMin = center.y() - r.height() / 2.0;
1503 const QgsRectangle rect(
1504 xMin, yMin,
1505 xMin + r.width(), yMin + r.height()
1506 );
1507 if ( ! rect.isEmpty() )
1508 {
1509 setExtent( rect, true );
1510 }
1511} // setCenter
1512
1514{
1516 return r.center();
1517}
1518
1519QgsPointXY QgsMapCanvas::cursorPoint() const
1520{
1521 return mCursorPoint;
1522}
1523
1525{
1526 return mapSettings().rotation();
1527} // rotation
1528
1529void QgsMapCanvas::setRotation( double degrees )
1530{
1531 double current = rotation();
1532
1533 if ( qgsDoubleNear( degrees, current ) )
1534 return;
1535
1536 mSettings.setRotation( degrees );
1537 emit rotationChanged( degrees );
1538 emit extentsChanged(); // visible extent changes with rotation
1539} // setRotation
1540
1541
1543{
1544 emit scaleChanged( mapSettings().scale() );
1545}
1546
1548{
1550 // If the full extent is an empty set, don't do the zoom
1551 if ( !extent.isEmpty() )
1552 {
1553 // Add a 5% margin around the full extent
1554 extent.scale( 1.05 );
1555 setExtent( extent );
1556 }
1557 refresh();
1558}
1559
1561{
1563
1564 // If the full extent is an empty set, don't do the zoom
1565 if ( !extent.isEmpty() )
1566 {
1567 // Add a 5% margin around the full extent
1568 extent.scale( 1.05 );
1569 setExtent( extent );
1570 }
1571 refresh();
1572}
1573
1575{
1576 if ( mLastExtentIndex > 0 )
1577 {
1578 mLastExtentIndex--;
1579 mSettings.setExtent( mLastExtent[mLastExtentIndex] );
1580 emit extentsChanged();
1581 updateScale();
1582 refresh();
1583 // update controls' enabled state
1584 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1585 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1586 }
1587
1588} // zoomToPreviousExtent
1589
1591{
1592 if ( mLastExtentIndex < mLastExtent.size() - 1 )
1593 {
1594 mLastExtentIndex++;
1595 mSettings.setExtent( mLastExtent[mLastExtentIndex] );
1596 emit extentsChanged();
1597 updateScale();
1598 refresh();
1599 // update controls' enabled state
1600 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1601 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1602 }
1603}// zoomToNextExtent
1604
1606{
1607 mLastExtent.clear(); // clear the zoom history list
1608 mLastExtent.append( mSettings.extent() ) ; // set the current extent in the list
1609 mLastExtentIndex = mLastExtent.size() - 1;
1610 // update controls' enabled state
1611 emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1612 emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1613}// clearExtentHistory
1614
1615QgsRectangle QgsMapCanvas::optimalExtentForPointLayer( QgsVectorLayer *layer, const QgsPointXY &center, int scaleFactor )
1616{
1617 QgsRectangle rect( center, center );
1618
1619 if ( layer->geometryType() == QgsWkbTypes::PointGeometry )
1620 {
1621 QgsPointXY centerLayerCoordinates = mSettings.mapToLayerCoordinates( layer, center );
1622 QgsRectangle extentRect = mSettings.mapToLayerCoordinates( layer, extent() ).scaled( 1.0 / scaleFactor, &centerLayerCoordinates );
1624 QgsFeatureIterator fit = layer->getFeatures( req );
1625 QgsFeature f;
1626 QgsPointXY closestPoint;
1627 double closestSquaredDistance = pow( extentRect.width(), 2.0 ) + pow( extentRect.height(), 2.0 );
1628 bool pointFound = false;
1629 while ( fit.nextFeature( f ) )
1630 {
1631 QgsPointXY point = f.geometry().asPoint();
1632 double sqrDist = point.sqrDist( centerLayerCoordinates );
1633 if ( sqrDist > closestSquaredDistance || sqrDist < 4 * std::numeric_limits<double>::epsilon() )
1634 continue;
1635 pointFound = true;
1636 closestPoint = point;
1637 closestSquaredDistance = sqrDist;
1638 }
1639 if ( pointFound )
1640 {
1641 // combine selected point with closest point and scale this rect
1642 rect.combineExtentWith( mSettings.layerToMapCoordinates( layer, closestPoint ) );
1643 rect.scale( scaleFactor, &center );
1644 }
1645 }
1646 return rect;
1647}
1648
1650{
1651 QgsTemporaryCursorOverride cursorOverride( Qt::WaitCursor );
1652
1653 if ( !layer )
1654 {
1655 // use current layer by default
1656 layer = mCurrentLayer;
1657 }
1658
1659 if ( !layer || !layer->isSpatial() )
1660 return;
1661
1662 QgsRectangle rect;
1663
1664 switch ( layer->type() )
1665 {
1667 {
1668 QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer );
1669 if ( vlayer->selectedFeatureCount() == 0 )
1670 return;
1671
1672 rect = vlayer->boundingBoxOfSelected();
1673 if ( rect.isNull() )
1674 {
1675 cursorOverride.release();
1676 emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1677 return;
1678 }
1679
1681
1682 // zoom in if point cannot be distinguished from others
1683 // also check that rect is empty, as it might not in case of multi points
1684 if ( vlayer->geometryType() == QgsWkbTypes::PointGeometry && rect.isEmpty() )
1685 {
1686 rect = optimalExtentForPointLayer( vlayer, rect.center() );
1687 }
1688 break;
1689 }
1690
1692 {
1693 QgsVectorTileLayer *vtLayer = qobject_cast< QgsVectorTileLayer * >( layer );
1694 if ( vtLayer->selectedFeatureCount() == 0 )
1695 return;
1696
1697 const QList< QgsFeature > selectedFeatures = vtLayer->selectedFeatures();
1698 for ( const QgsFeature &feature : selectedFeatures )
1699 {
1700 if ( !feature.hasGeometry() )
1701 continue;
1702
1703 rect.combineExtentWith( feature.geometry().boundingBox() );
1704 }
1705
1706 if ( rect.isNull() )
1707 {
1708 cursorOverride.release();
1709 emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1710 return;
1711 }
1712
1714 break;
1715 }
1716
1723 return; // not supported
1724 }
1725
1726 zoomToFeatureExtent( rect );
1727}
1728
1729void QgsMapCanvas::zoomToSelected( const QList<QgsMapLayer *> &layers )
1730{
1731 QgsRectangle rect;
1732 rect.setMinimal();
1733 QgsRectangle selectionExtent;
1734 selectionExtent.setMinimal();
1735
1736 for ( QgsMapLayer *mapLayer : layers )
1737 {
1738 if ( !mapLayer || !mapLayer->isSpatial() )
1739 continue;
1740
1741 switch ( mapLayer->type() )
1742 {
1744 {
1745 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
1746
1747 if ( layer->selectedFeatureCount() == 0 )
1748 continue;
1749
1750 rect = layer->boundingBoxOfSelected();
1751
1752 if ( rect.isNull() )
1753 continue;
1754
1756
1757 if ( layer->geometryType() == QgsWkbTypes::PointGeometry && rect.isEmpty() )
1758 rect = optimalExtentForPointLayer( layer, rect.center() );
1759
1760 selectionExtent.combineExtentWith( rect );
1761 break;
1762 }
1763
1765 {
1766 QgsVectorTileLayer *vtLayer = qobject_cast< QgsVectorTileLayer * >( mapLayer );
1767 if ( vtLayer->selectedFeatureCount() == 0 )
1768 continue;
1769
1770 const QList< QgsFeature > selectedFeatures = vtLayer->selectedFeatures();
1771 QgsRectangle rect;
1772 for ( const QgsFeature &feature : selectedFeatures )
1773 {
1774 if ( !feature.hasGeometry() )
1775 continue;
1776
1777 rect.combineExtentWith( feature.geometry().boundingBox() );
1778 }
1779
1780 rect = mapSettings().layerExtentToOutputExtent( vtLayer, rect );
1781 selectionExtent.combineExtentWith( rect );
1782 break;
1783 }
1784
1791 break;
1792 }
1793 }
1794
1795 if ( selectionExtent.isNull() )
1796 {
1797 emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1798 return;
1799 }
1800
1801 zoomToFeatureExtent( selectionExtent );
1802}
1803
1805{
1806 return mSettings.zRange();
1807}
1808
1810{
1811 if ( zRange() == range )
1812 return;
1813
1814 mSettings.setZRange( range );
1815
1816 emit zRangeChanged();
1817
1818 // we need to discard any previously cached images which are elevation aware, so that these will be updated when
1819 // the canvas is redrawn
1820 if ( !mJob )
1821 clearElevationCache();
1822
1823 autoRefreshTriggered();
1824}
1825
1827{
1828 // no selected features, only one selected point feature
1829 //or two point features with the same x- or y-coordinates
1830 if ( rect.isEmpty() )
1831 {
1832 // zoom in
1833 QgsPointXY c = rect.center();
1834 rect = extent();
1835 rect.scale( 1.0, &c );
1836 }
1837 //zoom to an area
1838 else
1839 {
1840 // Expand rect to give a bit of space around the selected
1841 // objects so as to keep them clear of the map boundaries
1842 // The same 5% should apply to all margins.
1843 rect.scale( 1.05 );
1844 }
1845
1846 setExtent( rect );
1847 refresh();
1848}
1849
1851{
1852 if ( !layer )
1853 {
1854 return;
1855 }
1856
1857 QgsRectangle bbox;
1858 QString errorMsg;
1859 if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
1860 {
1861 if ( bbox.isEmpty() )
1862 {
1863 bbox = optimalExtentForPointLayer( layer, bbox.center() );
1864 }
1865 zoomToFeatureExtent( bbox );
1866 }
1867 else
1868 {
1869 emit messageEmitted( tr( "Zoom to feature id failed" ), errorMsg, Qgis::MessageLevel::Warning );
1870 }
1871
1872}
1873
1874void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter )
1875{
1876 if ( !layer )
1877 {
1878 return;
1879 }
1880
1881 QgsRectangle bbox;
1882 QString errorMsg;
1883 if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
1884 {
1885 if ( alwaysRecenter || !mapSettings().extent().contains( bbox ) )
1886 setCenter( bbox.center() );
1887 refresh();
1888 }
1889 else
1890 {
1891 emit messageEmitted( tr( "Pan to feature id failed" ), errorMsg, Qgis::MessageLevel::Warning );
1892 }
1893}
1894
1895bool QgsMapCanvas::boundingBoxOfFeatureIds( const QgsFeatureIds &ids, QgsVectorLayer *layer, QgsRectangle &bbox, QString &errorMsg ) const
1896{
1897 QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
1898 bbox.setMinimal();
1899 QgsFeature fet;
1900 int featureCount = 0;
1901 errorMsg.clear();
1902
1903 while ( it.nextFeature( fet ) )
1904 {
1905 QgsGeometry geom = fet.geometry();
1906 if ( geom.isNull() )
1907 {
1908 errorMsg = tr( "Feature does not have a geometry" );
1909 }
1910 else if ( geom.constGet()->isEmpty() )
1911 {
1912 errorMsg = tr( "Feature geometry is empty" );
1913 }
1914 if ( !errorMsg.isEmpty() )
1915 {
1916 return false;
1917 }
1919 bbox.combineExtentWith( r );
1920 featureCount++;
1921 }
1922
1923 if ( featureCount != ids.count() )
1924 {
1925 errorMsg = tr( "Feature not found" );
1926 return false;
1927 }
1928
1929 return true;
1930}
1931
1933{
1934 if ( !layer )
1935 {
1936 // use current layer by default
1937 layer = mCurrentLayer;
1938 }
1939 if ( !layer || !layer->isSpatial() )
1940 return;
1941
1942 QgsRectangle rect;
1943 switch ( layer->type() )
1944 {
1946 {
1947 QgsVectorLayer *vLayer = qobject_cast< QgsVectorLayer * >( layer );
1948 if ( vLayer->selectedFeatureCount() == 0 )
1949 return;
1950
1951 rect = vLayer->boundingBoxOfSelected();
1952 break;
1953 }
1955 {
1956 QgsVectorTileLayer *vtLayer = qobject_cast< QgsVectorTileLayer * >( layer );
1957 if ( vtLayer->selectedFeatureCount() == 0 )
1958 return;
1959
1960 const QList< QgsFeature > selectedFeatures = vtLayer->selectedFeatures();
1961 for ( const QgsFeature &feature : selectedFeatures )
1962 {
1963 if ( !feature.hasGeometry() )
1964 continue;
1965
1966 rect.combineExtentWith( feature.geometry().boundingBox() );
1967 }
1968 break;
1969 }
1970
1977 return;
1978 }
1979
1980 if ( rect.isNull() )
1981 {
1982 emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1983 return;
1984 }
1985
1987 setCenter( rect.center() );
1988 refresh();
1989}
1990
1991void QgsMapCanvas::panToSelected( const QList<QgsMapLayer *> &layers )
1992{
1993 QgsRectangle selectionExtent;
1994 selectionExtent.setMinimal();
1995
1996 for ( QgsMapLayer *mapLayer : layers )
1997 {
1998 if ( !mapLayer || !mapLayer->isSpatial() )
1999 continue;
2000
2001 QgsRectangle rect;
2002 rect.setMinimal();
2003 switch ( mapLayer->type() )
2004 {
2006 {
2007 QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
2008 if ( layer->selectedFeatureCount() == 0 )
2009 continue;
2010
2011 rect = layer->boundingBoxOfSelected();
2012
2013 if ( rect.isNull() )
2014 continue;
2015
2017
2018 if ( layer->geometryType() == QgsWkbTypes::PointGeometry && rect.isEmpty() )
2019 rect = optimalExtentForPointLayer( layer, rect.center() );
2020 break;
2021 }
2022
2024 {
2025 QgsVectorTileLayer *vtLayer = qobject_cast< QgsVectorTileLayer * >( mapLayer );
2026 if ( vtLayer->selectedFeatureCount() == 0 )
2027 continue;
2028
2029 const QList< QgsFeature > selectedFeatures = vtLayer->selectedFeatures();
2030 for ( const QgsFeature &feature : selectedFeatures )
2031 {
2032 if ( !feature.hasGeometry() )
2033 continue;
2034
2035 rect.combineExtentWith( feature.geometry().boundingBox() );
2036 }
2037
2038 rect = mapSettings().layerExtentToOutputExtent( vtLayer, rect );
2039 break;
2040 }
2041
2048 continue;
2049 }
2050
2051 selectionExtent.combineExtentWith( rect );
2052 }
2053
2054 if ( selectionExtent.isNull() )
2055 {
2056 emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
2057 return;
2058 }
2059
2060 setCenter( selectionExtent.center() );
2061 refresh();
2062}
2063
2065 const QColor &color1, const QColor &color2,
2066 int flashes, int duration )
2067{
2068 if ( !layer )
2069 {
2070 return;
2071 }
2072
2073 QList< QgsGeometry > geoms;
2074
2075 QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
2076 QgsFeature fet;
2077 while ( it.nextFeature( fet ) )
2078 {
2079 if ( !fet.hasGeometry() )
2080 continue;
2081 geoms << fet.geometry();
2082 }
2083
2084 flashGeometries( geoms, layer->crs(), color1, color2, flashes, duration );
2085}
2086
2087void QgsMapCanvas::flashGeometries( const QList<QgsGeometry> &geometries, const QgsCoordinateReferenceSystem &crs, const QColor &color1, const QColor &color2, int flashes, int duration )
2088{
2089 if ( geometries.isEmpty() )
2090 return;
2091
2092 QgsWkbTypes::GeometryType geomType = QgsWkbTypes::geometryType( geometries.at( 0 ).wkbType() );
2093 QgsRubberBand *rb = new QgsRubberBand( this, geomType );
2094 for ( const QgsGeometry &geom : geometries )
2095 rb->addGeometry( geom, crs, false );
2096 rb->updatePosition();
2097 rb->update();
2098
2099 if ( geomType == QgsWkbTypes::LineGeometry || geomType == QgsWkbTypes::PointGeometry )
2100 {
2101 rb->setWidth( 2 );
2102 rb->setSecondaryStrokeColor( QColor( 255, 255, 255 ) );
2103 }
2104 if ( geomType == QgsWkbTypes::PointGeometry )
2106
2107 QColor startColor = color1;
2108 if ( !startColor.isValid() )
2109 {
2110 if ( geomType == QgsWkbTypes::PolygonGeometry )
2111 {
2112 startColor = rb->fillColor();
2113 }
2114 else
2115 {
2116 startColor = rb->strokeColor();
2117 }
2118 startColor.setAlpha( 255 );
2119 }
2120 QColor endColor = color2;
2121 if ( !endColor.isValid() )
2122 {
2123 endColor = startColor;
2124 endColor.setAlpha( 0 );
2125 }
2126
2127
2128 QVariantAnimation *animation = new QVariantAnimation( this );
2129 connect( animation, &QVariantAnimation::finished, this, [animation, rb]
2130 {
2131 animation->deleteLater();
2132 delete rb;
2133 } );
2134 connect( animation, &QPropertyAnimation::valueChanged, this, [rb, geomType]( const QVariant & value )
2135 {
2136 QColor c = value.value<QColor>();
2137 if ( geomType == QgsWkbTypes::PolygonGeometry )
2138 {
2139 rb->setFillColor( c );
2140 }
2141 else
2142 {
2143 rb->setStrokeColor( c );
2144 QColor c = rb->secondaryStrokeColor();
2145 c.setAlpha( c.alpha() );
2147 }
2148 rb->update();
2149 } );
2150
2151 animation->setDuration( duration * flashes );
2152 animation->setStartValue( endColor );
2153 double midStep = 0.2 / flashes;
2154 for ( int i = 0; i < flashes; ++i )
2155 {
2156 double start = static_cast< double >( i ) / flashes;
2157 animation->setKeyValueAt( start + midStep, startColor );
2158 double end = static_cast< double >( i + 1 ) / flashes;
2159 if ( !qgsDoubleNear( end, 1.0 ) )
2160 animation->setKeyValueAt( end, endColor );
2161 }
2162 animation->setEndValue( endColor );
2163 animation->start();
2164}
2165
2167{
2168 if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )
2169 {
2170 emit keyPressed( e );
2171 return;
2172 }
2173
2174 // Don't want to interfer with mouse events
2175 if ( ! mCanvasProperties->mouseButtonDown )
2176 {
2177 // this is backwards, but we can't change now without breaking api because
2178 // forever QgsMapTools have had to explicitly mark events as ignored in order to
2179 // indicate that they've consumed the event and that the default behavior should not
2180 // be applied..!
2181 e->accept();
2182 if ( mMapTool )
2183 {
2184 mMapTool->keyPressEvent( e );
2185 if ( !e->isAccepted() ) // map tool consumed event
2186 return;
2187 }
2188
2189 QgsRectangle currentExtent = mapSettings().visibleExtent();
2190 double dx = std::fabs( currentExtent.width() / 4 );
2191 double dy = std::fabs( currentExtent.height() / 4 );
2192
2193 switch ( e->key() )
2194 {
2195 case Qt::Key_Left:
2196 QgsDebugMsgLevel( QStringLiteral( "Pan left" ), 2 );
2197 setCenter( center() - QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
2198 refresh();
2199 break;
2200
2201 case Qt::Key_Right:
2202 QgsDebugMsgLevel( QStringLiteral( "Pan right" ), 2 );
2203 setCenter( center() + QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
2204 refresh();
2205 break;
2206
2207 case Qt::Key_Up:
2208 QgsDebugMsgLevel( QStringLiteral( "Pan up" ), 2 );
2209 setCenter( center() + QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
2210 refresh();
2211 break;
2212
2213 case Qt::Key_Down:
2214 QgsDebugMsgLevel( QStringLiteral( "Pan down" ), 2 );
2215 setCenter( center() - QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
2216 refresh();
2217 break;
2218
2219 case Qt::Key_Space:
2220 QgsDebugMsgLevel( QStringLiteral( "Pressing pan selector" ), 2 );
2221
2222 //mCanvasProperties->dragging = true;
2223 if ( ! e->isAutoRepeat() )
2224 {
2225 mTemporaryCursorOverride.reset( new QgsTemporaryCursorOverride( Qt::ClosedHandCursor ) );
2226 mCanvasProperties->panSelectorDown = true;
2227 panActionStart( mCanvasProperties->mouseLastXY );
2228 }
2229 break;
2230
2231 case Qt::Key_PageUp:
2232 QgsDebugMsgLevel( QStringLiteral( "Zoom in" ), 2 );
2233 zoomIn();
2234 break;
2235
2236 case Qt::Key_PageDown:
2237 QgsDebugMsgLevel( QStringLiteral( "Zoom out" ), 2 );
2238 zoomOut();
2239 break;
2240
2241#if 0
2242 case Qt::Key_P:
2243 mUseParallelRendering = !mUseParallelRendering;
2244 refresh();
2245 break;
2246
2247 case Qt::Key_S:
2248 mDrawRenderingStats = !mDrawRenderingStats;
2249 refresh();
2250 break;
2251#endif
2252
2253 default:
2254 // Pass it on
2255 if ( !mMapTool )
2256 {
2257 e->ignore();
2258 QgsDebugMsgLevel( "Ignoring key: " + QString::number( e->key() ), 2 );
2259 }
2260 }
2261 }
2262
2263 emit keyPressed( e );
2264}
2265
2267{
2268 QgsDebugMsgLevel( QStringLiteral( "keyRelease event" ), 2 );
2269
2270 switch ( e->key() )
2271 {
2272 case Qt::Key_Space:
2273 if ( !e->isAutoRepeat() && mCanvasProperties->panSelectorDown )
2274 {
2275 QgsDebugMsgLevel( QStringLiteral( "Releasing pan selector" ), 2 );
2276 mTemporaryCursorOverride.reset();
2277 mCanvasProperties->panSelectorDown = false;
2278 panActionEnd( mCanvasProperties->mouseLastXY );
2279 }
2280 break;
2281
2282 default:
2283 // Pass it on
2284 if ( mMapTool )
2285 {
2286 mMapTool->keyReleaseEvent( e );
2287 }
2288 else e->ignore();
2289
2290 QgsDebugMsgLevel( "Ignoring key release: " + QString::number( e->key() ), 2 );
2291 }
2292
2293 emit keyReleased( e );
2294
2295} //keyReleaseEvent()
2296
2297
2299{
2300 // call handler of current map tool
2301 if ( mMapTool )
2302 {
2303 std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2304 mMapTool->canvasDoubleClickEvent( me.get() );
2305 }
2306}// mouseDoubleClickEvent
2307
2308
2309void QgsMapCanvas::beginZoomRect( QPoint pos )
2310{
2311 mZoomRect.setRect( 0, 0, 0, 0 );
2312 mTemporaryCursorOverride.reset( new QgsTemporaryCursorOverride( mZoomCursor ) );
2313 mZoomDragging = true;
2314 mZoomRubberBand.reset( new QgsRubberBand( this, QgsWkbTypes::PolygonGeometry ) );
2315 QColor color( Qt::blue );
2316 color.setAlpha( 63 );
2317 mZoomRubberBand->setColor( color );
2318 mZoomRect.setTopLeft( pos );
2319}
2320
2321void QgsMapCanvas::stopZoomRect()
2322{
2323 if ( mZoomDragging )
2324 {
2325 mZoomDragging = false;
2326 mZoomRubberBand.reset( nullptr );
2327 mTemporaryCursorOverride.reset();
2328 }
2329}
2330
2331void QgsMapCanvas::endZoomRect( QPoint pos )
2332{
2333 stopZoomRect();
2334
2335 // store the rectangle
2336 mZoomRect.setRight( pos.x() );
2337 mZoomRect.setBottom( pos.y() );
2338
2339 //account for bottom right -> top left dragging
2340 mZoomRect = mZoomRect.normalized();
2341
2342 if ( mZoomRect.width() < 5 && mZoomRect.height() < 5 )
2343 {
2344 //probably a mistake - would result in huge zoom!
2345 return;
2346 }
2347
2348 // set center and zoom
2349 const QSize &zoomRectSize = mZoomRect.size();
2350 const QSize &canvasSize = mSettings.outputSize();
2351 double sfx = static_cast< double >( zoomRectSize.width() ) / canvasSize.width();
2352 double sfy = static_cast< double >( zoomRectSize.height() ) / canvasSize.height();
2353 double sf = std::max( sfx, sfy );
2354
2355 QgsPointXY c = mSettings.mapToPixel().toMapCoordinates( mZoomRect.center() );
2356
2357 zoomByFactor( sf, &c );
2358 refresh();
2359}
2360
2361void QgsMapCanvas::startPan()
2362{
2363 if ( !mCanvasProperties->panSelectorDown )
2364 {
2365 mCanvasProperties->panSelectorDown = true;
2366 mTemporaryCursorOverride.reset( new QgsTemporaryCursorOverride( Qt::ClosedHandCursor ) );
2367 panActionStart( mCanvasProperties->mouseLastXY );
2368 }
2369}
2370
2371void QgsMapCanvas::stopPan()
2372{
2373 if ( mCanvasProperties->panSelectorDown )
2374 {
2375 mCanvasProperties->panSelectorDown = false;
2376 mTemporaryCursorOverride.reset();
2377 panActionEnd( mCanvasProperties->mouseLastXY );
2378 }
2379}
2380
2381void QgsMapCanvas::mousePressEvent( QMouseEvent *e )
2382{
2383 // use shift+middle mouse button for zooming, map tools won't receive any events in that case
2384 if ( e->button() == Qt::MiddleButton &&
2385 e->modifiers() & Qt::ShiftModifier )
2386 {
2387 beginZoomRect( e->pos() );
2388 return;
2389 }
2390 //use middle mouse button for panning, map tools won't receive any events in that case
2391 else if ( e->button() == Qt::MiddleButton )
2392 {
2393 startPan();
2394 }
2395 else
2396 {
2397 // If doing a middle-button-click, followed by a right-button-click,
2398 // cancel the pan or zoomRect action started above.
2399 stopPan();
2400 stopZoomRect();
2401
2402 // call handler of current map tool
2403 if ( mMapTool )
2404 {
2405 if ( mMapTool->flags() & QgsMapTool::AllowZoomRect && e->button() == Qt::LeftButton
2406 && e->modifiers() & Qt::ShiftModifier )
2407 {
2408 beginZoomRect( e->pos() );
2409 return;
2410 }
2411 else if ( mMapTool->flags() & QgsMapTool::ShowContextMenu && e->button() == Qt::RightButton )
2412 {
2413 std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2414 showContextMenu( me.get() );
2415 return;
2416 }
2417 else
2418 {
2419 std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2420 mMapTool->canvasPressEvent( me.get() );
2421 }
2422 }
2423 }
2424
2425 if ( mCanvasProperties->panSelectorDown )
2426 {
2427 return;
2428 }
2429
2430 mCanvasProperties->mouseButtonDown = true;
2431 mCanvasProperties->rubberStartPoint = e->pos();
2432}
2433
2435{
2436 // if using shift+middle mouse button for zooming, end zooming and return
2437 if ( mZoomDragging &&
2438 e->button() == Qt::MiddleButton )
2439 {
2440 endZoomRect( e->pos() );
2441 return;
2442 }
2443 //use middle mouse button for panning, map tools won't receive any events in that case
2444 else if ( e->button() == Qt::MiddleButton )
2445 {
2446 stopPan();
2447 }
2448 else if ( e->button() == Qt::BackButton )
2449 {
2451 return;
2452 }
2453 else if ( e->button() == Qt::ForwardButton )
2454 {
2456 return;
2457 }
2458 else
2459 {
2460 if ( mZoomDragging && e->button() == Qt::LeftButton )
2461 {
2462 endZoomRect( e->pos() );
2463 return;
2464 }
2465
2466 // call handler of current map tool
2467 if ( mMapTool )
2468 {
2469 // right button was pressed in zoom tool? return to previous non zoom tool
2470 if ( e->button() == Qt::RightButton && mMapTool->flags() & QgsMapTool::Transient )
2471 {
2472 QgsDebugMsgLevel( QStringLiteral( "Right click in map tool zoom or pan, last tool is %1." ).arg(
2473 mLastNonZoomMapTool ? QStringLiteral( "not null" ) : QStringLiteral( "null" ) ), 2 );
2474
2475 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
2476
2477 // change to older non-zoom tool
2478 if ( mLastNonZoomMapTool
2479 && ( !( mLastNonZoomMapTool->flags() & QgsMapTool::EditTool )
2480 || ( vlayer && vlayer->isEditable() ) ) )
2481 {
2482 QgsMapTool *t = mLastNonZoomMapTool;
2483 mLastNonZoomMapTool = nullptr;
2484 setMapTool( t );
2485 }
2486 return;
2487 }
2488 std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2489 mMapTool->canvasReleaseEvent( me.get() );
2490 }
2491 }
2492
2493
2494 mCanvasProperties->mouseButtonDown = false;
2495
2496 if ( mCanvasProperties->panSelectorDown )
2497 return;
2498
2499}
2500
2501void QgsMapCanvas::resizeEvent( QResizeEvent *e )
2502{
2503 QGraphicsView::resizeEvent( e );
2504 mResizeTimer->start( 500 ); // in charge of refreshing canvas
2505
2506 double oldScale = mSettings.scale();
2507 QSize lastSize = viewport()->size();
2508 mSettings.setOutputSize( lastSize );
2509
2510 mScene->setSceneRect( QRectF( 0, 0, lastSize.width(), lastSize.height() ) );
2511
2512 moveCanvasContents( true );
2513
2514 if ( mScaleLocked )
2515 {
2516 double scaleFactor = oldScale / mSettings.scale();
2517 QgsRectangle r = mSettings.extent();
2518 QgsPointXY center = r.center();
2519 r.scale( scaleFactor, &center );
2520 mSettings.setExtent( r );
2521 }
2522 else
2523 {
2524 updateScale();
2525 }
2526
2527 emit extentsChanged();
2528}
2529
2530void QgsMapCanvas::paintEvent( QPaintEvent *e )
2531{
2532 // no custom event handling anymore
2533
2534 QGraphicsView::paintEvent( e );
2535} // paintEvent
2536
2538{
2539 if ( mBlockItemPositionUpdates )
2540 return;
2541
2542 const QList<QGraphicsItem *> items = mScene->items();
2543 for ( QGraphicsItem *gi : items )
2544 {
2545 QgsMapCanvasItem *item = dynamic_cast<QgsMapCanvasItem *>( gi );
2546
2547 if ( item )
2548 {
2549 item->updatePosition();
2550 }
2551 }
2552}
2553
2554
2555void QgsMapCanvas::wheelEvent( QWheelEvent *e )
2556{
2557 // Zoom the map canvas in response to a mouse wheel event. Moving the
2558 // wheel forward (away) from the user zooms in
2559
2560 QgsDebugMsgLevel( "Wheel event delta " + QString::number( e->angleDelta().y() ), 2 );
2561
2562 if ( mMapTool )
2563 {
2564 mMapTool->wheelEvent( e );
2565 if ( e->isAccepted() )
2566 return;
2567 }
2568
2569 if ( e->angleDelta().y() == 0 )
2570 {
2571 e->accept();
2572 return;
2573 }
2574
2575 double zoomFactor = e->angleDelta().y() > 0 ? 1. / zoomInFactor() : zoomOutFactor();
2576
2577 // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
2578 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( e->angleDelta().y() );
2579
2580 if ( e->modifiers() & Qt::ControlModifier )
2581 {
2582 //holding ctrl while wheel zooming results in a finer zoom
2583 zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
2584 }
2585
2586 double signedWheelFactor = e->angleDelta().y() > 0 ? 1 / zoomFactor : zoomFactor;
2587
2588 // zoom map to mouse cursor by scaling
2589 QgsPointXY oldCenter = center();
2590 QgsPointXY mousePos( getCoordinateTransform()->toMapCoordinates( e->position().x(), e->position().y() ) );
2591 QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * signedWheelFactor ),
2592 mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * signedWheelFactor ) );
2593
2594 zoomByFactor( signedWheelFactor, &newCenter );
2595 e->accept();
2596}
2597
2598void QgsMapCanvas::setWheelFactor( double factor )
2599{
2600 mWheelZoomFactor = factor;
2601}
2602
2604{
2605 // magnification is alreday handled in zoomByFactor
2607}
2608
2610{
2611 // magnification is alreday handled in zoomByFactor
2613}
2614
2615void QgsMapCanvas::zoomScale( double newScale, bool ignoreScaleLock )
2616{
2617 zoomByFactor( newScale / scale(), nullptr, ignoreScaleLock );
2618}
2619
2620void QgsMapCanvas::zoomWithCenter( int x, int y, bool zoomIn )
2621{
2622 double scaleFactor = ( zoomIn ? zoomInFactor() : zoomOutFactor() );
2623
2624 // transform the mouse pos to map coordinates
2626
2627 if ( mScaleLocked )
2628 {
2629 ScaleRestorer restorer( this );
2631 }
2632 else
2633 {
2635 r.scale( scaleFactor, &center );
2636 setExtent( r, true );
2637 refresh();
2638 }
2639}
2640
2641void QgsMapCanvas::setScaleLocked( bool isLocked )
2642{
2643 if ( mScaleLocked != isLocked )
2644 {
2645 mScaleLocked = isLocked;
2646 emit scaleLockChanged( mScaleLocked );
2647 }
2648}
2649
2650void QgsMapCanvas::mouseMoveEvent( QMouseEvent *e )
2651{
2652 mCanvasProperties->mouseLastXY = e->pos();
2653
2654 if ( mCanvasProperties->panSelectorDown )
2655 {
2656 panAction( e );
2657 }
2658 else if ( mZoomDragging )
2659 {
2660 mZoomRect.setBottomRight( e->pos() );
2661 mZoomRubberBand->setToCanvasRectangle( mZoomRect );
2662 mZoomRubberBand->show();
2663 }
2664 else
2665 {
2666 // call handler of current map tool
2667 if ( mMapTool )
2668 {
2669 std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2670 mMapTool->canvasMoveEvent( me.get() );
2671 }
2672 }
2673
2674 // show x y on status bar (if we are mid pan operation, then the cursor point hasn't changed!)
2675 if ( !panOperationInProgress() )
2676 {
2677 mCursorPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->mouseLastXY );
2678 emit xyCoordinates( mCursorPoint );
2679 }
2680}
2681
2682void QgsMapCanvas::setMapTool( QgsMapTool *tool, bool clean )
2683{
2684 if ( !tool )
2685 return;
2686
2687 if ( mMapTool )
2688 {
2689 if ( clean )
2690 mMapTool->clean();
2691
2692 disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2693 mMapTool->deactivate();
2694 }
2695
2696 if ( ( tool->flags() & QgsMapTool::Transient )
2697 && mMapTool && !( mMapTool->flags() & QgsMapTool::Transient ) )
2698 {
2699 // if zoom or pan tool will be active, save old tool
2700 // to bring it back on right click
2701 // (but only if it wasn't also zoom or pan tool)
2702 mLastNonZoomMapTool = mMapTool;
2703 }
2704 else
2705 {
2706 mLastNonZoomMapTool = nullptr;
2707 }
2708
2709 QgsMapTool *oldTool = mMapTool;
2710
2711 // set new map tool and activate it
2712 mMapTool = tool;
2713 emit mapToolSet( mMapTool, oldTool );
2714 if ( mMapTool )
2715 {
2716 connect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2717 mMapTool->activate();
2718 }
2719
2720} // setMapTool
2721
2723{
2724 if ( mMapTool && mMapTool == tool )
2725 {
2726 disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2727 QgsMapTool *oldTool = mMapTool;
2728 mMapTool = nullptr;
2729 oldTool->deactivate();
2730 emit mapToolSet( nullptr, oldTool );
2731 setCursor( Qt::ArrowCursor );
2732 }
2733
2734 if ( mLastNonZoomMapTool && mLastNonZoomMapTool == tool )
2735 {
2736 mLastNonZoomMapTool = nullptr;
2737 }
2738}
2739
2741{
2742 mProject = project;
2743}
2744
2745void QgsMapCanvas::setCanvasColor( const QColor &color )
2746{
2747 if ( canvasColor() == color )
2748 return;
2749
2750 // background of map's pixmap
2751 mSettings.setBackgroundColor( color );
2752
2753 // background of the QGraphicsView
2754 QBrush bgBrush( color );
2755 setBackgroundBrush( bgBrush );
2756#if 0
2757 QPalette palette;
2758 palette.setColor( backgroundRole(), color );
2759 setPalette( palette );
2760#endif
2761
2762 // background of QGraphicsScene
2763 mScene->setBackgroundBrush( bgBrush );
2764
2765 refresh();
2766
2767 emit canvasColorChanged();
2768}
2769
2771{
2772 return mScene->backgroundBrush().color();
2773}
2774
2775void QgsMapCanvas::setSelectionColor( const QColor &color )
2776{
2777 if ( mSettings.selectionColor() == color )
2778 return;
2779
2780 mSettings.setSelectionColor( color );
2781
2782 if ( mCache )
2783 {
2784 bool hasSelectedFeatures = false;
2785 const auto layers = mSettings.layers();
2786 for ( QgsMapLayer *layer : layers )
2787 {
2788 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
2789 if ( vlayer && vlayer->selectedFeatureCount() )
2790 {
2791 hasSelectedFeatures = true;
2792 break;
2793 }
2794 }
2795
2796 if ( hasSelectedFeatures )
2797 {
2798 mCache->clear();
2799 refresh();
2800 }
2801 }
2802}
2803
2805{
2806 return mSettings.selectionColor();
2807}
2808
2810{
2811 return mapSettings().layers().size();
2812}
2813
2814QList<QgsMapLayer *> QgsMapCanvas::layers( bool expandGroupLayers ) const
2815{
2816 return mapSettings().layers( expandGroupLayers );
2817}
2818
2820{
2821 // called when a layer has changed visibility setting
2822 refresh();
2823}
2824
2825void QgsMapCanvas::freeze( bool frozen )
2826{
2827 mFrozen = frozen;
2828}
2829
2831{
2832 return mFrozen;
2833}
2834
2836{
2837 return mapSettings().mapUnitsPerPixel();
2838}
2839
2841{
2842 return mapSettings().mapUnits();
2843}
2844
2845QMap<QString, QString> QgsMapCanvas::layerStyleOverrides() const
2846{
2847 return mSettings.layerStyleOverrides();
2848}
2849
2850void QgsMapCanvas::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
2851{
2852 if ( overrides == mSettings.layerStyleOverrides() )
2853 return;
2854
2855 mSettings.setLayerStyleOverrides( overrides );
2856 clearCache();
2858}
2859
2860void QgsMapCanvas::setTheme( const QString &theme )
2861{
2862 if ( mTheme == theme )
2863 return;
2864
2865 clearCache();
2866 if ( theme.isEmpty() || !QgsProject::instance()->mapThemeCollection()->hasMapTheme( theme ) )
2867 {
2868 mTheme.clear();
2869 mSettings.setLayerStyleOverrides( QMap< QString, QString>() );
2870 setLayers( QgsProject::instance()->mapThemeCollection()->masterVisibleLayers() );
2871 emit themeChanged( QString() );
2872 }
2873 else
2874 {
2875 mTheme = theme;
2876 setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
2877 emit themeChanged( theme );
2878 }
2879}
2880
2882{
2883 mRenderFlag = flag;
2884
2885 if ( mRenderFlag )
2886 {
2887 refresh();
2888 }
2889 else
2890 stopRendering();
2891}
2892
2893#if 0
2894void QgsMapCanvas::connectNotify( const char *signal )
2895{
2896 Q_UNUSED( signal )
2897 QgsDebugMsg( "QgsMapCanvas connected to " + QString( signal ) );
2898} //connectNotify
2899#endif
2900
2901void QgsMapCanvas::layerRepaintRequested( bool deferred )
2902{
2903 if ( !deferred )
2904 refresh();
2905}
2906
2907void QgsMapCanvas::autoRefreshTriggered()
2908{
2909 if ( mJob )
2910 {
2911 // canvas is currently being redrawn, so we defer the last requested
2912 // auto refresh until current rendering job finishes
2913 mRefreshAfterJob = true;
2914 return;
2915 }
2916
2917 refresh();
2918}
2919
2920void QgsMapCanvas::updateAutoRefreshTimer()
2921{
2922 // min auto refresh interval stores the smallest interval between layer auto refreshes. We automatically
2923 // trigger a map refresh on this minimum interval
2924 int minAutoRefreshInterval = -1;
2925 const auto layers = mSettings.layers();
2926 for ( QgsMapLayer *layer : layers )
2927 {
2928 int layerRefreshInterval = 0;
2929
2931 {
2932 layerRefreshInterval = layer->autoRefreshInterval();
2933 }
2934 else if ( QgsVectorLayer *vectorLayer = qobject_cast< QgsVectorLayer * >( layer ) )
2935 {
2936 if ( const QgsFeatureRenderer *renderer = vectorLayer->renderer() )
2937 {
2938 const double rendererRefreshRate = QgsSymbolLayerUtils::rendererFrameRate( renderer );
2939 if ( rendererRefreshRate > 0 )
2940 {
2941 layerRefreshInterval = 1000 / rendererRefreshRate;
2942 }
2943 }
2944 }
2945
2946 if ( layerRefreshInterval == 0 )
2947 continue;
2948
2949 minAutoRefreshInterval = minAutoRefreshInterval > 0 ? std::min( layerRefreshInterval, minAutoRefreshInterval ) : layerRefreshInterval;
2950 }
2951
2952 if ( minAutoRefreshInterval > 0 )
2953 {
2954 mAutoRefreshTimer.setInterval( minAutoRefreshInterval );
2955 mAutoRefreshTimer.start();
2956 }
2957 else
2958 {
2959 mAutoRefreshTimer.stop();
2960 }
2961}
2962
2963void QgsMapCanvas::projectThemesChanged()
2964{
2965 if ( mTheme.isEmpty() )
2966 return;
2967
2968 if ( !QgsProject::instance()->mapThemeCollection()->hasMapTheme( mTheme ) )
2969 {
2970 // theme has been removed - stop following
2971 setTheme( QString() );
2972 }
2973
2974}
2975
2977{
2978 return mMapTool;
2979}
2980
2982{
2983 return mProject;
2984}
2985
2986void QgsMapCanvas::panActionEnd( QPoint releasePoint )
2987{
2988 // move map image and other items to standard position
2989 moveCanvasContents( true ); // true means reset
2990
2991 // use start and end box points to calculate the extent
2993 QgsPointXY end = getCoordinateTransform()->toMapCoordinates( releasePoint );
2994
2995 // modify the center
2996 double dx = end.x() - start.x();
2997 double dy = end.y() - start.y();
2998 QgsPointXY c = center();
2999 c.set( c.x() - dx, c.y() - dy );
3000 setCenter( c );
3001
3002 refresh();
3003}
3004
3005void QgsMapCanvas::panActionStart( QPoint releasePoint )
3006{
3007 mCanvasProperties->rubberStartPoint = releasePoint;
3008
3009 mDa = QgsDistanceArea();
3010 mDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
3011 mDa.setSourceCrs( mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
3012}
3013
3014void QgsMapCanvas::panAction( QMouseEvent *e )
3015{
3016 Q_UNUSED( e )
3017
3018 QgsPointXY currentMapPoint = getCoordinateTransform()->toMapCoordinates( e->pos() );
3019 QgsPointXY startMapPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
3020 try
3021 {
3022 emit panDistanceBearingChanged( mDa.measureLine( currentMapPoint, startMapPoint ), mDa.lengthUnits(), mDa.bearing( currentMapPoint, startMapPoint ) * 180 / M_PI );
3023 }
3024 catch ( QgsCsException & )
3025 {}
3026
3027 // move all map canvas items
3029}
3030
3032{
3033 QPoint pnt( 0, 0 );
3034 if ( !reset )
3035 pnt += mCanvasProperties->mouseLastXY - mCanvasProperties->rubberStartPoint;
3036
3037 setSceneRect( -pnt.x(), -pnt.y(), viewport()->size().width(), viewport()->size().height() );
3038}
3039
3040void QgsMapCanvas::dropEvent( QDropEvent *event )
3041{
3042 if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
3043 {
3045 bool allHandled = true;
3046 for ( const QgsMimeDataUtils::Uri &uri : lst )
3047 {
3048 bool handled = false;
3049 for ( QgsCustomDropHandler *handler : std::as_const( mDropHandlers ) )
3050 {
3051 if ( handler && handler->customUriProviderKey() == uri.providerKey )
3052 {
3053 if ( handler->handleCustomUriCanvasDrop( uri, this ) )
3054 {
3055 handled = true;
3056 break;
3057 }
3058 }
3059 }
3060 if ( !handled )
3061 allHandled = false;
3062 }
3063 if ( allHandled )
3064 event->accept();
3065 else
3066 event->ignore();
3067 }
3068 else
3069 {
3070 event->ignore();
3071 }
3072}
3073
3074void QgsMapCanvas::showEvent( QShowEvent *event )
3075{
3076 Q_UNUSED( event )
3077 updateDevicePixelFromScreen();
3078}
3079
3081{
3082 return mCanvasProperties->mouseLastXY;
3083}
3084
3085void QgsMapCanvas::setPreviewModeEnabled( bool previewEnabled )
3086{
3087 if ( !mPreviewEffect )
3088 {
3089 return;
3090 }
3091
3092 mPreviewEffect->setEnabled( previewEnabled );
3093}
3094
3096{
3097 if ( !mPreviewEffect )
3098 {
3099 return false;
3100 }
3101
3102 return mPreviewEffect->isEnabled();
3103}
3104
3106{
3107 if ( !mPreviewEffect )
3108 {
3109 return;
3110 }
3111
3112 mPreviewEffect->setMode( mode );
3113}
3114
3116{
3117 if ( !mPreviewEffect )
3118 {
3120 }
3121
3122 return mPreviewEffect->mode();
3123}
3124
3126{
3127 if ( !mSnappingUtils )
3128 {
3129 // associate a dummy instance, but better than null pointer
3130 QgsMapCanvas *c = const_cast<QgsMapCanvas *>( this );
3131 c->mSnappingUtils = new QgsMapCanvasSnappingUtils( c, c );
3132 }
3133 return mSnappingUtils;
3134}
3135
3137{
3138 mSnappingUtils = utils;
3139}
3140
3141void QgsMapCanvas::readProject( const QDomDocument &doc )
3142{
3143 QgsProject *project = qobject_cast< QgsProject * >( sender() );
3144
3145 QDomNodeList nodes = doc.elementsByTagName( QStringLiteral( "mapcanvas" ) );
3146 if ( nodes.count() )
3147 {
3148 QDomNode node = nodes.item( 0 );
3149
3150 // Search the specific MapCanvas node using the name
3151 if ( nodes.count() > 1 )
3152 {
3153 for ( int i = 0; i < nodes.size(); ++i )
3154 {
3155 QDomElement elementNode = nodes.at( i ).toElement();
3156
3157 if ( elementNode.hasAttribute( QStringLiteral( "name" ) ) && elementNode.attribute( QStringLiteral( "name" ) ) == objectName() )
3158 {
3159 node = nodes.at( i );
3160 break;
3161 }
3162 }
3163 }
3164
3165 QgsMapSettings tmpSettings;
3166 tmpSettings.readXml( node );
3167 if ( objectName() != QLatin1String( "theMapCanvas" ) )
3168 {
3169 // never manually set the crs for the main canvas - this is instead connected to the project CRS
3170 setDestinationCrs( tmpSettings.destinationCrs() );
3171 }
3172 setExtent( tmpSettings.extent() );
3173 setRotation( tmpSettings.rotation() );
3175
3176 clearExtentHistory(); // clear the extent history on project load
3177
3178 QDomElement elem = node.toElement();
3179 if ( elem.hasAttribute( QStringLiteral( "theme" ) ) )
3180 {
3181 if ( QgsProject::instance()->mapThemeCollection()->hasMapTheme( elem.attribute( QStringLiteral( "theme" ) ) ) )
3182 {
3183 setTheme( elem.attribute( QStringLiteral( "theme" ) ) );
3184 }
3185 }
3186 setAnnotationsVisible( elem.attribute( QStringLiteral( "annotationsVisible" ), QStringLiteral( "1" ) ).toInt() );
3187
3188 // restore canvas expression context
3189 const QDomNodeList scopeElements = elem.elementsByTagName( QStringLiteral( "expressionContextScope" ) );
3190 if ( scopeElements.size() > 0 )
3191 {
3192 const QDomElement scopeElement = scopeElements.at( 0 ).toElement();
3193 mExpressionContextScope.readXml( scopeElement, QgsReadWriteContext() );
3194 }
3195 }
3196 else
3197 {
3198 QgsDebugMsg( QStringLiteral( "Couldn't read mapcanvas information from project" ) );
3200 {
3202 }
3203
3205 clearExtentHistory(); // clear the extent history on project load
3206 }
3207}
3208
3209void QgsMapCanvas::writeProject( QDomDocument &doc )
3210{
3211 // create node "mapcanvas" and call mMapRenderer->writeXml()
3212
3213 QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
3214 if ( !nl.count() )
3215 {
3216 QgsDebugMsg( QStringLiteral( "Unable to find qgis element in project file" ) );
3217 return;
3218 }
3219 QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
3220
3221 QDomElement mapcanvasNode = doc.createElement( QStringLiteral( "mapcanvas" ) );
3222 mapcanvasNode.setAttribute( QStringLiteral( "name" ), objectName() );
3223 if ( !mTheme.isEmpty() )
3224 mapcanvasNode.setAttribute( QStringLiteral( "theme" ), mTheme );
3225 mapcanvasNode.setAttribute( QStringLiteral( "annotationsVisible" ), mAnnotationsVisible );
3226 qgisNode.appendChild( mapcanvasNode );
3227
3228 mSettings.writeXml( mapcanvasNode, doc );
3229
3230 // store canvas expression context
3231 QDomElement scopeElement = doc.createElement( QStringLiteral( "expressionContextScope" ) );
3232 QgsExpressionContextScope tmpScope( mExpressionContextScope );
3233 tmpScope.removeVariable( QStringLiteral( "atlas_featurenumber" ) );
3234 tmpScope.removeVariable( QStringLiteral( "atlas_pagename" ) );
3235 tmpScope.removeVariable( QStringLiteral( "atlas_feature" ) );
3236 tmpScope.removeVariable( QStringLiteral( "atlas_featureid" ) );
3237 tmpScope.removeVariable( QStringLiteral( "atlas_geometry" ) );
3238 tmpScope.writeXml( scopeElement, doc, QgsReadWriteContext() );
3239 mapcanvasNode.appendChild( scopeElement );
3240
3241 // TODO: store only units, extent, projections, dest CRS
3242}
3243
3244void QgsMapCanvas::zoomByFactor( double scaleFactor, const QgsPointXY *center, bool ignoreScaleLock )
3245{
3246 if ( mScaleLocked && !ignoreScaleLock )
3247 {
3248 ScaleRestorer restorer( this );
3250 }
3251 else
3252 {
3254 r.scale( scaleFactor, center );
3255 setExtent( r, true );
3256 refresh();
3257 }
3258}
3259
3261{
3262 // Find out which layer it was that sent the signal.
3263 QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
3264 if ( layer )
3265 {
3266 emit selectionChanged( layer );
3267 refresh();
3268 }
3269}
3270
3271void QgsMapCanvas::dragEnterEvent( QDragEnterEvent *event )
3272{
3273 // By default graphics view delegates the drag events to graphics items.
3274 // But we do not want that and by ignoring the drag enter we let the
3275 // parent (e.g. QgisApp) to handle drops of map layers etc.
3276
3277 // so we ONLY accept the event if we know in advance that a custom drop handler
3278 // wants it
3279
3280 if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
3281 {
3283 bool allHandled = true;
3284 for ( const QgsMimeDataUtils::Uri &uri : lst )
3285 {
3286 bool handled = false;
3287 for ( QgsCustomDropHandler *handler : std::as_const( mDropHandlers ) )
3288 {
3289 if ( handler->canHandleCustomUriCanvasDrop( uri, this ) )
3290 {
3291 handled = true;
3292 break;
3293 }
3294 }
3295 if ( !handled )
3296 allHandled = false;
3297 }
3298 if ( allHandled )
3299 event->accept();
3300 else
3301 event->ignore();
3302 }
3303 else
3304 {
3305 event->ignore();
3306 }
3307}
3308
3309bool QgsMapCanvas::viewportEvent( QEvent *event )
3310{
3311 if ( event->type() == QEvent::ToolTip && mMapTool && mMapTool->canvasToolTipEvent( qgis::down_cast<QHelpEvent *>( event ) ) )
3312 {
3313 return true;
3314 }
3315 return QGraphicsView::viewportEvent( event );
3316}
3317
3318void QgsMapCanvas::mapToolDestroyed()
3319{
3320 QgsDebugMsgLevel( QStringLiteral( "maptool destroyed" ), 2 );
3321 mMapTool = nullptr;
3322}
3323
3324bool QgsMapCanvas::event( QEvent *e )
3325{
3326 if ( e->type() == QEvent::Gesture )
3327 {
3328 if ( QTapAndHoldGesture *tapAndHoldGesture = qobject_cast< QTapAndHoldGesture * >( static_cast<QGestureEvent *>( e )->gesture( Qt::TapAndHoldGesture ) ) )
3329 {
3330 QPointF pos = tapAndHoldGesture->position();
3331 pos = mapFromGlobal( QPoint( pos.x(), pos.y() ) );
3332 QgsPointXY mapPoint = getCoordinateTransform()->toMapCoordinates( pos.x(), pos.y() );
3333 emit tapAndHoldGestureOccurred( mapPoint, tapAndHoldGesture );
3334 }
3335
3336 // call handler of current map tool
3337 if ( mMapTool )
3338 {
3339 return mMapTool->gestureEvent( static_cast<QGestureEvent *>( e ) );
3340 }
3341 }
3342
3343 // pass other events to base class
3344 return QGraphicsView::event( e );
3345}
3346
3348{
3349 // reload all layers in canvas
3350 const QList<QgsMapLayer *> layers = mapSettings().layers();
3351 for ( QgsMapLayer *layer : layers )
3352 {
3353 layer->reload();
3354 }
3355
3357}
3358
3360{
3361 // clear the cache
3362 clearCache();
3363
3364 // and then refresh
3365 refresh();
3366}
3367
3369{
3370 while ( mRefreshScheduled || mJob )
3371 {
3372 QgsApplication::processEvents();
3373 }
3374}
3375
3377{
3378 mSettings.setSegmentationTolerance( tolerance );
3379}
3380
3382{
3383 mSettings.setSegmentationToleranceType( type );
3384}
3385
3386QList<QgsMapCanvasAnnotationItem *> QgsMapCanvas::annotationItems() const
3387{
3388 QList<QgsMapCanvasAnnotationItem *> annotationItemList;
3389 const QList<QGraphicsItem *> items = mScene->items();
3390 for ( QGraphicsItem *gi : items )
3391 {
3392 QgsMapCanvasAnnotationItem *aItem = dynamic_cast< QgsMapCanvasAnnotationItem *>( gi );
3393 if ( aItem )
3394 {
3395 annotationItemList.push_back( aItem );
3396 }
3397 }
3398
3399 return annotationItemList;
3400}
3401
3403{
3404 mAnnotationsVisible = show;
3405 const QList<QgsMapCanvasAnnotationItem *> items = annotationItems();
3406 for ( QgsMapCanvasAnnotationItem *item : items )
3407 {
3408 item->setVisible( show );
3409 }
3410}
3411
3413{
3414 mSettings.setLabelingEngineSettings( settings );
3415}
3416
3418{
3419 return mSettings.labelingEngineSettings();
3420}
3421
3422void QgsMapCanvas::startPreviewJobs()
3423{
3424 stopPreviewJobs(); //just in case still running
3425
3426 //canvas preview jobs aren't compatible with rotation
3427 // TODO fix this
3428 if ( !qgsDoubleNear( mSettings.rotation(), 0.0 ) )
3429 return;
3430
3431 schedulePreviewJob( 0 );
3432}
3433
3434void QgsMapCanvas::startPreviewJob( int number )
3435{
3436 QgsRectangle mapRect = mSettings.visibleExtent();
3437
3438 if ( number == 4 )
3439 number += 1;
3440
3441 int j = number / 3;
3442 int i = number % 3;
3443
3444 //copy settings, only update extent
3445 QgsMapSettings jobSettings = mSettings;
3446
3447 double dx = ( i - 1 ) * mapRect.width();
3448 double dy = ( 1 - j ) * mapRect.height();
3449 QgsRectangle jobExtent = mapRect;
3450
3451 jobExtent.setXMaximum( jobExtent.xMaximum() + dx );
3452 jobExtent.setXMinimum( jobExtent.xMinimum() + dx );
3453 jobExtent.setYMaximum( jobExtent.yMaximum() + dy );
3454 jobExtent.setYMinimum( jobExtent.yMinimum() + dy );
3455
3456 jobSettings.setExtent( jobExtent );
3457 jobSettings.setFlag( Qgis::MapSettingsFlag::DrawLabeling, false );
3459
3460 // truncate preview layers to fast layers
3461 const QList<QgsMapLayer *> layers = jobSettings.layers();
3462 QList< QgsMapLayer * > previewLayers;
3464 context.maxRenderingTimeMs = MAXIMUM_LAYER_PREVIEW_TIME_MS;
3465 for ( QgsMapLayer *layer : layers )
3466 {
3467 context.lastRenderingTimeMs = mLastLayerRenderTime.value( layer->id(), 0 );
3468 QgsDataProvider *provider = layer->dataProvider();
3469 if ( provider && !provider->renderInPreview( context ) )
3470 {
3471 QgsDebugMsgLevel( QStringLiteral( "Layer %1 not rendered because it does not match the renderInPreview criterion %2" ).arg( layer->id() ).arg( mLastLayerRenderTime.value( layer->id() ) ), 3 );
3472 continue;
3473 }
3474
3475 previewLayers << layer;
3476 }
3477 jobSettings.setLayers( previewLayers );
3478
3479 QgsMapRendererQImageJob *job = new QgsMapRendererSequentialJob( jobSettings );
3480 job->setProperty( "number", number );
3481 mPreviewJobs.append( job );
3482 connect( job, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
3483 job->start();
3484}
3485
3486void QgsMapCanvas::stopPreviewJobs()
3487{
3488 mPreviewTimer.stop();
3489 for ( auto previewJob = mPreviewJobs.constBegin(); previewJob != mPreviewJobs.constEnd(); ++previewJob )
3490 {
3491 if ( *previewJob )
3492 {
3493 disconnect( *previewJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
3494 connect( *previewJob, &QgsMapRendererQImageJob::finished, *previewJob, &QgsMapRendererQImageJob::deleteLater );
3495 ( *previewJob )->cancelWithoutBlocking();
3496 }
3497 }
3498 mPreviewJobs.clear();
3499}
3500
3501void QgsMapCanvas::schedulePreviewJob( int number )
3502{
3503 mPreviewTimer.setSingleShot( true );
3504 mPreviewTimer.setInterval( PREVIEW_JOB_DELAY_MS );
3505 disconnect( mPreviewTimerConnection );
3506 mPreviewTimerConnection = connect( &mPreviewTimer, &QTimer::timeout, this, [ = ]()
3507 {
3508 startPreviewJob( number );
3509 } );
3510 mPreviewTimer.start();
3511}
3512
3513bool QgsMapCanvas::panOperationInProgress()
3514{
3515 if ( mCanvasProperties->panSelectorDown )
3516 return true;
3517
3518 if ( QgsMapToolPan *panTool = qobject_cast< QgsMapToolPan *>( mMapTool ) )
3519 {
3520 if ( panTool->isDragging() )
3521 return true;
3522 }
3523
3524 return false;
3525}
3526
3527int QgsMapCanvas::nextZoomLevel( const QList<double> &resolutions, bool zoomIn ) const
3528{
3529 int resolutionLevel = -1;
3530 double currentResolution = mapUnitsPerPixel();
3531
3532 for ( int i = 0, n = resolutions.size(); i < n; ++i )
3533 {
3534 if ( qgsDoubleNear( resolutions[i], currentResolution, 0.0001 ) )
3535 {
3536 resolutionLevel = zoomIn ? ( i - 1 ) : ( i + 1 );
3537 break;
3538 }
3539 else if ( currentResolution <= resolutions[i] )
3540 {
3541 resolutionLevel = zoomIn ? ( i - 1 ) : i;
3542 break;
3543 }
3544 }
3545 return ( resolutionLevel < 0 || resolutionLevel >= resolutions.size() ) ? -1 : resolutionLevel;
3546}
3547
3549{
3550 if ( !mZoomResolutions.isEmpty() )
3551 {
3552 int zoomLevel = nextZoomLevel( mZoomResolutions, true );
3553 if ( zoomLevel != -1 )
3554 {
3555 return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
3556 }
3557 }
3558 return 1 / mWheelZoomFactor;
3559}
3560
3562{
3563 if ( !mZoomResolutions.isEmpty() )
3564 {
3565 int zoomLevel = nextZoomLevel( mZoomResolutions, false );
3566 if ( zoomLevel != -1 )
3567 {
3568 return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
3569 }
3570 }
3571 return mWheelZoomFactor;
3572}
@ YX
Northing/Easting (or Latitude/Longitude for geographic CRS)
@ View
Renderer used for displaying on screen.
@ BallparkTransformsAreAppropriate
Indicates that approximate "ballpark" results are appropriate for this coordinate transform....
@ IgnoreImpossibleTransformations
Indicates that impossible transformations (such as those which attempt to transform between two diffe...
@ DrawEditingInfo
Enable drawing of vertex markers for layers in editing mode.
@ RenderPreviewJob
Render is a 'canvas preview' render, and shortcuts should be taken to ensure fast rendering.
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
@ Antialiasing
Enable anti-aliasing for map rendering.
@ DrawLabeling
Enable drawing of labels on top of the map.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle.
@ MaximumAngle
Maximum angle between generating radii (lines from arc center to output vertices)
virtual bool isEmpty() const
Returns true if the geometry is empty.
static QCursor getThemeCursor(Cursor cursor)
Helper to get a theme cursor.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
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.
static Qgis::CoordinateOrder defaultCoordinateOrderForCrs(const QgsCoordinateReferenceSystem &crs)
Returns the default coordinate order to use for the specified crs.
static QString axisDirectionToAbbreviatedString(Qgis::CrsAxisDirection axis)
Returns a translated abbreviation representing an axis direction.
This class represents a coordinate reference system (CRS).
@ MediumString
A medium-length string, recommended for general purpose use.
void updateDefinition()
Updates the definition and parameters of the coordinate reference system to their latest values.
QString userFriendlyIdentifier(IdentifierType type=MediumString) const
Returns a user friendly identifier for the CRS.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
Q_GADGET QgsUnitTypes::DistanceUnit mapUnits
QList< Qgis::CrsAxisDirection > axisOrdering() const
Returns an ordered list of the axis directions reflecting the native axis order for the CRS.
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Abstract base class that may be implemented to handle new types of data to be dropped in QGIS.
Abstract base class for spatial data provider implementations.
virtual bool renderInPreview(const QgsDataProvider::PreviewContext &context)
Returns whether the layer must be rendered in preview jobs.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
double bearing(const QgsPointXY &p1, const QgsPointXY &p2) const SIP_THROW(QgsCsException)
Computes the bearing (in radians) between two points.
double measureLine(const QVector< QgsPointXY > &points) const
Measures the length of a line with multiple segments.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
QgsUnitTypes::DistanceUnit lengthUnits() const
Returns the units of distance for length calculations made by this object.
QgsRange which stores a range of double values.
Definition: qgsrange.h:203
QString what() const
Definition: qgsexception.h:48
Abstract interface for generating an expression context scope.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads scope variables from an XML element.
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const
Writes scope variables to an XML element.
bool removeVariable(const QString &name)
Removes a variable from the context scope, if found.
void setVariable(const QString &name, const QVariant &value, bool isStatic=false)
Convenience method for setting a variable in the context scope by name name and value.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * atlasScope(const QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setLimit(long long limit)
Set the maximum number of features to request.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:233
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Q_GADGET bool isNull
Definition: qgsgeometry.h:166
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
static QgsGeometry fromPointXY(const QgsPointXY &point) SIP_HOLDGIL
Creates a new geometry from a QgsPointXY object.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
void remoteImageFetched(const QString &url)
Emitted when the cache has finished retrieving an image file from a remote url.
Stores global configuration for labeling engine.
Class that stores computed placement from labeling engine.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
An interactive map canvas item which displays a QgsAnnotation.
An interface for objects which block interactions with a QgsMapCanvas.
Interaction
Available interactions to block.
An abstract class for items that can be placed on the map canvas.
virtual void updatePosition()
called on changed extent or resize event to update position of the item
Snapping utils instance that is connected to a canvas and updates the configuration (map settings + c...
Deprecated to be deleted, stuff from here should be moved elsewhere.
QPoint mouseLastXY
Last seen point of the mouse.
bool panSelectorDown
Flag to indicate the pan selector key is held down by user.
CanvasProperties()=default
Constructor for CanvasProperties.
QPoint rubberStartPoint
Beginning point of a rubber band.
bool mouseButtonDown
Flag to indicate status of mouse button.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:90
void setCurrentLayer(QgsMapLayer *layer)
void messageEmitted(const QString &title, const QString &message, Qgis::MessageLevel=Qgis::MessageLevel::Info)
emit a message (usually to be displayed in a message bar)
void contextMenuAboutToShow(QMenu *menu, QgsMapMouseEvent *event)
Emitted before the map canvas context menu will be shown.
void zoomToProjectExtent()
Zoom to the full extent the project associated with this canvas.
QgsUnitTypes::DistanceUnit mapUnits() const
Convenience function for returning the current canvas map units.
void panToSelected(QgsMapLayer *layer=nullptr)
Pan to the selected features of current ayer keeping same extent.
void freeze(bool frozen=true)
Freeze/thaw the map canvas.
void enableAntiAliasing(bool flag)
used to determine if anti-aliasing is enabled or not
void zoomToSelected(QgsMapLayer *layer=nullptr)
Zoom to the extent of the selected features of provided map layer.
void setSnappingUtils(QgsSnappingUtils *utils)
Assign an instance of snapping utils to the map canvas.
bool isCachingEnabled() const
Check whether images of rendered layers are curerently being cached.
void zoomToFullExtent()
Zoom to the full extent of all layers currently visible in the canvas.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers that should be shown in the canvas.
void setProject(QgsProject *project)
Sets the project linked to this canvas.
const QgsRenderedItemResults * renderedItemResults(bool allowOutdatedResults=true) const
Gets access to the rendered item results (may be nullptr), which includes the results of rendering an...
QColor selectionColor() const
Returns color for selected features.
bool event(QEvent *e) override
~QgsMapCanvas() override
void setCachingEnabled(bool enabled)
Set whether to cache images of rendered layers.
void mouseReleaseEvent(QMouseEvent *e) override
QgsDoubleRange zRange() const
Returns the range of z-values which will be visible in the map.
void setExtent(const QgsRectangle &r, bool magnified=false)
Sets the extent of the map canvas to the specified rectangle.
void setRenderFlag(bool flag)
Sets whether a user has disabled canvas renders via the GUI.
void selectionChanged(QgsMapLayer *layer)
Emitted when selection in any layer gets changed.
QList< QgsMapCanvasAnnotationItem * > annotationItems() const
Returns a list of all annotation items in the canvas.
void updateCanvasItemPositions()
called on resize or changed extent to notify canvas items to change their rectangle
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void extentsChanged()
Emitted when the extents of the map change.
QgsExpressionContextScope * defaultExpressionContextScope() const
Creates a new scope which contains default variables and functions relating to the map canvas.
void xyCoordinates(const QgsPointXY &p)
Emits current mouse position.
void stopRendering()
stop rendering (if there is any right now)
bool previewJobsEnabled
Definition: qgsmapcanvas.h:103
void magnificationChanged(double)
Emitted when the scale of the map changes.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets global labeling engine settings in the internal map settings.
QgsPointXY center() const
Gets map center, in geographical coordinates.
void showEvent(QShowEvent *event) override
int layerCount() const
Returns number of layers on the map.
bool antiAliasingEnabled() const
true if antialiasing is enabled
void setPreviewMode(QgsPreviewEffect::PreviewMode mode)
Sets a preview mode for the map canvas.
void layerStateChange()
This slot is connected to the visibility change of one or more layers.
QgsPreviewEffect::PreviewMode previewMode() const
Returns the current preview mode for the map canvas.
void zoomScale(double scale, bool ignoreScaleLock=false)
Zooms the canvas to a specific scale.
void zoomWithCenter(int x, int y, bool zoomIn)
Zooms in/out with a given center.
QPoint mouseLastXY()
returns last position of mouse cursor
void clearCache()
Make sure to remove any rendered images from cache (does nothing if cache is not enabled)
void renderComplete(QPainter *)
Emitted when the canvas has rendered.
void tapAndHoldGestureOccurred(const QgsPointXY &mapPoint, QTapAndHoldGesture *gesture)
Emitted whenever a tap and hold gesture occurs at the specified map point.
void zoomNextStatusChanged(bool)
Emitted when zoom next status changed.
const QgsDateTimeRange & temporalRange() const
Returns map canvas datetime range.
void setCanvasColor(const QColor &_newVal)
Write property of QColor bgColor.
void zoomByFactor(double scaleFactor, const QgsPointXY *center=nullptr, bool ignoreScaleLock=false)
Zoom with the factor supplied.
const QgsTemporalController * temporalController() const
Gets access to the temporal controller that will be used to update the canvas temporal range.
void flashGeometries(const QList< QgsGeometry > &geometries, const QgsCoordinateReferenceSystem &crs=QgsCoordinateReferenceSystem(), const QColor &startColor=QColor(255, 0, 0, 255), const QColor &endColor=QColor(255, 0, 0, 0), int flashes=3, int duration=500)
Causes a set of geometries to flash within the canvas.
void setMapUpdateInterval(int timeMilliseconds)
Set how often map preview should be updated while it is being rendered (in milliseconds)
void rotationChanged(double)
Emitted when the rotation of the map changes.
void dragEnterEvent(QDragEnterEvent *e) override
bool isDrawing()
Find out whether rendering is in progress.
void zRangeChanged()
Emitted when the map canvas z (elevation) range changes.
void keyPressEvent(QKeyEvent *e) override
void setZRange(const QgsDoubleRange &range)
Sets the range of z-values which will be visible in the map.
void clearExtentHistory()
Clears the list of extents and sets current extent as first item.
void zoomToPreviousExtent()
Zoom to the previous extent (view)
void enableMapTileRendering(bool flag)
sets map tile rendering flag
void panAction(QMouseEvent *event)
Called when mouse is moving and pan is activated.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the stored overrides of styles for rendering layers.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers shown within the map canvas.
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns global labeling engine settings from the internal map settings.
void mapToolSet(QgsMapTool *newTool, QgsMapTool *oldTool)
Emit map tool changed with the old tool.
void canvasColorChanged()
Emitted when canvas background color changes.
double zoomInFactor() const
Returns the zoom in factor.
void saveAsImage(const QString &fileName, QPixmap *QPixmap=nullptr, const QString &="PNG")
Save the contents of the map canvas to disk as an image.
void setSegmentationToleranceType(QgsAbstractGeometry::SegmentationToleranceType type)
Sets segmentation tolerance type (maximum angle or maximum difference between curve and approximation...
void setTemporalRange(const QgsDateTimeRange &range)
Set datetime range for the map canvas.
void moveCanvasContents(bool reset=false)
called when panning is in action, reset indicates end of panning
void zoomOut()
Zoom out with fixed factor.
void currentLayerChanged(QgsMapLayer *layer)
Emitted when the current layer is changed.
void setTemporalController(QgsTemporalController *controller)
Sets the temporal controller for this canvas.
void renderErrorOccurred(const QString &error, QgsMapLayer *layer)
Emitted whenever an error is encountered during a map render operation.
void waitWhileRendering()
Blocks until the rendering job has finished.
void mapRefreshCanceled()
Emitted when the pending map refresh has been canceled.
double magnificationFactor() const
Returns the magnification factor.
void writeProject(QDomDocument &)
called to write map canvas settings to project
void mousePressEvent(QMouseEvent *e) override
void updateScale()
Emits signal scaleChanged to update scale in main window.
void setMagnificationFactor(double factor, const QgsPointXY *center=nullptr)
Sets the factor of magnification to apply to the map canvas.
void refreshAllLayers()
Reload all layers (including refreshing layer properties from their data sources),...
void unsetMapTool(QgsMapTool *mapTool)
Unset the current map tool or last non zoom tool.
void panActionEnd(QPoint releasePoint)
Ends pan action and redraws the canvas.
void resizeEvent(QResizeEvent *e) override
double zoomOutFactor() const
Returns the zoom in factor.
void renderStarting()
Emitted when the canvas is about to be rendered.
void setMapSettingsFlags(Qgis::MapSettingsFlags flags)
Resets the flags for the canvas' map settings.
std::unique_ptr< CanvasProperties > mCanvasProperties
Handle pattern for implementation object.
void keyReleased(QKeyEvent *e)
Emit key release event.
void setWheelFactor(double factor)
Sets wheel zoom factor (should be greater than 1)
void setAnnotationsVisible(bool visible)
Sets whether annotations are visible in the canvas.
void layerStyleOverridesChanged()
Emitted when the configuration of overridden layer styles changes.
QgsMapCanvas(QWidget *parent=nullptr)
Constructor.
void panActionStart(QPoint releasePoint)
Starts a pan action.
void setPreviewJobsEnabled(bool enabled)
Sets whether canvas map preview jobs (low priority render jobs which render portions of the view just...
bool setReferencedExtent(const QgsReferencedRectangle &extent) SIP_THROW(QgsCsException)
Sets the canvas to the specified extent.
QgsRectangle fullExtent() const
Returns the combined extent for all layers on the map canvas.
void redrawAllLayers()
Clears all cached images and redraws all layers.
void keyReleaseEvent(QKeyEvent *e) override
bool isFrozen() const
Returns true if canvas is frozen.
void panToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter=true)
Centers canvas extent to feature ids.
void scaleChanged(double)
Emitted when the scale of the map changes.
void scaleLockChanged(bool locked)
Emitted when the scale locked state of the map changes.
const QgsLabelingResults * labelingResults(bool allowOutdatedResults=true) const
Gets access to the labeling results (may be nullptr).
void mouseMoveEvent(QMouseEvent *e) override
QgsRectangle projectExtent() const
Returns the associated project's full extent, in the canvas' CRS.
void setCenter(const QgsPointXY &center)
Set the center of the map canvas, in geographical coordinates.
void setParallelRenderingEnabled(bool enabled)
Set whether the layers are rendered in parallel or sequentially.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets destination coordinate reference system.
void flashFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, const QColor &startColor=QColor(255, 0, 0, 255), const QColor &endColor=QColor(255, 0, 0, 0), int flashes=3, int duration=500)
Causes a set of features with matching ids from a vector layer to flash within the canvas.
void installInteractionBlocker(QgsMapCanvasInteractionBlocker *blocker)
Installs an interaction blocker onto the canvas, which may prevent certain map canvas interactions fr...
bool isParallelRenderingEnabled() const
Check whether the layers are rendered in parallel or sequentially.
void panDistanceBearingChanged(double distance, QgsUnitTypes::DistanceUnit unit, double bearing)
Emitted whenever the distance or bearing of an in-progress panning operation is changed.
double scale() const
Returns the last reported scale of the canvas.
QgsSnappingUtils * snappingUtils() const
Returns snapping utility class that is associated with map canvas.
double rotation() const
Gets the current map canvas rotation in clockwise degrees.
void temporalRangeChanged()
Emitted when the map canvas temporal range changes.
void paintEvent(QPaintEvent *e) override
void setSegmentationTolerance(double tolerance)
Sets the segmentation tolerance applied when rendering curved geometries.
void themeChanged(const QString &theme)
Emitted when the canvas has been assigned a different map theme.
void destinationCrsChanged()
Emitted when map CRS has changed.
void transformContextChanged()
Emitted when the canvas transform context is changed.
QgsMapTool * mapTool()
Returns the currently active tool.
void keyPressed(QKeyEvent *e)
Emit key press event.
void setMapTool(QgsMapTool *mapTool, bool clean=false)
Sets the map tool currently being used on the canvas.
QColor canvasColor() const
Read property of QColor bgColor.
void mapCanvasRefreshed()
Emitted when canvas finished a refresh request.
int mapUpdateInterval() const
Find out how often map preview should be updated while it is being rendered (in milliseconds)
void zoomLastStatusChanged(bool)
Emitted when zoom last status changed.
void setSelectionColor(const QColor &color)
Set color of selected vector features.
double mapUnitsPerPixel() const
Returns the mapUnitsPerPixel (map units per pixel) for the canvas.
void mouseDoubleClickEvent(QMouseEvent *e) override
void selectionChangedSlot()
Receives signal about selection change, and pass it on with layer info.
bool viewportEvent(QEvent *event) override
void setCustomDropHandlers(const QVector< QPointer< QgsCustomDropHandler > > &handlers)
Sets a list of custom drop handlers to use when drop events occur on the canvas.
void zoomToNextExtent()
Zoom to the next extent (view)
void layersChanged()
Emitted when a new set of layers has been received.
void zoomToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids)
Set canvas extent to the bounding box of a set of features.
void zoomIn()
Zoom in with fixed factor.
QgsMapLayer * layer(int index)
Returns the map layer at position index in the layer stack.
void cancelJobs()
Cancel any rendering job, in a blocking way.
bool allowInteraction(QgsMapCanvasInteractionBlocker::Interaction interaction) const
Returns true if the specified interaction is currently permitted on the canvas.
void wheelEvent(QWheelEvent *e) override
bool previewModeEnabled() const
Returns whether a preview mode is enabled for the map canvas.
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
void dropEvent(QDropEvent *event) override
void setPreviewModeEnabled(bool previewEnabled)
Enables a preview mode for the map canvas.
QgsProject * project()
Returns the project linked to this canvas.
QString theme
Definition: qgsmapcanvas.h:102
void setScaleLocked(bool isLocked)
Lock the scale, so zooming can be performed using magnication.
void setRotation(double degrees)
Set the rotation of the map canvas in clockwise degrees.
void removeInteractionBlocker(QgsMapCanvasInteractionBlocker *blocker)
Removes an interaction blocker from the canvas.
void readProject(const QDomDocument &)
called to read map canvas settings from project
void setTheme(const QString &theme)
Sets a map theme to show in the canvas.
void zoomToFeatureExtent(QgsRectangle &rect)
Zooms to feature extent.
QMap< QString, QString > layerStyleOverrides() const
Returns the stored overrides of styles for layers.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
void refresh()
Repaints the canvas map.
QgsMapLayer * currentLayer()
returns current layer (set by legend widget)
virtual QgsMapLayerElevationProperties::Flags flags() const
Returns flags associated to the elevation properties.
@ FlagDontInvalidateCachedRendersWhenRangeChanges
Any cached rendering will not be invalidated when z range context is modified.
virtual bool hasElevation() const
Returns true if the layer has an elevation or z component.
static QgsRectangle combinedExtent(const QList< QgsMapLayer * > &layers, const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext)
Returns the combined extent of a list of layers.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
virtual bool isSpatial() const
Returns true if the layer is considered a spatial layer, ie it has some form of geometry associated w...
QgsMapLayerType type
Definition: qgsmaplayer.h:80
void autoRefreshIntervalChanged(int interval)
Emitted when the auto refresh interval changes.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
bool hasAutoRefreshEnabled() const
Returns true if auto refresh is enabled for the layer.
void rendererChanged()
Signal emitted when renderer is changed.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1503
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Definition: qgsmaplayer.h:1510
int autoRefreshInterval
Definition: qgsmaplayer.h:77
virtual Q_INVOKABLE QgsDataProvider * dataProvider()
Returns the layer's data provider, it may be nullptr.
virtual Q_INVOKABLE void reload()
Synchronises with changes in the datasource.
Definition: qgsmaplayer.h:537
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
This class is responsible for keeping cache of rendered images resulting from a map rendering job.
void clear()
Invalidates the cache contents, clearing all cached images.
void invalidateCacheForLayer(QgsMapLayer *layer)
Invalidates cached images which relate to the specified map layer.
void clearCacheImage(const QString &cacheKey)
Removes an image from the cache with matching cacheKey.
Job implementation that renders everything sequentially using a custom painter.
void waitForFinished() override
Block until the job has finished.
virtual void waitForFinished()=0
Block until the job has finished.
void setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
QHash< QgsMapLayer *, int > perLayerRenderingTime() const
Returns the render time (in ms) per layer.
virtual bool usedCachedLabels() const =0
Returns true if the render job was able to use a cached labeling solution.
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
static const QgsSettingsEntryBool settingsLogCanvasRefreshEvent
Settings entry log canvas refresh event.
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
void finished()
emitted when asynchronous rendering is finished (or canceled).
void start()
Start the rendering job and immediately return.
int renderingTime() const
Returns the total time it took to finish the job (in milliseconds).
QStringList layersRedrawnFromCache() const
Returns a list of the layer IDs for all layers which were redrawn from cached images.
QList< QgsMapRendererJob::Error > Errors
QgsRenderedItemResults * takeRenderedItemResults()
Takes the rendered item results from the map render job and returns them.
virtual bool isActive() const =0
Tell whether the rendering job is currently running in background.
virtual QgsLabelingResults * takeLabelingResults()=0
Gets pointer to internal labeling engine (in order to get access to the results).
void setLayerRenderingTimeHints(const QHash< QString, int > &hints)
Sets approximate render times (in ms) for map layers.
virtual void cancelWithoutBlocking()=0
Triggers cancellation of the rendering job without blocking.
Job implementation that renders all layers in parallel.
Intermediate base class adding functionality that allows client to query the rendered image.
virtual QImage renderedImage()=0
Gets a preview/resulting image.
Job implementation that renders everything sequentially in one thread.
static QString worldFileContent(const QgsMapSettings &mapSettings)
Creates the content of a world file.
The QgsMapSettings class contains configuration for rendering of the map.
void writeXml(QDomNode &node, QDomDocument &doc)
QgsPointXY layerToMapCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from layer's CRS to output CRS
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns the global configuration of the labeling engine.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered in the map.
void setSelectionColor(const QColor &color)
Sets the color that is used for drawing of selected vector features.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
double scale() const
Returns the calculated map scale.
void setFrameRate(double rate)
Sets the frame rate of the map (in frames per second), for maps which are part of an animation.
void setFlags(Qgis::MapSettingsFlags flags)
Sets combination of flags that will be used for rendering.
QgsRectangle layerExtentToOutputExtent(const QgsMapLayer *layer, QgsRectangle extent) const
transform bounding box from layer's CRS to output CRS
QgsDoubleRange zRange() const
Returns the range of z-values which will be visible in the map.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
void setDpiTarget(double dpi)
Sets the target dpi (dots per inch) to be taken into consideration when rendering.
double magnificationFactor() const
Returns the magnification factor.
QStringList layerIds(bool expandGroupLayers=false) const
Returns the list of layer IDs which will be rendered in the map.
void setDevicePixelRatio(float dpr)
Sets the device pixel ratio.
void setZRange(const QgsDoubleRange &range)
Sets the range of z-values which will be visible in the map.
QColor backgroundColor() const
Returns the background color of the map.
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
const QgsMapToPixel & mapToPixel() const
double mapUnitsPerPixel() const
Returns the distance in geographical coordinates that equals to one pixel in the map.
void setRendererUsage(Qgis::RendererUsage rendererUsage)
Sets the rendering usage.
float devicePixelRatio() const
Returns the device pixel ratio.
QSize outputSize() const
Returns the size of the resulting map image, in pixels.
QgsRectangle extent() const
Returns geographical coordinates of the rectangle that should be rendered.
void setSegmentationTolerance(double tolerance)
Sets the segmentation tolerance applied when rendering curved geometries.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the map of map layer style overrides (key: layer ID, value: style name) where a different style ...
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
QgsUnitTypes::DistanceUnit mapUnits() const
Returns the units of the map's geographical coordinates - used for scale calculation.
QColor selectionColor() const
Returns the color that is used for drawing of selected vector features.
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
QgsRectangle fullExtent() const
returns current extent of layer set
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
void setRotation(double rotation)
Sets the rotation of the resulting map image, in degrees clockwise.
void setCurrentFrame(long long frame)
Sets the current frame of the map, for maps which are part of an animation.
void setPathResolver(const QgsPathResolver &resolver)
Sets the path resolver for conversion between relative and absolute paths during rendering operations...
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
QMap< QString, QString > layerStyleOverrides() const
Returns the map of map layer style overrides (key: layer ID, value: style name) where a different sty...
double rotation() const
Returns the rotation of the resulting map image, in degrees clockwise.
bool hasValidSettings() const
Check whether the map settings are valid and can be used for rendering.
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
void setSegmentationToleranceType(QgsAbstractGeometry::SegmentationToleranceType type)
Sets segmentation tolerance type (maximum angle or maximum difference between curve and approximation...
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
void readXml(QDomNode &node)
void setMagnificationFactor(double factor, const QgsPointXY *center=nullptr)
Set the magnification factor.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
void mapThemesChanged()
Emitted when map themes within the collection are changed.
void mapThemeRenamed(const QString &name, const QString &newName)
Emitted when a map theme within the collection is renamed.
bool hasMapTheme(const QString &name) const
Returns whether a map theme with a matching name exists.
void mapThemeChanged(const QString &theme)
Emitted when a map theme changes definition.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns the current map units per pixel.
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
A map tool for panning the map.
Definition: qgsmaptoolpan.h:33
Abstract base class for all map tools.
Definition: qgsmaptool.h:71
virtual void populateContextMenu(QMenu *menu)
Allows the tool to populate and customize the given menu, prior to showing it in response to a right-...
Definition: qgsmaptool.cpp:249
virtual bool canvasToolTipEvent(QHelpEvent *e)
Tooltip event for overriding.
Definition: qgsmaptool.cpp:209
virtual void canvasDoubleClickEvent(QgsMapMouseEvent *e)
Mouse double-click event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:173
virtual bool populateContextMenuWithEvent(QMenu *menu, QgsMapMouseEvent *event)
Allows the tool to populate and customize the given menu, prior to showing it in response to a right-...
Definition: qgsmaptool.cpp:255
virtual void canvasPressEvent(QgsMapMouseEvent *e)
Mouse press event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:178
virtual void canvasMoveEvent(QgsMapMouseEvent *e)
Mouse move event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:168
virtual void keyPressEvent(QKeyEvent *e)
Key event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:193
virtual void keyReleaseEvent(QKeyEvent *e)
Key event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:198
virtual Flags flags() const
Returns the flags for the map tool.
Definition: qgsmaptool.h:125
virtual void canvasReleaseEvent(QgsMapMouseEvent *e)
Mouse release event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:183
virtual void wheelEvent(QWheelEvent *e)
Mouse wheel event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:188
@ AllowZoomRect
Allow zooming by rectangle (by holding shift and dragging) while the tool is active.
Definition: qgsmaptool.h:116
@ EditTool
Map tool is an edit tool, which can only be used when layer is editable.
Definition: qgsmaptool.h:115
@ ShowContextMenu
Show a context menu when right-clicking with the tool (since QGIS 3.14). See populateContextMenu().
Definition: qgsmaptool.h:117
virtual void clean()
convenient method to clean members
Definition: qgsmaptool.cpp:120
virtual void activate()
called when set as currently active map tool
Definition: qgsmaptool.cpp:94
virtual bool gestureEvent(QGestureEvent *e)
gesture event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:203
virtual void deactivate()
called when map tool is being deactivated
Definition: qgsmaptool.cpp:110
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static bool isUriList(const QMimeData *data)
QList< QgsMimeDataUtils::Uri > UriList
static UriList decodeUriList(const QMimeData *data)
A class to represent a 2D point.
Definition: qgspointxy.h:59
double sqrDist(double x, double y) const SIP_HOLDGIL
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:190
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
A graphics effect which can be applied to a widget to simulate various printing and color blindness m...
void setMode(PreviewMode mode)
Sets the mode for the preview effect, which controls how the effect modifies a widgets appearance.
PreviewMode mode() const
Returns the mode used for the preview effect.
double defaultRotation() const
Returns the default map rotation (in clockwise degrees) for maps in the project.
QgsReferencedRectangle defaultViewExtent() const
Returns the default view extent, which should be used as the initial map extent when this project is ...
QgsReferencedRectangle fullExtent() const
Returns the full extent of the project, which represents the maximal limits of the project.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:104
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:477
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void ellipsoidChanged(const QString &ellipsoid)
Emitted when the project ellipsoid is changed.
QgsMapThemeCollection * mapThemeCollection
Definition: qgsproject.h:112
void projectColorsChanged()
Emitted whenever the project's color scheme has been changed.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:110
void readProject(const QDomDocument &)
Emitted when a project is being read.
void writeProject(QDomDocument &)
Emitted when the project is being written.
const QgsProjectViewSettings * viewSettings() const
Returns the project's view settings, which contains settings and properties relating to how a QgsProj...
void transformContextChanged()
Emitted when the project transformContext() is changed.
A generic dialog to prompt the user for a Coordinate Reference System.
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:256
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
void setMinimal() SIP_HOLDGIL
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:172
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
A QgsRectangle with associated coordinate reference system.
Stores collated details of rendered items during a map rendering operation.
void transferResults(QgsRenderedItemResults *other, const QStringList &layerIds)
Transfers all results from an other QgsRenderedItemResults object where the items have layer IDs matc...
A class for drawing transient features (e.g.
Definition: qgsrubberband.h:52
QColor strokeColor
Definition: qgsrubberband.h:76
void setWidth(int width)
Sets the width of the line.
void setSecondaryStrokeColor(const QColor &color)
Sets a secondary stroke color for the rubberband which will be drawn under the main stroke color.
@ ICON_CIRCLE
A circle is used to highlight points (○)
QColor fillColor
Definition: qgsrubberband.h:75
void setStrokeColor(const QColor &color)
Sets the stroke color for the rubberband.
QColor secondaryStrokeColor
Definition: qgsrubberband.h:78
void setIcon(IconType icon)
Sets the icon type to highlight point geometries.
void updatePosition() override
called on changed extent or resize event to update position of the item
void addGeometry(const QgsGeometry &geometry, QgsMapLayer *layer, bool doUpdate=true)
Adds the geometry of an existing feature to a rubberband This is useful for multi feature highlightin...
void setFillColor(const QColor &color)
Sets the fill color for the rubberband.
Scoped object for saving and restoring a QPainter object's state.
Scoped object for logging of the runtime for a single operation or group of operations.
A utility class for dynamic handling of changes to screen properties.
void screenDpiChanged(double dpi)
Emitted whenever the screen dpi associated with the widget is changed.
static const QgsSettingsEntryBool settingsRespectScreenDPI
Settings entry respect screen dpi.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
T enumValue(const QString &key, const T &defaultValue, const Section section=NoSection)
Returns the setting value for a setting based on an enum.
Definition: qgssettings.h:284
This class has all the configuration of snapping and can return answers to snapping queries.
void remoteSvgFetched(const QString &url)
Emitted when the cache has finished retrieving an SVG file from a remote url.
static double rendererFrameRate(const QgsFeatureRenderer *renderer)
Calculates the frame rate (in frames per second) at which the given renderer must be redrawn.
A controller base class for temporal objects, contains a signal for notifying updates of the objects ...
void updateTemporalRange(const QgsDateTimeRange &range)
Signals that a temporal range has changed and needs to be updated in all connected objects.
Implements a temporal controller based on a frame by frame navigation and animation.
@ NavigationOff
Temporal navigation is disabled.
@ FixedRange
Temporal navigation relies on a fixed datetime range.
@ Animated
Temporal navigation relies on frames within a datetime range.
void navigationModeChanged(QgsTemporalNavigationObject::NavigationMode mode)
Emitted whenever the navigation mode changes.
bool isActive() const
Returns true if the temporal property is active.
virtual QgsTemporalProperty::Flags flags() const
Returns flags associated to the temporal property.
@ FlagDontInvalidateCachedRendersWhenRangeChanges
Any cached rendering will not be invalidated when temporal range context is modified.
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
void setIsTemporal(bool enabled)
Sets whether the temporal range is enabled (i.e.
void setTemporalRange(const QgsDateTimeRange &range)
Sets the temporal range for the object.
Temporarily sets a cursor override for the QApplication for the lifetime of the object.
Definition: qgsguiutils.h:221
void release()
Releases the cursor override early (i.e.
DistanceUnit
Units of distance.
Definition: qgsunittypes.h:68
@ DistanceDegrees
Degrees, for planar geographic CRS distance measurements.
Definition: qgsunittypes.h:75
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
Q_INVOKABLE QgsRectangle boundingBoxOfSelected() const
Returns the bounding box of the selected features. If there is no selection, QgsRectangle(0,...
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
Implements a map layer that is dedicated to rendering of vector tiles.
QList< QgsFeature > selectedFeatures() const
Returns the list of features currently selected in the layer.
void selectionChanged()
Emitted whenever the selected features in the layer are changed.
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
A class to represent a vector.
Definition: qgsvector.h:30
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:968
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:141
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
constexpr double CANVAS_MAGNIFICATION_MIN
Minimum magnification level allowed in map canvases.
Definition: qgsguiutils.h:61
constexpr double CANVAS_MAGNIFICATION_MAX
Maximum magnification level allowed in map canvases.
Definition: qgsguiutils.h:69
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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2527
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2453
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
Stores settings related to the context in which a preview job runs.
double maxRenderingTimeMs
Default maximum allowable render time, in ms.
double lastRenderingTimeMs
Previous rendering time for the layer, in ms.