QGIS API Documentation  3.27.0-Master (aef1b1ec20)
qgsmapcanvas.cpp
Go to the documentation of this file.
1 /***************************************************************************
2 qgsmapcanvas.cpp - description
3 ------------------ -
4 begin : Sun Jun 30 2002
5 copyright : (C) 2002 by Gary E.Sherman
6 email : 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"
51 #include "qgsfeatureiterator.h"
52 #include "qgslogger.h"
53 #include "qgsmapcanvas.h"
54 #include "qgsmapcanvasmap.h"
56 #include "qgsmaplayer.h"
57 #include "qgsmapmouseevent.h"
58 #include "qgsmaptoolpan.h"
59 #include "qgsmaptoolzoom.h"
60 #include "qgsmaptopixel.h"
61 #include "qgsmapoverviewcanvas.h"
62 #include "qgsmaprenderercache.h"
64 #include "qgsmaprendererjob.h"
67 #include "qgsmapsettingsutils.h"
68 #include "qgsmessagelog.h"
69 #include "qgsmessageviewer.h"
70 #include "qgspallabeling.h"
71 #include "qgsproject.h"
72 #include "qgsrubberband.h"
73 #include "qgsvectorlayer.h"
74 #include "qgsmapthemecollection.h"
77 #include "qgssvgcache.h"
78 #include "qgsimagecache.h"
80 #include "qgsmimedatautils.h"
81 #include "qgscustomdrophandler.h"
82 #include "qgsreferencedgeometry.h"
83 #include "qgsprojectviewsettings.h"
87 #include "qgstemporalcontroller.h"
88 #include "qgsruntimeprofiler.h"
90 #include "qgsannotationlayer.h"
93 #include "qgslabelingresults.h"
94 #include "qgsmaplayerutils.h"
95 #include "qgssettingsregistrygui.h"
96 #include "qgsrendereditemresults.h"
98 #include "qgssymbollayerutils.h"
99 
105 //TODO QGIS 4.0 - remove
107 {
108  public:
109 
113  CanvasProperties() = default;
114 
116  bool mouseButtonDown{ false };
117 
119  QPoint mouseLastXY;
120 
123 
125  bool panSelectorDown{ false };
126 };
127 
128 
129 
130 QgsMapCanvas::QgsMapCanvas( QWidget *parent )
131  : QGraphicsView( parent )
132  , mCanvasProperties( new CanvasProperties )
133  , mExpressionContextScope( tr( "Map Canvas" ) )
134 {
135  mScene = new QGraphicsScene();
136  setScene( mScene );
137  setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
138  setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
139  setMouseTracking( true );
140  setFocusPolicy( Qt::StrongFocus );
141 
142  mResizeTimer = new QTimer( this );
143  mResizeTimer->setSingleShot( true );
144  connect( mResizeTimer, &QTimer::timeout, this, &QgsMapCanvas::refresh );
145 
146  mRefreshTimer = new QTimer( this );
147  mRefreshTimer->setSingleShot( true );
148  connect( mRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::refreshMap );
149 
150  // create map canvas item which will show the map
151  mMap = new QgsMapCanvasMap( this );
152 
153  // project handling
155  this, &QgsMapCanvas::readProject );
158 
159  connect( QgsProject::instance()->mainAnnotationLayer(), &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
160  connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsMapCanvas::mapThemeChanged );
161  connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsMapCanvas::mapThemeRenamed );
162  connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemesChanged, this, &QgsMapCanvas::projectThemesChanged );
163 
164  {
165  QgsScopedRuntimeProfile profile( "Map settings initialization" );
169  mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
171  this, [ = ]
172  {
173  mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
174  refresh();
175  } );
178  this, [ = ]
179  {
180  mSettings.setTransformContext( QgsProject::instance()->transformContext() );
182  refresh();
183  } );
184 
186  {
189  if ( mSettings.destinationCrs() != crs )
190  {
191  // user crs has changed definition, refresh the map
192  setDestinationCrs( crs );
193  }
194  } );
195  }
196 
197  // refresh canvas when a remote svg/image has finished downloading
200  // refresh canvas when project color scheme is changed -- if layers use project colors, they need to be redrawn
202 
203  //segmentation parameters
204  QgsSettings settings;
205  double segmentationTolerance = settings.value( QStringLiteral( "qgis/segmentationTolerance" ), "0.01745" ).toDouble();
206  QgsAbstractGeometry::SegmentationToleranceType toleranceType = settings.enumValue( QStringLiteral( "qgis/segmentationToleranceType" ), QgsAbstractGeometry::MaximumAngle );
207  mSettings.setSegmentationTolerance( segmentationTolerance );
208  mSettings.setSegmentationToleranceType( toleranceType );
209 
210  mWheelZoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
211 
212  QSize s = viewport()->size();
213  mSettings.setOutputSize( s );
214 
216 
217  setSceneRect( 0, 0, s.width(), s.height() );
218  mScene->setSceneRect( QRectF( 0, 0, s.width(), s.height() ) );
219 
220  moveCanvasContents( true );
221 
222  connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsMapCanvas::mapUpdateTimeout );
223  mMapUpdateTimer.setInterval( 250 );
224 
225 #ifdef Q_OS_WIN
226  // Enable touch event on Windows.
227  // Qt on Windows needs to be told it can take touch events or else it ignores them.
228  grabGesture( Qt::PinchGesture );
229  grabGesture( Qt::TapAndHoldGesture );
230  viewport()->setAttribute( Qt::WA_AcceptTouchEvents );
231 #endif
232 
233  mPreviewEffect = new QgsPreviewEffect( this );
234  viewport()->setGraphicsEffect( mPreviewEffect );
235 
236  mZoomCursor = QgsApplication::getThemeCursor( QgsApplication::Cursor::ZoomIn );
237 
238  connect( &mAutoRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::autoRefreshTriggered );
239 
241 
242  setInteractive( false );
243 
244  // make sure we have the same default in QgsMapSettings and the scene's background brush
245  // (by default map settings has white bg color, scene background brush is black)
246  setCanvasColor( mSettings.backgroundColor() );
247 
248  setTemporalRange( mSettings.temporalRange() );
249  refresh();
250 }
251 
252 
254 {
255  if ( mMapTool )
256  {
257  mMapTool->deactivate();
258  mMapTool = nullptr;
259  }
260  mLastNonZoomMapTool = nullptr;
261 
262  cancelJobs();
263 
264  // delete canvas items prior to deleting the canvas
265  // because they might try to update canvas when it's
266  // already being destructed, ends with segfault
267  qDeleteAll( mScene->items() );
268 
269  mScene->deleteLater(); // crashes in python tests on windows
270 
271  delete mCache;
272 }
273 
274 
276 {
277  // rendering job may still end up writing into canvas map item
278  // so kill it before deleting canvas items
279  if ( mJob )
280  {
281  whileBlocking( mJob )->cancel();
282  delete mJob;
283  mJob = nullptr;
284  }
285 
286  for ( auto previewJob = mPreviewJobs.constBegin(); previewJob != mPreviewJobs.constEnd(); ++previewJob )
287  {
288  if ( *previewJob )
289  {
290  whileBlocking( *previewJob )->cancel();
291  delete *previewJob;
292  }
293  }
294  mPreviewJobs.clear();
295 }
296 
297 void QgsMapCanvas::setMagnificationFactor( double factor, const QgsPointXY *center )
298 {
299  // do not go higher or lower than min max magnification ratio
300  double magnifierMin = QgsGuiUtils::CANVAS_MAGNIFICATION_MIN;
301  double magnifierMax = QgsGuiUtils::CANVAS_MAGNIFICATION_MAX;
302  factor = std::clamp( factor, magnifierMin, magnifierMax );
303 
304  // the magnifier widget is in integer percent
305  if ( !qgsDoubleNear( factor, mSettings.magnificationFactor(), 0.01 ) )
306  {
307  mSettings.setMagnificationFactor( factor, center );
308  refresh();
309  emit magnificationChanged( factor );
310  }
311 }
312 
314 {
315  return mSettings.magnificationFactor();
316 }
317 
319 {
320  mSettings.setFlag( Qgis::MapSettingsFlag::Antialiasing, flag );
322 }
323 
325 {
326  return mSettings.testFlag( Qgis::MapSettingsFlag::Antialiasing );
327 }
328 
330 {
332 }
333 
335 {
336  QList<QgsMapLayer *> layers = mapSettings().layers();
337  if ( index >= 0 && index < layers.size() )
338  return layers[index];
339  else
340  return nullptr;
341 }
342 
343 QgsMapLayer *QgsMapCanvas::layer( const QString &id )
344 {
345  // first check for layers from canvas map settings
346  const QList<QgsMapLayer *> layers = mapSettings().layers();
347  for ( QgsMapLayer *layer : layers )
348  {
349  if ( layer && layer->id() == id )
350  return layer;
351  }
352 
353  // else fallback to searching project layers
354  // TODO: allow a specific project to be associated with a canvas!
355  return QgsProject::instance()->mapLayer( id );
356 }
357 
359 {
360  if ( mCurrentLayer == layer )
361  return;
362 
363  mCurrentLayer = layer;
364  emit currentLayerChanged( layer );
365 }
366 
367 double QgsMapCanvas::scale() const
368 {
369  return mapSettings().scale();
370 }
371 
373 {
374  return nullptr != mJob;
375 } // isDrawing
376 
377 // return the current coordinate transform based on the extents and
378 // device size
380 {
381  return &mapSettings().mapToPixel();
382 }
383 
384 void QgsMapCanvas::setLayers( const QList<QgsMapLayer *> &layers )
385 {
386  // following a theme => request denied!
387  if ( !mTheme.isEmpty() )
388  return;
389 
390  setLayersPrivate( layers );
391 }
392 
393 void QgsMapCanvas::setLayersPrivate( const QList<QgsMapLayer *> &layers )
394 {
395  QList<QgsMapLayer *> oldLayers = mSettings.layers();
396 
397  // update only if needed
398  if ( layers == oldLayers )
399  return;
400 
401  const auto constOldLayers = oldLayers;
402  for ( QgsMapLayer *layer : constOldLayers )
403  {
404  disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
405  disconnect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
406  if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
407  {
409  disconnect( vlayer, &QgsVectorLayer::rendererChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
410  }
411  }
412 
413  mSettings.setLayers( layers );
414 
415  const auto constLayers = layers;
416  for ( QgsMapLayer *layer : constLayers )
417  {
418  if ( !layer )
419  continue;
420  connect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
421  connect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
422  if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
423  {
425  connect( vlayer, &QgsVectorLayer::rendererChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
426  }
427  }
428 
429  QgsDebugMsgLevel( QStringLiteral( "Layers have changed, refreshing" ), 2 );
430  emit layersChanged();
431 
432  updateAutoRefreshTimer();
433  refresh();
434 }
435 
436 
438 {
439  return mSettings;
440 }
441 
443 {
444  if ( mSettings.destinationCrs() == crs )
445  return;
446 
447  // try to reproject current extent to the new one
448  QgsRectangle rect;
449  if ( !mSettings.visibleExtent().isEmpty() )
450  {
451  const QgsCoordinateTransform transform( mSettings.destinationCrs(), crs, QgsProject::instance(),
454  try
455  {
456  rect = transform.transformBoundingBox( mSettings.visibleExtent() );
457  }
458  catch ( QgsCsException &e )
459  {
460  Q_UNUSED( e )
461  QgsDebugMsg( QStringLiteral( "Transform error caught: %1" ).arg( e.what() ) );
462  }
463  }
464 
465  if ( !rect.isEmpty() )
466  {
467  // we will be manually calling updateCanvasItemPositions() later, AFTER setting the updating the mSettings destination CRS, and we don't
468  // want to do that twice!
469  mBlockItemPositionUpdates++;
470  setExtent( rect );
471  mBlockItemPositionUpdates--;
472  }
473 
474  mSettings.setDestinationCrs( crs );
475  updateScale();
477 
478  QgsDebugMsgLevel( QStringLiteral( "refreshing after destination CRS changed" ), 2 );
479  refresh();
480 
481  emit destinationCrsChanged();
482 }
483 
485 {
486  if ( mController )
488  if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast< QgsTemporalNavigationObject * >( mController ) )
489  {
490  disconnect( temporalNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsMapCanvas::temporalControllerModeChanged );
491 
492  // clear any existing animation settings from map settings. We don't do this on every render, as a 3rd party plugin
493  // might be in control of these!
494  mSettings.setFrameRate( -1 );
495  mSettings.setCurrentFrame( -1 );
496  }
497 
498  mController = controller;
500  if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast< QgsTemporalNavigationObject * >( mController ) )
501  connect( temporalNavigationObject, &QgsTemporalNavigationObject::navigationModeChanged, this, &QgsMapCanvas::temporalControllerModeChanged );
502 }
503 
504 void QgsMapCanvas::temporalControllerModeChanged()
505 {
506  if ( QgsTemporalNavigationObject *temporalNavigationObject = qobject_cast< QgsTemporalNavigationObject * >( mController ) )
507  {
508  switch ( temporalNavigationObject->navigationMode() )
509  {
511  mSettings.setFrameRate( temporalNavigationObject->framesPerSecond() );
512  mSettings.setCurrentFrame( temporalNavigationObject->currentFrameNumber() );
513  break;
514 
517  // clear any existing animation settings from map settings. We don't do this on every render, as a 3rd party plugin
518  // might be in control of these!
519  mSettings.setFrameRate( -1 );
520  mSettings.setCurrentFrame( -1 );
521  break;
522  }
523  }
524 }
525 
527 {
528  return mController;
529 }
530 
531 void QgsMapCanvas::setMapSettingsFlags( Qgis::MapSettingsFlags flags )
532 {
533  mSettings.setFlags( flags );
534  clearCache();
535  refresh();
536 }
537 
538 const QgsLabelingResults *QgsMapCanvas::labelingResults( bool allowOutdatedResults ) const
539 {
540  if ( !allowOutdatedResults && mLabelingResultsOutdated )
541  return nullptr;
542 
543  return mLabelingResults.get();
544 }
545 
546 const QgsRenderedItemResults *QgsMapCanvas::renderedItemResults( bool allowOutdatedResults ) const
547 {
548  if ( !allowOutdatedResults && mRenderedItemResultsOutdated )
549  return nullptr;
550 
551  return mRenderedItemResults.get();
552 }
553 
555 {
556  if ( enabled == isCachingEnabled() )
557  return;
558 
559  if ( mJob && mJob->isActive() )
560  {
561  // wait for the current rendering to finish, before touching the cache
562  mJob->waitForFinished();
563  }
564 
565  if ( enabled )
566  {
567  mCache = new QgsMapRendererCache;
568  }
569  else
570  {
571  delete mCache;
572  mCache = nullptr;
573  }
574  mPreviousRenderedItemResults.reset();
575 }
576 
578 {
579  return nullptr != mCache;
580 }
581 
583 {
584  if ( mCache )
585  mCache->clear();
586 
587  if ( mPreviousRenderedItemResults )
588  mPreviousRenderedItemResults.reset();
589  if ( mRenderedItemResults )
590  mRenderedItemResults.reset();
591 }
592 
594 {
595  mUseParallelRendering = enabled;
596 }
597 
599 {
600  return mUseParallelRendering;
601 }
602 
603 void QgsMapCanvas::setMapUpdateInterval( int timeMilliseconds )
604 {
605  mMapUpdateTimer.setInterval( timeMilliseconds );
606 }
607 
609 {
610  return mMapUpdateTimer.interval();
611 }
612 
613 
615 {
616  return mCurrentLayer;
617 }
618 
620 {
621  QgsExpressionContextScope *s = new QgsExpressionContextScope( QObject::tr( "Map Canvas" ) );
622  s->setVariable( QStringLiteral( "canvas_cursor_point" ), QgsGeometry::fromPointXY( cursorPoint() ), true );
623  return s;
624 }
625 
627 {
628  //build the expression context
629  QgsExpressionContext expressionContext;
630  expressionContext << QgsExpressionContextUtils::globalScope()
634  if ( QgsExpressionContextScopeGenerator *generator = dynamic_cast< QgsExpressionContextScopeGenerator * >( mController ) )
635  {
636  expressionContext << generator->createExpressionContextScope();
637  }
638  expressionContext << defaultExpressionContextScope()
639  << new QgsExpressionContextScope( mExpressionContextScope );
640  return expressionContext;
641 }
642 
644 {
645  if ( !mSettings.hasValidSettings() )
646  {
647  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh - invalid settings -> nothing to do" ), 2 );
648  return;
649  }
650 
651  if ( !mRenderFlag || mFrozen )
652  {
653  QgsDebugMsgLevel( QStringLiteral( "CANVAS render flag off" ), 2 );
654  return;
655  }
656 
657  if ( mRefreshScheduled )
658  {
659  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh already scheduled" ), 2 );
660  return;
661  }
662 
663  mRefreshScheduled = true;
664 
665  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh scheduling" ), 2 );
666 
667  // schedule a refresh
668  mRefreshTimer->start( 1 );
669 
670  mLabelingResultsOutdated = true;
671  mRenderedItemResultsOutdated = true;
672 }
673 
674 void QgsMapCanvas::refreshMap()
675 {
676  Q_ASSERT( mRefreshScheduled );
677 
678  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh!" ), 3 );
679 
680  stopRendering(); // if any...
681  stopPreviewJobs();
682 
684 
685  // if using the temporal controller in animation mode, get the frame settings from that
686  if ( QgsTemporalNavigationObject *temporalNavigationObject = dynamic_cast < QgsTemporalNavigationObject * >( mController ) )
687  {
688  switch ( temporalNavigationObject->navigationMode() )
689  {
691  mSettings.setFrameRate( temporalNavigationObject->framesPerSecond() );
692  mSettings.setCurrentFrame( temporalNavigationObject->currentFrameNumber() );
693  break;
694 
697  break;
698  }
699  }
700 
701  mSettings.setPathResolver( QgsProject::instance()->pathResolver() );
702 
703  if ( !mTheme.isEmpty() )
704  {
705  // IMPORTANT: we MUST set the layer style overrides here! (At the time of writing this
706  // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
707  // current state of the style. If we had stored the style overrides earlier (such as in
708  // mapThemeChanged slot) then this xml could be out of date...
709  // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
710  // just return the style name, we can instead set the overrides in mapThemeChanged and not here
711  mSettings.setLayerStyleOverrides( QgsProject::instance()->mapThemeCollection()->mapThemeStyleOverrides( mTheme ) );
712  }
713 
714  // render main annotation layer above all other layers
715  QgsMapSettings renderSettings = mSettings;
716  QList<QgsMapLayer *> allLayers = renderSettings.layers();
717  allLayers.insert( 0, QgsProject::instance()->mainAnnotationLayer() );
718  renderSettings.setLayers( allLayers );
719 
720  // create the renderer job
721  Q_ASSERT( !mJob );
722  mJobCanceled = false;
723  if ( mUseParallelRendering )
724  mJob = new QgsMapRendererParallelJob( renderSettings );
725  else
726  mJob = new QgsMapRendererSequentialJob( renderSettings );
727 
728  connect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
729  mJob->setCache( mCache );
730  mJob->setLayerRenderingTimeHints( mLastLayerRenderTime );
731 
732  mJob->start();
733 
734  // from now on we can accept refresh requests again
735  // this must be reset only after the job has been started, because
736  // some providers (yes, it's you WCS and AMS!) during preparation
737  // do network requests and start an internal event loop, which may
738  // end up calling refresh() and would schedule another refresh,
739  // deleting the one we have just started.
740  mRefreshScheduled = false;
741 
742  mMapUpdateTimer.start();
743 
744  emit renderStarting();
745 }
746 
747 void QgsMapCanvas::mapThemeChanged( const QString &theme )
748 {
749  if ( theme == mTheme )
750  {
751  // set the canvas layers to match the new layers contained in the map theme
752  // NOTE: we do this when the theme layers change and not when we are refreshing the map
753  // as setLayers() sets up necessary connections to handle changes to the layers
754  setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
755  // IMPORTANT: we don't set the layer style overrides here! (At the time of writing this
756  // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
757  // current state of the style. If changes were made to the style then this xml
758  // snapshot goes out of sync...
759  // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
760  // just return the style name, we can instead set the overrides here and not in refreshMap()
761 
762  clearCache();
763  refresh();
764  }
765 }
766 
767 void QgsMapCanvas::mapThemeRenamed( const QString &theme, const QString &newTheme )
768 {
769  if ( mTheme.isEmpty() || theme != mTheme )
770  {
771  return;
772  }
773 
774  setTheme( newTheme );
775  refresh();
776 }
777 
778 void QgsMapCanvas::rendererJobFinished()
779 {
780  QgsDebugMsgLevel( QStringLiteral( "CANVAS finish! %1" ).arg( !mJobCanceled ), 2 );
781 
782  mMapUpdateTimer.stop();
783 
784  notifyRendererErrors( mJob->errors() );
785 
786  if ( !mJobCanceled )
787  {
788  // take labeling results before emitting renderComplete, so labeling map tools
789  // connected to signal work with correct results
790  if ( !mJob->usedCachedLabels() )
791  {
792  mLabelingResults.reset( mJob->takeLabelingResults() );
793  }
794  mLabelingResultsOutdated = false;
795 
796  std::unique_ptr< QgsRenderedItemResults > renderedItemResults( mJob->takeRenderedItemResults() );
797  // if a layer was redrawn from the cached version, we should copy any existing rendered item results from that layer
798  if ( mRenderedItemResults )
799  {
800  renderedItemResults->transferResults( mRenderedItemResults.get(), mJob->layersRedrawnFromCache() );
801  }
802  if ( mPreviousRenderedItemResults )
803  {
804  // also transfer any results from previous renders which happened before this
805  renderedItemResults->transferResults( mPreviousRenderedItemResults.get(), mJob->layersRedrawnFromCache() );
806  }
807 
808  if ( mCache && !mPreviousRenderedItemResults )
809  mPreviousRenderedItemResults = std::make_unique< QgsRenderedItemResults >( mJob->mapSettings().extent() );
810 
811  if ( mRenderedItemResults && mPreviousRenderedItemResults )
812  {
813  // for other layers which ARE present in the most recent rendered item results BUT were not part of this render, we
814  // store the results in a temporary store in case they are later switched back on and the layer's image is taken
815  // from the cache
816  mPreviousRenderedItemResults->transferResults( mRenderedItemResults.get() );
817  }
818  if ( mPreviousRenderedItemResults )
819  {
820  mPreviousRenderedItemResults->eraseResultsFromLayers( mJob->mapSettings().layerIds() );
821  }
822 
823  mRenderedItemResults = std::move( renderedItemResults );
824  mRenderedItemResultsOutdated = false;
825 
826  QImage img = mJob->renderedImage();
827 
828  // emit renderComplete to get our decorations drawn
829  QPainter p( &img );
830  emit renderComplete( &p );
831 
833  {
834  QString logMsg = tr( "Canvas refresh: %1 ms" ).arg( mJob->renderingTime() );
835  QgsMessageLog::logMessage( logMsg, tr( "Rendering" ) );
836  }
837 
838  if ( mDrawRenderingStats )
839  {
840  int w = img.width(), h = img.height();
841  QFont fnt = p.font();
842  fnt.setBold( true );
843  p.setFont( fnt );
844  int lh = p.fontMetrics().height() * 2;
845  QRect r( 0, h - lh, w, lh );
846  p.setPen( Qt::NoPen );
847  p.setBrush( QColor( 0, 0, 0, 110 ) );
848  p.drawRect( r );
849  p.setPen( Qt::white );
850  QString msg = QStringLiteral( "%1 :: %2 ms" ).arg( mUseParallelRendering ? QStringLiteral( "PARALLEL" ) : QStringLiteral( "SEQUENTIAL" ) ).arg( mJob->renderingTime() );
851  p.drawText( r, msg, QTextOption( Qt::AlignCenter ) );
852  }
853 
854  p.end();
855 
856  mMap->setContent( img, imageRect( img, mSettings ) );
857 
858  mLastLayerRenderTime.clear();
859  const auto times = mJob->perLayerRenderingTime();
860  for ( auto it = times.constBegin(); it != times.constEnd(); ++it )
861  {
862  mLastLayerRenderTime.insert( it.key()->id(), it.value() );
863  }
864  if ( mUsePreviewJobs && !mRefreshAfterJob )
865  startPreviewJobs();
866  }
867  else
868  {
869  mRefreshAfterJob = false;
870  }
871 
872  // now we are in a slot called from mJob - do not delete it immediately
873  // so the class is still valid when the execution returns to the class
874  mJob->deleteLater();
875  mJob = nullptr;
876 
877  emit mapCanvasRefreshed();
878 
879  if ( mRefreshAfterJob )
880  {
881  mRefreshAfterJob = false;
882  clearTemporalCache();
883  clearElevationCache();
884  refresh();
885  }
886 }
887 
888 void QgsMapCanvas::previewJobFinished()
889 {
890  QgsMapRendererQImageJob *job = qobject_cast<QgsMapRendererQImageJob *>( sender() );
891  Q_ASSERT( job );
892 
893  if ( mMap )
894  {
895  mMap->addPreviewImage( job->renderedImage(), job->mapSettings().extent() );
896  mPreviewJobs.removeAll( job );
897 
898  int number = job->property( "number" ).toInt();
899  if ( number < 8 )
900  {
901  startPreviewJob( number + 1 );
902  }
903 
904  delete job;
905  }
906 }
907 
908 QgsRectangle QgsMapCanvas::imageRect( const QImage &img, const QgsMapSettings &mapSettings )
909 {
910  // This is a hack to pass QgsMapCanvasItem::setRect what it
911  // expects (encoding of position and size of the item)
912  const QgsMapToPixel &m2p = mapSettings.mapToPixel();
913  QgsPointXY topLeft = m2p.toMapCoordinates( 0, 0 );
914 #ifdef QGISDEBUG
915  // do not assert this, since it might lead to crashes when changing screen while rendering
916  if ( img.devicePixelRatio() != mapSettings.devicePixelRatio() )
917  {
918  QgsLogger::warning( QStringLiteral( "The renderer map has a wrong device pixel ratio" ) );
919  }
920 #endif
921  double res = m2p.mapUnitsPerPixel() / img.devicePixelRatioF();
922  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + img.width()*res, topLeft.y() - img.height()*res );
923  return rect;
924 }
925 
927 {
928  return mUsePreviewJobs;
929 }
930 
932 {
933  mUsePreviewJobs = enabled;
934 }
935 
936 void QgsMapCanvas::setCustomDropHandlers( const QVector<QPointer<QgsCustomDropHandler> > &handlers )
937 {
938  mDropHandlers = handlers;
939 }
940 
941 void QgsMapCanvas::clearTemporalCache()
942 {
943  if ( mCache )
944  {
945  bool invalidateLabels = false;
946  const QList<QgsMapLayer *> layerList = mapSettings().layers();
947  for ( QgsMapLayer *layer : layerList )
948  {
949  bool alreadyInvalidatedThisLayer = false;
950  if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
951  {
952  if ( vl->renderer() && QgsSymbolLayerUtils::rendererFrameRate( vl->renderer() ) > -1 )
953  {
954  // layer has an animated symbol assigned, so we have to redraw it regardless of whether
955  // or not it has temporal settings
956  mCache->invalidateCacheForLayer( layer );
957  alreadyInvalidatedThisLayer = true;
958  // we can't shortcut and "continue" here, as we still need to check whether the layer
959  // will cause label invalidation using the logic below
960  }
961  }
962 
964  {
965  if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
966  {
967  if ( vl->labelsEnabled() || vl->diagramsEnabled() )
968  invalidateLabels = true;
969  }
970 
972  continue;
973 
974  if ( !alreadyInvalidatedThisLayer )
975  mCache->invalidateCacheForLayer( layer );
976  }
977  }
978 
979  if ( invalidateLabels )
980  {
981  mCache->clearCacheImage( QStringLiteral( "_labels_" ) );
982  mCache->clearCacheImage( QStringLiteral( "_preview_labels_" ) );
983  }
984  }
985 }
986 
987 void QgsMapCanvas::clearElevationCache()
988 {
989  if ( mCache )
990  {
991  bool invalidateLabels = false;
992  const QList<QgsMapLayer *> layerList = mapSettings().layers();
993  for ( QgsMapLayer *layer : layerList )
994  {
996  {
997  if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
998  {
999  if ( vl->labelsEnabled() || vl->diagramsEnabled() )
1000  invalidateLabels = true;
1001  }
1002 
1004  continue;
1005 
1006  mCache->invalidateCacheForLayer( layer );
1007  }
1008  }
1009 
1010  if ( invalidateLabels )
1011  {
1012  mCache->clearCacheImage( QStringLiteral( "_labels_" ) );
1013  mCache->clearCacheImage( QStringLiteral( "_preview_labels_" ) );
1014  }
1015  }
1016 }
1017 
1018 void QgsMapCanvas::showContextMenu( QgsMapMouseEvent *event )
1019 {
1020  const QgsPointXY mapPoint = event->originalMapPoint();
1021 
1022  QMenu menu;
1023 
1024  QMenu *copyCoordinateMenu = new QMenu( tr( "Copy Coordinate" ), &menu );
1025  copyCoordinateMenu->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ) );
1026 
1027  auto addCoordinateFormat = [ &, this]( const QString identifier, const QgsCoordinateReferenceSystem & crs )
1028  {
1029  const QgsCoordinateTransform ct( mSettings.destinationCrs(), crs, mSettings.transformContext() );
1030  try
1031  {
1032  const QgsPointXY transformedPoint = ct.transform( mapPoint );
1033 
1034  // calculate precision based on visible map extent -- if user is zoomed in, we get better precision!
1035  int displayPrecision = 0;
1036  try
1037  {
1038  QgsCoordinateTransform extentTransform = ct;
1039  extentTransform.setBallparkTransformsAreAppropriate( true );
1040  QgsRectangle extentReproj = extentTransform.transformBoundingBox( extent() );
1041  const double mapUnitsPerPixel = ( extentReproj.width() / width() + extentReproj.height() / height() ) * 0.5;
1042  if ( mapUnitsPerPixel > 10 )
1043  displayPrecision = 0;
1044  else if ( mapUnitsPerPixel > 1 )
1045  displayPrecision = 1;
1046  else if ( mapUnitsPerPixel > 0.1 )
1047  displayPrecision = 2;
1048  else if ( mapUnitsPerPixel > 0.01 )
1049  displayPrecision = 3;
1050  else if ( mapUnitsPerPixel > 0.001 )
1051  displayPrecision = 4;
1052  else if ( mapUnitsPerPixel > 0.0001 )
1053  displayPrecision = 5;
1054  else if ( mapUnitsPerPixel > 0.00001 )
1055  displayPrecision = 6;
1056  else if ( mapUnitsPerPixel > 0.000001 )
1057  displayPrecision = 7;
1058  else if ( mapUnitsPerPixel > 0.0000001 )
1059  displayPrecision = 8;
1060  else
1061  displayPrecision = 9;
1062  }
1063  catch ( QgsCsException & )
1064  {
1065  displayPrecision = crs.mapUnits() == QgsUnitTypes::DistanceDegrees ? 5 : 3;
1066  }
1067 
1068  const QList< Qgis::CrsAxisDirection > axisList = crs.axisOrdering();
1069  QString firstSuffix;
1070  QString secondSuffix;
1071  if ( axisList.size() >= 2 )
1072  {
1075  }
1076 
1077  QString firstNumber;
1078  QString secondNumber;
1080  {
1081  firstNumber = QString::number( transformedPoint.y(), 'f', displayPrecision );
1082  secondNumber = QString::number( transformedPoint.x(), 'f', displayPrecision );
1083  }
1084  else
1085  {
1086  firstNumber = QString::number( transformedPoint.x(), 'f', displayPrecision );
1087  secondNumber = QString::number( transformedPoint.y(), 'f', displayPrecision );
1088  }
1089 
1090  QAction *copyCoordinateAction = new QAction( QStringLiteral( "%5 (%1%2, %3%4)" ).arg(
1091  firstNumber, firstSuffix, secondNumber, secondSuffix, identifier ), &menu );
1092 
1093  connect( copyCoordinateAction, &QAction::triggered, this, [firstNumber, secondNumber, transformedPoint]
1094  {
1095  QClipboard *clipboard = QApplication::clipboard();
1096 
1097  const QString coordinates = firstNumber + ',' + secondNumber;
1098 
1099  //if we are on x11 system put text into selection ready for middle button pasting
1100  if ( clipboard->supportsSelection() )
1101  {
1102  clipboard->setText( coordinates, QClipboard::Selection );
1103  }
1104  clipboard->setText( coordinates, QClipboard::Clipboard );
1105 
1106  } );
1107  copyCoordinateMenu->addAction( copyCoordinateAction );
1108  }
1109  catch ( QgsCsException & )
1110  {
1111 
1112  }
1113  };
1114 
1115  addCoordinateFormat( tr( "Map CRS — %1" ).arg( mSettings.destinationCrs().userFriendlyIdentifier( QgsCoordinateReferenceSystem::MediumString ) ), mSettings.destinationCrs() );
1116  QgsCoordinateReferenceSystem wgs84( QStringLiteral( "EPSG:4326" ) );
1117  if ( mSettings.destinationCrs() != wgs84 )
1118  addCoordinateFormat( wgs84.userFriendlyIdentifier( QgsCoordinateReferenceSystem::MediumString ), wgs84 );
1119 
1120  QgsSettings settings;
1121  const QString customCrsString = settings.value( QStringLiteral( "qgis/custom_coordinate_crs" ) ).toString();
1122  if ( !customCrsString.isEmpty() )
1123  {
1124  QgsCoordinateReferenceSystem customCrs( customCrsString );
1125  if ( customCrs != mSettings.destinationCrs() && customCrs != QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) )
1126  {
1127  addCoordinateFormat( customCrs.userFriendlyIdentifier( QgsCoordinateReferenceSystem::MediumString ), customCrs );
1128  }
1129  }
1130  copyCoordinateMenu->addSeparator();
1131  QAction *setCustomCrsAction = new QAction( tr( "Set Custom CRS…" ), &menu );
1132  connect( setCustomCrsAction, &QAction::triggered, this, [ = ]
1133  {
1134  QgsProjectionSelectionDialog selector( this );
1135  selector.setCrs( QgsCoordinateReferenceSystem( customCrsString ) );
1136  if ( selector.exec() )
1137  {
1138  QgsSettings().setValue( QStringLiteral( "qgis/custom_coordinate_crs" ), selector.crs().authid().isEmpty() ? selector.crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) : selector.crs().authid() );
1139  }
1140  } );
1141  copyCoordinateMenu->addAction( setCustomCrsAction );
1142 
1143  menu.addMenu( copyCoordinateMenu );
1144 
1145  if ( mMapTool )
1146  if ( !mapTool()->populateContextMenuWithEvent( &menu, event ) )
1147  mMapTool->populateContextMenu( &menu );
1148 
1149  emit contextMenuAboutToShow( &menu, event );
1150 
1151  menu.exec( event->globalPos() );
1152 }
1153 
1154 void QgsMapCanvas::notifyRendererErrors( const QgsMapRendererJob::Errors &errors )
1155 {
1156  const QDateTime currentTime = QDateTime::currentDateTime();
1157 
1158  // remove errors too old
1159  for ( const QgsMapRendererJob::Error &error : errors )
1160  {
1161  const QString errorKey = error.layerID + ':' + error.message;
1162  if ( mRendererErrors.contains( errorKey ) )
1163  {
1164  const QDateTime sameErrorTime = mRendererErrors.value( errorKey );
1165 
1166  if ( sameErrorTime.secsTo( currentTime ) < 60 )
1167  continue;
1168  }
1169 
1170  mRendererErrors[errorKey] = currentTime;
1171 
1172  if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( error.layerID ) )
1173  emit renderErrorOccurred( error.message, layer );
1174  }
1175 }
1176 
1177 void QgsMapCanvas::updateDevicePixelFromScreen()
1178 {
1179  mSettings.setDevicePixelRatio( devicePixelRatio() );
1180  // TODO: QGIS 4 -> always respect screen dpi
1182  {
1183  if ( window()->windowHandle() )
1184  {
1185  mSettings.setOutputDpi( window()->windowHandle()->screen()->physicalDotsPerInch() );
1186  mSettings.setDpiTarget( window()->windowHandle()->screen()->physicalDotsPerInch() );
1187  }
1188  }
1189  else
1190  {
1191  // Fallback: compatibility with QGIS <= 3.20; always assume low dpi screens
1192  mSettings.setOutputDpi( window()->windowHandle()->screen()->logicalDotsPerInch() );
1193  mSettings.setDpiTarget( window()->windowHandle()->screen()->logicalDotsPerInch() );
1194  }
1195 }
1196 
1197 void QgsMapCanvas::setTemporalRange( const QgsDateTimeRange &dateTimeRange )
1198 {
1199  if ( temporalRange() == dateTimeRange )
1200  return;
1201 
1202  mSettings.setTemporalRange( dateTimeRange );
1203  mSettings.setIsTemporal( dateTimeRange.begin().isValid() || dateTimeRange.end().isValid() );
1204 
1205  emit temporalRangeChanged();
1206 
1207  // we need to discard any previously cached images which have temporal properties enabled, so that these will be updated when
1208  // the canvas is redrawn
1209  if ( !mJob )
1210  clearTemporalCache();
1211 
1212  autoRefreshTriggered();
1213 }
1214 
1215 const QgsDateTimeRange &QgsMapCanvas::temporalRange() const
1216 {
1217  return mSettings.temporalRange();
1218 }
1219 
1221 {
1222  mInteractionBlockers.append( blocker );
1223 }
1224 
1226 {
1227  mInteractionBlockers.removeAll( blocker );
1228 }
1229 
1231 {
1232  for ( const QgsMapCanvasInteractionBlocker *block : mInteractionBlockers )
1233  {
1234  if ( block->blockCanvasInteraction( interaction ) )
1235  return false;
1236  }
1237  return true;
1238 }
1239 
1240 void QgsMapCanvas::mapUpdateTimeout()
1241 {
1242  if ( mJob )
1243  {
1244  const QImage &img = mJob->renderedImage();
1245  mMap->setContent( img, imageRect( img, mSettings ) );
1246  }
1247 }
1248 
1250 {
1251  if ( mJob )
1252  {
1253  QgsDebugMsgLevel( QStringLiteral( "CANVAS stop rendering!" ), 2 );
1254  mJobCanceled = true;
1255  disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
1256  connect( mJob, &QgsMapRendererQImageJob::finished, mJob, &QgsMapRendererQImageJob::deleteLater );
1257  mJob->cancelWithoutBlocking();
1258  mJob = nullptr;
1259  emit mapRefreshCanceled();
1260  }
1261  stopPreviewJobs();
1262 }
1263 
1264 //the format defaults to "PNG" if not specified
1265 void QgsMapCanvas::saveAsImage( const QString &fileName, QPixmap *theQPixmap, const QString &format )
1266 {
1267  QPainter painter;
1268  QImage image;
1269 
1270  //
1271  //check if the optional QPaintDevice was supplied
1272  //
1273  if ( theQPixmap )
1274  {
1275  image = theQPixmap->toImage();
1276  painter.begin( &image );
1277 
1278  // render
1279  QgsMapRendererCustomPainterJob job( mSettings, &painter );
1280  job.start();
1281  job.waitForFinished();
1282  emit renderComplete( &painter );
1283  }
1284  else //use the map view
1285  {
1286  image = mMap->contentImage().copy();
1287  painter.begin( &image );
1288  }
1289 
1290  // draw annotations
1291  QStyleOptionGraphicsItem option;
1292  option.initFrom( this );
1293  QGraphicsItem *item = nullptr;
1294  QListIterator<QGraphicsItem *> i( items() );
1295  i.toBack();
1296  while ( i.hasPrevious() )
1297  {
1298  item = i.previous();
1299 
1300  if ( !( item && dynamic_cast< QgsMapCanvasAnnotationItem * >( item ) ) )
1301  {
1302  continue;
1303  }
1304 
1305  QgsScopedQPainterState painterState( &painter );
1306 
1307  QPointF itemScenePos = item->scenePos();
1308  painter.translate( itemScenePos.x(), itemScenePos.y() );
1309 
1310  item->paint( &painter, &option );
1311  }
1312 
1313  painter.end();
1314  image.save( fileName, format.toLocal8Bit().data() );
1315 
1316  QFileInfo myInfo = QFileInfo( fileName );
1317 
1318  // build the world file name
1319  QString outputSuffix = myInfo.suffix();
1320  QString myWorldFileName = myInfo.absolutePath() + '/' + myInfo.completeBaseName() + '.'
1321  + outputSuffix.at( 0 ) + outputSuffix.at( myInfo.suffix().size() - 1 ) + 'w';
1322  QFile myWorldFile( myWorldFileName );
1323  if ( !myWorldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) //don't use QIODevice::Text
1324  {
1325  return;
1326  }
1327  QTextStream myStream( &myWorldFile );
1329 }
1330 
1332 {
1333  return mapSettings().visibleExtent();
1334 }
1335 
1337 {
1338  return QgsMapLayerUtils::combinedExtent( mSettings.layers(), mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
1339 }
1340 
1342 {
1344  QgsCoordinateTransform ct( extent.crs(), mapSettings().destinationCrs(), mProject ? mProject->transformContext() : QgsProject::instance()->transformContext() );
1346  QgsRectangle rect;
1347  try
1348  {
1349  rect = ct.transformBoundingBox( extent );
1350  }
1351  catch ( QgsCsException & )
1352  {
1353  rect = mapSettings().fullExtent();
1354  }
1355 
1356  return rect;
1357 }
1358 
1359 void QgsMapCanvas::setExtent( const QgsRectangle &r, bool magnified )
1360 {
1361  QgsRectangle current = extent();
1362 
1363  if ( ( r == current ) && magnified )
1364  return;
1365 
1366  if ( r.isEmpty() )
1367  {
1368  if ( !mSettings.hasValidSettings() )
1369  {
1370  // we can't even just move the map center
1371  QgsDebugMsgLevel( QStringLiteral( "Empty extent - ignoring" ), 2 );
1372  return;
1373  }
1374 
1375  // ### QGIS 3: do not allow empty extent - require users to call setCenter() explicitly
1376  QgsDebugMsgLevel( QStringLiteral( "Empty extent - keeping old scale with new center!" ), 2 );
1377  setCenter( r.center() );
1378  }
1379  else
1380  {
1381  // If scale is locked we need to maintain the current scale, so we
1382  // - magnify and recenter the map
1383  // - restore locked scale
1384  if ( mScaleLocked && magnified )
1385  {
1386  ScaleRestorer restorer( this );
1387  const double ratio { extent().width() / extent().height() };
1388  const double factor { r.width() / r.height() > ratio ? extent().width() / r.width() : extent().height() / r.height() };
1389  const double scaleFactor { std::clamp( mSettings.magnificationFactor() * factor, QgsGuiUtils::CANVAS_MAGNIFICATION_MIN, QgsGuiUtils::CANVAS_MAGNIFICATION_MAX ) };
1390  const QgsPointXY newCenter { r.center() };
1391  mSettings.setMagnificationFactor( scaleFactor, &newCenter );
1392  emit magnificationChanged( scaleFactor );
1393  }
1394  else
1395  {
1396  mSettings.setExtent( r, magnified );
1397  }
1398  }
1399  emit extentsChanged();
1400  updateScale();
1401 
1402  //clear all extent items after current index
1403  for ( int i = mLastExtent.size() - 1; i > mLastExtentIndex; i-- )
1404  {
1405  mLastExtent.removeAt( i );
1406  }
1407 
1408  if ( !mLastExtent.isEmpty() && mLastExtent.last() != extent() )
1409  {
1410  mLastExtent.append( extent() );
1411  }
1412 
1413  // adjust history to no more than 100
1414  if ( mLastExtent.size() > 100 )
1415  {
1416  mLastExtent.removeAt( 0 );
1417  }
1418 
1419  // the last item is the current extent
1420  mLastExtentIndex = mLastExtent.size() - 1;
1421 
1422  // update controls' enabled state
1423  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1424  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1425 }
1426 
1428 {
1429  QgsRectangle canvasExtent = extent;
1430  if ( extent.crs() != mapSettings().destinationCrs() )
1431  {
1432  QgsCoordinateTransform ct( extent.crs(), mapSettings().destinationCrs(), QgsProject::instance() );
1434  canvasExtent = ct.transformBoundingBox( extent );
1435 
1436  if ( canvasExtent.isEmpty() )
1437  {
1438  return false;
1439  }
1440  }
1441 
1442  setExtent( canvasExtent, true );
1443  return true;
1444 }
1445 
1447 {
1448  const QgsRectangle r = mapSettings().extent();
1449  const double xMin = center.x() - r.width() / 2.0;
1450  const double yMin = center.y() - r.height() / 2.0;
1451  const QgsRectangle rect(
1452  xMin, yMin,
1453  xMin + r.width(), yMin + r.height()
1454  );
1455  if ( ! rect.isEmpty() )
1456  {
1457  setExtent( rect, true );
1458  }
1459 } // setCenter
1460 
1462 {
1464  return r.center();
1465 }
1466 
1467 QgsPointXY QgsMapCanvas::cursorPoint() const
1468 {
1469  return mCursorPoint;
1470 }
1471 
1473 {
1474  return mapSettings().rotation();
1475 } // rotation
1476 
1477 void QgsMapCanvas::setRotation( double degrees )
1478 {
1479  double current = rotation();
1480 
1481  if ( qgsDoubleNear( degrees, current ) )
1482  return;
1483 
1484  mSettings.setRotation( degrees );
1485  emit rotationChanged( degrees );
1486  emit extentsChanged(); // visible extent changes with rotation
1487 } // setRotation
1488 
1489 
1491 {
1492  emit scaleChanged( mapSettings().scale() );
1493 }
1494 
1496 {
1498  // If the full extent is an empty set, don't do the zoom
1499  if ( !extent.isEmpty() )
1500  {
1501  // Add a 5% margin around the full extent
1502  extent.scale( 1.05 );
1503  setExtent( extent );
1504  }
1505  refresh();
1506 }
1507 
1509 {
1511 
1512  // If the full extent is an empty set, don't do the zoom
1513  if ( !extent.isEmpty() )
1514  {
1515  // Add a 5% margin around the full extent
1516  extent.scale( 1.05 );
1517  setExtent( extent );
1518  }
1519  refresh();
1520 }
1521 
1523 {
1524  if ( mLastExtentIndex > 0 )
1525  {
1526  mLastExtentIndex--;
1527  mSettings.setExtent( mLastExtent[mLastExtentIndex] );
1528  emit extentsChanged();
1529  updateScale();
1530  refresh();
1531  // update controls' enabled state
1532  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1533  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1534  }
1535 
1536 } // zoomToPreviousExtent
1537 
1539 {
1540  if ( mLastExtentIndex < mLastExtent.size() - 1 )
1541  {
1542  mLastExtentIndex++;
1543  mSettings.setExtent( mLastExtent[mLastExtentIndex] );
1544  emit extentsChanged();
1545  updateScale();
1546  refresh();
1547  // update controls' enabled state
1548  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1549  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1550  }
1551 }// zoomToNextExtent
1552 
1554 {
1555  mLastExtent.clear(); // clear the zoom history list
1556  mLastExtent.append( extent() ) ; // set the current extent in the list
1557  mLastExtentIndex = mLastExtent.size() - 1;
1558  // update controls' enabled state
1559  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1560  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1561 }// clearExtentHistory
1562 
1563 QgsRectangle QgsMapCanvas::optimalExtentForPointLayer( QgsVectorLayer *layer, const QgsPointXY &center, int scaleFactor )
1564 {
1565  QgsRectangle rect( center, center );
1566 
1567  if ( layer->geometryType() == QgsWkbTypes::PointGeometry )
1568  {
1569  QgsPointXY centerLayerCoordinates = mSettings.mapToLayerCoordinates( layer, center );
1570  QgsRectangle extentRect = mSettings.mapToLayerCoordinates( layer, extent() ).scaled( 1.0 / scaleFactor, &centerLayerCoordinates );
1572  QgsFeatureIterator fit = layer->getFeatures( req );
1573  QgsFeature f;
1574  QgsPointXY closestPoint;
1575  double closestSquaredDistance = pow( extentRect.width(), 2.0 ) + pow( extentRect.height(), 2.0 );
1576  bool pointFound = false;
1577  while ( fit.nextFeature( f ) )
1578  {
1579  QgsPointXY point = f.geometry().asPoint();
1580  double sqrDist = point.sqrDist( centerLayerCoordinates );
1581  if ( sqrDist > closestSquaredDistance || sqrDist < 4 * std::numeric_limits<double>::epsilon() )
1582  continue;
1583  pointFound = true;
1584  closestPoint = point;
1585  closestSquaredDistance = sqrDist;
1586  }
1587  if ( pointFound )
1588  {
1589  // combine selected point with closest point and scale this rect
1590  rect.combineExtentWith( mSettings.layerToMapCoordinates( layer, closestPoint ) );
1591  rect.scale( scaleFactor, &center );
1592  }
1593  }
1594  return rect;
1595 }
1596 
1598 {
1599  QgsTemporaryCursorOverride cursorOverride( Qt::WaitCursor );
1600 
1601  if ( !layer )
1602  {
1603  // use current layer by default
1604  layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1605  }
1606 
1607  if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
1608  return;
1609 
1610  QgsRectangle rect = layer->boundingBoxOfSelected();
1611  if ( rect.isNull() )
1612  {
1613  cursorOverride.release();
1614  emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1615  return;
1616  }
1617 
1618  rect = mapSettings().layerExtentToOutputExtent( layer, rect );
1619 
1620  // zoom in if point cannot be distinguished from others
1621  // also check that rect is empty, as it might not in case of multi points
1622  if ( layer->geometryType() == QgsWkbTypes::PointGeometry && rect.isEmpty() )
1623  {
1624  rect = optimalExtentForPointLayer( layer, rect.center() );
1625  }
1626  zoomToFeatureExtent( rect );
1627 }
1628 
1629 void QgsMapCanvas::zoomToSelected( const QList<QgsMapLayer *> &layers )
1630 {
1631  QgsRectangle rect;
1632  rect.setMinimal();
1633  QgsRectangle selectionExtent;
1634  selectionExtent.setMinimal();
1635 
1636  for ( QgsMapLayer *mapLayer : layers )
1637  {
1638  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
1639 
1640  if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
1641  continue;
1642 
1643  rect = layer->boundingBoxOfSelected();
1644 
1645  if ( rect.isNull() )
1646  continue;
1647 
1648  rect = mapSettings().layerExtentToOutputExtent( layer, rect );
1649 
1650  if ( layer->geometryType() == QgsWkbTypes::PointGeometry && rect.isEmpty() )
1651  rect = optimalExtentForPointLayer( layer, rect.center() );
1652 
1653  selectionExtent.combineExtentWith( rect );
1654  }
1655 
1656  if ( selectionExtent.isNull() )
1657  {
1658  emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1659  return;
1660  }
1661 
1662  zoomToFeatureExtent( selectionExtent );
1663 }
1664 
1666 {
1667  return mSettings.zRange();
1668 }
1669 
1671 {
1672  if ( zRange() == range )
1673  return;
1674 
1675  mSettings.setZRange( range );
1676 
1677  emit zRangeChanged();
1678 
1679  // we need to discard any previously cached images which are elevation aware, so that these will be updated when
1680  // the canvas is redrawn
1681  if ( !mJob )
1682  clearElevationCache();
1683 
1684  autoRefreshTriggered();
1685 }
1686 
1688 {
1689  // no selected features, only one selected point feature
1690  //or two point features with the same x- or y-coordinates
1691  if ( rect.isEmpty() )
1692  {
1693  // zoom in
1694  QgsPointXY c = rect.center();
1695  rect = extent();
1696  rect.scale( 1.0, &c );
1697  }
1698  //zoom to an area
1699  else
1700  {
1701  // Expand rect to give a bit of space around the selected
1702  // objects so as to keep them clear of the map boundaries
1703  // The same 5% should apply to all margins.
1704  rect.scale( 1.05 );
1705  }
1706 
1707  setExtent( rect );
1708  refresh();
1709 }
1710 
1712 {
1713  if ( !layer )
1714  {
1715  return;
1716  }
1717 
1718  QgsRectangle bbox;
1719  QString errorMsg;
1720  if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
1721  {
1722  if ( bbox.isEmpty() )
1723  {
1724  bbox = optimalExtentForPointLayer( layer, bbox.center() );
1725  }
1726  zoomToFeatureExtent( bbox );
1727  }
1728  else
1729  {
1730  emit messageEmitted( tr( "Zoom to feature id failed" ), errorMsg, Qgis::MessageLevel::Warning );
1731  }
1732 
1733 }
1734 
1735 void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter )
1736 {
1737  if ( !layer )
1738  {
1739  return;
1740  }
1741 
1742  QgsRectangle bbox;
1743  QString errorMsg;
1744  if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
1745  {
1746  if ( alwaysRecenter || !mapSettings().extent().contains( bbox ) )
1747  setCenter( bbox.center() );
1748  refresh();
1749  }
1750  else
1751  {
1752  emit messageEmitted( tr( "Pan to feature id failed" ), errorMsg, Qgis::MessageLevel::Warning );
1753  }
1754 }
1755 
1756 bool QgsMapCanvas::boundingBoxOfFeatureIds( const QgsFeatureIds &ids, QgsVectorLayer *layer, QgsRectangle &bbox, QString &errorMsg ) const
1757 {
1758  QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
1759  bbox.setMinimal();
1760  QgsFeature fet;
1761  int featureCount = 0;
1762  errorMsg.clear();
1763 
1764  while ( it.nextFeature( fet ) )
1765  {
1766  QgsGeometry geom = fet.geometry();
1767  if ( geom.isNull() )
1768  {
1769  errorMsg = tr( "Feature does not have a geometry" );
1770  }
1771  else if ( geom.constGet()->isEmpty() )
1772  {
1773  errorMsg = tr( "Feature geometry is empty" );
1774  }
1775  if ( !errorMsg.isEmpty() )
1776  {
1777  return false;
1778  }
1780  bbox.combineExtentWith( r );
1781  featureCount++;
1782  }
1783 
1784  if ( featureCount != ids.count() )
1785  {
1786  errorMsg = tr( "Feature not found" );
1787  return false;
1788  }
1789 
1790  return true;
1791 }
1792 
1794 {
1795  if ( !layer )
1796  {
1797  // use current layer by default
1798  layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1799  }
1800 
1801  if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
1802  return;
1803 
1804  QgsRectangle rect = layer->boundingBoxOfSelected();
1805  if ( rect.isNull() )
1806  {
1807  emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1808  return;
1809  }
1810 
1811  rect = mapSettings().layerExtentToOutputExtent( layer, rect );
1812  setCenter( rect.center() );
1813  refresh();
1814 }
1815 
1816 void QgsMapCanvas::panToSelected( const QList<QgsMapLayer *> &layers )
1817 {
1818  QgsRectangle rect;
1819  rect.setMinimal();
1820  QgsRectangle selectionExtent;
1821  selectionExtent.setMinimal();
1822 
1823  for ( QgsMapLayer *mapLayer : layers )
1824  {
1825  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
1826 
1827  if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
1828  continue;
1829 
1830  rect = layer->boundingBoxOfSelected();
1831 
1832  if ( rect.isNull() )
1833  continue;
1834 
1835  rect = mapSettings().layerExtentToOutputExtent( layer, rect );
1836 
1837  if ( layer->geometryType() == QgsWkbTypes::PointGeometry && rect.isEmpty() )
1838  rect = optimalExtentForPointLayer( layer, rect.center() );
1839 
1840  selectionExtent.combineExtentWith( rect );
1841  }
1842 
1843  if ( selectionExtent.isNull() )
1844  {
1845  emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::MessageLevel::Warning );
1846  return;
1847  }
1848 
1849  setCenter( selectionExtent.center() );
1850  refresh();
1851 }
1852 
1854  const QColor &color1, const QColor &color2,
1855  int flashes, int duration )
1856 {
1857  if ( !layer )
1858  {
1859  return;
1860  }
1861 
1862  QList< QgsGeometry > geoms;
1863 
1864  QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
1865  QgsFeature fet;
1866  while ( it.nextFeature( fet ) )
1867  {
1868  if ( !fet.hasGeometry() )
1869  continue;
1870  geoms << fet.geometry();
1871  }
1872 
1873  flashGeometries( geoms, layer->crs(), color1, color2, flashes, duration );
1874 }
1875 
1876 void QgsMapCanvas::flashGeometries( const QList<QgsGeometry> &geometries, const QgsCoordinateReferenceSystem &crs, const QColor &color1, const QColor &color2, int flashes, int duration )
1877 {
1878  if ( geometries.isEmpty() )
1879  return;
1880 
1881  QgsWkbTypes::GeometryType geomType = QgsWkbTypes::geometryType( geometries.at( 0 ).wkbType() );
1882  QgsRubberBand *rb = new QgsRubberBand( this, geomType );
1883  for ( const QgsGeometry &geom : geometries )
1884  rb->addGeometry( geom, crs, false );
1885  rb->updatePosition();
1886  rb->update();
1887 
1888  if ( geomType == QgsWkbTypes::LineGeometry || geomType == QgsWkbTypes::PointGeometry )
1889  {
1890  rb->setWidth( 2 );
1891  rb->setSecondaryStrokeColor( QColor( 255, 255, 255 ) );
1892  }
1893  if ( geomType == QgsWkbTypes::PointGeometry )
1895 
1896  QColor startColor = color1;
1897  if ( !startColor.isValid() )
1898  {
1899  if ( geomType == QgsWkbTypes::PolygonGeometry )
1900  {
1901  startColor = rb->fillColor();
1902  }
1903  else
1904  {
1905  startColor = rb->strokeColor();
1906  }
1907  startColor.setAlpha( 255 );
1908  }
1909  QColor endColor = color2;
1910  if ( !endColor.isValid() )
1911  {
1912  endColor = startColor;
1913  endColor.setAlpha( 0 );
1914  }
1915 
1916 
1917  QVariantAnimation *animation = new QVariantAnimation( this );
1918  connect( animation, &QVariantAnimation::finished, this, [animation, rb]
1919  {
1920  animation->deleteLater();
1921  delete rb;
1922  } );
1923  connect( animation, &QPropertyAnimation::valueChanged, this, [rb, geomType]( const QVariant & value )
1924  {
1925  QColor c = value.value<QColor>();
1926  if ( geomType == QgsWkbTypes::PolygonGeometry )
1927  {
1928  rb->setFillColor( c );
1929  }
1930  else
1931  {
1932  rb->setStrokeColor( c );
1933  QColor c = rb->secondaryStrokeColor();
1934  c.setAlpha( c.alpha() );
1935  rb->setSecondaryStrokeColor( c );
1936  }
1937  rb->update();
1938  } );
1939 
1940  animation->setDuration( duration * flashes );
1941  animation->setStartValue( endColor );
1942  double midStep = 0.2 / flashes;
1943  for ( int i = 0; i < flashes; ++i )
1944  {
1945  double start = static_cast< double >( i ) / flashes;
1946  animation->setKeyValueAt( start + midStep, startColor );
1947  double end = static_cast< double >( i + 1 ) / flashes;
1948  if ( !qgsDoubleNear( end, 1.0 ) )
1949  animation->setKeyValueAt( end, endColor );
1950  }
1951  animation->setEndValue( endColor );
1952  animation->start();
1953 }
1954 
1955 void QgsMapCanvas::keyPressEvent( QKeyEvent *e )
1956 {
1957  if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )
1958  {
1959  emit keyPressed( e );
1960  return;
1961  }
1962 
1963  // Don't want to interfer with mouse events
1964  if ( ! mCanvasProperties->mouseButtonDown )
1965  {
1966  // this is backwards, but we can't change now without breaking api because
1967  // forever QgsMapTools have had to explicitly mark events as ignored in order to
1968  // indicate that they've consumed the event and that the default behavior should not
1969  // be applied..!
1970  e->accept();
1971  if ( mMapTool )
1972  {
1973  mMapTool->keyPressEvent( e );
1974  if ( !e->isAccepted() ) // map tool consumed event
1975  return;
1976  }
1977 
1978  QgsRectangle currentExtent = mapSettings().visibleExtent();
1979  double dx = std::fabs( currentExtent.width() / 4 );
1980  double dy = std::fabs( currentExtent.height() / 4 );
1981 
1982  switch ( e->key() )
1983  {
1984  case Qt::Key_Left:
1985  QgsDebugMsgLevel( QStringLiteral( "Pan left" ), 2 );
1986  setCenter( center() - QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
1987  refresh();
1988  break;
1989 
1990  case Qt::Key_Right:
1991  QgsDebugMsgLevel( QStringLiteral( "Pan right" ), 2 );
1992  setCenter( center() + QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
1993  refresh();
1994  break;
1995 
1996  case Qt::Key_Up:
1997  QgsDebugMsgLevel( QStringLiteral( "Pan up" ), 2 );
1998  setCenter( center() + QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
1999  refresh();
2000  break;
2001 
2002  case Qt::Key_Down:
2003  QgsDebugMsgLevel( QStringLiteral( "Pan down" ), 2 );
2004  setCenter( center() - QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
2005  refresh();
2006  break;
2007 
2008  case Qt::Key_Space:
2009  QgsDebugMsgLevel( QStringLiteral( "Pressing pan selector" ), 2 );
2010 
2011  //mCanvasProperties->dragging = true;
2012  if ( ! e->isAutoRepeat() )
2013  {
2014  mTemporaryCursorOverride.reset( new QgsTemporaryCursorOverride( Qt::ClosedHandCursor ) );
2015  mCanvasProperties->panSelectorDown = true;
2016  panActionStart( mCanvasProperties->mouseLastXY );
2017  }
2018  break;
2019 
2020  case Qt::Key_PageUp:
2021  QgsDebugMsgLevel( QStringLiteral( "Zoom in" ), 2 );
2022  zoomIn();
2023  break;
2024 
2025  case Qt::Key_PageDown:
2026  QgsDebugMsgLevel( QStringLiteral( "Zoom out" ), 2 );
2027  zoomOut();
2028  break;
2029 
2030 #if 0
2031  case Qt::Key_P:
2032  mUseParallelRendering = !mUseParallelRendering;
2033  refresh();
2034  break;
2035 
2036  case Qt::Key_S:
2037  mDrawRenderingStats = !mDrawRenderingStats;
2038  refresh();
2039  break;
2040 #endif
2041 
2042  default:
2043  // Pass it on
2044  if ( !mMapTool )
2045  {
2046  e->ignore();
2047  QgsDebugMsgLevel( "Ignoring key: " + QString::number( e->key() ), 2 );
2048  }
2049  }
2050  }
2051 
2052  emit keyPressed( e );
2053 }
2054 
2055 void QgsMapCanvas::keyReleaseEvent( QKeyEvent *e )
2056 {
2057  QgsDebugMsgLevel( QStringLiteral( "keyRelease event" ), 2 );
2058 
2059  switch ( e->key() )
2060  {
2061  case Qt::Key_Space:
2062  if ( !e->isAutoRepeat() && mCanvasProperties->panSelectorDown )
2063  {
2064  QgsDebugMsgLevel( QStringLiteral( "Releasing pan selector" ), 2 );
2065  mTemporaryCursorOverride.reset();
2066  mCanvasProperties->panSelectorDown = false;
2067  panActionEnd( mCanvasProperties->mouseLastXY );
2068  }
2069  break;
2070 
2071  default:
2072  // Pass it on
2073  if ( mMapTool )
2074  {
2075  mMapTool->keyReleaseEvent( e );
2076  }
2077  else e->ignore();
2078 
2079  QgsDebugMsgLevel( "Ignoring key release: " + QString::number( e->key() ), 2 );
2080  }
2081 
2082  emit keyReleased( e );
2083 
2084 } //keyReleaseEvent()
2085 
2086 
2088 {
2089  // call handler of current map tool
2090  if ( mMapTool )
2091  {
2092  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2093  mMapTool->canvasDoubleClickEvent( me.get() );
2094  }
2095 }// mouseDoubleClickEvent
2096 
2097 
2098 void QgsMapCanvas::beginZoomRect( QPoint pos )
2099 {
2100  mZoomRect.setRect( 0, 0, 0, 0 );
2101  mTemporaryCursorOverride.reset( new QgsTemporaryCursorOverride( mZoomCursor ) );
2102  mZoomDragging = true;
2103  mZoomRubberBand.reset( new QgsRubberBand( this, QgsWkbTypes::PolygonGeometry ) );
2104  QColor color( Qt::blue );
2105  color.setAlpha( 63 );
2106  mZoomRubberBand->setColor( color );
2107  mZoomRect.setTopLeft( pos );
2108 }
2109 
2110 void QgsMapCanvas::stopZoomRect()
2111 {
2112  if ( mZoomDragging )
2113  {
2114  mZoomDragging = false;
2115  mZoomRubberBand.reset( nullptr );
2116  mTemporaryCursorOverride.reset();
2117  }
2118 }
2119 
2120 void QgsMapCanvas::endZoomRect( QPoint pos )
2121 {
2122  stopZoomRect();
2123 
2124  // store the rectangle
2125  mZoomRect.setRight( pos.x() );
2126  mZoomRect.setBottom( pos.y() );
2127 
2128  //account for bottom right -> top left dragging
2129  mZoomRect = mZoomRect.normalized();
2130 
2131  if ( mZoomRect.width() < 5 && mZoomRect.height() < 5 )
2132  {
2133  //probably a mistake - would result in huge zoom!
2134  return;
2135  }
2136 
2137  // set center and zoom
2138  const QSize &zoomRectSize = mZoomRect.size();
2139  const QSize &canvasSize = mSettings.outputSize();
2140  double sfx = static_cast< double >( zoomRectSize.width() ) / canvasSize.width();
2141  double sfy = static_cast< double >( zoomRectSize.height() ) / canvasSize.height();
2142  double sf = std::max( sfx, sfy );
2143 
2144  QgsPointXY c = mSettings.mapToPixel().toMapCoordinates( mZoomRect.center() );
2145 
2146  zoomByFactor( sf, &c );
2147  refresh();
2148 }
2149 
2150 void QgsMapCanvas::startPan()
2151 {
2152  if ( !mCanvasProperties->panSelectorDown )
2153  {
2154  mCanvasProperties->panSelectorDown = true;
2155  mTemporaryCursorOverride.reset( new QgsTemporaryCursorOverride( Qt::ClosedHandCursor ) );
2156  panActionStart( mCanvasProperties->mouseLastXY );
2157  }
2158 }
2159 
2160 void QgsMapCanvas::stopPan()
2161 {
2162  if ( mCanvasProperties->panSelectorDown )
2163  {
2164  mCanvasProperties->panSelectorDown = false;
2165  mTemporaryCursorOverride.reset();
2166  panActionEnd( mCanvasProperties->mouseLastXY );
2167  }
2168 }
2169 
2170 void QgsMapCanvas::mousePressEvent( QMouseEvent *e )
2171 {
2172  // use shift+middle mouse button for zooming, map tools won't receive any events in that case
2173  if ( e->button() == Qt::MiddleButton &&
2174  e->modifiers() & Qt::ShiftModifier )
2175  {
2176  beginZoomRect( e->pos() );
2177  return;
2178  }
2179  //use middle mouse button for panning, map tools won't receive any events in that case
2180  else if ( e->button() == Qt::MiddleButton )
2181  {
2182  startPan();
2183  }
2184  else
2185  {
2186  // If doing a middle-button-click, followed by a right-button-click,
2187  // cancel the pan or zoomRect action started above.
2188  stopPan();
2189  stopZoomRect();
2190 
2191  // call handler of current map tool
2192  if ( mMapTool )
2193  {
2194  if ( mMapTool->flags() & QgsMapTool::AllowZoomRect && e->button() == Qt::LeftButton
2195  && e->modifiers() & Qt::ShiftModifier )
2196  {
2197  beginZoomRect( e->pos() );
2198  return;
2199  }
2200  else if ( mMapTool->flags() & QgsMapTool::ShowContextMenu && e->button() == Qt::RightButton )
2201  {
2202  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2203  showContextMenu( me.get() );
2204  return;
2205  }
2206  else
2207  {
2208  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2209  mMapTool->canvasPressEvent( me.get() );
2210  }
2211  }
2212  }
2213 
2214  if ( mCanvasProperties->panSelectorDown )
2215  {
2216  return;
2217  }
2218 
2219  mCanvasProperties->mouseButtonDown = true;
2220  mCanvasProperties->rubberStartPoint = e->pos();
2221 }
2222 
2223 void QgsMapCanvas::mouseReleaseEvent( QMouseEvent *e )
2224 {
2225  // if using shift+middle mouse button for zooming, end zooming and return
2226  if ( mZoomDragging &&
2227  e->button() == Qt::MiddleButton )
2228  {
2229  endZoomRect( e->pos() );
2230  return;
2231  }
2232  //use middle mouse button for panning, map tools won't receive any events in that case
2233  else if ( e->button() == Qt::MiddleButton )
2234  {
2235  stopPan();
2236  }
2237  else if ( e->button() == Qt::BackButton )
2238  {
2240  return;
2241  }
2242  else if ( e->button() == Qt::ForwardButton )
2243  {
2244  zoomToNextExtent();
2245  return;
2246  }
2247  else
2248  {
2249  if ( mZoomDragging && e->button() == Qt::LeftButton )
2250  {
2251  endZoomRect( e->pos() );
2252  return;
2253  }
2254 
2255  // call handler of current map tool
2256  if ( mMapTool )
2257  {
2258  // right button was pressed in zoom tool? return to previous non zoom tool
2259  if ( e->button() == Qt::RightButton && mMapTool->flags() & QgsMapTool::Transient )
2260  {
2261  QgsDebugMsgLevel( QStringLiteral( "Right click in map tool zoom or pan, last tool is %1." ).arg(
2262  mLastNonZoomMapTool ? QStringLiteral( "not null" ) : QStringLiteral( "null" ) ), 2 );
2263 
2264  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
2265 
2266  // change to older non-zoom tool
2267  if ( mLastNonZoomMapTool
2268  && ( !( mLastNonZoomMapTool->flags() & QgsMapTool::EditTool )
2269  || ( vlayer && vlayer->isEditable() ) ) )
2270  {
2271  QgsMapTool *t = mLastNonZoomMapTool;
2272  mLastNonZoomMapTool = nullptr;
2273  setMapTool( t );
2274  }
2275  return;
2276  }
2277  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2278  mMapTool->canvasReleaseEvent( me.get() );
2279  }
2280  }
2281 
2282 
2283  mCanvasProperties->mouseButtonDown = false;
2284 
2285  if ( mCanvasProperties->panSelectorDown )
2286  return;
2287 
2288 }
2289 
2290 void QgsMapCanvas::resizeEvent( QResizeEvent *e )
2291 {
2292  QGraphicsView::resizeEvent( e );
2293  mResizeTimer->start( 500 ); // in charge of refreshing canvas
2294 
2295  double oldScale = mSettings.scale();
2296  QSize lastSize = viewport()->size();
2297  mSettings.setOutputSize( lastSize );
2298 
2299  mScene->setSceneRect( QRectF( 0, 0, lastSize.width(), lastSize.height() ) );
2300 
2301  moveCanvasContents( true );
2302 
2303  if ( mScaleLocked )
2304  {
2305  double scaleFactor = oldScale / mSettings.scale();
2306  QgsRectangle r = mSettings.extent();
2307  QgsPointXY center = r.center();
2308  r.scale( scaleFactor, &center );
2309  mSettings.setExtent( r );
2310  }
2311  else
2312  {
2313  updateScale();
2314  }
2315 
2316  emit extentsChanged();
2317 }
2318 
2319 void QgsMapCanvas::paintEvent( QPaintEvent *e )
2320 {
2321  // no custom event handling anymore
2322 
2323  QGraphicsView::paintEvent( e );
2324 } // paintEvent
2325 
2327 {
2328  if ( mBlockItemPositionUpdates )
2329  return;
2330 
2331  const QList<QGraphicsItem *> items = mScene->items();
2332  for ( QGraphicsItem *gi : items )
2333  {
2334  QgsMapCanvasItem *item = dynamic_cast<QgsMapCanvasItem *>( gi );
2335 
2336  if ( item )
2337  {
2338  item->updatePosition();
2339  }
2340  }
2341 }
2342 
2343 
2344 void QgsMapCanvas::wheelEvent( QWheelEvent *e )
2345 {
2346  // Zoom the map canvas in response to a mouse wheel event. Moving the
2347  // wheel forward (away) from the user zooms in
2348 
2349  QgsDebugMsgLevel( "Wheel event delta " + QString::number( e->angleDelta().y() ), 2 );
2350 
2351  if ( mMapTool )
2352  {
2353  mMapTool->wheelEvent( e );
2354  if ( e->isAccepted() )
2355  return;
2356  }
2357 
2358  if ( e->angleDelta().y() == 0 )
2359  {
2360  e->accept();
2361  return;
2362  }
2363 
2364  double zoomFactor = e->angleDelta().y() > 0 ? 1. / zoomInFactor() : zoomOutFactor();
2365 
2366  // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
2367  zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( e->angleDelta().y() );
2368 
2369  if ( e->modifiers() & Qt::ControlModifier )
2370  {
2371  //holding ctrl while wheel zooming results in a finer zoom
2372  zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
2373  }
2374 
2375  double signedWheelFactor = e->angleDelta().y() > 0 ? 1 / zoomFactor : zoomFactor;
2376 
2377  // zoom map to mouse cursor by scaling
2378  QgsPointXY oldCenter = center();
2379 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
2380  QgsPointXY mousePos( getCoordinateTransform()->toMapCoordinates( e->pos().x(), e->pos().y() ) );
2381 #else
2382  QgsPointXY mousePos( getCoordinateTransform()->toMapCoordinates( e->position().x(), e->position().y() ) );
2383 #endif
2384  QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * signedWheelFactor ),
2385  mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * signedWheelFactor ) );
2386 
2387  zoomByFactor( signedWheelFactor, &newCenter );
2388  e->accept();
2389 }
2390 
2391 void QgsMapCanvas::setWheelFactor( double factor )
2392 {
2393  mWheelZoomFactor = factor;
2394 }
2395 
2397 {
2398  // magnification is alreday handled in zoomByFactor
2400 }
2401 
2403 {
2404  // magnification is alreday handled in zoomByFactor
2406 }
2407 
2408 void QgsMapCanvas::zoomScale( double newScale, bool ignoreScaleLock )
2409 {
2410  zoomByFactor( newScale / scale(), nullptr, ignoreScaleLock );
2411 }
2412 
2413 void QgsMapCanvas::zoomWithCenter( int x, int y, bool zoomIn )
2414 {
2415  double scaleFactor = ( zoomIn ? zoomInFactor() : zoomOutFactor() );
2416 
2417  // transform the mouse pos to map coordinates
2419 
2420  if ( mScaleLocked )
2421  {
2422  ScaleRestorer restorer( this );
2424  }
2425  else
2426  {
2428  r.scale( scaleFactor, &center );
2429  setExtent( r, true );
2430  refresh();
2431  }
2432 }
2433 
2434 void QgsMapCanvas::setScaleLocked( bool isLocked )
2435 {
2436  if ( mScaleLocked != isLocked )
2437  {
2438  mScaleLocked = isLocked;
2439  emit scaleLockChanged( mScaleLocked );
2440  }
2441 }
2442 
2443 void QgsMapCanvas::mouseMoveEvent( QMouseEvent *e )
2444 {
2445  mCanvasProperties->mouseLastXY = e->pos();
2446 
2447  if ( mCanvasProperties->panSelectorDown )
2448  {
2449  panAction( e );
2450  }
2451  else if ( mZoomDragging )
2452  {
2453  mZoomRect.setBottomRight( e->pos() );
2454  mZoomRubberBand->setToCanvasRectangle( mZoomRect );
2455  mZoomRubberBand->show();
2456  }
2457  else
2458  {
2459  // call handler of current map tool
2460  if ( mMapTool )
2461  {
2462  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
2463  mMapTool->canvasMoveEvent( me.get() );
2464  }
2465  }
2466 
2467  // show x y on status bar (if we are mid pan operation, then the cursor point hasn't changed!)
2468  if ( !panOperationInProgress() )
2469  {
2470  mCursorPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->mouseLastXY );
2471  emit xyCoordinates( mCursorPoint );
2472  }
2473 }
2474 
2475 void QgsMapCanvas::setMapTool( QgsMapTool *tool, bool clean )
2476 {
2477  if ( !tool )
2478  return;
2479 
2480  if ( mMapTool )
2481  {
2482  if ( clean )
2483  mMapTool->clean();
2484 
2485  disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2486  mMapTool->deactivate();
2487  }
2488 
2489  if ( ( tool->flags() & QgsMapTool::Transient )
2490  && mMapTool && !( mMapTool->flags() & QgsMapTool::Transient ) )
2491  {
2492  // if zoom or pan tool will be active, save old tool
2493  // to bring it back on right click
2494  // (but only if it wasn't also zoom or pan tool)
2495  mLastNonZoomMapTool = mMapTool;
2496  }
2497  else
2498  {
2499  mLastNonZoomMapTool = nullptr;
2500  }
2501 
2502  QgsMapTool *oldTool = mMapTool;
2503 
2504  // set new map tool and activate it
2505  mMapTool = tool;
2506  emit mapToolSet( mMapTool, oldTool );
2507  if ( mMapTool )
2508  {
2509  connect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2510  mMapTool->activate();
2511  }
2512 
2513 } // setMapTool
2514 
2516 {
2517  if ( mMapTool && mMapTool == tool )
2518  {
2519  disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
2520  QgsMapTool *oldTool = mMapTool;
2521  mMapTool = nullptr;
2522  oldTool->deactivate();
2523  emit mapToolSet( nullptr, oldTool );
2524  setCursor( Qt::ArrowCursor );
2525  }
2526 
2527  if ( mLastNonZoomMapTool && mLastNonZoomMapTool == tool )
2528  {
2529  mLastNonZoomMapTool = nullptr;
2530  }
2531 }
2532 
2534 {
2535  mProject = project;
2536 }
2537 
2538 void QgsMapCanvas::setCanvasColor( const QColor &color )
2539 {
2540  if ( canvasColor() == color )
2541  return;
2542 
2543  // background of map's pixmap
2544  mSettings.setBackgroundColor( color );
2545 
2546  // background of the QGraphicsView
2547  QBrush bgBrush( color );
2548  setBackgroundBrush( bgBrush );
2549 #if 0
2550  QPalette palette;
2551  palette.setColor( backgroundRole(), color );
2552  setPalette( palette );
2553 #endif
2554 
2555  // background of QGraphicsScene
2556  mScene->setBackgroundBrush( bgBrush );
2557 
2558  refresh();
2559 
2560  emit canvasColorChanged();
2561 }
2562 
2564 {
2565  return mScene->backgroundBrush().color();
2566 }
2567 
2568 void QgsMapCanvas::setSelectionColor( const QColor &color )
2569 {
2570  if ( mSettings.selectionColor() == color )
2571  return;
2572 
2573  mSettings.setSelectionColor( color );
2574 
2575  if ( mCache )
2576  {
2577  bool hasSelectedFeatures = false;
2578  const auto layers = mSettings.layers();
2579  for ( QgsMapLayer *layer : layers )
2580  {
2581  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
2582  if ( vlayer && vlayer->selectedFeatureCount() )
2583  {
2584  hasSelectedFeatures = true;
2585  break;
2586  }
2587  }
2588 
2589  if ( hasSelectedFeatures )
2590  {
2591  mCache->clear();
2592  refresh();
2593  }
2594  }
2595 }
2596 
2598 {
2599  return mSettings.selectionColor();
2600 }
2601 
2603 {
2604  return mapSettings().layers().size();
2605 }
2606 
2607 QList<QgsMapLayer *> QgsMapCanvas::layers( bool expandGroupLayers ) const
2608 {
2609  return mapSettings().layers( expandGroupLayers );
2610 }
2611 
2613 {
2614  // called when a layer has changed visibility setting
2615  refresh();
2616 }
2617 
2618 void QgsMapCanvas::freeze( bool frozen )
2619 {
2620  mFrozen = frozen;
2621 }
2622 
2624 {
2625  return mFrozen;
2626 }
2627 
2629 {
2630  return mapSettings().mapUnitsPerPixel();
2631 }
2632 
2634 {
2635  return mapSettings().mapUnits();
2636 }
2637 
2638 QMap<QString, QString> QgsMapCanvas::layerStyleOverrides() const
2639 {
2640  return mSettings.layerStyleOverrides();
2641 }
2642 
2643 void QgsMapCanvas::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
2644 {
2645  if ( overrides == mSettings.layerStyleOverrides() )
2646  return;
2647 
2648  mSettings.setLayerStyleOverrides( overrides );
2649  clearCache();
2651 }
2652 
2653 void QgsMapCanvas::setTheme( const QString &theme )
2654 {
2655  if ( mTheme == theme )
2656  return;
2657 
2658  clearCache();
2659  if ( theme.isEmpty() || !QgsProject::instance()->mapThemeCollection()->hasMapTheme( theme ) )
2660  {
2661  mTheme.clear();
2662  mSettings.setLayerStyleOverrides( QMap< QString, QString>() );
2663  setLayers( QgsProject::instance()->mapThemeCollection()->masterVisibleLayers() );
2664  emit themeChanged( QString() );
2665  }
2666  else
2667  {
2668  mTheme = theme;
2669  setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
2670  emit themeChanged( theme );
2671  }
2672 }
2673 
2675 {
2676  mRenderFlag = flag;
2677 
2678  if ( mRenderFlag )
2679  {
2680  refresh();
2681  }
2682  else
2683  stopRendering();
2684 }
2685 
2686 #if 0
2687 void QgsMapCanvas::connectNotify( const char *signal )
2688 {
2689  Q_UNUSED( signal )
2690  QgsDebugMsg( "QgsMapCanvas connected to " + QString( signal ) );
2691 } //connectNotify
2692 #endif
2693 
2694 void QgsMapCanvas::layerRepaintRequested( bool deferred )
2695 {
2696  if ( !deferred )
2697  refresh();
2698 }
2699 
2700 void QgsMapCanvas::autoRefreshTriggered()
2701 {
2702  if ( mJob )
2703  {
2704  // canvas is currently being redrawn, so we defer the last requested
2705  // auto refresh until current rendering job finishes
2706  mRefreshAfterJob = true;
2707  return;
2708  }
2709 
2710  refresh();
2711 }
2712 
2713 void QgsMapCanvas::updateAutoRefreshTimer()
2714 {
2715  // min auto refresh interval stores the smallest interval between layer auto refreshes. We automatically
2716  // trigger a map refresh on this minimum interval
2717  int minAutoRefreshInterval = -1;
2718  const auto layers = mSettings.layers();
2719  for ( QgsMapLayer *layer : layers )
2720  {
2721  int layerRefreshInterval = 0;
2722 
2724  {
2725  layerRefreshInterval = layer->autoRefreshInterval();
2726  }
2727  else if ( QgsVectorLayer *vectorLayer = qobject_cast< QgsVectorLayer * >( layer ) )
2728  {
2729  if ( const QgsFeatureRenderer *renderer = vectorLayer->renderer() )
2730  {
2731  const double rendererRefreshRate = QgsSymbolLayerUtils::rendererFrameRate( renderer );
2732  if ( rendererRefreshRate > 0 )
2733  {
2734  layerRefreshInterval = 1000 / rendererRefreshRate;
2735  }
2736  }
2737  }
2738 
2739  if ( layerRefreshInterval == 0 )
2740  continue;
2741 
2742  minAutoRefreshInterval = minAutoRefreshInterval > 0 ? std::min( layerRefreshInterval, minAutoRefreshInterval ) : layerRefreshInterval;
2743  }
2744 
2745  if ( minAutoRefreshInterval > 0 )
2746  {
2747  mAutoRefreshTimer.setInterval( minAutoRefreshInterval );
2748  mAutoRefreshTimer.start();
2749  }
2750  else
2751  {
2752  mAutoRefreshTimer.stop();
2753  }
2754 }
2755 
2756 void QgsMapCanvas::projectThemesChanged()
2757 {
2758  if ( mTheme.isEmpty() )
2759  return;
2760 
2761  if ( !QgsProject::instance()->mapThemeCollection()->hasMapTheme( mTheme ) )
2762  {
2763  // theme has been removed - stop following
2764  setTheme( QString() );
2765  }
2766 
2767 }
2768 
2770 {
2771  return mMapTool;
2772 }
2773 
2775 {
2776  return mProject;
2777 }
2778 
2779 void QgsMapCanvas::panActionEnd( QPoint releasePoint )
2780 {
2781  // move map image and other items to standard position
2782  moveCanvasContents( true ); // true means reset
2783 
2784  // use start and end box points to calculate the extent
2785  QgsPointXY start = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
2786  QgsPointXY end = getCoordinateTransform()->toMapCoordinates( releasePoint );
2787 
2788  // modify the center
2789  double dx = end.x() - start.x();
2790  double dy = end.y() - start.y();
2791  QgsPointXY c = center();
2792  c.set( c.x() - dx, c.y() - dy );
2793  setCenter( c );
2794 
2795  refresh();
2796 }
2797 
2798 void QgsMapCanvas::panActionStart( QPoint releasePoint )
2799 {
2800  mCanvasProperties->rubberStartPoint = releasePoint;
2801 
2802  mDa = QgsDistanceArea();
2803  mDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
2804  mDa.setSourceCrs( mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
2805 }
2806 
2807 void QgsMapCanvas::panAction( QMouseEvent *e )
2808 {
2809  Q_UNUSED( e )
2810 
2811  QgsPointXY currentMapPoint = getCoordinateTransform()->toMapCoordinates( e->pos() );
2812  QgsPointXY startMapPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
2813  try
2814  {
2815  emit panDistanceBearingChanged( mDa.measureLine( currentMapPoint, startMapPoint ), mDa.lengthUnits(), mDa.bearing( currentMapPoint, startMapPoint ) * 180 / M_PI );
2816  }
2817  catch ( QgsCsException & )
2818  {}
2819 
2820  // move all map canvas items
2822 }
2823 
2825 {
2826  QPoint pnt( 0, 0 );
2827  if ( !reset )
2828  pnt += mCanvasProperties->mouseLastXY - mCanvasProperties->rubberStartPoint;
2829 
2830  setSceneRect( -pnt.x(), -pnt.y(), viewport()->size().width(), viewport()->size().height() );
2831 }
2832 
2833 void QgsMapCanvas::dropEvent( QDropEvent *event )
2834 {
2835  if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
2836  {
2838  bool allHandled = true;
2839  for ( const QgsMimeDataUtils::Uri &uri : lst )
2840  {
2841  bool handled = false;
2842  for ( QgsCustomDropHandler *handler : std::as_const( mDropHandlers ) )
2843  {
2844  if ( handler && handler->customUriProviderKey() == uri.providerKey )
2845  {
2846  if ( handler->handleCustomUriCanvasDrop( uri, this ) )
2847  {
2848  handled = true;
2849  break;
2850  }
2851  }
2852  }
2853  if ( !handled )
2854  allHandled = false;
2855  }
2856  if ( allHandled )
2857  event->accept();
2858  else
2859  event->ignore();
2860  }
2861  else
2862  {
2863  event->ignore();
2864  }
2865 }
2866 
2867 void QgsMapCanvas::showEvent( QShowEvent *event )
2868 {
2869  Q_UNUSED( event )
2870  updateDevicePixelFromScreen();
2871  // keep device pixel ratio up to date on screen or resolution change
2872  QWindow *l_window = window()->windowHandle();
2873  if ( l_window )
2874  {
2875  connect( l_window, &QWindow::screenChanged, this, [ = ]( QScreen * )
2876  {
2877  disconnect( mScreenDpiChangedConnection );
2878  QWindow *windowInLambda = window()->windowHandle();
2879  if ( windowInLambda )
2880  {
2881  mScreenDpiChangedConnection = connect( windowInLambda->screen(), &QScreen::physicalDotsPerInchChanged, this, &QgsMapCanvas::updateDevicePixelFromScreen );
2882  updateDevicePixelFromScreen();
2883  }
2884  } );
2885 
2886  mScreenDpiChangedConnection = connect( l_window->screen(), &QScreen::physicalDotsPerInchChanged, this, &QgsMapCanvas::updateDevicePixelFromScreen );
2887  }
2888 }
2889 
2891 {
2892  return mCanvasProperties->mouseLastXY;
2893 }
2894 
2895 void QgsMapCanvas::setPreviewModeEnabled( bool previewEnabled )
2896 {
2897  if ( !mPreviewEffect )
2898  {
2899  return;
2900  }
2901 
2902  mPreviewEffect->setEnabled( previewEnabled );
2903 }
2904 
2906 {
2907  if ( !mPreviewEffect )
2908  {
2909  return false;
2910  }
2911 
2912  return mPreviewEffect->isEnabled();
2913 }
2914 
2916 {
2917  if ( !mPreviewEffect )
2918  {
2919  return;
2920  }
2921 
2922  mPreviewEffect->setMode( mode );
2923 }
2924 
2926 {
2927  if ( !mPreviewEffect )
2928  {
2930  }
2931 
2932  return mPreviewEffect->mode();
2933 }
2934 
2936 {
2937  if ( !mSnappingUtils )
2938  {
2939  // associate a dummy instance, but better than null pointer
2940  QgsMapCanvas *c = const_cast<QgsMapCanvas *>( this );
2941  c->mSnappingUtils = new QgsMapCanvasSnappingUtils( c, c );
2942  }
2943  return mSnappingUtils;
2944 }
2945 
2947 {
2948  mSnappingUtils = utils;
2949 }
2950 
2951 void QgsMapCanvas::readProject( const QDomDocument &doc )
2952 {
2953  QgsProject *project = qobject_cast< QgsProject * >( sender() );
2954 
2955  QDomNodeList nodes = doc.elementsByTagName( QStringLiteral( "mapcanvas" ) );
2956  if ( nodes.count() )
2957  {
2958  QDomNode node = nodes.item( 0 );
2959 
2960  // Search the specific MapCanvas node using the name
2961  if ( nodes.count() > 1 )
2962  {
2963  for ( int i = 0; i < nodes.size(); ++i )
2964  {
2965  QDomElement elementNode = nodes.at( i ).toElement();
2966 
2967  if ( elementNode.hasAttribute( QStringLiteral( "name" ) ) && elementNode.attribute( QStringLiteral( "name" ) ) == objectName() )
2968  {
2969  node = nodes.at( i );
2970  break;
2971  }
2972  }
2973  }
2974 
2975  QgsMapSettings tmpSettings;
2976  tmpSettings.readXml( node );
2977  if ( objectName() != QLatin1String( "theMapCanvas" ) )
2978  {
2979  // never manually set the crs for the main canvas - this is instead connected to the project CRS
2980  setDestinationCrs( tmpSettings.destinationCrs() );
2981  }
2982  setExtent( tmpSettings.extent() );
2983  setRotation( tmpSettings.rotation() );
2985 
2986  clearExtentHistory(); // clear the extent history on project load
2987 
2988  QDomElement elem = node.toElement();
2989  if ( elem.hasAttribute( QStringLiteral( "theme" ) ) )
2990  {
2991  if ( QgsProject::instance()->mapThemeCollection()->hasMapTheme( elem.attribute( QStringLiteral( "theme" ) ) ) )
2992  {
2993  setTheme( elem.attribute( QStringLiteral( "theme" ) ) );
2994  }
2995  }
2996  setAnnotationsVisible( elem.attribute( QStringLiteral( "annotationsVisible" ), QStringLiteral( "1" ) ).toInt() );
2997 
2998  // restore canvas expression context
2999  const QDomNodeList scopeElements = elem.elementsByTagName( QStringLiteral( "expressionContextScope" ) );
3000  if ( scopeElements.size() > 0 )
3001  {
3002  const QDomElement scopeElement = scopeElements.at( 0 ).toElement();
3003  mExpressionContextScope.readXml( scopeElement, QgsReadWriteContext() );
3004  }
3005  }
3006  else
3007  {
3008  QgsDebugMsg( QStringLiteral( "Couldn't read mapcanvas information from project" ) );
3010  {
3012  clearExtentHistory(); // clear the extent history on project load
3013  }
3014  }
3015 }
3016 
3017 void QgsMapCanvas::writeProject( QDomDocument &doc )
3018 {
3019  // create node "mapcanvas" and call mMapRenderer->writeXml()
3020 
3021  QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
3022  if ( !nl.count() )
3023  {
3024  QgsDebugMsg( QStringLiteral( "Unable to find qgis element in project file" ) );
3025  return;
3026  }
3027  QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
3028 
3029  QDomElement mapcanvasNode = doc.createElement( QStringLiteral( "mapcanvas" ) );
3030  mapcanvasNode.setAttribute( QStringLiteral( "name" ), objectName() );
3031  if ( !mTheme.isEmpty() )
3032  mapcanvasNode.setAttribute( QStringLiteral( "theme" ), mTheme );
3033  mapcanvasNode.setAttribute( QStringLiteral( "annotationsVisible" ), mAnnotationsVisible );
3034  qgisNode.appendChild( mapcanvasNode );
3035 
3036  mSettings.writeXml( mapcanvasNode, doc );
3037 
3038  // store canvas expression context
3039  QDomElement scopeElement = doc.createElement( QStringLiteral( "expressionContextScope" ) );
3040  QgsExpressionContextScope tmpScope( mExpressionContextScope );
3041  tmpScope.removeVariable( QStringLiteral( "atlas_featurenumber" ) );
3042  tmpScope.removeVariable( QStringLiteral( "atlas_pagename" ) );
3043  tmpScope.removeVariable( QStringLiteral( "atlas_feature" ) );
3044  tmpScope.removeVariable( QStringLiteral( "atlas_featureid" ) );
3045  tmpScope.removeVariable( QStringLiteral( "atlas_geometry" ) );
3046  tmpScope.writeXml( scopeElement, doc, QgsReadWriteContext() );
3047  mapcanvasNode.appendChild( scopeElement );
3048 
3049  // TODO: store only units, extent, projections, dest CRS
3050 }
3051 
3052 void QgsMapCanvas::zoomByFactor( double scaleFactor, const QgsPointXY *center, bool ignoreScaleLock )
3053 {
3054  if ( mScaleLocked && !ignoreScaleLock )
3055  {
3056  ScaleRestorer restorer( this );
3058  }
3059  else
3060  {
3062  r.scale( scaleFactor, center );
3063  setExtent( r, true );
3064  refresh();
3065  }
3066 }
3067 
3069 {
3070  // Find out which layer it was that sent the signal.
3071  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( sender() );
3072  if ( layer )
3073  {
3074  emit selectionChanged( layer );
3075  refresh();
3076  }
3077 }
3078 
3079 void QgsMapCanvas::dragEnterEvent( QDragEnterEvent *event )
3080 {
3081  // By default graphics view delegates the drag events to graphics items.
3082  // But we do not want that and by ignoring the drag enter we let the
3083  // parent (e.g. QgisApp) to handle drops of map layers etc.
3084 
3085  // so we ONLY accept the event if we know in advance that a custom drop handler
3086  // wants it
3087 
3088  if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
3089  {
3091  bool allHandled = true;
3092  for ( const QgsMimeDataUtils::Uri &uri : lst )
3093  {
3094  bool handled = false;
3095  for ( QgsCustomDropHandler *handler : std::as_const( mDropHandlers ) )
3096  {
3097  if ( handler->canHandleCustomUriCanvasDrop( uri, this ) )
3098  {
3099  handled = true;
3100  break;
3101  }
3102  }
3103  if ( !handled )
3104  allHandled = false;
3105  }
3106  if ( allHandled )
3107  event->accept();
3108  else
3109  event->ignore();
3110  }
3111  else
3112  {
3113  event->ignore();
3114  }
3115 }
3116 
3117 bool QgsMapCanvas::viewportEvent( QEvent *event )
3118 {
3119  if ( event->type() == QEvent::ToolTip && mMapTool && mMapTool->canvasToolTipEvent( qgis::down_cast<QHelpEvent *>( event ) ) )
3120  {
3121  return true;
3122  }
3123  return QGraphicsView::viewportEvent( event );
3124 }
3125 
3126 void QgsMapCanvas::mapToolDestroyed()
3127 {
3128  QgsDebugMsgLevel( QStringLiteral( "maptool destroyed" ), 2 );
3129  mMapTool = nullptr;
3130 }
3131 
3132 bool QgsMapCanvas::event( QEvent *e )
3133 {
3134  if ( !QTouchDevice::devices().empty() )
3135  {
3136  if ( e->type() == QEvent::Gesture )
3137  {
3138  if ( QTapAndHoldGesture *tapAndHoldGesture = qobject_cast< QTapAndHoldGesture * >( static_cast<QGestureEvent *>( e )->gesture( Qt::TapAndHoldGesture ) ) )
3139  {
3140  QPointF pos = tapAndHoldGesture->position();
3141  pos = mapFromGlobal( QPoint( pos.x(), pos.y() ) );
3142  QgsPointXY mapPoint = getCoordinateTransform()->toMapCoordinates( pos.x(), pos.y() );
3143  emit tapAndHoldGestureOccurred( mapPoint, tapAndHoldGesture );
3144  }
3145 
3146  // call handler of current map tool
3147  if ( mMapTool )
3148  {
3149  return mMapTool->gestureEvent( static_cast<QGestureEvent *>( e ) );
3150  }
3151  }
3152  }
3153 
3154  // pass other events to base class
3155  return QGraphicsView::event( e );
3156 }
3157 
3159 {
3160  // reload all layers in canvas
3161  const QList<QgsMapLayer *> layers = mapSettings().layers();
3162  for ( QgsMapLayer *layer : layers )
3163  {
3164  layer->reload();
3165  }
3166 
3167  redrawAllLayers();
3168 }
3169 
3171 {
3172  // clear the cache
3173  clearCache();
3174 
3175  // and then refresh
3176  refresh();
3177 }
3178 
3180 {
3181  while ( mRefreshScheduled || mJob )
3182  {
3183  QgsApplication::processEvents();
3184  }
3185 }
3186 
3188 {
3189  mSettings.setSegmentationTolerance( tolerance );
3190 }
3191 
3193 {
3194  mSettings.setSegmentationToleranceType( type );
3195 }
3196 
3197 QList<QgsMapCanvasAnnotationItem *> QgsMapCanvas::annotationItems() const
3198 {
3199  QList<QgsMapCanvasAnnotationItem *> annotationItemList;
3200  const QList<QGraphicsItem *> items = mScene->items();
3201  for ( QGraphicsItem *gi : items )
3202  {
3203  QgsMapCanvasAnnotationItem *aItem = dynamic_cast< QgsMapCanvasAnnotationItem *>( gi );
3204  if ( aItem )
3205  {
3206  annotationItemList.push_back( aItem );
3207  }
3208  }
3209 
3210  return annotationItemList;
3211 }
3212 
3214 {
3215  mAnnotationsVisible = show;
3216  const QList<QgsMapCanvasAnnotationItem *> items = annotationItems();
3217  for ( QgsMapCanvasAnnotationItem *item : items )
3218  {
3219  item->setVisible( show );
3220  }
3221 }
3222 
3224 {
3225  mSettings.setLabelingEngineSettings( settings );
3226 }
3227 
3229 {
3230  return mSettings.labelingEngineSettings();
3231 }
3232 
3233 void QgsMapCanvas::startPreviewJobs()
3234 {
3235  stopPreviewJobs(); //just in case still running
3236 
3237  //canvas preview jobs aren't compatible with rotation
3238  // TODO fix this
3239  if ( !qgsDoubleNear( mSettings.rotation(), 0.0 ) )
3240  return;
3241 
3242  schedulePreviewJob( 0 );
3243 }
3244 
3245 void QgsMapCanvas::startPreviewJob( int number )
3246 {
3247  QgsRectangle mapRect = mSettings.visibleExtent();
3248 
3249  if ( number == 4 )
3250  number += 1;
3251 
3252  int j = number / 3;
3253  int i = number % 3;
3254 
3255  //copy settings, only update extent
3256  QgsMapSettings jobSettings = mSettings;
3257 
3258  double dx = ( i - 1 ) * mapRect.width();
3259  double dy = ( 1 - j ) * mapRect.height();
3260  QgsRectangle jobExtent = mapRect;
3261 
3262  jobExtent.setXMaximum( jobExtent.xMaximum() + dx );
3263  jobExtent.setXMinimum( jobExtent.xMinimum() + dx );
3264  jobExtent.setYMaximum( jobExtent.yMaximum() + dy );
3265  jobExtent.setYMinimum( jobExtent.yMinimum() + dy );
3266 
3267  jobSettings.setExtent( jobExtent );
3268  jobSettings.setFlag( Qgis::MapSettingsFlag::DrawLabeling, false );
3269  jobSettings.setFlag( Qgis::MapSettingsFlag::RenderPreviewJob, true );
3270 
3271  // truncate preview layers to fast layers
3272  const QList<QgsMapLayer *> layers = jobSettings.layers();
3273  QList< QgsMapLayer * > previewLayers;
3275  context.maxRenderingTimeMs = MAXIMUM_LAYER_PREVIEW_TIME_MS;
3276  for ( QgsMapLayer *layer : layers )
3277  {
3278  context.lastRenderingTimeMs = mLastLayerRenderTime.value( layer->id(), 0 );
3279  QgsDataProvider *provider = layer->dataProvider();
3280  if ( provider && !provider->renderInPreview( context ) )
3281  {
3282  QgsDebugMsgLevel( QStringLiteral( "Layer %1 not rendered because it does not match the renderInPreview criterion %2" ).arg( layer->id() ).arg( mLastLayerRenderTime.value( layer->id() ) ), 3 );
3283  continue;
3284  }
3285 
3286  previewLayers << layer;
3287  }
3288  jobSettings.setLayers( previewLayers );
3289 
3290  QgsMapRendererQImageJob *job = new QgsMapRendererSequentialJob( jobSettings );
3291  job->setProperty( "number", number );
3292  mPreviewJobs.append( job );
3293  connect( job, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
3294  job->start();
3295 }
3296 
3297 void QgsMapCanvas::stopPreviewJobs()
3298 {
3299  mPreviewTimer.stop();
3300  for ( auto previewJob = mPreviewJobs.constBegin(); previewJob != mPreviewJobs.constEnd(); ++previewJob )
3301  {
3302  if ( *previewJob )
3303  {
3304  disconnect( *previewJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
3305  connect( *previewJob, &QgsMapRendererQImageJob::finished, *previewJob, &QgsMapRendererQImageJob::deleteLater );
3306  ( *previewJob )->cancelWithoutBlocking();
3307  }
3308  }
3309  mPreviewJobs.clear();
3310 }
3311 
3312 void QgsMapCanvas::schedulePreviewJob( int number )
3313 {
3314  mPreviewTimer.setSingleShot( true );
3315  mPreviewTimer.setInterval( PREVIEW_JOB_DELAY_MS );
3316  disconnect( mPreviewTimerConnection );
3317  mPreviewTimerConnection = connect( &mPreviewTimer, &QTimer::timeout, this, [ = ]()
3318  {
3319  startPreviewJob( number );
3320  } );
3321  mPreviewTimer.start();
3322 }
3323 
3324 bool QgsMapCanvas::panOperationInProgress()
3325 {
3326  if ( mCanvasProperties->panSelectorDown )
3327  return true;
3328 
3329  if ( QgsMapToolPan *panTool = qobject_cast< QgsMapToolPan *>( mMapTool ) )
3330  {
3331  if ( panTool->isDragging() )
3332  return true;
3333  }
3334 
3335  return false;
3336 }
3337 
3338 int QgsMapCanvas::nextZoomLevel( const QList<double> &resolutions, bool zoomIn ) const
3339 {
3340  int resolutionLevel = -1;
3341  double currentResolution = mapUnitsPerPixel();
3342 
3343  for ( int i = 0, n = resolutions.size(); i < n; ++i )
3344  {
3345  if ( qgsDoubleNear( resolutions[i], currentResolution, 0.0001 ) )
3346  {
3347  resolutionLevel = zoomIn ? ( i - 1 ) : ( i + 1 );
3348  break;
3349  }
3350  else if ( currentResolution <= resolutions[i] )
3351  {
3352  resolutionLevel = zoomIn ? ( i - 1 ) : i;
3353  break;
3354  }
3355  }
3356  return ( resolutionLevel < 0 || resolutionLevel >= resolutions.size() ) ? -1 : resolutionLevel;
3357 }
3358 
3360 {
3361  if ( !mZoomResolutions.isEmpty() )
3362  {
3363  int zoomLevel = nextZoomLevel( mZoomResolutions, true );
3364  if ( zoomLevel != -1 )
3365  {
3366  return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
3367  }
3368  }
3369  return 1 / mWheelZoomFactor;
3370 }
3371 
3373 {
3374  if ( !mZoomResolutions.isEmpty() )
3375  {
3376  int zoomLevel = nextZoomLevel( mZoomResolutions, false );
3377  if ( zoomLevel != -1 )
3378  {
3379  return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
3380  }
3381  }
3382  return mWheelZoomFactor;
3383 }
@ 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:223
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
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:127
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 setCustomDropHandlers(const QVector< QPointer< QgsCustomDropHandler >> &handlers)
Sets a list of custom drop handlers to use when drop events occur on the canvas.
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 freeze(bool frozen=true)
Freeze/thaw the map canvas.
void enableAntiAliasing(bool flag)
used to determine if anti-aliasing is enabled or not
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.
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 selectionChanged(QgsVectorLayer *layer)
Emitted when selection in any layer gets changed.
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 panToSelected(QgsVectorLayer *layer=nullptr)
Pan to the selected features of current (vector) layer keeping same extent.
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 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 zoomToSelected(QgsVectorLayer *layer=nullptr)
Zoom to the extent of the selected features of provided (vector) layer.
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...
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.
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:1509
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
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1502
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.
virtual QgsLabelingResults * takeLabelingResults()=0
Gets pointer to internal labeling engine (in order to get access to the results).
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
virtual bool isActive() const =0
Tell whether the rendering job is currently running in background.
void setLayerRenderingTimeHints(const QHash< QString, int > &hints)
Sets approximate render times (in ms) for map layers.
QgsRenderedItemResults * takeRenderedItemResults()
Takes the rendered item results from the map render job and returns them.
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
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.
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.
const QgsMapToPixel & mapToPixel() const
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...
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns the global configuration of the labeling engine.
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:123
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:114
@ EditTool
Map tool is an edit tool, which can only be used when layer is editable.
Definition: qgsmaptool.h:113
@ ShowContextMenu
Show a context menu when right-clicking with the tool (since QGIS 3.14). See populateContextMenu().
Definition: qgsmaptool.h:115
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.
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:479
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.
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:283
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.
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.
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
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:2260
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2186
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.