QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 
41 #include "qgis.h"
42 #include "qgssettings.h"
44 #include "qgsapplication.h"
45 #include "qgsexception.h"
47 #include "qgsfeatureiterator.h"
48 #include "qgslogger.h"
49 #include "qgsmapcanvas.h"
50 #include "qgsmapcanvasmap.h"
52 #include "qgsmaplayer.h"
53 #include "qgsmapmouseevent.h"
54 #include "qgsmaptoolpan.h"
55 #include "qgsmaptoolzoom.h"
56 #include "qgsmaptopixel.h"
57 #include "qgsmapoverviewcanvas.h"
58 #include "qgsmaprenderercache.h"
62 #include "qgsmapsettingsutils.h"
63 #include "qgsmessagelog.h"
64 #include "qgsmessageviewer.h"
65 #include "qgspallabeling.h"
66 #include "qgsproject.h"
67 #include "qgsrubberband.h"
68 #include "qgsvectorlayer.h"
69 #include "qgsmapthemecollection.h"
71 #include "qgssvgcache.h"
72 #include "qgsimagecache.h"
74 #include "qgsmimedatautils.h"
75 #include "qgscustomdrophandler.h"
76 #include "qgsreferencedgeometry.h"
77 #include "qgsprojectviewsettings.h"
78 
84 //TODO QGIS 4.0 - remove
86 {
87  public:
88 
92  CanvasProperties() = default;
93 
95  bool mouseButtonDown{ false };
96 
98  QPoint mouseLastXY;
99 
102 
104  bool panSelectorDown{ false };
105 };
106 
107 
108 
109 QgsMapCanvas::QgsMapCanvas( QWidget *parent )
110  : QGraphicsView( parent )
112  , mExpressionContextScope( tr( "Map Canvas" ) )
113 {
114  mScene = new QGraphicsScene();
115  setScene( mScene );
116  setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
117  setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
118  setMouseTracking( true );
119  setFocusPolicy( Qt::StrongFocus );
120 
121  mResizeTimer = new QTimer( this );
122  mResizeTimer->setSingleShot( true );
123  connect( mResizeTimer, &QTimer::timeout, this, &QgsMapCanvas::refresh );
124 
125  mRefreshTimer = new QTimer( this );
126  mRefreshTimer->setSingleShot( true );
127  connect( mRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::refreshMap );
128 
129  // create map canvas item which will show the map
130  mMap = new QgsMapCanvasMap( this );
131 
132  // project handling
134  this, &QgsMapCanvas::readProject );
137 
138  connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsMapCanvas::mapThemeChanged );
139  connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemesChanged, this, &QgsMapCanvas::projectThemesChanged );
140 
144  mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
146  this, [ = ]
147  {
148  mSettings.setEllipsoid( QgsProject::instance()->ellipsoid() );
149  refresh();
150  } );
151  mSettings.setTransformContext( QgsProject::instance()->transformContext() );
153  this, [ = ]
154  {
155  mSettings.setTransformContext( QgsProject::instance()->transformContext() );
157  refresh();
158  } );
159 
160  // refresh canvas when a remote svg/image has finished downloading
163  // refresh canvas when project color scheme is changed -- if layers use project colors, they need to be redrawn
165 
166  //segmentation parameters
167  QgsSettings settings;
168  double segmentationTolerance = settings.value( QStringLiteral( "qgis/segmentationTolerance" ), "0.01745" ).toDouble();
169  QgsAbstractGeometry::SegmentationToleranceType toleranceType = settings.enumValue( QStringLiteral( "qgis/segmentationToleranceType" ), QgsAbstractGeometry::MaximumAngle );
170  mSettings.setSegmentationTolerance( segmentationTolerance );
171  mSettings.setSegmentationToleranceType( toleranceType );
172 
173  mWheelZoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
174 
175  QSize s = viewport()->size();
176  mSettings.setOutputSize( s );
177  mSettings.setDevicePixelRatio( devicePixelRatio() );
178  setSceneRect( 0, 0, s.width(), s.height() );
179  mScene->setSceneRect( QRectF( 0, 0, s.width(), s.height() ) );
180 
181  moveCanvasContents( true );
182 
183  // keep device pixel ratio up to date on screen or resolution change
184  if ( window()->windowHandle() )
185  {
186  connect( window()->windowHandle(), &QWindow::screenChanged, this, [ = ]( QScreen * ) {mSettings.setDevicePixelRatio( devicePixelRatio() );} );
187  connect( window()->windowHandle()->screen(), &QScreen::physicalDotsPerInchChanged, this, [ = ]( qreal ) {mSettings.setDevicePixelRatio( devicePixelRatio() );} );
188  }
189 
190  connect( &mMapUpdateTimer, &QTimer::timeout, this, &QgsMapCanvas::mapUpdateTimeout );
191  mMapUpdateTimer.setInterval( 250 );
192 
193 #ifdef Q_OS_WIN
194  // Enable touch event on Windows.
195  // Qt on Windows needs to be told it can take touch events or else it ignores them.
196  grabGesture( Qt::PinchGesture );
197  grabGesture( Qt::TapAndHoldGesture );
198  viewport()->setAttribute( Qt::WA_AcceptTouchEvents );
199 #endif
200 
201  mPreviewEffect = new QgsPreviewEffect( this );
202  viewport()->setGraphicsEffect( mPreviewEffect );
203 
204  mZoomCursor = QgsApplication::getThemeCursor( QgsApplication::Cursor::ZoomIn );
205 
206  connect( &mAutoRefreshTimer, &QTimer::timeout, this, &QgsMapCanvas::autoRefreshTriggered );
207 
209 
210  setInteractive( false );
211 
212  // make sure we have the same default in QgsMapSettings and the scene's background brush
213  // (by default map settings has white bg color, scene background brush is black)
214  setCanvasColor( mSettings.backgroundColor() );
215 
216  refresh();
217 
218 } // QgsMapCanvas ctor
219 
220 
222 {
223  if ( mMapTool )
224  {
225  mMapTool->deactivate();
226  mMapTool = nullptr;
227  }
228  mLastNonZoomMapTool = nullptr;
229 
230  // rendering job may still end up writing into canvas map item
231  // so kill it before deleting canvas items
232  if ( mJob )
233  {
234  whileBlocking( mJob )->cancel();
235  delete mJob;
236  }
237 
238  QList< QgsMapRendererQImageJob * >::const_iterator previewJob = mPreviewJobs.constBegin();
239  for ( ; previewJob != mPreviewJobs.constEnd(); ++previewJob )
240  {
241  if ( *previewJob )
242  {
243  whileBlocking( *previewJob )->cancel();
244  delete *previewJob;
245  }
246  }
247 
248  // delete canvas items prior to deleting the canvas
249  // because they might try to update canvas when it's
250  // already being destructed, ends with segfault
251  qDeleteAll( mScene->items() );
252 
253  mScene->deleteLater(); // crashes in python tests on windows
254 
255  delete mCache;
256  delete mLabelingResults;
257 }
258 
260 {
261  // do not go higher or lower than min max magnification ratio
262  double magnifierMin = QgsGuiUtils::CANVAS_MAGNIFICATION_MIN;
263  double magnifierMax = QgsGuiUtils::CANVAS_MAGNIFICATION_MAX;
264  factor = qBound( magnifierMin, factor, magnifierMax );
265 
266  // the magnifier widget is in integer percent
267  if ( !qgsDoubleNear( factor, mSettings.magnificationFactor(), 0.01 ) )
268  {
269  mSettings.setMagnificationFactor( factor );
270  refresh();
271  emit magnificationChanged( factor );
272  }
273 }
274 
276 {
277  return mSettings.magnificationFactor();
278 }
279 
281 {
282  mSettings.setFlag( QgsMapSettings::Antialiasing, flag );
283 } // anti aliasing
284 
286 {
287  mSettings.setFlag( QgsMapSettings::RenderMapTile, flag );
288 }
289 
291 {
292  QList<QgsMapLayer *> layers = mapSettings().layers();
293  if ( index >= 0 && index < layers.size() )
294  return layers[index];
295  else
296  return nullptr;
297 }
298 
300 {
301  if ( mCurrentLayer == layer )
302  return;
303 
304  mCurrentLayer = layer;
305  emit currentLayerChanged( layer );
306 }
307 
308 double QgsMapCanvas::scale() const
309 {
310  return mapSettings().scale();
311 }
312 
314 {
315  return nullptr != mJob;
316 } // isDrawing
317 
318 // return the current coordinate transform based on the extents and
319 // device size
321 {
322  return &mapSettings().mapToPixel();
323 }
324 
325 void QgsMapCanvas::setLayers( const QList<QgsMapLayer *> &layers )
326 {
327  // following a theme => request denied!
328  if ( !mTheme.isEmpty() )
329  return;
330 
331  setLayersPrivate( layers );
332 }
333 
334 void QgsMapCanvas::setLayersPrivate( const QList<QgsMapLayer *> &layers )
335 {
336  QList<QgsMapLayer *> oldLayers = mSettings.layers();
337 
338  // update only if needed
339  if ( layers == oldLayers )
340  return;
341 
342  const auto constOldLayers = oldLayers;
343  for ( QgsMapLayer *layer : constOldLayers )
344  {
345  disconnect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
346  disconnect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
347  if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
348  {
350  }
351  }
352 
353  mSettings.setLayers( layers );
354 
355  const auto constLayers = layers;
356  for ( QgsMapLayer *layer : constLayers )
357  {
358  if ( !layer )
359  continue;
360  connect( layer, &QgsMapLayer::repaintRequested, this, &QgsMapCanvas::layerRepaintRequested );
361  connect( layer, &QgsMapLayer::autoRefreshIntervalChanged, this, &QgsMapCanvas::updateAutoRefreshTimer );
362  if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
363  {
365  }
366  }
367 
368  QgsDebugMsgLevel( QStringLiteral( "Layers have changed, refreshing" ), 2 );
369  emit layersChanged();
370 
371  updateAutoRefreshTimer();
372  refresh();
373 }
374 
375 
377 {
378  return mSettings;
379 }
380 
382 {
383  if ( mSettings.destinationCrs() == crs )
384  return;
385 
386  // try to reproject current extent to the new one
387  QgsRectangle rect;
388  if ( !mSettings.visibleExtent().isEmpty() )
389  {
390  QgsCoordinateTransform transform( mSettings.destinationCrs(), crs, QgsProject::instance() );
391  try
392  {
393  rect = transform.transformBoundingBox( mSettings.visibleExtent() );
394  }
395  catch ( QgsCsException &e )
396  {
397  Q_UNUSED( e )
398  QgsDebugMsg( QStringLiteral( "Transform error caught: %1" ).arg( e.what() ) );
399  }
400  }
401 
402  if ( !rect.isEmpty() )
403  {
404  setExtent( rect );
405  }
406 
407  mSettings.setDestinationCrs( crs );
408  updateScale();
409 
410  QgsDebugMsgLevel( QStringLiteral( "refreshing after destination CRS changed" ), 2 );
411  refresh();
412 
413  emit destinationCrsChanged();
414 }
415 
416 void QgsMapCanvas::setMapSettingsFlags( QgsMapSettings::Flags flags )
417 {
418  mSettings.setFlags( flags );
419  clearCache();
420  refresh();
421 }
422 
424 {
425  return mLabelingResults;
426 }
427 
429 {
430  if ( enabled == isCachingEnabled() )
431  return;
432 
433  if ( mJob && mJob->isActive() )
434  {
435  // wait for the current rendering to finish, before touching the cache
436  mJob->waitForFinished();
437  }
438 
439  if ( enabled )
440  {
441  mCache = new QgsMapRendererCache;
442  }
443  else
444  {
445  delete mCache;
446  mCache = nullptr;
447  }
448 }
449 
451 {
452  return nullptr != mCache;
453 }
454 
456 {
457  if ( mCache )
458  mCache->clear();
459 }
460 
462 {
463  mUseParallelRendering = enabled;
464 }
465 
467 {
468  return mUseParallelRendering;
469 }
470 
471 void QgsMapCanvas::setMapUpdateInterval( int timeMilliseconds )
472 {
473  mMapUpdateTimer.setInterval( timeMilliseconds );
474 }
475 
477 {
478  return mMapUpdateTimer.interval();
479 }
480 
481 
483 {
484  return mCurrentLayer;
485 }
486 
488 {
489  QgsExpressionContextScope *s = new QgsExpressionContextScope( QObject::tr( "Map Canvas" ) );
490  s->setVariable( QStringLiteral( "canvas_cursor_point" ), QgsGeometry::fromPointXY( cursorPoint() ), true );
491 
492  return s;
493 }
494 
496 {
497  if ( !mSettings.hasValidSettings() )
498  {
499  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh - invalid settings -> nothing to do" ), 2 );
500  return;
501  }
502 
503  if ( !mRenderFlag || mFrozen )
504  {
505  QgsDebugMsgLevel( QStringLiteral( "CANVAS render flag off" ), 2 );
506  return;
507  }
508 
509  if ( mRefreshScheduled )
510  {
511  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh already scheduled" ), 2 );
512  return;
513  }
514 
515  mRefreshScheduled = true;
516 
517  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh scheduling" ), 2 );
518 
519  // schedule a refresh
520  mRefreshTimer->start( 1 );
521 }
522 
523 void QgsMapCanvas::refreshMap()
524 {
525  Q_ASSERT( mRefreshScheduled );
526 
527  QgsDebugMsgLevel( QStringLiteral( "CANVAS refresh!" ), 3 );
528 
529  stopRendering(); // if any...
530  stopPreviewJobs();
531 
532  //build the expression context
533  QgsExpressionContext expressionContext;
534  expressionContext << QgsExpressionContextUtils::globalScope()
539  << new QgsExpressionContextScope( mExpressionContextScope );
540 
541  mSettings.setExpressionContext( expressionContext );
542  mSettings.setPathResolver( QgsProject::instance()->pathResolver() );
543 
544  if ( !mTheme.isEmpty() )
545  {
546  // IMPORTANT: we MUST set the layer style overrides here! (At the time of writing this
547  // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
548  // current state of the style. If we had stored the style overrides earlier (such as in
549  // mapThemeChanged slot) then this xml could be out of date...
550  // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
551  // just return the style name, we can instead set the overrides in mapThemeChanged and not here
552  mSettings.setLayerStyleOverrides( QgsProject::instance()->mapThemeCollection()->mapThemeStyleOverrides( mTheme ) );
553  }
554 
555  // create the renderer job
556  Q_ASSERT( !mJob );
557  mJobCanceled = false;
558  if ( mUseParallelRendering )
559  mJob = new QgsMapRendererParallelJob( mSettings );
560  else
561  mJob = new QgsMapRendererSequentialJob( mSettings );
562  connect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
563  mJob->setCache( mCache );
564 
565  mJob->start();
566 
567  // from now on we can accept refresh requests again
568  // this must be reset only after the job has been started, because
569  // some providers (yes, it's you WCS and AMS!) during preparation
570  // do network requests and start an internal event loop, which may
571  // end up calling refresh() and would schedule another refresh,
572  // deleting the one we have just started.
573  mRefreshScheduled = false;
574 
575  mMapUpdateTimer.start();
576 
577  emit renderStarting();
578 }
579 
580 void QgsMapCanvas::mapThemeChanged( const QString &theme )
581 {
582  if ( theme == mTheme )
583  {
584  // set the canvas layers to match the new layers contained in the map theme
585  // NOTE: we do this when the theme layers change and not when we are refreshing the map
586  // as setLayers() sets up necessary connections to handle changes to the layers
587  setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
588  // IMPORTANT: we don't set the layer style overrides here! (At the time of writing this
589  // comment) retrieving layer styles from the theme collection gives an XML snapshot of the
590  // current state of the style. If changes were made to the style then this xml
591  // snapshot goes out of sync...
592  // TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
593  // just return the style name, we can instead set the overrides here and not in refreshMap()
594 
595  clearCache();
596  refresh();
597  }
598 }
599 
600 void QgsMapCanvas::rendererJobFinished()
601 {
602  QgsDebugMsgLevel( QStringLiteral( "CANVAS finish! %1" ).arg( !mJobCanceled ), 2 );
603 
604  mMapUpdateTimer.stop();
605 
606  // TODO: would be better to show the errors in message bar
607  const auto constErrors = mJob->errors();
608  for ( const QgsMapRendererJob::Error &error : constErrors )
609  {
610  QgsMapLayer *layer = QgsProject::instance()->mapLayer( error.layerID );
611  emit renderErrorOccurred( error.message, layer );
612  QgsMessageLog::logMessage( error.layerID + " :: " + error.message, tr( "Rendering" ) );
613  }
614 
615  if ( !mJobCanceled )
616  {
617  // take labeling results before emitting renderComplete, so labeling map tools
618  // connected to signal work with correct results
619  if ( !mJob->usedCachedLabels() )
620  {
621  delete mLabelingResults;
622  mLabelingResults = mJob->takeLabelingResults();
623  }
624 
625  QImage img = mJob->renderedImage();
626 
627  // emit renderComplete to get our decorations drawn
628  QPainter p( &img );
629  emit renderComplete( &p );
630 
631  QgsSettings settings;
632  if ( settings.value( QStringLiteral( "Map/logCanvasRefreshEvent" ), false ).toBool() )
633  {
634  QString logMsg = tr( "Canvas refresh: %1 ms" ).arg( mJob->renderingTime() );
635  QgsMessageLog::logMessage( logMsg, tr( "Rendering" ) );
636  }
637 
638  if ( mDrawRenderingStats )
639  {
640  int w = img.width(), h = img.height();
641  QFont fnt = p.font();
642  fnt.setBold( true );
643  p.setFont( fnt );
644  int lh = p.fontMetrics().height() * 2;
645  QRect r( 0, h - lh, w, lh );
646  p.setPen( Qt::NoPen );
647  p.setBrush( QColor( 0, 0, 0, 110 ) );
648  p.drawRect( r );
649  p.setPen( Qt::white );
650  QString msg = QStringLiteral( "%1 :: %2 ms" ).arg( mUseParallelRendering ? QStringLiteral( "PARALLEL" ) : QStringLiteral( "SEQUENTIAL" ) ).arg( mJob->renderingTime() );
651  p.drawText( r, msg, QTextOption( Qt::AlignCenter ) );
652  }
653 
654  p.end();
655 
656  mMap->setContent( img, imageRect( img, mSettings ) );
657 
658  mLastLayerRenderTime.clear();
659  const auto times = mJob->perLayerRenderingTime();
660  for ( auto it = times.constBegin(); it != times.constEnd(); ++it )
661  {
662  mLastLayerRenderTime.insert( it.key()->id(), it.value() );
663  }
664  if ( mUsePreviewJobs )
665  startPreviewJobs();
666  }
667 
668  // now we are in a slot called from mJob - do not delete it immediately
669  // so the class is still valid when the execution returns to the class
670  mJob->deleteLater();
671  mJob = nullptr;
672 
673  emit mapCanvasRefreshed();
674 }
675 
676 void QgsMapCanvas::previewJobFinished()
677 {
678  QgsMapRendererQImageJob *job = qobject_cast<QgsMapRendererQImageJob *>( sender() );
679  Q_ASSERT( job );
680 
681  if ( mMap )
682  {
683  mMap->addPreviewImage( job->renderedImage(), job->mapSettings().extent() );
684  mPreviewJobs.removeAll( job );
685 
686  int number = job->property( "number" ).toInt();
687  if ( number < 8 )
688  {
689  startPreviewJob( number + 1 );
690  }
691 
692  delete job;
693  }
694 }
695 
696 QgsRectangle QgsMapCanvas::imageRect( const QImage &img, const QgsMapSettings &mapSettings )
697 {
698  // This is a hack to pass QgsMapCanvasItem::setRect what it
699  // expects (encoding of position and size of the item)
700  const QgsMapToPixel &m2p = mapSettings.mapToPixel();
701  QgsPointXY topLeft = m2p.toMapCoordinates( 0, 0 );
702 #ifdef QGISDEBUG
703  // do not assert this, since it might lead to crashes when changing screen while rendering
704  if ( img.devicePixelRatio() != mapSettings.devicePixelRatio() )
705  {
706  QgsLogger::warning( QStringLiteral( "The renderer map has a wrong device pixel ratio" ) );
707  }
708 #endif
709  double res = m2p.mapUnitsPerPixel() / img.devicePixelRatioF();
710  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + img.width()*res, topLeft.y() - img.height()*res );
711  return rect;
712 }
713 
715 {
716  return mUsePreviewJobs;
717 }
718 
720 {
721  mUsePreviewJobs = enabled;
722 }
723 
724 void QgsMapCanvas::setCustomDropHandlers( const QVector<QPointer<QgsCustomDropHandler> > &handlers )
725 {
726  mDropHandlers = handlers;
727 }
728 
729 void QgsMapCanvas::mapUpdateTimeout()
730 {
731  if ( mJob )
732  {
733  const QImage &img = mJob->renderedImage();
734  mMap->setContent( img, imageRect( img, mSettings ) );
735  }
736 }
737 
739 {
740  if ( mJob )
741  {
742  QgsDebugMsgLevel( QStringLiteral( "CANVAS stop rendering!" ), 2 );
743  mJobCanceled = true;
744  disconnect( mJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::rendererJobFinished );
745  connect( mJob, &QgsMapRendererQImageJob::finished, mJob, &QgsMapRendererQImageJob::deleteLater );
746  mJob->cancelWithoutBlocking();
747  mJob = nullptr;
748  }
749  stopPreviewJobs();
750 }
751 
752 //the format defaults to "PNG" if not specified
753 void QgsMapCanvas::saveAsImage( const QString &fileName, QPixmap *theQPixmap, const QString &format )
754 {
755  QPainter painter;
756  QImage image;
757 
758  //
759  //check if the optional QPaintDevice was supplied
760  //
761  if ( theQPixmap )
762  {
763  image = theQPixmap->toImage();
764  painter.begin( &image );
765 
766  // render
767  QgsMapRendererCustomPainterJob job( mSettings, &painter );
768  job.start();
769  job.waitForFinished();
770  emit renderComplete( &painter );
771  }
772  else //use the map view
773  {
774  image = mMap->contentImage().copy();
775  painter.begin( &image );
776  }
777 
778  // draw annotations
779  QStyleOptionGraphicsItem option;
780  option.initFrom( this );
781  QGraphicsItem *item = nullptr;
782  QListIterator<QGraphicsItem *> i( items() );
783  i.toBack();
784  while ( i.hasPrevious() )
785  {
786  item = i.previous();
787 
788  if ( !( item && dynamic_cast< QgsMapCanvasAnnotationItem * >( item ) ) )
789  {
790  continue;
791  }
792 
793  painter.save();
794 
795  QPointF itemScenePos = item->scenePos();
796  painter.translate( itemScenePos.x(), itemScenePos.y() );
797 
798  item->paint( &painter, &option );
799 
800  painter.restore();
801  }
802 
803  painter.end();
804  image.save( fileName, format.toLocal8Bit().data() );
805 
806  QFileInfo myInfo = QFileInfo( fileName );
807 
808  // build the world file name
809  QString outputSuffix = myInfo.suffix();
810  QString myWorldFileName = myInfo.absolutePath() + '/' + myInfo.baseName() + '.'
811  + outputSuffix.at( 0 ) + outputSuffix.at( myInfo.suffix().size() - 1 ) + 'w';
812  QFile myWorldFile( myWorldFileName );
813  if ( !myWorldFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) //don't use QIODevice::Text
814  {
815  return;
816  }
817  QTextStream myStream( &myWorldFile );
819 } // saveAsImage
820 
821 
822 
824 {
825  return mapSettings().visibleExtent();
826 } // extent
827 
829 {
830  return mapSettings().fullExtent();
831 } // extent
832 
833 
834 void QgsMapCanvas::setExtent( const QgsRectangle &r, bool magnified )
835 {
836  QgsRectangle current = extent();
837 
838  if ( ( r == current ) && magnified )
839  return;
840 
841  if ( r.isEmpty() )
842  {
843  if ( !mSettings.hasValidSettings() )
844  {
845  // we can't even just move the map center
846  QgsDebugMsgLevel( QStringLiteral( "Empty extent - ignoring" ), 2 );
847  return;
848  }
849 
850  // ### QGIS 3: do not allow empty extent - require users to call setCenter() explicitly
851  QgsDebugMsgLevel( QStringLiteral( "Empty extent - keeping old scale with new center!" ), 2 );
852  setCenter( r.center() );
853  }
854  else
855  {
856  mSettings.setExtent( r, magnified );
857  }
858  emit extentsChanged();
859  updateScale();
860  if ( mLastExtent.size() > 20 )
861  mLastExtent.removeAt( 0 );
862 
863  //clear all extent items after current index
864  for ( int i = mLastExtent.size() - 1; i > mLastExtentIndex; i-- )
865  {
866  mLastExtent.removeAt( i );
867  }
868 
869  mLastExtent.append( extent() );
870 
871  // adjust history to no more than 20
872  if ( mLastExtent.size() > 20 )
873  {
874  mLastExtent.removeAt( 0 );
875  }
876 
877  // the last item is the current extent
878  mLastExtentIndex = mLastExtent.size() - 1;
879 
880  // update controls' enabled state
881  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
882  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
883 }
884 
886 {
887  QgsRectangle canvasExtent = extent;
888  if ( extent.crs() != mapSettings().destinationCrs() )
889  {
891  canvasExtent = ct.transform( extent );
892 
893  if ( canvasExtent.isEmpty() )
894  {
895  return false;
896  }
897  }
898 
899  setExtent( canvasExtent );
900  return true;
901 }
902 
904 {
905  const QgsRectangle r = mapSettings().extent();
906  const double x = center.x();
907  const double y = center.y();
908  const QgsRectangle rect(
909  x - r.width() / 2.0, y - r.height() / 2.0,
910  x + r.width() / 2.0, y + r.height() / 2.0
911  );
912  if ( ! rect.isEmpty() )
913  {
914  setExtent(
915  rect,
916  true
917  );
918  }
919 } // setCenter
920 
922 {
924  return r.center();
925 }
926 
927 QgsPointXY QgsMapCanvas::cursorPoint() const
928 {
929  return mCursorPoint;
930 }
931 
933 {
934  return mapSettings().rotation();
935 } // rotation
936 
937 void QgsMapCanvas::setRotation( double degrees )
938 {
939  double current = rotation();
940 
941  if ( qgsDoubleNear( degrees, current ) )
942  return;
943 
944  mSettings.setRotation( degrees );
945  emit rotationChanged( degrees );
946  emit extentsChanged(); // visible extent changes with rotation
947 } // setRotation
948 
949 
951 {
952  emit scaleChanged( mapSettings().scale() );
953 }
954 
955 
957 {
959  // If the full extent is an empty set, don't do the zoom
960  if ( !extent.isEmpty() )
961  {
962  // Add a 5% margin around the full extent
963  extent.scale( 1.05 );
964  setExtent( extent );
965  }
966  refresh();
967 
968 } // zoomToFullExtent
969 
970 
972 {
973  if ( mLastExtentIndex > 0 )
974  {
975  mLastExtentIndex--;
976  mSettings.setExtent( mLastExtent[mLastExtentIndex] );
977  emit extentsChanged();
978  updateScale();
979  refresh();
980  // update controls' enabled state
981  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
982  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
983  }
984 
985 } // zoomToPreviousExtent
986 
988 {
989  if ( mLastExtentIndex < mLastExtent.size() - 1 )
990  {
991  mLastExtentIndex++;
992  mSettings.setExtent( mLastExtent[mLastExtentIndex] );
993  emit extentsChanged();
994  updateScale();
995  refresh();
996  // update controls' enabled state
997  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
998  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
999  }
1000 }// zoomToNextExtent
1001 
1003 {
1004  mLastExtent.clear(); // clear the zoom history list
1005  mLastExtent.append( extent() ) ; // set the current extent in the list
1006  mLastExtentIndex = mLastExtent.size() - 1;
1007  // update controls' enabled state
1008  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1009  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1010 }// clearExtentHistory
1011 
1013 {
1014  if ( !layer )
1015  {
1016  // use current layer by default
1017  layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1018  }
1019 
1020  if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
1021  return;
1022 
1023  QgsRectangle rect = layer->boundingBoxOfSelected();
1024  if ( rect.isNull() )
1025  {
1026  emit messageEmitted( tr( "Cannot zoom to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::Warning );
1027  return;
1028  }
1029 
1030  rect = mapSettings().layerExtentToOutputExtent( layer, rect );
1031 
1032  // zoom in if point cannot be distinguished from others
1033  // also check that rect is empty, as it might not in case of multi points
1034  if ( layer->geometryType() == QgsWkbTypes::PointGeometry && rect.isEmpty() )
1035  {
1036  int scaleFactor = 5;
1037  QgsPointXY centerMapCoordinates = rect.center();
1038  QgsPointXY centerLayerCoordinates = mSettings.mapToLayerCoordinates( layer, centerMapCoordinates );
1039  QgsRectangle extentRect = mSettings.mapToLayerCoordinates( layer, extent() ).scaled( 1.0 / scaleFactor, &centerLayerCoordinates );
1041  QgsFeatureIterator fit = layer->getFeatures( req );
1042  QgsFeature f;
1043  QgsPointXY closestPoint;
1044  double closestSquaredDistance = pow( extentRect.width(), 2.0 ) + pow( extentRect.height(), 2.0 );
1045  bool pointFound = false;
1046  while ( fit.nextFeature( f ) )
1047  {
1048  QgsPointXY point = f.geometry().asPoint();
1049  double sqrDist = point.sqrDist( centerLayerCoordinates );
1050  if ( sqrDist > closestSquaredDistance || sqrDist < 4 * std::numeric_limits<double>::epsilon() )
1051  continue;
1052  pointFound = true;
1053  closestPoint = point;
1054  closestSquaredDistance = sqrDist;
1055  }
1056  if ( pointFound )
1057  {
1058  // combine selected point with closest point and scale this rect
1059  rect.combineExtentWith( mSettings.layerToMapCoordinates( layer, closestPoint ) );
1060  rect.scale( scaleFactor, &centerMapCoordinates );
1061  }
1062  }
1063 
1064  zoomToFeatureExtent( rect );
1065 }
1066 
1068 {
1069  // no selected features, only one selected point feature
1070  //or two point features with the same x- or y-coordinates
1071  if ( rect.isEmpty() )
1072  {
1073  // zoom in
1074  QgsPointXY c = rect.center();
1075  rect = extent();
1076  rect.scale( 1.0, &c );
1077  }
1078  //zoom to an area
1079  else
1080  {
1081  // Expand rect to give a bit of space around the selected
1082  // objects so as to keep them clear of the map boundaries
1083  // The same 5% should apply to all margins.
1084  rect.scale( 1.05 );
1085  }
1086 
1087  setExtent( rect );
1088  refresh();
1089 }
1090 
1092 {
1093  if ( !layer )
1094  {
1095  return;
1096  }
1097 
1098  QgsRectangle bbox;
1099  QString errorMsg;
1100  if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
1101  {
1102  zoomToFeatureExtent( bbox );
1103  }
1104  else
1105  {
1106  emit messageEmitted( tr( "Zoom to feature id failed" ), errorMsg, Qgis::Warning );
1107  }
1108 
1109 }
1110 
1111 void QgsMapCanvas::panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter )
1112 {
1113  if ( !layer )
1114  {
1115  return;
1116  }
1117 
1118  QgsRectangle bbox;
1119  QString errorMsg;
1120  if ( boundingBoxOfFeatureIds( ids, layer, bbox, errorMsg ) )
1121  {
1122  if ( alwaysRecenter || !mapSettings().extent().contains( bbox ) )
1123  setCenter( bbox.center() );
1124  refresh();
1125  }
1126  else
1127  {
1128  emit messageEmitted( tr( "Pan to feature id failed" ), errorMsg, Qgis::Warning );
1129  }
1130 }
1131 
1132 bool QgsMapCanvas::boundingBoxOfFeatureIds( const QgsFeatureIds &ids, QgsVectorLayer *layer, QgsRectangle &bbox, QString &errorMsg ) const
1133 {
1134  QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
1135  bbox.setMinimal();
1136  QgsFeature fet;
1137  int featureCount = 0;
1138  errorMsg.clear();
1139 
1140  while ( it.nextFeature( fet ) )
1141  {
1142  QgsGeometry geom = fet.geometry();
1143  if ( geom.isNull() )
1144  {
1145  errorMsg = tr( "Feature does not have a geometry" );
1146  }
1147  else if ( geom.constGet()->isEmpty() )
1148  {
1149  errorMsg = tr( "Feature geometry is empty" );
1150  }
1151  if ( !errorMsg.isEmpty() )
1152  {
1153  return false;
1154  }
1156  bbox.combineExtentWith( r );
1157  featureCount++;
1158  }
1159 
1160  if ( featureCount != ids.count() )
1161  {
1162  errorMsg = tr( "Feature not found" );
1163  return false;
1164  }
1165 
1166  return true;
1167 }
1168 
1170 {
1171  if ( !layer )
1172  {
1173  // use current layer by default
1174  layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1175  }
1176 
1177  if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
1178  return;
1179 
1180  QgsRectangle rect = layer->boundingBoxOfSelected();
1181  if ( rect.isNull() )
1182  {
1183  emit messageEmitted( tr( "Cannot pan to selected feature(s)" ), tr( "No extent could be determined." ), Qgis::Warning );
1184  return;
1185  }
1186 
1187  rect = mapSettings().layerExtentToOutputExtent( layer, rect );
1188  setCenter( rect.center() );
1189  refresh();
1190 }
1191 
1193  const QColor &color1, const QColor &color2,
1194  int flashes, int duration )
1195 {
1196  if ( !layer )
1197  {
1198  return;
1199  }
1200 
1201  QList< QgsGeometry > geoms;
1202 
1203  QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setFilterFids( ids ).setNoAttributes() );
1204  QgsFeature fet;
1205  while ( it.nextFeature( fet ) )
1206  {
1207  if ( !fet.hasGeometry() )
1208  continue;
1209  geoms << fet.geometry();
1210  }
1211 
1212  flashGeometries( geoms, layer->crs(), color1, color2, flashes, duration );
1213 }
1214 
1215 void QgsMapCanvas::flashGeometries( const QList<QgsGeometry> &geometries, const QgsCoordinateReferenceSystem &crs, const QColor &color1, const QColor &color2, int flashes, int duration )
1216 {
1217  if ( geometries.isEmpty() )
1218  return;
1219 
1220  QgsWkbTypes::GeometryType geomType = QgsWkbTypes::geometryType( geometries.at( 0 ).wkbType() );
1221  QgsRubberBand *rb = new QgsRubberBand( this, geomType );
1222  for ( const QgsGeometry &geom : geometries )
1223  rb->addGeometry( geom, crs );
1224 
1225  if ( geomType == QgsWkbTypes::LineGeometry || geomType == QgsWkbTypes::PointGeometry )
1226  {
1227  rb->setWidth( 2 );
1228  rb->setSecondaryStrokeColor( QColor( 255, 255, 255 ) );
1229  }
1230  if ( geomType == QgsWkbTypes::PointGeometry )
1231  rb->setIcon( QgsRubberBand::ICON_CIRCLE );
1232 
1233  QColor startColor = color1;
1234  if ( !startColor.isValid() )
1235  {
1236  if ( geomType == QgsWkbTypes::PolygonGeometry )
1237  {
1238  startColor = rb->fillColor();
1239  }
1240  else
1241  {
1242  startColor = rb->strokeColor();
1243  }
1244  startColor.setAlpha( 255 );
1245  }
1246  QColor endColor = color2;
1247  if ( !endColor.isValid() )
1248  {
1249  endColor = startColor;
1250  endColor.setAlpha( 0 );
1251  }
1252 
1253 
1254  QVariantAnimation *animation = new QVariantAnimation( this );
1255  connect( animation, &QVariantAnimation::finished, this, [animation, rb]
1256  {
1257  animation->deleteLater();
1258  delete rb;
1259  } );
1260  connect( animation, &QPropertyAnimation::valueChanged, this, [rb, geomType]( const QVariant & value )
1261  {
1262  QColor c = value.value<QColor>();
1263  if ( geomType == QgsWkbTypes::PolygonGeometry )
1264  {
1265  rb->setFillColor( c );
1266  }
1267  else
1268  {
1269  rb->setStrokeColor( c );
1270  QColor c = rb->secondaryStrokeColor();
1271  c.setAlpha( c.alpha() );
1272  rb->setSecondaryStrokeColor( c );
1273  }
1274  rb->update();
1275  } );
1276 
1277  animation->setDuration( duration * flashes );
1278  animation->setStartValue( endColor );
1279  double midStep = 0.2 / flashes;
1280  for ( int i = 0; i < flashes; ++i )
1281  {
1282  double start = static_cast< double >( i ) / flashes;
1283  animation->setKeyValueAt( start + midStep, startColor );
1284  double end = static_cast< double >( i + 1 ) / flashes;
1285  if ( !qgsDoubleNear( end, 1.0 ) )
1286  animation->setKeyValueAt( end, endColor );
1287  }
1288  animation->setEndValue( endColor );
1289  animation->start();
1290 }
1291 
1292 void QgsMapCanvas::keyPressEvent( QKeyEvent *e )
1293 {
1294  if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )
1295  {
1296  emit keyPressed( e );
1297  return;
1298  }
1299 
1300  if ( ! mCanvasProperties->mouseButtonDown )
1301  {
1302  // Don't want to interfer with mouse events
1303 
1304  QgsRectangle currentExtent = mapSettings().visibleExtent();
1305  double dx = std::fabs( currentExtent.width() / 4 );
1306  double dy = std::fabs( currentExtent.height() / 4 );
1307 
1308  switch ( e->key() )
1309  {
1310  case Qt::Key_Left:
1311  QgsDebugMsgLevel( QStringLiteral( "Pan left" ), 2 );
1312  setCenter( center() - QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
1313  refresh();
1314  break;
1315 
1316  case Qt::Key_Right:
1317  QgsDebugMsgLevel( QStringLiteral( "Pan right" ), 2 );
1318  setCenter( center() + QgsVector( dx, 0 ).rotateBy( rotation() * M_PI / 180.0 ) );
1319  refresh();
1320  break;
1321 
1322  case Qt::Key_Up:
1323  QgsDebugMsgLevel( QStringLiteral( "Pan up" ), 2 );
1324  setCenter( center() + QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
1325  refresh();
1326  break;
1327 
1328  case Qt::Key_Down:
1329  QgsDebugMsgLevel( QStringLiteral( "Pan down" ), 2 );
1330  setCenter( center() - QgsVector( 0, dy ).rotateBy( rotation() * M_PI / 180.0 ) );
1331  refresh();
1332  break;
1333 
1334 
1335 
1336  case Qt::Key_Space:
1337  QgsDebugMsgLevel( QStringLiteral( "Pressing pan selector" ), 2 );
1338 
1339  //mCanvasProperties->dragging = true;
1340  if ( ! e->isAutoRepeat() )
1341  {
1342  QApplication::setOverrideCursor( Qt::ClosedHandCursor );
1343  mCanvasProperties->panSelectorDown = true;
1344  panActionStart( mCanvasProperties->mouseLastXY );
1345  }
1346  break;
1347 
1348  case Qt::Key_PageUp:
1349  QgsDebugMsgLevel( QStringLiteral( "Zoom in" ), 2 );
1350  zoomIn();
1351  break;
1352 
1353  case Qt::Key_PageDown:
1354  QgsDebugMsgLevel( QStringLiteral( "Zoom out" ), 2 );
1355  zoomOut();
1356  break;
1357 
1358 #if 0
1359  case Qt::Key_P:
1360  mUseParallelRendering = !mUseParallelRendering;
1361  refresh();
1362  break;
1363 
1364  case Qt::Key_S:
1365  mDrawRenderingStats = !mDrawRenderingStats;
1366  refresh();
1367  break;
1368 #endif
1369 
1370  default:
1371  // Pass it on
1372  if ( mMapTool )
1373  {
1374  mMapTool->keyPressEvent( e );
1375  }
1376  else e->ignore();
1377 
1378  QgsDebugMsgLevel( "Ignoring key: " + QString::number( e->key() ), 2 );
1379  }
1380  }
1381 
1382  emit keyPressed( e );
1383 
1384 } //keyPressEvent()
1385 
1386 void QgsMapCanvas::keyReleaseEvent( QKeyEvent *e )
1387 {
1388  QgsDebugMsgLevel( QStringLiteral( "keyRelease event" ), 2 );
1389 
1390  switch ( e->key() )
1391  {
1392  case Qt::Key_Space:
1393  if ( !e->isAutoRepeat() && mCanvasProperties->panSelectorDown )
1394  {
1395  QgsDebugMsgLevel( QStringLiteral( "Releasing pan selector" ), 2 );
1396  QApplication::restoreOverrideCursor();
1397  mCanvasProperties->panSelectorDown = false;
1398  panActionEnd( mCanvasProperties->mouseLastXY );
1399  }
1400  break;
1401 
1402  default:
1403  // Pass it on
1404  if ( mMapTool )
1405  {
1406  mMapTool->keyReleaseEvent( e );
1407  }
1408  else e->ignore();
1409 
1410  QgsDebugMsgLevel( "Ignoring key release: " + QString::number( e->key() ), 2 );
1411  }
1412 
1413  emit keyReleased( e );
1414 
1415 } //keyReleaseEvent()
1416 
1417 
1419 {
1420  // call handler of current map tool
1421  if ( mMapTool )
1422  {
1423  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
1424  mMapTool->canvasDoubleClickEvent( me.get() );
1425  }
1426 }// mouseDoubleClickEvent
1427 
1428 
1429 void QgsMapCanvas::beginZoomRect( QPoint pos )
1430 {
1431  mZoomRect.setRect( 0, 0, 0, 0 );
1432  QApplication::setOverrideCursor( mZoomCursor );
1433  mZoomDragging = true;
1434  mZoomRubberBand.reset( new QgsRubberBand( this, QgsWkbTypes::PolygonGeometry ) );
1435  QColor color( Qt::blue );
1436  color.setAlpha( 63 );
1437  mZoomRubberBand->setColor( color );
1438  mZoomRect.setTopLeft( pos );
1439 }
1440 
1441 void QgsMapCanvas::endZoomRect( QPoint pos )
1442 {
1443  mZoomDragging = false;
1444  mZoomRubberBand.reset( nullptr );
1445  QApplication::restoreOverrideCursor();
1446 
1447  // store the rectangle
1448  mZoomRect.setRight( pos.x() );
1449  mZoomRect.setBottom( pos.y() );
1450 
1451  if ( mZoomRect.width() < 5 && mZoomRect.height() < 5 )
1452  {
1453  //probably a mistake - would result in huge zoom!
1454  return;
1455  }
1456 
1457  //account for bottom right -> top left dragging
1458  mZoomRect = mZoomRect.normalized();
1459 
1460  // set center and zoom
1461  const QSize &zoomRectSize = mZoomRect.size();
1462  const QSize &canvasSize = mSettings.outputSize();
1463  double sfx = static_cast< double >( zoomRectSize.width() ) / canvasSize.width();
1464  double sfy = static_cast< double >( zoomRectSize.height() ) / canvasSize.height();
1465  double sf = std::max( sfx, sfy );
1466 
1467  QgsPointXY c = mSettings.mapToPixel().toMapCoordinates( mZoomRect.center() );
1468 
1469  zoomByFactor( sf, &c );
1470  refresh();
1471 }
1472 
1473 void QgsMapCanvas::mousePressEvent( QMouseEvent *e )
1474 {
1475  //use middle mouse button for panning, map tools won't receive any events in that case
1476  if ( e->button() == Qt::MidButton )
1477  {
1478  mCanvasProperties->panSelectorDown = true;
1479  panActionStart( mCanvasProperties->mouseLastXY );
1480  }
1481  else
1482  {
1483  // call handler of current map tool
1484  if ( mMapTool )
1485  {
1486  if ( mMapTool->flags() & QgsMapTool::AllowZoomRect && e->button() == Qt::LeftButton
1487  && e->modifiers() & Qt::ShiftModifier )
1488  {
1489  beginZoomRect( e->pos() );
1490  return;
1491  }
1492  else
1493  {
1494  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
1495  mMapTool->canvasPressEvent( me.get() );
1496  }
1497  }
1498  }
1499 
1500  if ( mCanvasProperties->panSelectorDown )
1501  {
1502  return;
1503  }
1504 
1505  mCanvasProperties->mouseButtonDown = true;
1506  mCanvasProperties->rubberStartPoint = e->pos();
1507 
1508 } // mousePressEvent
1509 
1510 
1511 void QgsMapCanvas::mouseReleaseEvent( QMouseEvent *e )
1512 {
1513  //use middle mouse button for panning, map tools won't receive any events in that case
1514  if ( e->button() == Qt::MidButton )
1515  {
1516  mCanvasProperties->panSelectorDown = false;
1517  panActionEnd( mCanvasProperties->mouseLastXY );
1518  }
1519  else if ( e->button() == Qt::BackButton )
1520  {
1522  return;
1523  }
1524  else if ( e->button() == Qt::ForwardButton )
1525  {
1526  zoomToNextExtent();
1527  return;
1528  }
1529  else
1530  {
1531  if ( mZoomDragging && e->button() == Qt::LeftButton )
1532  {
1533  endZoomRect( e->pos() );
1534  return;
1535  }
1536 
1537  // call handler of current map tool
1538  if ( mMapTool )
1539  {
1540  // right button was pressed in zoom tool? return to previous non zoom tool
1541  if ( e->button() == Qt::RightButton && mMapTool->flags() & QgsMapTool::Transient )
1542  {
1543  QgsDebugMsgLevel( QStringLiteral( "Right click in map tool zoom or pan, last tool is %1." ).arg(
1544  mLastNonZoomMapTool ? QStringLiteral( "not null" ) : QStringLiteral( "null" ) ), 2 );
1545 
1546  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1547 
1548  // change to older non-zoom tool
1549  if ( mLastNonZoomMapTool
1550  && ( !( mLastNonZoomMapTool->flags() & QgsMapTool::EditTool )
1551  || ( vlayer && vlayer->isEditable() ) ) )
1552  {
1553  QgsMapTool *t = mLastNonZoomMapTool;
1554  mLastNonZoomMapTool = nullptr;
1555  setMapTool( t );
1556  }
1557  return;
1558  }
1559  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
1560  mMapTool->canvasReleaseEvent( me.get() );
1561  }
1562  }
1563 
1564 
1565  mCanvasProperties->mouseButtonDown = false;
1566 
1567  if ( mCanvasProperties->panSelectorDown )
1568  return;
1569 
1570 } // mouseReleaseEvent
1571 
1572 void QgsMapCanvas::resizeEvent( QResizeEvent *e )
1573 {
1574  QGraphicsView::resizeEvent( e );
1575  mResizeTimer->start( 500 ); // in charge of refreshing canvas
1576 
1577  double oldScale = mSettings.scale();
1578  QSize lastSize = viewport()->size();
1579  mSettings.setOutputSize( lastSize );
1580 
1581  mScene->setSceneRect( QRectF( 0, 0, lastSize.width(), lastSize.height() ) );
1582 
1583  moveCanvasContents( true );
1584 
1585  if ( mScaleLocked )
1586  {
1587  double scaleFactor = oldScale / mSettings.scale();
1588  QgsRectangle r = mSettings.extent();
1589  QgsPointXY center = r.center();
1590  r.scale( scaleFactor, &center );
1591  mSettings.setExtent( r );
1592  }
1593  else
1594  {
1595  updateScale();
1596  }
1597 
1598  emit extentsChanged();
1599 }
1600 
1601 void QgsMapCanvas::paintEvent( QPaintEvent *e )
1602 {
1603  // no custom event handling anymore
1604 
1605  QGraphicsView::paintEvent( e );
1606 } // paintEvent
1607 
1609 {
1610  const QList<QGraphicsItem *> items = mScene->items();
1611  for ( QGraphicsItem *gi : items )
1612  {
1613  QgsMapCanvasItem *item = dynamic_cast<QgsMapCanvasItem *>( gi );
1614 
1615  if ( item )
1616  {
1617  item->updatePosition();
1618  }
1619  }
1620 }
1621 
1622 
1623 void QgsMapCanvas::wheelEvent( QWheelEvent *e )
1624 {
1625  // Zoom the map canvas in response to a mouse wheel event. Moving the
1626  // wheel forward (away) from the user zooms in
1627 
1628  QgsDebugMsgLevel( "Wheel event delta " + QString::number( e->delta() ), 2 );
1629 
1630  if ( mMapTool )
1631  {
1632  mMapTool->wheelEvent( e );
1633  if ( e->isAccepted() )
1634  return;
1635  }
1636 
1637  if ( e->delta() == 0 )
1638  {
1639  e->accept();
1640  return;
1641  }
1642 
1643  double zoomFactor = e->angleDelta().y() > 0 ? 1. / zoomInFactor() : zoomOutFactor();
1644 
1645  // "Normal" mouse have an angle delta of 120, precision mouses provide data faster, in smaller steps
1646  zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 120.0 * std::fabs( e->angleDelta().y() );
1647 
1648  if ( e->modifiers() & Qt::ControlModifier )
1649  {
1650  //holding ctrl while wheel zooming results in a finer zoom
1651  zoomFactor = 1.0 + ( zoomFactor - 1.0 ) / 20.0;
1652  }
1653 
1654  double signedWheelFactor = e->angleDelta().y() > 0 ? 1 / zoomFactor : zoomFactor;
1655 
1656  // zoom map to mouse cursor by scaling
1657  QgsPointXY oldCenter = center();
1658  QgsPointXY mousePos( getCoordinateTransform()->toMapCoordinates( e->x(), e->y() ) );
1659  QgsPointXY newCenter( mousePos.x() + ( ( oldCenter.x() - mousePos.x() ) * signedWheelFactor ),
1660  mousePos.y() + ( ( oldCenter.y() - mousePos.y() ) * signedWheelFactor ) );
1661 
1662  zoomByFactor( signedWheelFactor, &newCenter );
1663  e->accept();
1664 }
1665 
1666 void QgsMapCanvas::setWheelFactor( double factor )
1667 {
1668  mWheelZoomFactor = factor;
1669 }
1670 
1672 {
1673  // magnification is alreday handled in zoomByFactor
1674  zoomByFactor( zoomInFactor() );
1675 }
1676 
1678 {
1679  // magnification is alreday handled in zoomByFactor
1680  zoomByFactor( zoomOutFactor() );
1681 }
1682 
1683 void QgsMapCanvas::zoomScale( double newScale )
1684 {
1685  zoomByFactor( newScale / scale() );
1686 }
1687 
1688 void QgsMapCanvas::zoomWithCenter( int x, int y, bool zoomIn )
1689 {
1690  double scaleFactor = ( zoomIn ? zoomInFactor() : zoomOutFactor() );
1691 
1692  if ( mScaleLocked )
1693  {
1695  }
1696  else
1697  {
1698  // transform the mouse pos to map coordinates
1701  r.scale( scaleFactor, &center );
1702  setExtent( r, true );
1703  refresh();
1704  }
1705 }
1706 
1707 void QgsMapCanvas::setScaleLocked( bool isLocked )
1708 {
1709  mScaleLocked = isLocked;
1710 }
1711 
1712 void QgsMapCanvas::mouseMoveEvent( QMouseEvent *e )
1713 {
1714  mCanvasProperties->mouseLastXY = e->pos();
1715 
1716  if ( mCanvasProperties->panSelectorDown )
1717  {
1718  panAction( e );
1719  }
1720  else if ( mZoomDragging )
1721  {
1722  mZoomRect.setBottomRight( e->pos() );
1723  mZoomRubberBand->setToCanvasRectangle( mZoomRect );
1724  mZoomRubberBand->show();
1725  }
1726  else
1727  {
1728  // call handler of current map tool
1729  if ( mMapTool )
1730  {
1731  std::unique_ptr<QgsMapMouseEvent> me( new QgsMapMouseEvent( this, e ) );
1732  mMapTool->canvasMoveEvent( me.get() );
1733  }
1734  }
1735 
1736  // show x y on status bar (if we are mid pan operation, then the cursor point hasn't changed!)
1737  if ( !panOperationInProgress() )
1738  {
1739  mCursorPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->mouseLastXY );
1740  emit xyCoordinates( mCursorPoint );
1741  }
1742 }
1743 
1744 void QgsMapCanvas::setMapTool( QgsMapTool *tool, bool clean )
1745 {
1746  if ( !tool )
1747  return;
1748 
1749  if ( mMapTool )
1750  {
1751  if ( clean )
1752  mMapTool->clean();
1753 
1754  disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
1755  mMapTool->deactivate();
1756  }
1757 
1758  if ( ( tool->flags() & QgsMapTool::Transient )
1759  && mMapTool && !( mMapTool->flags() & QgsMapTool::Transient ) )
1760  {
1761  // if zoom or pan tool will be active, save old tool
1762  // to bring it back on right click
1763  // (but only if it wasn't also zoom or pan tool)
1764  mLastNonZoomMapTool = mMapTool;
1765  }
1766  else
1767  {
1768  mLastNonZoomMapTool = nullptr;
1769  }
1770 
1771  QgsMapTool *oldTool = mMapTool;
1772 
1773  // set new map tool and activate it
1774  mMapTool = tool;
1775  emit mapToolSet( mMapTool, oldTool );
1776  if ( mMapTool )
1777  {
1778  connect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
1779  mMapTool->activate();
1780  }
1781 
1782 } // setMapTool
1783 
1785 {
1786  if ( mMapTool && mMapTool == tool )
1787  {
1788  disconnect( mMapTool, &QObject::destroyed, this, &QgsMapCanvas::mapToolDestroyed );
1789  QgsMapTool *oldTool = mMapTool;
1790  mMapTool = nullptr;
1791  oldTool->deactivate();
1792  emit mapToolSet( nullptr, oldTool );
1793  setCursor( Qt::ArrowCursor );
1794  }
1795 
1796  if ( mLastNonZoomMapTool && mLastNonZoomMapTool == tool )
1797  {
1798  mLastNonZoomMapTool = nullptr;
1799  }
1800 }
1801 
1802 void QgsMapCanvas::setCanvasColor( const QColor &color )
1803 {
1804  if ( canvasColor() == color )
1805  return;
1806 
1807  // background of map's pixmap
1808  mSettings.setBackgroundColor( color );
1809 
1810  // background of the QGraphicsView
1811  QBrush bgBrush( color );
1812  setBackgroundBrush( bgBrush );
1813 #if 0
1814  QPalette palette;
1815  palette.setColor( backgroundRole(), color );
1816  setPalette( palette );
1817 #endif
1818 
1819  // background of QGraphicsScene
1820  mScene->setBackgroundBrush( bgBrush );
1821 
1822  refresh();
1823 
1824  emit canvasColorChanged();
1825 }
1826 
1828 {
1829  return mScene->backgroundBrush().color();
1830 }
1831 
1832 void QgsMapCanvas::setSelectionColor( const QColor &color )
1833 {
1834  if ( mSettings.selectionColor() == color )
1835  return;
1836 
1837  mSettings.setSelectionColor( color );
1838 
1839  if ( mCache )
1840  {
1841  bool hasSelectedFeatures = false;
1842  const auto layers = mSettings.layers();
1843  for ( QgsMapLayer *layer : layers )
1844  {
1845  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
1846  if ( vlayer && vlayer->selectedFeatureCount() )
1847  {
1848  hasSelectedFeatures = true;
1849  break;
1850  }
1851  }
1852 
1853  if ( hasSelectedFeatures )
1854  {
1855  mCache->clear();
1856  refresh();
1857  }
1858  }
1859 }
1860 
1862 {
1863  return mSettings.selectionColor();
1864 }
1865 
1867 {
1868  return mapSettings().layers().size();
1869 } // layerCount
1870 
1871 
1872 QList<QgsMapLayer *> QgsMapCanvas::layers() const
1873 {
1874  return mapSettings().layers();
1875 }
1876 
1878 {
1879  // called when a layer has changed visibility setting
1880  refresh();
1881 }
1882 
1883 void QgsMapCanvas::freeze( bool frozen )
1884 {
1885  mFrozen = frozen;
1886 }
1887 
1889 {
1890  return mFrozen;
1891 }
1892 
1894 {
1895  return mapSettings().mapUnitsPerPixel();
1896 }
1897 
1899 {
1900  return mapSettings().mapUnits();
1901 }
1902 
1903 QMap<QString, QString> QgsMapCanvas::layerStyleOverrides() const
1904 {
1905  return mSettings.layerStyleOverrides();
1906 }
1907 
1908 void QgsMapCanvas::setLayerStyleOverrides( const QMap<QString, QString> &overrides )
1909 {
1910  if ( overrides == mSettings.layerStyleOverrides() )
1911  return;
1912 
1913  mSettings.setLayerStyleOverrides( overrides );
1914  clearCache();
1916 }
1917 
1918 void QgsMapCanvas::setTheme( const QString &theme )
1919 {
1920  if ( mTheme == theme )
1921  return;
1922 
1923  clearCache();
1924  if ( theme.isEmpty() || !QgsProject::instance()->mapThemeCollection()->hasMapTheme( theme ) )
1925  {
1926  mTheme.clear();
1927  mSettings.setLayerStyleOverrides( QMap< QString, QString>() );
1928  setLayers( QgsProject::instance()->mapThemeCollection()->masterVisibleLayers() );
1929  emit themeChanged( QString() );
1930  }
1931  else
1932  {
1933  mTheme = theme;
1934  setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
1935  emit themeChanged( theme );
1936  }
1937 }
1938 
1940 {
1941  mRenderFlag = flag;
1942 
1943  if ( mRenderFlag )
1944  {
1945  refresh();
1946  }
1947  else
1948  stopRendering();
1949 }
1950 
1951 #if 0
1952 void QgsMapCanvas::connectNotify( const char *signal )
1953 {
1954  Q_UNUSED( signal )
1955  QgsDebugMsg( "QgsMapCanvas connected to " + QString( signal ) );
1956 } //connectNotify
1957 #endif
1958 
1959 void QgsMapCanvas::layerRepaintRequested( bool deferred )
1960 {
1961  if ( !deferred )
1962  refresh();
1963 }
1964 
1965 void QgsMapCanvas::autoRefreshTriggered()
1966 {
1967  if ( mJob )
1968  {
1969  // canvas is currently being redrawn, so we skip this auto refresh
1970  // otherwise we could get stuck in the situation where an auto refresh is triggered
1971  // too often to allow the canvas to ever finish rendering
1972  return;
1973  }
1974 
1975  refresh();
1976 }
1977 
1978 void QgsMapCanvas::updateAutoRefreshTimer()
1979 {
1980  // min auto refresh interval stores the smallest interval between layer auto refreshes. We automatically
1981  // trigger a map refresh on this minimum interval
1982  int minAutoRefreshInterval = -1;
1983  const auto layers = mSettings.layers();
1984  for ( QgsMapLayer *layer : layers )
1985  {
1986  if ( layer->hasAutoRefreshEnabled() && layer->autoRefreshInterval() > 0 )
1987  minAutoRefreshInterval = minAutoRefreshInterval > 0 ? std::min( layer->autoRefreshInterval(), minAutoRefreshInterval ) : layer->autoRefreshInterval();
1988  }
1989 
1990  if ( minAutoRefreshInterval > 0 )
1991  {
1992  mAutoRefreshTimer.setInterval( minAutoRefreshInterval );
1993  mAutoRefreshTimer.start();
1994  }
1995  else
1996  {
1997  mAutoRefreshTimer.stop();
1998  }
1999 }
2000 
2001 void QgsMapCanvas::projectThemesChanged()
2002 {
2003  if ( mTheme.isEmpty() )
2004  return;
2005 
2006  if ( !QgsProject::instance()->mapThemeCollection()->hasMapTheme( mTheme ) )
2007  {
2008  // theme has been removed - stop following
2009  setTheme( QString() );
2010  }
2011 
2012 }
2013 
2015 {
2016  return mMapTool;
2017 }
2018 
2019 void QgsMapCanvas::panActionEnd( QPoint releasePoint )
2020 {
2021  // move map image and other items to standard position
2022  moveCanvasContents( true ); // true means reset
2023 
2024  // use start and end box points to calculate the extent
2025  QgsPointXY start = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
2026  QgsPointXY end = getCoordinateTransform()->toMapCoordinates( releasePoint );
2027 
2028  // modify the center
2029  double dx = end.x() - start.x();
2030  double dy = end.y() - start.y();
2031  QgsPointXY c = center();
2032  c.set( c.x() - dx, c.y() - dy );
2033  setCenter( c );
2034 
2035  refresh();
2036 }
2037 
2038 void QgsMapCanvas::panActionStart( QPoint releasePoint )
2039 {
2040  mCanvasProperties->rubberStartPoint = releasePoint;
2041 
2042  mDa = QgsDistanceArea();
2043  mDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
2044  mDa.setSourceCrs( mapSettings().destinationCrs(), QgsProject::instance()->transformContext() );
2045 }
2046 
2047 void QgsMapCanvas::panAction( QMouseEvent *e )
2048 {
2049  Q_UNUSED( e )
2050 
2051  QgsPointXY currentMapPoint = getCoordinateTransform()->toMapCoordinates( e->pos() );
2052  QgsPointXY startMapPoint = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
2053  emit panDistanceBearingChanged( mDa.measureLine( currentMapPoint, startMapPoint ), mDa.lengthUnits(), mDa.bearing( currentMapPoint, startMapPoint ) * 180 / M_PI );
2054 
2055  // move all map canvas items
2057 }
2058 
2060 {
2061  QPoint pnt( 0, 0 );
2062  if ( !reset )
2063  pnt += mCanvasProperties->mouseLastXY - mCanvasProperties->rubberStartPoint;
2064 
2065  setSceneRect( -pnt.x(), -pnt.y(), viewport()->size().width(), viewport()->size().height() );
2066 }
2067 
2068 void QgsMapCanvas::dropEvent( QDropEvent *event )
2069 {
2070  if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
2071  {
2072  const QgsMimeDataUtils::UriList lst = QgsMimeDataUtils::decodeUriList( event->mimeData() );
2073  bool allHandled = true;
2074  for ( const QgsMimeDataUtils::Uri &uri : lst )
2075  {
2076  bool handled = false;
2077  for ( QgsCustomDropHandler *handler : qgis::as_const( mDropHandlers ) )
2078  {
2079  if ( handler && handler->customUriProviderKey() == uri.providerKey )
2080  {
2081  if ( handler->handleCustomUriCanvasDrop( uri, this ) )
2082  {
2083  handled = true;
2084  break;
2085  }
2086  }
2087  }
2088  if ( !handled )
2089  allHandled = false;
2090  }
2091  if ( allHandled )
2092  event->accept();
2093  else
2094  event->ignore();
2095  }
2096  else
2097  {
2098  event->ignore();
2099  }
2100 }
2101 
2103 {
2104  return mCanvasProperties->mouseLastXY;
2105 }
2106 
2107 void QgsMapCanvas::setPreviewModeEnabled( bool previewEnabled )
2108 {
2109  if ( !mPreviewEffect )
2110  {
2111  return;
2112  }
2113 
2114  mPreviewEffect->setEnabled( previewEnabled );
2115 }
2116 
2118 {
2119  if ( !mPreviewEffect )
2120  {
2121  return false;
2122  }
2123 
2124  return mPreviewEffect->isEnabled();
2125 }
2126 
2128 {
2129  if ( !mPreviewEffect )
2130  {
2131  return;
2132  }
2133 
2134  mPreviewEffect->setMode( mode );
2135 }
2136 
2138 {
2139  if ( !mPreviewEffect )
2140  {
2142  }
2143 
2144  return mPreviewEffect->mode();
2145 }
2146 
2148 {
2149  if ( !mSnappingUtils )
2150  {
2151  // associate a dummy instance, but better than null pointer
2152  QgsMapCanvas *c = const_cast<QgsMapCanvas *>( this );
2153  c->mSnappingUtils = new QgsMapCanvasSnappingUtils( c, c );
2154  }
2155  return mSnappingUtils;
2156 }
2157 
2159 {
2160  mSnappingUtils = utils;
2161 }
2162 
2163 void QgsMapCanvas::readProject( const QDomDocument &doc )
2164 {
2165  QgsProject *project = qobject_cast< QgsProject * >( sender() );
2166 
2167  QDomNodeList nodes = doc.elementsByTagName( QStringLiteral( "mapcanvas" ) );
2168  if ( nodes.count() )
2169  {
2170  QDomNode node = nodes.item( 0 );
2171 
2172  // Search the specific MapCanvas node using the name
2173  if ( nodes.count() > 1 )
2174  {
2175  for ( int i = 0; i < nodes.size(); ++i )
2176  {
2177  QDomElement elementNode = nodes.at( i ).toElement();
2178 
2179  if ( elementNode.hasAttribute( QStringLiteral( "name" ) ) && elementNode.attribute( QStringLiteral( "name" ) ) == objectName() )
2180  {
2181  node = nodes.at( i );
2182  break;
2183  }
2184  }
2185  }
2186 
2187  QgsMapSettings tmpSettings;
2188  tmpSettings.readXml( node );
2189  if ( objectName() != QStringLiteral( "theMapCanvas" ) )
2190  {
2191  // never manually set the crs for the main canvas - this is instead connected to the project CRS
2192  setDestinationCrs( tmpSettings.destinationCrs() );
2193  }
2194  setExtent( tmpSettings.extent() );
2195  setRotation( tmpSettings.rotation() );
2197 
2198  clearExtentHistory(); // clear the extent history on project load
2199 
2200  QDomElement elem = node.toElement();
2201  if ( elem.hasAttribute( QStringLiteral( "theme" ) ) )
2202  {
2203  if ( QgsProject::instance()->mapThemeCollection()->hasMapTheme( elem.attribute( QStringLiteral( "theme" ) ) ) )
2204  {
2205  setTheme( elem.attribute( QStringLiteral( "theme" ) ) );
2206  }
2207  }
2208  setAnnotationsVisible( elem.attribute( QStringLiteral( "annotationsVisible" ), QStringLiteral( "1" ) ).toInt() );
2209 
2210  // restore canvas expression context
2211  const QDomNodeList scopeElements = elem.elementsByTagName( QStringLiteral( "expressionContextScope" ) );
2212  if ( scopeElements.size() > 0 )
2213  {
2214  const QDomElement scopeElement = scopeElements.at( 0 ).toElement();
2215  mExpressionContextScope.readXml( scopeElement, QgsReadWriteContext() );
2216  }
2217  }
2218  else
2219  {
2220  QgsDebugMsg( QStringLiteral( "Couldn't read mapcanvas information from project" ) );
2221  if ( !project->viewSettings()->defaultViewExtent().isNull() )
2222  {
2224  clearExtentHistory(); // clear the extent history on project load
2225  }
2226  }
2227 }
2228 
2229 void QgsMapCanvas::writeProject( QDomDocument &doc )
2230 {
2231  // create node "mapcanvas" and call mMapRenderer->writeXml()
2232 
2233  QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
2234  if ( !nl.count() )
2235  {
2236  QgsDebugMsg( QStringLiteral( "Unable to find qgis element in project file" ) );
2237  return;
2238  }
2239  QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
2240 
2241  QDomElement mapcanvasNode = doc.createElement( QStringLiteral( "mapcanvas" ) );
2242  mapcanvasNode.setAttribute( QStringLiteral( "name" ), objectName() );
2243  if ( !mTheme.isEmpty() )
2244  mapcanvasNode.setAttribute( QStringLiteral( "theme" ), mTheme );
2245  mapcanvasNode.setAttribute( QStringLiteral( "annotationsVisible" ), mAnnotationsVisible );
2246  qgisNode.appendChild( mapcanvasNode );
2247 
2248  mSettings.writeXml( mapcanvasNode, doc );
2249 
2250  // store canvas expression context
2251  QDomElement scopeElement = doc.createElement( QStringLiteral( "expressionContextScope" ) );
2252  QgsExpressionContextScope tmpScope( mExpressionContextScope );
2253  tmpScope.removeVariable( QStringLiteral( "atlas_featurenumber" ) );
2254  tmpScope.removeVariable( QStringLiteral( "atlas_pagename" ) );
2255  tmpScope.removeVariable( QStringLiteral( "atlas_feature" ) );
2256  tmpScope.removeVariable( QStringLiteral( "atlas_featureid" ) );
2257  tmpScope.removeVariable( QStringLiteral( "atlas_geometry" ) );
2258  tmpScope.writeXml( scopeElement, doc, QgsReadWriteContext() );
2259  mapcanvasNode.appendChild( scopeElement );
2260 
2261  // TODO: store only units, extent, projections, dest CRS
2262 }
2263 
2264 void QgsMapCanvas::zoomByFactor( double scaleFactor, const QgsPointXY *center )
2265 {
2266  if ( mScaleLocked )
2267  {
2268  // zoom map to mouse cursor by magnifying
2270  }
2271  else
2272  {
2274  r.scale( scaleFactor, center );
2275  setExtent( r, true );
2276  refresh();
2277  }
2278 }
2279 
2281 {
2282  // Find out which layer it was that sent the signal.
2283  QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( sender() );
2284  if ( layer )
2285  {
2286  emit selectionChanged( layer );
2287  refresh();
2288  }
2289 }
2290 
2291 void QgsMapCanvas::dragEnterEvent( QDragEnterEvent *event )
2292 {
2293  // By default graphics view delegates the drag events to graphics items.
2294  // But we do not want that and by ignoring the drag enter we let the
2295  // parent (e.g. QgisApp) to handle drops of map layers etc.
2296 
2297  // so we ONLY accept the event if we know in advance that a custom drop handler
2298  // wants it
2299 
2300  if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
2301  {
2302  const QgsMimeDataUtils::UriList lst = QgsMimeDataUtils::decodeUriList( event->mimeData() );
2303  bool allHandled = true;
2304  for ( const QgsMimeDataUtils::Uri &uri : lst )
2305  {
2306  bool handled = false;
2307  for ( QgsCustomDropHandler *handler : qgis::as_const( mDropHandlers ) )
2308  {
2309  if ( handler->canHandleCustomUriCanvasDrop( uri, this ) )
2310  {
2311  handled = true;
2312  break;
2313  }
2314  }
2315  if ( !handled )
2316  allHandled = false;
2317  }
2318  if ( allHandled )
2319  event->accept();
2320  else
2321  event->ignore();
2322  }
2323  else
2324  {
2325  event->ignore();
2326  }
2327 }
2328 
2329 void QgsMapCanvas::mapToolDestroyed()
2330 {
2331  QgsDebugMsgLevel( QStringLiteral( "maptool destroyed" ), 2 );
2332  mMapTool = nullptr;
2333 }
2334 
2335 bool QgsMapCanvas::event( QEvent *e )
2336 {
2337  if ( !QTouchDevice::devices().empty() )
2338  {
2339  if ( e->type() == QEvent::Gesture )
2340  {
2341  if ( QTapAndHoldGesture *tapAndHoldGesture = qobject_cast< QTapAndHoldGesture * >( static_cast<QGestureEvent *>( e )->gesture( Qt::TapAndHoldGesture ) ) )
2342  {
2343  QPointF pos = tapAndHoldGesture->position();
2344  pos = mapFromGlobal( QPoint( pos.x(), pos.y() ) );
2345  QgsPointXY mapPoint = getCoordinateTransform()->toMapCoordinates( pos.x(), pos.y() );
2346  emit tapAndHoldGestureOccurred( mapPoint, tapAndHoldGesture );
2347  }
2348 
2349  // call handler of current map tool
2350  if ( mMapTool )
2351  {
2352  return mMapTool->gestureEvent( static_cast<QGestureEvent *>( e ) );
2353  }
2354  }
2355  }
2356 
2357  // pass other events to base class
2358  return QGraphicsView::event( e );
2359 }
2360 
2362 {
2363  // reload all layers in canvas
2364  const QList<QgsMapLayer *> layers = mapSettings().layers();
2365  for ( QgsMapLayer *layer : layers )
2366  {
2367  layer->reload();
2368  }
2369 
2370  redrawAllLayers();
2371 }
2372 
2374 {
2375  // clear the cache
2376  clearCache();
2377 
2378  // and then refresh
2379  refresh();
2380 }
2381 
2383 {
2384  while ( mRefreshScheduled || mJob )
2385  {
2386  QgsApplication::processEvents();
2387  }
2388 }
2389 
2391 {
2392  mSettings.setSegmentationTolerance( tolerance );
2393 }
2394 
2396 {
2397  mSettings.setSegmentationToleranceType( type );
2398 }
2399 
2400 QList<QgsMapCanvasAnnotationItem *> QgsMapCanvas::annotationItems() const
2401 {
2402  QList<QgsMapCanvasAnnotationItem *> annotationItemList;
2403  const QList<QGraphicsItem *> items = mScene->items();
2404  for ( QGraphicsItem *gi : items )
2405  {
2406  QgsMapCanvasAnnotationItem *aItem = dynamic_cast< QgsMapCanvasAnnotationItem *>( gi );
2407  if ( aItem )
2408  {
2409  annotationItemList.push_back( aItem );
2410  }
2411  }
2412 
2413  return annotationItemList;
2414 }
2415 
2417 {
2418  mAnnotationsVisible = show;
2419  const QList<QgsMapCanvasAnnotationItem *> items = annotationItems();
2420  for ( QgsMapCanvasAnnotationItem *item : items )
2421  {
2422  item->setVisible( show );
2423  }
2424 }
2425 
2427 {
2428  mSettings.setLabelingEngineSettings( settings );
2429 }
2430 
2432 {
2433  return mSettings.labelingEngineSettings();
2434 }
2435 
2436 void QgsMapCanvas::startPreviewJobs()
2437 {
2438  stopPreviewJobs(); //just in case still running
2439 
2440  //canvas preview jobs aren't compatible with rotation
2441  // TODO fix this
2442  if ( !qgsDoubleNear( mSettings.rotation(), 0.0 ) )
2443  return;
2444 
2445  schedulePreviewJob( 0 );
2446 }
2447 
2448 void QgsMapCanvas::startPreviewJob( int number )
2449 {
2450  QgsRectangle mapRect = mSettings.visibleExtent();
2451 
2452  if ( number == 4 )
2453  number += 1;
2454 
2455  int j = number / 3;
2456  int i = number % 3;
2457 
2458  //copy settings, only update extent
2459  QgsMapSettings jobSettings = mSettings;
2460 
2461  double dx = ( i - 1 ) * mapRect.width();
2462  double dy = ( 1 - j ) * mapRect.height();
2463  QgsRectangle jobExtent = mapRect;
2464 
2465  jobExtent.setXMaximum( jobExtent.xMaximum() + dx );
2466  jobExtent.setXMinimum( jobExtent.xMinimum() + dx );
2467  jobExtent.setYMaximum( jobExtent.yMaximum() + dy );
2468  jobExtent.setYMinimum( jobExtent.yMinimum() + dy );
2469 
2470  jobSettings.setExtent( jobExtent );
2471  jobSettings.setFlag( QgsMapSettings::DrawLabeling, false );
2472  jobSettings.setFlag( QgsMapSettings::RenderPreviewJob, true );
2473 
2474  // truncate preview layers to fast layers
2475  const QList<QgsMapLayer *> layers = jobSettings.layers();
2476  QList< QgsMapLayer * > previewLayers;
2478  context.maxRenderingTimeMs = MAXIMUM_LAYER_PREVIEW_TIME_MS;
2479  for ( QgsMapLayer *layer : layers )
2480  {
2481  context.lastRenderingTimeMs = mLastLayerRenderTime.value( layer->id(), 0 );
2482  QgsDataProvider *provider = layer->dataProvider();
2483  if ( provider && !provider->renderInPreview( context ) )
2484  {
2485  QgsDebugMsgLevel( QStringLiteral( "Layer %1 not rendered because it does not match the renderInPreview criterion %2" ).arg( layer->id() ).arg( mLastLayerRenderTime.value( layer->id() ) ), 3 );
2486  continue;
2487  }
2488 
2489  previewLayers << layer;
2490  }
2491  jobSettings.setLayers( previewLayers );
2492 
2493  QgsMapRendererQImageJob *job = new QgsMapRendererSequentialJob( jobSettings );
2494  job->setProperty( "number", number );
2495  mPreviewJobs.append( job );
2496  connect( job, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
2497  job->start();
2498 }
2499 
2500 void QgsMapCanvas::stopPreviewJobs()
2501 {
2502  mPreviewTimer.stop();
2503  const auto previewJobs = mPreviewJobs;
2504  for ( auto previewJob : previewJobs )
2505  {
2506  if ( previewJob )
2507  {
2508  disconnect( previewJob, &QgsMapRendererJob::finished, this, &QgsMapCanvas::previewJobFinished );
2509  connect( previewJob, &QgsMapRendererQImageJob::finished, previewJob, &QgsMapRendererQImageJob::deleteLater );
2510  previewJob->cancelWithoutBlocking();
2511  }
2512  }
2513  mPreviewJobs.clear();
2514 }
2515 
2516 void QgsMapCanvas::schedulePreviewJob( int number )
2517 {
2518  mPreviewTimer.setSingleShot( true );
2519  mPreviewTimer.setInterval( PREVIEW_JOB_DELAY_MS );
2520  disconnect( mPreviewTimerConnection );
2521  mPreviewTimerConnection = connect( &mPreviewTimer, &QTimer::timeout, this, [ = ]()
2522  {
2523  startPreviewJob( number );
2524  } );
2525  mPreviewTimer.start();
2526 }
2527 
2528 bool QgsMapCanvas::panOperationInProgress()
2529 {
2530  if ( mCanvasProperties->panSelectorDown )
2531  return true;
2532 
2533  if ( QgsMapToolPan *panTool = qobject_cast< QgsMapToolPan *>( mMapTool ) )
2534  {
2535  if ( panTool->isDragging() )
2536  return true;
2537  }
2538 
2539  return false;
2540 }
2541 
2542 int QgsMapCanvas::nextZoomLevel( const QList<double> &resolutions, bool zoomIn ) const
2543 {
2544  int resolutionLevel = -1;
2545  double currentResolution = mapUnitsPerPixel();
2546 
2547  for ( int i = 0, n = resolutions.size(); i < n; ++i )
2548  {
2549  if ( qgsDoubleNear( resolutions[i], currentResolution, 0.0001 ) )
2550  {
2551  resolutionLevel = zoomIn ? ( i - 1 ) : ( i + 1 );
2552  break;
2553  }
2554  else if ( currentResolution <= resolutions[i] )
2555  {
2556  resolutionLevel = zoomIn ? ( i - 1 ) : i;
2557  break;
2558  }
2559  }
2560  return ( resolutionLevel < 0 || resolutionLevel >= resolutions.size() ) ? -1 : resolutionLevel;
2561 }
2562 
2563 double QgsMapCanvas::zoomInFactor() const
2564 {
2565  if ( !mZoomResolutions.isEmpty() )
2566  {
2567  int zoomLevel = nextZoomLevel( mZoomResolutions, true );
2568  if ( zoomLevel != -1 )
2569  {
2570  return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
2571  }
2572  }
2573  return 1 / mWheelZoomFactor;
2574 }
2575 
2576 double QgsMapCanvas::zoomOutFactor() const
2577 {
2578  if ( !mZoomResolutions.isEmpty() )
2579  {
2580  int zoomLevel = nextZoomLevel( mZoomResolutions, false );
2581  if ( zoomLevel != -1 )
2582  {
2583  return mZoomResolutions.at( zoomLevel ) / mapUnitsPerPixel();
2584  }
2585  }
2586  return mWheelZoomFactor;
2587 }
void unsetMapTool(QgsMapTool *mapTool)
Unset the current map tool or last non zoom tool.
bool previewJobsEnabled() const
Returns true if canvas map preview jobs (low priority render jobs which render portions of the view j...
bool setReferencedExtent(const QgsReferencedRectangle &extent) SIP_THROW(QgsCsException)
Sets the canvas to the specified extent.
QgsUnitTypes::DistanceUnit lengthUnits() const
Returns the units of distance for length calculations made by this object.
The class is used as a container of context for various read/write operations on other objects...
static QgsSvgCache * svgCache()
Returns the application&#39;s SVG cache, used for caching SVG images and handling parameter replacement w...
Wrapper for iterator of features from vector data provider or vector layer.
void updateCanvasItemPositions()
called on resize or changed extent to notify canvas items to change their rectangle ...
int autoRefreshInterval
Definition: qgsmaplayer.h:84
void finished()
emitted when asynchronous rendering is finished (or canceled).
void setParallelRenderingEnabled(bool enabled)
Set whether the layers are rendered in parallel or sequentially.
void set(double x, double y)
Sets the x and y value of the point.
Definition: qgspointxy.h:124
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
sets destination coordinate reference system
QPoint mouseLastXY
Last seen point of the mouse.
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns global labeling engine settings from the internal map settings.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
Base class for all map layer types.
Definition: qgsmaplayer.h:79
void setExtent(const QgsRectangle &rect, bool magnified=true)
Set coordinates of the rectangle which should be rendered.
Job implementation that renders everything sequentially using a custom painter.
std::unique_ptr< CanvasProperties > mCanvasProperties
Handle pattern for implementation object.
Definition: qgsmapcanvas.h:946
virtual bool isEmpty() const
Returns true if the geometry is empty.
void setRotation(double degrees)
Set the rotation of the map canvas in clockwise degrees.
virtual void canvasMoveEvent(QgsMapMouseEvent *e)
Mouse move event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:157
void setRenderFlag(bool flag)
Sets whether a user has disabled canvas renders via the GUI.
int mapUpdateInterval() const
Find out how often map preview should be updated while it is being rendered (in milliseconds) ...
QList< QgsMapCanvasAnnotationItem * > annotationItems() const
Returns a list of all annotation items in the canvas.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
void zoomToNextExtent()
Zoom to the next extent (view)
bool hasMapTheme(const QString &name) const
Returns whether a map theme with a matching name exists.
void zoomWithCenter(int x, int y, bool zoomIn)
Zooms in/out with a given center.
void setCanvasColor(const QColor &_newVal)
Write property of QColor bgColor.
void setCenter(const QgsPointXY &center)
Set the center of the map canvas, in geographical coordinates.
void setMinimal()
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:151
void setSegmentationToleranceType(QgsAbstractGeometry::SegmentationToleranceType type)
Sets segmentation tolerance type (maximum angle or maximum difference between curve and approximation...
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
QList< QgsMapLayer * > layers() const
Returns the list of layers shown within the map canvas.
void panActionStart(QPoint releasePoint)
Starts a pan action.
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads scope variables from an XML element.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Sets the stored overrides of styles for rendering layers.
double magnificationFactor() const
Returns the magnification factor.
double rotation() const
Returns the rotation of the resulting map image, in degrees clockwise.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:135
void clearExtentHistory()
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
Maximum angle between generating radii (lines from arc center to output vertices) ...
bool event(QEvent *e) override
QColor selectionColor() const
Returns color for selected features.
bool mouseButtonDown
Flag to indicate status of mouse button.
void wheelEvent(QWheelEvent *e) override
void canvasColorChanged()
Emitted when canvas background color changes.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
QgsReferencedRectangle defaultViewExtent() const
Returns the default view extent, which should be used as the initial map extent when this project is ...
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void stopRendering()
stop rendering (if there is any right now)
static UriList decodeUriList(const QMimeData *data)
double y
Definition: qgspointxy.h:48
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
void setPreviewJobsEnabled(bool enabled)
Sets whether canvas map preview jobs (low priority render jobs which render portions of the view just...
static QgsImageCache * imageCache()
Returns the application&#39;s image cache, used for caching resampled versions of raster images...
A class to represent a 2D point.
Definition: qgspointxy.h:43
double rotation() const
Gets the current map canvas rotation in clockwise degrees.
QgsPreviewEffect::PreviewMode previewMode() const
Returns the current preview mode for the map canvas.
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:235
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
void dropEvent(QDropEvent *event) override
QColor backgroundColor() const
Gets the background color of the map.
void keyPressEvent(QKeyEvent *e) override
void zoomToFeatureExtent(QgsRectangle &rect)
Zooms to feature extent.
Allow zooming by rectangle (by holding shift and dragging) while the tool is active.
Definition: qgsmaptool.h:94
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
void enableAntiAliasing(bool flag)
used to determine if anti-aliasing is enabled or not
An abstract class for items that can be placed on the map canvas.
void setCurrentLayer(QgsMapLayer *layer)
int layerCount() const
Returns number of layers on the map.
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
void moveCanvasContents(bool reset=false)
called when panning is in action, reset indicates end of panning
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
void setFlags(QgsMapSettings::Flags flags)
Sets combination of flags that will be used for rendering.
void setAnnotationsVisible(bool visible)
Sets whether annotations are visible in the canvas.
constexpr double CANVAS_MAGNIFICATION_MAX
Maximum magnification level allowed in map canvases.
Definition: qgsguiutils.h:69
void readProject(const QDomDocument &)
called to read map canvas settings from project
bool panSelectorDown
Flag to indicate the pan selector key is held down by user.
void refresh()
Repaints the canvas map.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:122
void tapAndHoldGestureOccurred(const QgsPointXY &mapPoint, QTapAndHoldGesture *gesture)
Emitted whenever a tap and hold gesture occurs at the specified map point.
void renderComplete(QPainter *)
Emitted when the canvas has rendered.
void projectColorsChanged()
Emitted whenever the project&#39;s color scheme has been changed.
Snapping utils instance that is connected to a canvas and updates the configuration (map settings + c...
Abstract base class for spatial data provider implementations.
bool isCachingEnabled() const
Check whether images of rendered layers are curerently being cached.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
void setLayerStyleOverrides(const QMap< QString, QString > &overrides)
Set map of map layer style overrides (key: layer ID, value: style name) where a different style shoul...
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
QgsMapTool * mapTool()
Returns the currently active tool.
SegmentationToleranceType
Segmentation tolerance as maximum angle or maximum difference between approximation and circle...
QList< QgsMapLayer * > layers() const
Gets list of layers for map rendering The layers are stored in the reverse order of how they are rend...
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
const QgsCoordinateReferenceSystem & crs
void mousePressEvent(QMouseEvent *e) override
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
virtual Flags flags() const
Returns the flags for the map tool.
Definition: qgsmaptool.h:102
void mapThemesChanged()
Emitted when map themes within the collection are changed.
static QgsExpressionContextScope * atlasScope(const QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
void setSegmentationTolerance(double tolerance)
Sets the segmentation tolerance applied when rendering curved geometries.
virtual QImage renderedImage()=0
Gets a preview/resulting image.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
int renderingTime() const
Returns the total time it took to finish the job (in milliseconds).
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:75
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes takes output image size into accou...
QgsExpressionContextScope * defaultExpressionContextScope()
Creates a new scope which contains default variables and functions relating to the map canvas...
void selectionChanged(QgsVectorLayer *layer)
Emitted when selection in any layer gets changed.
Abstract base class that may be implemented to handle new types of data to be dropped in QGIS...
Enable drawing of labels on top of the map.
static QString worldFileContent(const QgsMapSettings &mapSettings)
Creates the content of a world file.
double maxRenderingTimeMs
Default maximum allowable render time, in ms.
void zoomLastStatusChanged(bool)
Emitted when zoom last status changed.
A graphics effect which can be applied to a widget to simulate various printing and color blindness m...
virtual void canvasPressEvent(QgsMapMouseEvent *e)
Mouse press event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:167
QgsCoordinateReferenceSystem destinationCrs() const
returns CRS of destination coordinate reference system
void setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
void magnificationChanged(double)
Emitted when the scale of the map changes.
QString what() const
Definition: qgsexception.h:48
void setFlag(Flag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void updateScale()
Emits signal scaleChanged to update scale in main window.
QgsUnitTypes::DistanceUnit mapUnits() const
Gets units of map&#39;s geographical coordinates - used for scale calculation.
static bool isUriList(const QMimeData *data)
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
sets destination coordinate reference system
The QgsMapSettings class contains configuration for rendering of the map.
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.
void resizeEvent(QResizeEvent *e) override
Deprecated to be deleted, stuff from here should be moved elsewhere.
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:175
void enableMapTileRendering(bool flag)
sets map tile rendering flag
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
virtual void activate()
called when set as currently active map tool
Definition: qgsmaptool.cpp:83
QgsCoordinateReferenceSystem crs() const
Returns the associated coordinate reference system, or an invalid CRS if no reference system is set...
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:37
void setSnappingUtils(QgsSnappingUtils *utils)
Assign an instance of snapping utils to the map canvas.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
virtual void keyReleaseEvent(QKeyEvent *e)
Key event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:187
bool removeVariable(const QString &name)
Removes a variable from the context scope, if found.
void mapThemeChanged(const QString &theme)
Emitted when a map theme changes definition.
Q_INVOKABLE QgsRectangle boundingBoxOfSelected() const
Returns the bounding box of the selected features. If there is no selection, QgsRectangle(0,0,0,0) is returned.
QgsRectangle extent() const
Returns geographical coordinates of the rectangle that should be rendered.
void setMapTool(QgsMapTool *mapTool, bool clean=false)
Sets the map tool currently being used on the canvas.
Map tool is an edit tool, which can only be used when layer is editable.
Definition: qgsmaptool.h:93
void setOutputSize(QSize size)
Sets the size of the resulting map image.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
double mapUnitsPerPixel() const
Returns the mapUnitsPerPixel (map units per pixel) for the canvas.
void start() override
Start the rendering job and immediately return.
void saveAsImage(const QString &fileName, QPixmap *QPixmap=nullptr, const QString &="PNG")
Save the contents of the map canvas to disk as an image.
QgsMapThemeCollection mapThemeCollection
Definition: qgsproject.h:100
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets global labeling engine settings in the internal map settings.
void mapCanvasRefreshed()
Emitted when canvas finished a refresh request.
void rotationChanged(double)
Emitted when the rotation of the map changes.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
void setMagnificationFactor(double factor)
Set the magnification factor.
void zoomNextStatusChanged(bool)
Emitted when zoom next status changed.
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:426
A circle is used to highlight points (â—‹)
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 clearCache()
Make sure to remove any rendered images from cache (does nothing if cache is not enabled) ...
void reload() FINAL
Synchronises with changes in the datasource.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsMapCanvas(QWidget *parent=nullptr)
Constructor.
A class for drawing transient features (e.g.
Definition: qgsrubberband.h:49
double scale() const
Returns the calculated map scale.
Job implementation that renders all layers in parallel.
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:140
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setSegmentationTolerance(double tolerance)
Sets the segmentation tolerance applied when rendering curved geometries.
void setMapUpdateInterval(int timeMilliseconds)
Set how often map preview should be updated while it is being rendered (in milliseconds) ...
void keyReleased(QKeyEvent *e)
Emit key release event.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
virtual void waitForFinished()=0
Block until the job has finished.
double mapUnitsPerPixel() const
Returns the distance in geographical coordinates that equals to one pixel in the map.
void setDevicePixelRatio(float dpr)
Sets the device pixel ratio Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" d...
void readProject(const QDomDocument &)
Emitted when a project is being read.
Enable anti-aliasing for map rendering.
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
static GeometryType geometryType(Type type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:812
void mouseDoubleClickEvent(QMouseEvent *e) override
This class wraps a request for features to a vector layer (or directly its vector data provider)...
void panToSelected(QgsVectorLayer *layer=nullptr)
Pan to the selected features of current (vector) layer keeping same extent.
void setCustomDropHandlers(const QVector< QPointer< QgsCustomDropHandler >> &handlers)
Sets a list of custom drop handlers to use when drop events occur on the canvas.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
QgsRectangle layerExtentToOutputExtent(const QgsMapLayer *layer, QgsRectangle extent) const
transform bounding box from layer&#39;s CRS to output CRS
void setWheelFactor(double factor)
Sets wheel zoom factor (should be greater than 1)
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:91
void destinationCrsChanged()
Emitted when map CRS has changed.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
bool hasValidSettings() const
Check whether the map settings are valid and can be used for rendering.
bool isFrozen() const
Returns true if canvas is frozen.
A QgsRectangle with associated coordinate reference system.
void setCachingEnabled(bool enabled)
Set whether to cache images of rendered layers.
virtual void deactivate()
called when map tool is being deactivated
Definition: qgsmaptool.cpp:99
Single scope for storing variables and functions for use within a QgsExpressionContext.
const QgsProjectViewSettings * viewSettings() const
Returns the project&#39;s view settings, which contains settings and properties relating to how a QgsProj...
double mapUnitsPerPixel() const
Returns current map units per pixel.
void setPreviewMode(QgsPreviewEffect::PreviewMode mode)
Sets a preview mode for the map canvas.
virtual void wheelEvent(QWheelEvent *e)
Mouse wheel event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:177
QHash< QgsMapLayer *, int > perLayerRenderingTime() const
Returns the render time (in ms) per layer.
void transformContextChanged()
Emitted when the canvas transform context is changed.
bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const
Writes scope variables to an XML element.
void setMode(PreviewMode mode)
Sets the mode for the preview effect, which controls how the effect modifies a widgets appearance...
void renderStarting()
Emitted when the canvas is about to be rendered.
float devicePixelRatio() const
Returns device pixel ratio Common values are 1 for normal-dpi displays and 2 for high-dpi "retina" di...
const QgsMapToPixel & mapToPixel() const
void keyPressed(QKeyEvent *e)
Emit key press event.
void currentLayerChanged(QgsMapLayer *layer)
Emitted when the current layer is changed.
void zoomOut()
Zoom out with fixed factor.
Enable drawing of vertex markers for layers in editing mode.
void waitWhileRendering()
Blocks until the rendering job has finished.
constexpr double CANVAS_MAGNIFICATION_MIN
Minimum magnification level allowed in map canvases.
Definition: qgsguiutils.h:61
void zoomToPreviousExtent()
Zoom to the previous extent (view)
bool isDrawing()
Find out whether rendering is in progress.
void zoomByFactor(double scaleFactor, const QgsPointXY *center=nullptr)
Zoom with the factor supplied.
double x
Definition: qgspointxy.h:47
virtual bool renderInPreview(const QgsDataProvider::PreviewContext &context)
Returns whether the layer must be rendered in preview jobs.
void zoomToSelected(QgsVectorLayer *layer=nullptr)
Zoom to the extent of the selected features of provided (vector) layer.
A class to represent a vector.
Definition: qgsvector.h:29
PreviewMode mode() const
Returns the mode used for the preview effect.
void repaintRequested(bool deferredUpdate=false)
By emitting this signal the layer tells that either appearance or content have been changed and any v...
void setPreviewModeEnabled(bool previewEnabled)
Enables a preview mode for the map canvas.
virtual void start()=0
Start the rendering job and immediately return.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QPoint mouseLastXY()
returns last position of mouse cursor
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
double magnificationFactor() const
Returns the magnification factor.
void setRotation(double rotation)
Sets the rotation of the resulting map image, in degrees clockwise.
QgsMapLayer * currentLayer()
returns current layer (set by legend widget)
QMap< QString, QString > layerStyleOverrides() const
Returns the stored overrides of styles for layers.
void mouseMoveEvent(QMouseEvent *e) override
void keyReleaseEvent(QKeyEvent *e) override
virtual void keyPressEvent(QKeyEvent *e)
Key event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:182
DistanceUnit
Units of distance.
Definition: qgsunittypes.h:66
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.
double scale() const
Returns the last reported scale of the canvas.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle...
Definition: qgsrectangle.h:359
void remoteImageFetched(const QString &url)
Emitted when the cache has finished retrieving an image file from a remote url.
QColor canvasColor() const
Read property of QColor bgColor.
void clear()
Invalidates the cache contents, clearing all cached images.
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:139
Abstract base class for all map tools.
Definition: qgsmaptool.h:62
void selectionChangedSlot()
Receives signal about selection change, and pass it on with layer info.
Draw map such that there are no problems between adjacent tiles.
Job implementation that renders everything sequentially in one thread.
A general purpose distance and area calculator, capable of performing ellipsoid based calculations...
Render is a &#39;canvas preview&#39; render, and shortcuts should be taken to ensure fast rendering...
QgsUnitTypes::DistanceUnit mapUnits() const
Convenience function for returning the current canvas map units.
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
void mouseReleaseEvent(QMouseEvent *e) override
void writeProject(QDomDocument &)
Emitted when the project is being written.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:262
QMap< QString, QString > layerStyleOverrides() const
Gets map of map layer style overrides (key: layer ID, value: style name) where a different style shou...
void zoomToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids)
Set canvas extent to the bounding box of a set of features.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object...
QString theme() const
Returns the map&#39;s theme shown in the canvas, if set.
Definition: qgsmapcanvas.h:437
QgsPointXY center() const
Gets map center, in geographical coordinates.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
virtual bool gestureEvent(QGestureEvent *e)
gesture event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:192
void setSelectionColor(const QColor &color)
Sets color that is used for drawing of selected vector features.
void layerStyleOverridesChanged()
Emitted when the configuration of overridden layer styles changes.
virtual void canvasDoubleClickEvent(QgsMapMouseEvent *e)
Mouse double-click event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:162
void dragEnterEvent(QDragEnterEvent *e) override
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:145
QgsPointXY layerToMapCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from layer&#39;s CRS to output CRS
void writeProject(QDomDocument &)
called to write map canvas settings to project
void panAction(QMouseEvent *event)
Called when mouse is moving and pan is activated.
void setLayers(const QList< QgsMapLayer *> &layers)
Sets the list of layers that should be shown in the canvas.
QgsRectangle fullExtent() const
returns current extent of layer set
Intermediate base class adding functionality that allows client to query the rendered image...
void panToFeatureIds(QgsVectorLayer *layer, const QgsFeatureIds &ids, bool alwaysRecenter=true)
Centers canvas extent to feature ids.
Stores global configuration for labeling engine.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:450
void zoomToFullExtent()
Zoom to the full extent of all layers.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
This class represents a coordinate reference system (CRS).
QgsRectangle fullExtent() const
Returns the combined extent for all layers on the map canvas.
This class has all the configuration of snapping and can return answers to snapping queries...
const QgsLabelingResults * labelingResults() const
Gets access to the labeling results (may be nullptr)
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:436
void setMapSettingsFlags(QgsMapSettings::Flags flags)
Resets the flags for the canvas&#39; map settings.
void refreshAllLayers()
Reload all layers (including refreshing layer properties from their data sources), clears the cache and refreshes the canvas.
QgsFeatureRequest & setLimit(long limit)
Set the maximum number of features to request.
QList< QgsMimeDataUtils::Uri > UriList
void zoomScale(double scale)
Zooms the canvas to a specific scale.
Class for doing transforms between two map coordinate systems.
void setExtent(const QgsRectangle &r, bool magnified=false)
Sets the extent of the map canvas.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
void setLayers(const QList< QgsMapLayer *> &layers)
Set list of layers for map rendering.
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
void scaleChanged(double)
Emitted when the scale of the map changes.
void renderErrorOccurred(const QString &error, QgsMapLayer *layer)
Emitted whenever an error is encountered during a map render operation.
void setPathResolver(const QgsPathResolver &resolver)
Sets the path resolver for conversion between relative and absolute paths during rendering operations...
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:245
void setSegmentationToleranceType(QgsAbstractGeometry::SegmentationToleranceType type)
Sets segmentation tolerance type (maximum angle or maximum difference between curve and approximation...
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
virtual void cancelWithoutBlocking()=0
Triggers cancellation of the rendering job without blocking.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
void setSelectionColor(const QColor &color)
Set color of selected vector features.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
void paintEvent(QPaintEvent *e) override
void layerStateChange()
This slot is connected to the visibility change of one or more layers.
double lastRenderingTimeMs
Previous rendering time for the layer, in ms.
Enable vector simplification and other rendering optimizations.
static QCursor getThemeCursor(Cursor cursor)
Helper to get a theme cursor.
void freeze(bool frozen=true)
Freeze/thaw the map canvas.
void setScaleLocked(bool isLocked)
Lock the scale, so zooming can be performed using magnication.
Class that stores computed placement from labeling engine.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
void remoteSvgFetched(const QString &url)
Emitted when the cache has finished retrieving an SVG file from a remote url.
This class is responsible for keeping cache of rendered images resulting from a map rendering job...
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
QgsGeometry geometry
Definition: qgsfeature.h:67
void transformContextChanged()
Emitted when the project transformContext() is changed.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
void setTheme(const QString &theme)
Sets a map theme to show in the canvas.
QColor selectionColor() const
Gets color that is used for drawing of selected vector features.
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:230
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer&#39;s data provider, it may be nullptr.
void readXml(QDomNode &node)
bool nextFeature(QgsFeature &f)
QPoint rubberStartPoint
Beginning point of a rubber band.
bool hasAutoRefreshEnabled() const
Returns true if auto refresh is enabled for the layer.
double bearing(const QgsPointXY &p1, const QgsPointXY &p2) const
Computes the bearing (in radians) between two points.
QgsSnappingUtils * snappingUtils() const
Returns snapping utility class that is associated with map canvas.
virtual QgsLabelingResults * takeLabelingResults()=0
Gets pointer to internal labeling engine (in order to get access to the results). ...
void xyCoordinates(const QgsPointXY &p)
Emits current mouse position.
void autoRefreshIntervalChanged(int interval)
Emitted when the auto refresh interval changes.
void writeXml(QDomNode &node, QDomDocument &doc)
void zoomIn()
Zoom in with fixed factor.
Stores settings related to the context in which a preview job runs.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer&#39;s CRS
void waitForFinished() override
Block until the job has finished.
Represents a vector layer which manages a vector based data sets.
virtual void updatePosition()
called on changed extent or resize event to update position of the item
bool isParallelRenderingEnabled() const
Check whether the layers are rendered in parallel or sequentially.
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
void mapToolSet(QgsMapTool *newTool, QgsMapTool *oldTool)
Emit map tool changed with the old tool.
CanvasProperties()=default
Constructor for CanvasProperties.
An interactive map canvas item which displays a QgsAnnotation.
void themeChanged(const QString &theme)
Emitted when the canvas has been assigned a different map theme.
void extentsChanged()
Emitted when the extents of the map change.
virtual void clean()
convenient method to clean members
Definition: qgsmaptool.cpp:109
QSize outputSize() const
Returns the size of the resulting map image.
void ellipsoidChanged(const QString &ellipsoid)
Emitted when the project ellipsoid is changed.
void panDistanceBearingChanged(double distance, QgsUnitTypes::DistanceUnit unit, double bearing)
Emitted whenever the distance or bearing of an in-progress panning operation is changed.
QgsPointXY toMapCoordinates(int x, int y) const
Transform device coordinates to map (world) coordinates.
double measureLine(const QVector< QgsPointXY > &points) const
Measures the length of a line with multiple segments.
QgsMapLayer * layer(int index)
Returns the map layer at position index in the layer stack.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
A map tool for panning the map.
Definition: qgsmaptoolpan.h:32
virtual bool isActive() const =0
Tell whether the rendering job is currently running in background.
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:130
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:86
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
void setMagnificationFactor(double factor)
Sets the factor of magnification to apply to the map canvas.
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns the global configuration of the labeling engine.
void panActionEnd(QPoint releasePoint)
Ends pan action and redraws the canvas.
void redrawAllLayers()
Clears all cached images and redraws all layers.
bool previewModeEnabled() const
Returns whether a preview mode is enabled for the map canvas.
void layersChanged()
Emitted when a new set of layers has been received.
void messageEmitted(const QString &title, const QString &message, Qgis::MessageLevel=Qgis::Info)
emit a message (usually to be displayed in a message bar)
virtual bool usedCachedLabels() const =0
Returns true if the render job was able to use a cached labeling solution.
~QgsMapCanvas() override
virtual void canvasReleaseEvent(QgsMapMouseEvent *e)
Mouse release event for overriding. Default implementation does nothing.
Definition: qgsmaptool.cpp:172