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