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