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