QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 
19 #include <QtGlobal>
20 #include <QApplication>
21 #include <QCursor>
22 #include <QDir>
23 #include <QFile>
24 #include <QGraphicsItem>
25 #include <QGraphicsScene>
26 #include <QGraphicsView>
27 #include <QKeyEvent>
28 #include <QMouseEvent>
29 #include <QPainter>
30 #include <QPaintEvent>
31 #include <QPixmap>
32 #include <QRect>
33 #include <QSettings>
34 #include <QTextStream>
35 #include <QResizeEvent>
36 #include <QString>
37 #include <QStringList>
38 #include <QWheelEvent>
39 
40 #include "qgis.h"
41 #include "qgsapplication.h"
42 #include "qgscrscache.h"
44 #include "qgslogger.h"
45 #include "qgsmapcanvas.h"
46 #include "qgsmapcanvasmap.h"
48 #include "qgsmaplayer.h"
49 #include "qgsmaplayerregistry.h"
50 #include "qgsmaptoolpan.h"
51 #include "qgsmaptoolzoom.h"
52 #include "qgsmaptopixel.h"
53 #include "qgsmapoverviewcanvas.h"
54 #include "qgsmaprenderer.h"
55 #include "qgsmaprenderercache.h"
59 #include "qgsmessagelog.h"
60 #include "qgsmessageviewer.h"
61 #include "qgspallabeling.h"
62 #include "qgsproject.h"
63 #include "qgsrubberband.h"
64 #include "qgsvectorlayer.h"
65 #include <math.h>
66 
67 
70 {
71  public:
72 
74 
77 
79  QPoint mouseLastXY;
80 
83 
86 };
87 
88 
90  : QObject( canvas )
91  , mCanvas( canvas )
92  , mRenderer( renderer )
93  , mSyncingExtent( false )
94 {
95  connect( mCanvas, SIGNAL( extentsChanged() ), this, SLOT( onExtentC2R() ) );
96  connect( mRenderer, SIGNAL( extentsChanged() ), this, SLOT( onExtentR2C() ) );
97 
98  connect( mCanvas, SIGNAL( mapUnitsChanged() ), this, SLOT( onMapUnitsC2R() ) );
99  connect( mRenderer, SIGNAL( mapUnitsChanged() ), this, SLOT( onMapUnitsR2C() ) );
100 
101  connect( mCanvas, SIGNAL( rotationChanged( double ) ), this, SLOT( onMapRotationC2R() ) );
102  connect( mRenderer, SIGNAL( rotationChanged( double ) ), this, SLOT( onMapRotationR2C() ) );
103 
104  connect( mCanvas, SIGNAL( hasCrsTransformEnabledChanged( bool ) ), this, SLOT( onCrsTransformC2R() ) );
105  connect( mRenderer, SIGNAL( hasCrsTransformEnabled( bool ) ), this, SLOT( onCrsTransformR2C() ) );
106 
107  connect( mCanvas, SIGNAL( destinationCrsChanged() ), this, SLOT( onDestCrsC2R() ) );
108  connect( mRenderer, SIGNAL( destinationSrsChanged() ), this, SLOT( onDestCrsR2C() ) );
109 
110  connect( mCanvas, SIGNAL( layersChanged() ), this, SLOT( onLayersC2R() ) );
111  // TODO: layers R2C ? (should not happen!)
112 
113 }
114 
116 {
117  // protection against possible bounce back
118  if ( mSyncingExtent )
119  return;
120 
121  mSyncingExtent = true;
123  mSyncingExtent = false;
124 }
125 
127 {
128  // protection against possible bounce back
129  if ( mSyncingExtent )
130  return;
131 
132  mSyncingExtent = true;
134  mSyncingExtent = false;
135 }
136 
138 {
140 }
141 
143 {
145 }
146 
148 {
150 }
151 
153 {
155 }
156 
158 {
160 }
161 
163 {
165 }
166 
168 {
170 }
171 
173 {
175 }
176 
178 {
180 }
181 
182 
183 
184 QgsMapCanvas::QgsMapCanvas( QWidget * parent, const char *name )
185  : QGraphicsView( parent )
186  , mCanvasProperties( new CanvasProperties )
187  , mJob( 0 )
188  , mJobCancelled( false )
189  , mLabelingResults( 0 )
190  , mUseParallelRendering( false )
191  , mDrawRenderingStats( false )
192  , mCache( 0 )
193  , mPreviewEffect( 0 )
194  , mSnappingUtils( 0 )
195 {
196  setObjectName( name );
197  mScene = new QGraphicsScene();
198  setScene( mScene );
199  setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
200  setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
201  mLastExtentIndex = -1;
202  mCurrentLayer = NULL;
203  mMapOverview = NULL;
204  mMapTool = NULL;
205  mLastNonZoomMapTool = NULL;
206 
207  mFrozen = false;
208  mRefreshScheduled = false;
209 
211 
212  // by default, the canvas is rendered
213  mRenderFlag = true;
214 
215  setMouseTracking( true );
216  setFocusPolicy( Qt::StrongFocus );
217 
218  mMapRenderer = new QgsMapRenderer;
219 
220  mResizeTimer = new QTimer( this );
221  mResizeTimer->setSingleShot( true );
222  connect( mResizeTimer, SIGNAL( timeout() ), this, SLOT( refresh() ) );
223 
224  // create map canvas item which will show the map
225  mMap = new QgsMapCanvasMap( this );
226  mScene->addItem( mMap );
227 
228  // project handling
229  connect( QgsProject::instance(), SIGNAL( readProject( const QDomDocument & ) ),
230  this, SLOT( readProject( const QDomDocument & ) ) );
231  connect( QgsProject::instance(), SIGNAL( writeProject( QDomDocument & ) ),
232  this, SLOT( writeProject( QDomDocument & ) ) );
233 
236 
237  // class that will sync most of the changes between canvas and (legacy) map renderer
238  // it is parented to map canvas, will be deleted automatically
239  new QgsMapCanvasRendererSync( this, mMapRenderer );
240 
241  QSize s = viewport()->size();
242  mSettings.setOutputSize( s );
243  mMapRenderer->setOutputSize( s, mSettings.outputDpi() );
244  setSceneRect( 0, 0, s.width(), s.height() );
245  mScene->setSceneRect( QRectF( 0, 0, s.width(), s.height() ) );
246 
247  moveCanvasContents( true );
248 
249  connect( &mMapUpdateTimer, SIGNAL( timeout() ), SLOT( mapUpdateTimeout() ) );
250  mMapUpdateTimer.setInterval( 250 );
251 
252 #ifdef Q_OS_WIN
253  // Enable touch event on Windows.
254  // Qt on Windows needs to be told it can take touch events or else it ignores them.
255  grabGesture( Qt::PinchGesture );
256  viewport()->setAttribute( Qt::WA_AcceptTouchEvents );
257 #endif
258 
259  mPreviewEffect = new QgsPreviewEffect( this );
260  viewport()->setGraphicsEffect( mPreviewEffect );
261 
262  setInteractive( false );
263 
264  refresh();
265 
266 } // QgsMapCanvas ctor
267 
268 
270 {
271  if ( mMapTool )
272  {
273  mMapTool->deactivate();
274  mMapTool = NULL;
275  }
276  mLastNonZoomMapTool = NULL;
277 
278  // delete canvas items prior to deleteing the canvas
279  // because they might try to update canvas when it's
280  // already being destructed, ends with segfault
281  QList<QGraphicsItem*> list = mScene->items();
282  QList<QGraphicsItem*>::iterator it = list.begin();
283  while ( it != list.end() )
284  {
285  QGraphicsItem* item = *it;
286  delete item;
287  ++it;
288  }
289 
290  mScene->deleteLater(); // crashes in python tests on windows
291 
292  delete mMapRenderer;
293  // mCanvasProperties auto-deleted via QScopedPointer
294  // CanvasProperties struct has its own dtor for freeing resources
295 
296  if ( mJob )
297  {
298  mJob->cancel();
299  Q_ASSERT( mJob == 0 );
300  }
301 
302  delete mCache;
303 
304  delete mLabelingResults;
305 
306 } // dtor
307 
309 {
310  mSettings.setFlag( QgsMapSettings::Antialiasing, theFlag );
311 
312  if ( mMapOverview )
313  mMapOverview->enableAntiAliasing( theFlag );
314 } // anti aliasing
315 
316 void QgsMapCanvas::useImageToRender( bool theFlag )
317 {
318  Q_UNUSED( theFlag );
319 }
320 
322 {
323  return mMap;
324 }
325 
327 {
328  return mMapRenderer;
329 }
330 
331 
333 {
334  const QStringList& layers = mapSettings().layers();
335  if ( index >= 0 && index < ( int ) layers.size() )
336  return QgsMapLayerRegistry::instance()->mapLayer( layers[index] );
337  else
338  return NULL;
339 }
340 
341 
343 {
344  mCurrentLayer = layer;
345  emit currentLayerChanged( layer );
346 }
347 
349 {
350  return mapSettings().scale();
351 } // scale
352 
353 void QgsMapCanvas::setDirty( bool dirty )
354 {
355  if ( dirty )
356  refresh();
357 }
358 
360 {
361  return false;
362 }
363 
364 
365 
367 {
368  return mJob != 0;
369 } // isDrawing
370 
371 
372 // return the current coordinate transform based on the extents and
373 // device size
375 {
376  return &mapSettings().mapToPixel();
377 }
378 
379 void QgsMapCanvas::setLayerSet( QList<QgsMapCanvasLayer> &layers )
380 {
381  // create layer set
382  QStringList layerSet, layerSetOverview;
383 
384  int i;
385  for ( i = 0; i < layers.size(); i++ )
386  {
387  QgsMapCanvasLayer &lyr = layers[i];
388  if ( !lyr.layer() )
389  {
390  continue;
391  }
392 
393  if ( lyr.isVisible() )
394  {
395  layerSet.push_back( lyr.layer()->id() );
396  }
397 
398  if ( lyr.isInOverview() )
399  {
400  layerSetOverview.push_back( lyr.layer()->id() );
401  }
402  }
403 
404  const QStringList& layerSetOld = mapSettings().layers();
405 
406  bool layerSetChanged = layerSetOld != layerSet;
407 
408  // update only if needed
409  if ( layerSetChanged )
410  {
411  QgsDebugMsg( "Layers changed to: " + layerSet.join( ", " ) );
412 
413  for ( i = 0; i < layerCount(); i++ )
414  {
415  // Add check if vector layer when disconnecting from selectionChanged slot
416  // Ticket #811 - racicot
418  if ( !currentLayer )
419  continue;
420  disconnect( currentLayer, SIGNAL( repaintRequested() ), this, SLOT( refresh() ) );
421  disconnect( currentLayer, SIGNAL( layerCrsChanged() ), this, SLOT( layerCrsChange() ) );
422  QgsVectorLayer *isVectLyr = qobject_cast<QgsVectorLayer *>( currentLayer );
423  if ( isVectLyr )
424  {
425  disconnect( currentLayer, SIGNAL( selectionChanged() ), this, SLOT( selectionChangedSlot() ) );
426  }
427  }
428 
429  mSettings.setLayers( layerSet );
430 
431  for ( i = 0; i < layerCount(); i++ )
432  {
433  // Add check if vector layer when connecting to selectionChanged slot
434  // Ticket #811 - racicot
436  connect( currentLayer, SIGNAL( repaintRequested() ), this, SLOT( refresh() ) );
437  connect( currentLayer, SIGNAL( layerCrsChanged() ), this, SLOT( layerCrsChange() ) );
438  QgsVectorLayer *isVectLyr = qobject_cast<QgsVectorLayer *>( currentLayer );
439  if ( isVectLyr )
440  {
441  connect( currentLayer, SIGNAL( selectionChanged() ), this, SLOT( selectionChangedSlot() ) );
442  }
443  }
444 
446 
447  QgsDebugMsg( "Layers have changed, refreshing" );
448  emit layersChanged();
449 
450  refresh();
451  }
452 
453  if ( mMapOverview )
454  {
455  const QStringList& layerSetOvOld = mMapOverview->layerSet();
456  if ( layerSetOvOld != layerSetOverview )
457  {
458  mMapOverview->setLayerSet( layerSetOverview );
459  }
460 
461  // refresh overview maplayers even if layer set is the same
462  // because full extent might have changed
463  updateOverview();
464  }
465 } // setLayerSet
466 
468 {
469  if ( mMapOverview )
470  {
471  // disconnect old map overview if exists
472  disconnect( this, SIGNAL( hasCrsTransformEnabledChanged( bool ) ),
473  mMapOverview, SLOT( hasCrsTransformEnabled( bool ) ) );
474  disconnect( this, SIGNAL( destinationCrsChanged() ),
475  mMapOverview, SLOT( destinationSrsChanged() ) );
476 
477  // map overview is not owned by map canvas so don't delete it...
478  }
479 
480  mMapOverview = overview;
481 
482  if ( overview )
483  {
484  // connect to the map render to copy its projection settings
485  connect( this, SIGNAL( hasCrsTransformEnabledChanged( bool ) ),
486  overview, SLOT( hasCrsTransformEnabled( bool ) ) );
487  connect( this, SIGNAL( destinationCrsChanged() ),
488  overview, SLOT( destinationSrsChanged() ) );
489  }
490 }
491 
493 {
494  return mSettings;
495 }
496 
498 {
499  if ( mSettings.hasCrsTransformEnabled() == enabled )
500  return;
501 
502  mSettings.setCrsTransformEnabled( enabled );
503 
505 
506  refresh();
507 
508  emit hasCrsTransformEnabledChanged( enabled );
509 }
510 
512 {
513  if ( mSettings.destinationCrs() == crs )
514  return;
515 
516  if ( mSettings.hasCrsTransformEnabled() )
517  {
518  // try to reproject current extent to the new one
519  QgsRectangle rect;
520  if ( !mSettings.visibleExtent().isEmpty() )
521  {
522  QgsCoordinateTransform transform( mSettings.destinationCrs(), crs );
523  try
524  {
525  rect = transform.transformBoundingBox( mSettings.visibleExtent() );
526  }
527  catch ( QgsCsException &e )
528  {
529  QgsDebugMsg( QString( "Transform error caught: %1" ).arg( e.what() ) );
530  }
531  }
532  if ( !rect.isEmpty() )
533  {
534  setExtent( rect );
535  }
536 
537  QgsDebugMsg( "refreshing after destination CRS changed" );
538  refresh();
539  }
540 
541  mSettings.setDestinationCrs( crs );
542 
544 
545  emit destinationCrsChanged();
546 }
547 
549 {
550  return mLabelingResults;
551 }
552 
554 {
555  if ( enabled == isCachingEnabled() )
556  return;
557 
558  if ( enabled )
559  {
560  mCache = new QgsMapRendererCache;
561  }
562  else
563  {
564  delete mCache;
565  mCache = 0;
566  }
567 }
568 
570 {
571  return mCache != 0;
572 }
573 
575 {
576  if ( mCache )
577  mCache->clear();
578 }
579 
581 {
582  mUseParallelRendering = enabled;
583 }
584 
586 {
587  return mUseParallelRendering;
588 }
589 
590 void QgsMapCanvas::setMapUpdateInterval( int timeMiliseconds )
591 {
592  mMapUpdateTimer.setInterval( timeMiliseconds );
593 }
594 
596 {
597  return mMapUpdateTimer.interval();
598 }
599 
600 
602 {
603  // redraw overview
604  if ( mMapOverview )
605  {
606  mMapOverview->refresh();
607  }
608 }
609 
610 
612 {
613  return mCurrentLayer;
614 }
615 
616 
618 {
619  if ( !mSettings.hasValidSettings() )
620  {
621  QgsDebugMsg( "CANVAS refresh - invalid settings -> nothing to do" );
622  return;
623  }
624 
625  if ( !mRenderFlag || mFrozen ) // do we really need two flags controlling rendering?
626  {
627  QgsDebugMsg( "CANVAS render flag off" );
628  return;
629  }
630 
631  if ( mRefreshScheduled )
632  {
633  QgsDebugMsg( "CANVAS refresh already scheduled" );
634  return;
635  }
636 
637  mRefreshScheduled = true;
638 
639  QgsDebugMsg( "CANVAS refresh scheduling" );
640 
641  // schedule a refresh
642  QTimer::singleShot( 1, this, SLOT( refreshMap() ) );
643 } // refresh
644 
645 void QgsMapCanvas::refreshMap()
646 {
647  Q_ASSERT( mRefreshScheduled );
648 
649  QgsDebugMsg( "CANVAS refresh!" );
650 
651  stopRendering(); // if any...
652 
653  // from now on we can accept refresh requests again
654  mRefreshScheduled = false;
655 
656  //update $map variable to canvas
657  QgsExpression::setSpecialColumn( "$map", tr( "canvas" ) );
658 
659  // create the renderer job
660  Q_ASSERT( mJob == 0 );
661  mJobCancelled = false;
662  if ( mUseParallelRendering )
663  mJob = new QgsMapRendererParallelJob( mSettings );
664  else
665  mJob = new QgsMapRendererSequentialJob( mSettings );
666  connect( mJob, SIGNAL( finished() ), SLOT( rendererJobFinished() ) );
667  mJob->setCache( mCache );
668 
669  QStringList layersForGeometryCache;
670  foreach ( QString id, mSettings.layers() )
671  {
672  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( id ) ) )
673  {
674  if ( vl->isEditable() )
675  layersForGeometryCache << id;
676  }
677  }
678  mJob->setRequestedGeometryCacheForLayers( layersForGeometryCache );
679 
680  mJob->start();
681 
682  mMapUpdateTimer.start();
683 
684  emit renderStarting();
685 }
686 
687 
688 void QgsMapCanvas::rendererJobFinished()
689 {
690  QgsDebugMsg( QString( "CANVAS finish! %1" ).arg( !mJobCancelled ) );
691 
692  mMapUpdateTimer.stop();
693 
694  // TODO: would be better to show the errors in message bar
695  foreach ( const QgsMapRendererJob::Error& error, mJob->errors() )
696  {
697  QgsMessageLog::logMessage( error.layerID + " :: " + error.message, tr( "Rendering" ) );
698  }
699 
700  if ( !mJobCancelled )
701  {
702  // take labeling results before emitting renderComplete, so labeling map tools
703  // connected to signal work with correct results
704  delete mLabelingResults;
705  mLabelingResults = mJob->takeLabelingResults();
706 
707  QImage img = mJob->renderedImage();
708 
709  // emit renderComplete to get our decorations drawn
710  QPainter p( &img );
711  emit renderComplete( &p );
712 
713  QSettings settings;
714  if ( settings.value( "/Map/logCanvasRefreshEvent", false ).toBool() )
715  {
716  QString logMsg = tr( "Canvas refresh: %1 ms" ).arg( mJob->renderingTime() );
717  QgsMessageLog::logMessage( logMsg, tr( "Rendering" ) );
718  }
719 
720  if ( mDrawRenderingStats )
721  {
722  int w = img.width(), h = img.height();
723  QFont fnt = p.font();
724  fnt.setBold( true );
725  p.setFont( fnt );
726  int lh = p.fontMetrics().height() * 2;
727  QRect r( 0, h - lh, w, lh );
728  p.setPen( Qt::NoPen );
729  p.setBrush( QColor( 0, 0, 0, 110 ) );
730  p.drawRect( r );
731  p.setPen( Qt::white );
732  QString msg = QString( "%1 :: %2 ms" ).arg( mUseParallelRendering ? "PARALLEL" : "SEQUENTIAL" ).arg( mJob->renderingTime() );
733  p.drawText( r, msg, QTextOption( Qt::AlignCenter ) );
734  }
735 
736  p.end();
737 
738  mMap->setContent( img, imageRect( img, mJob->mapSettings() ) );
739  }
740 
741  // now we are in a slot called from mJob - do not delete it immediately
742  // so the class is still valid when the execution returns to the class
743  mJob->deleteLater();
744  mJob = 0;
745 
746  emit mapCanvasRefreshed();
747 }
748 
749 QgsRectangle QgsMapCanvas::imageRect( const QImage& img, const QgsMapSettings& mapSettings )
750 {
751  // This is a hack to pass QgsMapCanvasItem::setRect what it
752  // expects (encoding of position and size of the item)
753  const QgsMapToPixel& m2p = mapSettings.mapToPixel();
754  QgsPoint topLeft = m2p.toMapPoint( 0, 0 );
755  double res = m2p.mapUnitsPerPixel();
756  QgsRectangle rect( topLeft.x(), topLeft.y(), topLeft.x() + img.width()*res, topLeft.y() - img.height()*res );
757  return rect;
758 }
759 
760 void QgsMapCanvas::mapUpdateTimeout()
761 {
762  const QImage& img = mJob->renderedImage();
763  mMap->setContent( img, imageRect( img, mJob->mapSettings() ) );
764 }
765 
767 {
768  if ( mJob )
769  {
770  QgsDebugMsg( "CANVAS stop rendering!" );
771  mJobCancelled = true;
772  mJob->cancel();
773  Q_ASSERT( mJob == 0 ); // no need to delete here: already deleted in finished()
774  }
775 }
776 
778 {
779 }
780 
781 //the format defaults to "PNG" if not specified
782 void QgsMapCanvas::saveAsImage( QString theFileName, QPixmap * theQPixmap, QString theFormat )
783 {
784  //
785  //check if the optional QPaintDevice was supplied
786  //
787  if ( theQPixmap != NULL )
788  {
789  // render
790  QPainter painter;
791  painter.begin( theQPixmap );
792  QgsMapRendererCustomPainterJob job( mSettings, &painter );
793  job.start();
794  job.waitForFinished();
795  emit renderComplete( &painter );
796  painter.end();
797 
798  theQPixmap->save( theFileName, theFormat.toLocal8Bit().data() );
799  }
800  else //use the map view
801  {
802  mMap->contentImage().save( theFileName, theFormat.toLocal8Bit().data() );
803  }
804  //create a world file to go with the image...
806  QString myHeader;
807  // note: use 17 places of precision for all numbers output
808  //Pixel XDim
809  myHeader += qgsDoubleToString( mapUnitsPerPixel() ) + "\r\n";
810  //Rotation on y axis - hard coded
811  myHeader += "0 \r\n";
812  //Rotation on x axis - hard coded
813  myHeader += "0 \r\n";
814  //Pixel YDim - almost always negative - see
815  //http://en.wikipedia.org/wiki/World_file#cite_note-2
816  myHeader += "-" + qgsDoubleToString( mapUnitsPerPixel() ) + "\r\n";
817  //Origin X (center of top left cell)
818  myHeader += qgsDoubleToString( myRect.xMinimum() + ( mapUnitsPerPixel() / 2 ) ) + "\r\n";
819  //Origin Y (center of top left cell)
820  myHeader += qgsDoubleToString( myRect.yMaximum() - ( mapUnitsPerPixel() / 2 ) ) + "\r\n";
821  QFileInfo myInfo = QFileInfo( theFileName );
822  // allow dotted names
823  QString myWorldFileName = myInfo.absolutePath() + "/" + myInfo.completeBaseName() + "." + theFormat + "w";
824  QFile myWorldFile( myWorldFileName );
825  if ( !myWorldFile.open( QIODevice::WriteOnly ) ) //don't use QIODevice::Text
826  {
827  return;
828  }
829  QTextStream myStream( &myWorldFile );
830  myStream << myHeader;
831 } // saveAsImage
832 
833 
834 
836 {
837  return mapSettings().visibleExtent();
838 } // extent
839 
841 {
842  return mapSettings().fullExtent();
843 } // extent
844 
845 
847 {
848  QgsRectangle current = extent();
849 
850  if ( r == current )
851  return;
852 
853  if ( r.isEmpty() )
854  {
855  if ( !mSettings.hasValidSettings() )
856  {
857  // we can't even just move the map center
858  QgsDebugMsg( "Empty extent - ignoring" );
859  return;
860  }
861 
862  // ### QGIS 3: do not allow empty extent - require users to call setCenter() explicitly
863  QgsDebugMsg( "Empty extent - keeping old scale with new center!" );
864  setCenter( r.center() );
865  }
866  else
867  {
868  mSettings.setExtent( r );
869  }
870  emit extentsChanged();
871  updateScale();
872  if ( mLastExtent.size() > 20 )
873  mLastExtent.removeAt( 0 );
874 
875  //clear all extent items after current index
876  for ( int i = mLastExtent.size() - 1; i > mLastExtentIndex; i-- )
877  {
878  mLastExtent.removeAt( i );
879  }
880 
881  mLastExtent.append( extent() );
882 
883  // adjust history to no more than 20
884  if ( mLastExtent.size() > 20 )
885  {
886  mLastExtent.removeAt( 0 );
887  }
888 
889  // the last item is the current extent
890  mLastExtentIndex = mLastExtent.size() - 1;
891 
892  // update controls' enabled state
893  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
894  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
895  // notify canvas items of change
897 
898 } // setExtent
899 
900 void QgsMapCanvas::setCenter( const QgsPoint& center )
901 {
903  double x = center.x();
904  double y = center.y();
905  setExtent(
906  QgsRectangle(
907  x - r.width() / 2.0, y - r.height() / 2.0,
908  x + r.width() / 2.0, y + r.height() / 2.0
909  )
910  );
911 } // setCenter
912 
914 {
916  return r.center();
917 }
918 
919 
921 {
922  return mapSettings().rotation();
923 } // rotation
924 
925 void QgsMapCanvas::setRotation( double degrees )
926 {
927  if ( !rotationEnabled() )
928  return;
929 
930  double current = rotation();
931 
932  if ( degrees == current )
933  return;
934 
935  mSettings.setRotation( degrees );
936  emit rotationChanged( degrees );
937  emit extentsChanged(); // visible extent changes with rotation
938 
939  // notify canvas items of change (needed?)
941 
942 } // setRotation
943 
944 
946 {
947  emit scaleChanged( mapSettings().scale() );
948 }
949 
950 
952 {
953  refresh();
954 } // clear
955 
956 
957 
959 {
961  // If the full extent is an empty set, don't do the zoom
962  if ( !extent.isEmpty() )
963  {
964  // Add a 5% margin around the full extent
965  extent.scale( 1.05 );
966  setExtent( extent );
967  }
968  refresh();
969 
970 } // zoomToFullExtent
971 
972 
973 
975 {
976  if ( mLastExtentIndex > 0 )
977  {
978  mLastExtentIndex--;
979  mSettings.setExtent( mLastExtent[mLastExtentIndex] );
980  emit extentsChanged();
981  updateScale();
982  refresh();
983  // update controls' enabled state
984  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
985  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
986  // notify canvas items of change
988  }
989 
990 } // zoomToPreviousExtent
991 
993 {
994  if ( mLastExtentIndex < mLastExtent.size() - 1 )
995  {
996  mLastExtentIndex++;
997  mSettings.setExtent( mLastExtent[mLastExtentIndex] );
998  emit extentsChanged();
999  updateScale();
1000  refresh();
1001  // update controls' enabled state
1002  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1003  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1004  // notify canvas items of change
1006  }
1007 }// zoomToNextExtent
1008 
1010 {
1011  mLastExtent.clear(); // clear the zoom history list
1012  mLastExtent.append( extent() ) ; // set the current extent in the list
1013  mLastExtentIndex = mLastExtent.size() - 1;
1014  // update controls' enabled state
1015  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
1016  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
1017 }// clearExtentHistory
1018 
1019 
1021 {
1023 }
1024 
1026 {
1027  if ( layer == NULL )
1028  {
1029  // use current layer by default
1030  layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1031  }
1032 
1033  if ( layer == NULL )
1034  {
1035  return;
1036  }
1037 
1038  if ( layer->selectedFeatureCount() == 0 )
1039  {
1040  return;
1041  }
1042 
1044 
1045  // no selected features, only one selected point feature
1046  //or two point features with the same x- or y-coordinates
1047  if ( rect.isEmpty() )
1048  {
1049  // zoom in
1050  QgsPoint c = rect.center();
1051  rect = extent();
1052  rect.scale( 1.0, &c );
1053  }
1054  //zoom to an area
1055  else
1056  {
1057  // Expand rect to give a bit of space around the selected
1058  // objects so as to keep them clear of the map boundaries
1059  // The same 5% should apply to all margins.
1060  rect.scale( 1.05 );
1061  }
1062 
1063  setExtent( rect );
1064  refresh();
1065 } // zoomToSelected
1066 
1068 {
1069  if ( layer == NULL )
1070  {
1071  // use current layer by default
1072  layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1073  }
1074 
1075  if ( layer == NULL )
1076  {
1077  return;
1078  }
1079 
1080  if ( layer->selectedFeatureCount() == 0 )
1081  {
1082  return;
1083  }
1084 
1086  setExtent( QgsRectangle( rect.center(), rect.center() ) );
1087  refresh();
1088 } // panToSelected
1089 
1090 void QgsMapCanvas::keyPressEvent( QKeyEvent * e )
1091 {
1092  if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )
1093  {
1094  emit keyPressed( e );
1095  return;
1096  }
1097 
1098  QPainter paint;
1099  QPen pen( Qt::gray );
1100  QgsPoint ll, ur;
1101 
1102  if ( ! mCanvasProperties->mouseButtonDown )
1103  {
1104  // Don't want to interfer with mouse events
1105 
1106  QgsRectangle currentExtent = mapSettings().visibleExtent();
1107  double dx = qAbs(( currentExtent.xMaximum() - currentExtent.xMinimum() ) / 4 );
1108  double dy = qAbs(( currentExtent.yMaximum() - currentExtent.yMinimum() ) / 4 );
1109 
1110  switch ( e->key() )
1111  {
1112  case Qt::Key_Left:
1113  QgsDebugMsg( "Pan left" );
1114 
1115  currentExtent.setXMinimum( currentExtent.xMinimum() - dx );
1116  currentExtent.setXMaximum( currentExtent.xMaximum() - dx );
1117  setExtent( currentExtent );
1118  refresh();
1119  break;
1120 
1121  case Qt::Key_Right:
1122  QgsDebugMsg( "Pan right" );
1123 
1124  currentExtent.setXMinimum( currentExtent.xMinimum() + dx );
1125  currentExtent.setXMaximum( currentExtent.xMaximum() + dx );
1126  setExtent( currentExtent );
1127  refresh();
1128  break;
1129 
1130  case Qt::Key_Up:
1131  QgsDebugMsg( "Pan up" );
1132 
1133  currentExtent.setYMaximum( currentExtent.yMaximum() + dy );
1134  currentExtent.setYMinimum( currentExtent.yMinimum() + dy );
1135  setExtent( currentExtent );
1136  refresh();
1137  break;
1138 
1139  case Qt::Key_Down:
1140  QgsDebugMsg( "Pan down" );
1141 
1142  currentExtent.setYMaximum( currentExtent.yMaximum() - dy );
1143  currentExtent.setYMinimum( currentExtent.yMinimum() - dy );
1144  setExtent( currentExtent );
1145  refresh();
1146  break;
1147 
1148 
1149 
1150  case Qt::Key_Space:
1151  QgsDebugMsg( "Pressing pan selector" );
1152 
1153  //mCanvasProperties->dragging = true;
1154  if ( ! e->isAutoRepeat() )
1155  {
1156  mCanvasProperties->panSelectorDown = true;
1157  mCanvasProperties->rubberStartPoint = mCanvasProperties->mouseLastXY;
1158  }
1159  break;
1160 
1161  case Qt::Key_PageUp:
1162  QgsDebugMsg( "Zoom in" );
1163  zoomIn();
1164  break;
1165 
1166  case Qt::Key_PageDown:
1167  QgsDebugMsg( "Zoom out" );
1168  zoomOut();
1169  break;
1170 
1171 #if 0
1172  case Qt::Key_P:
1173  mUseParallelRendering = !mUseParallelRendering;
1174  refresh();
1175  break;
1176 
1177  case Qt::Key_S:
1178  mDrawRenderingStats = !mDrawRenderingStats;
1179  refresh();
1180  break;
1181 #endif
1182 
1183  default:
1184  // Pass it on
1185  if ( mMapTool )
1186  {
1187  mMapTool->keyPressEvent( e );
1188  }
1189  else e->ignore();
1190 
1191  QgsDebugMsg( "Ignoring key: " + QString::number( e->key() ) );
1192  }
1193  }
1194 
1195  emit keyPressed( e );
1196 
1197 } //keyPressEvent()
1198 
1199 void QgsMapCanvas::keyReleaseEvent( QKeyEvent * e )
1200 {
1201  QgsDebugMsg( "keyRelease event" );
1202 
1203  switch ( e->key() )
1204  {
1205  case Qt::Key_Space:
1206  if ( !e->isAutoRepeat() && mCanvasProperties->panSelectorDown )
1207  {
1208  QgsDebugMsg( "Releasing pan selector" );
1209 
1210  mCanvasProperties->panSelectorDown = false;
1211  panActionEnd( mCanvasProperties->mouseLastXY );
1212  }
1213  break;
1214 
1215  default:
1216  // Pass it on
1217  if ( mMapTool )
1218  {
1219  mMapTool->keyReleaseEvent( e );
1220  }
1221  else e->ignore();
1222 
1223  QgsDebugMsg( "Ignoring key release: " + QString::number( e->key() ) );
1224  }
1225 
1226  emit keyReleased( e );
1227 
1228 } //keyReleaseEvent()
1229 
1230 
1232 {
1233  // call handler of current map tool
1234  if ( mMapTool )
1235  {
1236  mMapTool->canvasDoubleClickEvent( e );
1237  }
1238 }// mouseDoubleClickEvent
1239 
1240 
1241 void QgsMapCanvas::mousePressEvent( QMouseEvent * e )
1242 {
1243  //use middle mouse button for panning, map tools won't receive any events in that case
1244  if ( e->button() == Qt::MidButton )
1245  {
1246  mCanvasProperties->panSelectorDown = true;
1247  mCanvasProperties->rubberStartPoint = mCanvasProperties->mouseLastXY;
1248  }
1249  else
1250  {
1251 
1252  // call handler of current map tool
1253  if ( mMapTool )
1254  {
1255  mMapTool->canvasPressEvent( e );
1256  }
1257  }
1258 
1259  if ( mCanvasProperties->panSelectorDown )
1260  {
1261  return;
1262  }
1263 
1264  mCanvasProperties->mouseButtonDown = true;
1265  mCanvasProperties->rubberStartPoint = e->pos();
1266 
1267 } // mousePressEvent
1268 
1269 
1270 void QgsMapCanvas::mouseReleaseEvent( QMouseEvent * e )
1271 {
1272  //use middle mouse button for panning, map tools won't receive any events in that case
1273  if ( e->button() == Qt::MidButton )
1274  {
1275  mCanvasProperties->panSelectorDown = false;
1276  panActionEnd( mCanvasProperties->mouseLastXY );
1277  }
1278  else
1279  {
1280  // call handler of current map tool
1281  if ( mMapTool )
1282  {
1283  // right button was pressed in zoom tool? return to previous non zoom tool
1284  if ( e->button() == Qt::RightButton && mMapTool->isTransient() )
1285  {
1286  QgsDebugMsg( "Right click in map tool zoom or pan, last tool is " +
1287  QString( mLastNonZoomMapTool ? "not null." : "null." ) );
1288 
1289  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1290 
1291  // change to older non-zoom tool
1292  if ( mLastNonZoomMapTool
1293  && ( !mLastNonZoomMapTool->isEditTool() || ( vlayer && vlayer->isEditable() ) ) )
1294  {
1295  QgsMapTool* t = mLastNonZoomMapTool;
1296  mLastNonZoomMapTool = NULL;
1297  setMapTool( t );
1298  }
1299  return;
1300  }
1301  mMapTool->canvasReleaseEvent( e );
1302  }
1303  }
1304 
1305 
1306  mCanvasProperties->mouseButtonDown = false;
1307 
1308  if ( mCanvasProperties->panSelectorDown )
1309  return;
1310 
1311 } // mouseReleaseEvent
1312 
1313 void QgsMapCanvas::resizeEvent( QResizeEvent * e )
1314 {
1316  mResizeTimer->start( 500 );
1317 
1318  QSize lastSize = viewport()->size();
1319 
1320  mSettings.setOutputSize( lastSize );
1321  mMapRenderer->setOutputSize( lastSize, mSettings.outputDpi() );
1322 
1323  mScene->setSceneRect( QRectF( 0, 0, lastSize.width(), lastSize.height() ) );
1324 
1325  moveCanvasContents( true );
1326 
1327  // notify canvas items of change
1329 
1330  updateScale();
1331 
1332  //refresh();
1333 
1334  emit extentsChanged();
1335 }
1336 
1337 void QgsMapCanvas::paintEvent( QPaintEvent *e )
1338 {
1339  // no custom event handling anymore
1340 
1342 } // paintEvent
1343 
1345 {
1346  QList<QGraphicsItem*> list = mScene->items();
1347  QList<QGraphicsItem*>::iterator it = list.begin();
1348  while ( it != list.end() )
1349  {
1350  QgsMapCanvasItem* item = dynamic_cast<QgsMapCanvasItem *>( *it );
1351 
1352  if ( item )
1353  {
1354  item->updatePosition();
1355  }
1356 
1357  ++it;
1358  }
1359 }
1360 
1361 
1362 void QgsMapCanvas::wheelEvent( QWheelEvent *e )
1363 {
1364  // Zoom the map canvas in response to a mouse wheel event. Moving the
1365  // wheel forward (away) from the user zooms in
1366 
1367  QgsDebugMsg( "Wheel event delta " + QString::number( e->delta() ) );
1368 
1369  if ( mMapTool )
1370  {
1371  mMapTool->wheelEvent( e );
1372  }
1373 
1374  if ( QgsApplication::keyboardModifiers() )
1375  {
1376  // leave the wheel for map tools if any modifier pressed
1377  return;
1378  }
1379 
1380  switch ( mWheelAction )
1381  {
1382  case WheelZoom:
1383  // zoom without changing extent
1384  if ( e->delta() > 0 )
1385  zoomIn();
1386  else
1387  zoomOut();
1388  break;
1389 
1390  case WheelZoomAndRecenter:
1391  // zoom and don't change extent
1392  zoomWithCenter( e->x(), e->y(), e->delta() > 0 );
1393  break;
1394 
1396  {
1397  // zoom map to mouse cursor
1398  double scaleFactor = e->delta() > 0 ? 1 / mWheelZoomFactor : mWheelZoomFactor;
1399 
1400  QgsPoint oldCenter = center();
1401  QgsPoint mousePos( getCoordinateTransform()->toMapPoint( e->x(), e->y() ) );
1402  QgsPoint newCenter( mousePos.x() + (( oldCenter.x() - mousePos.x() ) * scaleFactor ),
1403  mousePos.y() + (( oldCenter.y() - mousePos.y() ) * scaleFactor ) );
1404 
1405  zoomByFactor( scaleFactor, &newCenter );
1406  break;
1407  }
1408 
1409  case WheelNothing:
1410  // well, nothing!
1411  break;
1412  }
1413 }
1414 
1415 void QgsMapCanvas::setWheelAction( WheelAction action, double factor )
1416 {
1417  mWheelAction = action;
1418  mWheelZoomFactor = factor;
1419 }
1420 
1422 {
1423  zoomByFactor( 1 / mWheelZoomFactor );
1424 }
1425 
1427 {
1428  zoomByFactor( mWheelZoomFactor );
1429 }
1430 
1431 void QgsMapCanvas::zoomScale( double newScale )
1432 {
1433  zoomByFactor( newScale / scale() );
1434 }
1435 
1436 void QgsMapCanvas::zoomWithCenter( int x, int y, bool zoomIn )
1437 {
1438  double scaleFactor = ( zoomIn ? 1 / mWheelZoomFactor : mWheelZoomFactor );
1439 
1440  // transform the mouse pos to map coordinates
1443  r.scale( scaleFactor, &center );
1444  setExtent( r );
1445  refresh();
1446 }
1447 
1448 void QgsMapCanvas::mouseMoveEvent( QMouseEvent * e )
1449 {
1450  mCanvasProperties->mouseLastXY = e->pos();
1451 
1452  if ( mCanvasProperties->panSelectorDown )
1453  {
1454  panAction( e );
1455  }
1456  else
1457  {
1458  // call handler of current map tool
1459  if ( mMapTool )
1460  {
1461  mMapTool->canvasMoveEvent( e );
1462  }
1463  }
1464 
1465  // show x y on status bar
1466  QPoint xy = e->pos();
1468  emit xyCoordinates( coord );
1469 } // mouseMoveEvent
1470 
1471 
1472 
1475 {
1476  if ( !tool )
1477  return;
1478 
1479  if ( mMapTool )
1480  {
1481  disconnect( mMapTool, SIGNAL( destroyed() ), this, SLOT( mapToolDestroyed() ) );
1482  mMapTool->deactivate();
1483  }
1484 
1485  if ( tool->isTransient() && mMapTool && !mMapTool->isTransient() )
1486  {
1487  // if zoom or pan tool will be active, save old tool
1488  // to bring it back on right click
1489  // (but only if it wasn't also zoom or pan tool)
1490  mLastNonZoomMapTool = mMapTool;
1491  }
1492  else
1493  {
1494  mLastNonZoomMapTool = NULL;
1495  }
1496 
1497  QgsMapTool* oldTool = mMapTool;
1498 
1499  // set new map tool and activate it
1500  mMapTool = tool;
1501  if ( mMapTool )
1502  {
1503  connect( mMapTool, SIGNAL( destroyed() ), this, SLOT( mapToolDestroyed() ) );
1504  mMapTool->activate();
1505  }
1506 
1507  emit mapToolSet( mMapTool );
1508  emit mapToolSet( mMapTool, oldTool );
1509 } // setMapTool
1510 
1512 {
1513  if ( mMapTool && mMapTool == tool )
1514  {
1515  mMapTool->deactivate();
1516  mMapTool = NULL;
1517  emit mapToolSet( NULL );
1518  emit mapToolSet( NULL, mMapTool );
1519  setCursor( Qt::ArrowCursor );
1520  }
1521 
1522  if ( mLastNonZoomMapTool && mLastNonZoomMapTool == tool )
1523  {
1524  mLastNonZoomMapTool = NULL;
1525  }
1526 }
1527 
1529 void QgsMapCanvas::setCanvasColor( const QColor & theColor )
1530 {
1531  // background of map's pixmap
1532  mSettings.setBackgroundColor( theColor );
1533 
1534  // background of the QGraphicsView
1535  QBrush bgBrush( theColor );
1536  setBackgroundBrush( bgBrush );
1537 #if 0
1538  QPalette palette;
1539  palette.setColor( backgroundRole(), theColor );
1540  setPalette( palette );
1541 #endif
1542 
1543  // background of QGraphicsScene
1544  mScene->setBackgroundBrush( bgBrush );
1545 } // setBackgroundColor
1546 
1548 {
1549  return mScene->backgroundBrush().color();
1550 }
1551 
1552 void QgsMapCanvas::setSelectionColor( const QColor& color )
1553 {
1554  mSettings.setSelectionColor( color );
1555 }
1556 
1558 {
1559  return mapSettings().layers().size();
1560 } // layerCount
1561 
1562 
1563 QList<QgsMapLayer*> QgsMapCanvas::layers() const
1564 {
1565  QList<QgsMapLayer*> lst;
1566  foreach ( QString layerID, mapSettings().layers() )
1567  {
1569  if ( layer )
1570  lst.append( layer );
1571  }
1572  return lst;
1573 }
1574 
1575 
1577 {
1578  // called when a layer has changed visibility setting
1579 
1580  refresh();
1581 
1582 } // layerStateChange
1583 
1585 {
1586  // called when a layer's CRS has been changed
1587  QObject *theSender = sender();
1588  QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( theSender );
1589  QString destAuthId = mSettings.destinationCrs().authid();
1590  getDatumTransformInfo( layer, layer->crs().authid(), destAuthId );
1591 
1592 } // layerCrsChange
1593 
1594 
1595 void QgsMapCanvas::freeze( bool frz )
1596 {
1597  mFrozen = frz;
1598 } // freeze
1599 
1601 {
1602  return mFrozen;
1603 } // freeze
1604 
1605 
1607 {
1609  return mMap->paintDevice();
1611 }
1612 
1614 {
1615  return mapSettings().mapUnitsPerPixel();
1616 } // mapUnitsPerPixel
1617 
1618 
1620 {
1621  if ( mSettings.mapUnits() == u )
1622  return;
1623 
1624  QgsDebugMsg( "Setting map units to " + QString::number( static_cast<int>( u ) ) );
1625  mSettings.setMapUnits( u );
1626 
1627  updateScale();
1628 
1629  refresh(); // this will force the scale bar to be updated
1630 
1631  emit mapUnitsChanged();
1632 }
1633 
1634 
1636 {
1637  return mapSettings().mapUnits();
1638 }
1639 
1640 
1641 void QgsMapCanvas::setRenderFlag( bool theFlag )
1642 {
1643  mRenderFlag = theFlag;
1644 
1645  if ( mRenderFlag )
1646  {
1647  refresh();
1648  }
1649  else
1650  stopRendering();
1651 }
1652 
1653 #if 0
1654 void QgsMapCanvas::connectNotify( const char * signal )
1655 {
1656  Q_UNUSED( signal );
1657  QgsDebugMsg( "QgsMapCanvas connected to " + QString( signal ) );
1658 } //connectNotify
1659 #endif
1660 
1662 {
1663  if ( !mSettings.hasCrsTransformEnabled() )
1664  return;
1665 
1666  QString destAuthId = mSettings.destinationCrs().authid();
1667  foreach ( QString layerID, mSettings.layers() )
1668  {
1670  if ( !layer )
1671  continue;
1672 
1673  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
1674  if ( vl && vl->geometryType() == QGis::NoGeometry )
1675  continue;
1676 
1677  // if there are more options, ask the user which datum transform to use
1678  if ( !mSettings.datumTransformStore().hasEntryForLayer( layer ) )
1679  getDatumTransformInfo( layer, layer->crs().authid(), destAuthId );
1680  }
1681 }
1682 
1683 
1684 
1686 {
1687  return mMapTool;
1688 }
1689 
1690 void QgsMapCanvas::panActionEnd( QPoint releasePoint )
1691 {
1692  // move map image and other items to standard position
1693  moveCanvasContents( true ); // true means reset
1694 
1695  // use start and end box points to calculate the extent
1696  QgsPoint start = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
1697  QgsPoint end = getCoordinateTransform()->toMapCoordinates( releasePoint );
1698 
1699  // modify the center
1700  double dx = end.x() - start.x();
1701  double dy = end.y() - start.y();
1702  QgsPoint c = center();
1703  c.set( c.x() - dx, c.y() - dy );
1704  setCenter( c );
1705 
1706  refresh();
1707 }
1708 
1709 void QgsMapCanvas::panAction( QMouseEvent * e )
1710 {
1711  Q_UNUSED( e );
1712 
1713  // move all map canvas items
1715 }
1716 
1718 {
1719  QPoint pnt( 0, 0 );
1720  if ( !reset )
1721  pnt += mCanvasProperties->mouseLastXY - mCanvasProperties->rubberStartPoint;
1722 
1723  setSceneRect( -pnt.x(), -pnt.y(), viewport()->size().width(), viewport()->size().height() );
1724 }
1725 
1727 {
1728  Q_UNUSED( mapLayer );
1729 }
1730 
1732 {
1733  return mCanvasProperties->mouseLastXY;
1734 }
1735 
1736 void QgsMapCanvas::setPreviewModeEnabled( bool previewEnabled )
1737 {
1738  if ( !mPreviewEffect )
1739  {
1740  return;
1741  }
1742 
1743  mPreviewEffect->setEnabled( previewEnabled );
1744 }
1745 
1747 {
1748  if ( !mPreviewEffect )
1749  {
1750  return false;
1751  }
1752 
1753  return mPreviewEffect->isEnabled();
1754 }
1755 
1757 {
1758  if ( !mPreviewEffect )
1759  {
1760  return;
1761  }
1762 
1763  mPreviewEffect->setMode( mode );
1764 }
1765 
1767 {
1768  if ( !mPreviewEffect )
1769  {
1771  }
1772 
1773  return mPreviewEffect->mode();
1774 }
1775 
1777 {
1778  if ( !mSnappingUtils )
1779  {
1780  // associate a dummy instance, but better than null pointer
1781  QgsMapCanvas* c = const_cast<QgsMapCanvas*>( this );
1782  c->mSnappingUtils = new QgsMapCanvasSnappingUtils( c, c );
1783  }
1784  return mSnappingUtils;
1785 }
1786 
1788 {
1789  mSnappingUtils = utils;
1790 }
1791 
1792 void QgsMapCanvas::readProject( const QDomDocument & doc )
1793 {
1794  QDomNodeList nodes = doc.elementsByTagName( "mapcanvas" );
1795  if ( nodes.count() )
1796  {
1797  QDomNode node = nodes.item( 0 );
1798 
1799  QgsMapSettings tmpSettings;
1800  tmpSettings.readXML( node );
1801  setMapUnits( tmpSettings.mapUnits() );
1803  setDestinationCrs( tmpSettings.destinationCrs() );
1804  setExtent( tmpSettings.extent() );
1805  setRotation( tmpSettings.rotation() );
1806  mSettings.datumTransformStore() = tmpSettings.datumTransformStore();
1807 
1808  clearExtentHistory(); // clear the extent history on project load
1809  }
1810  else
1811  {
1812  QgsDebugMsg( "Couldn't read mapcanvas information from project" );
1813  }
1814 }
1815 
1816 void QgsMapCanvas::writeProject( QDomDocument & doc )
1817 {
1818  // create node "mapcanvas" and call mMapRenderer->writeXML()
1819 
1820  QDomNodeList nl = doc.elementsByTagName( "qgis" );
1821  if ( !nl.count() )
1822  {
1823  QgsDebugMsg( "Unable to find qgis element in project file" );
1824  return;
1825  }
1826  QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element ok
1827 
1828  QDomElement mapcanvasNode = doc.createElement( "mapcanvas" );
1829  qgisNode.appendChild( mapcanvasNode );
1830 
1831  mSettings.writeXML( mapcanvasNode, doc );
1832  // TODO: store only units, extent, projections, dest CRS
1833 }
1834 
1836 void QgsMapCanvas::getDatumTransformInfo( const QgsMapLayer* ml, const QString& srcAuthId, const QString& destAuthId )
1837 {
1838  if ( !ml )
1839  {
1840  return;
1841  }
1842 
1843  //check if default datum transformation available
1844  QSettings s;
1845  QString settingsString = "/Projections/" + srcAuthId + "//" + destAuthId;
1846  QVariant defaultSrcTransform = s.value( settingsString + "_srcTransform" );
1847  QVariant defaultDestTransform = s.value( settingsString + "_destTransform" );
1848  if ( defaultSrcTransform.isValid() && defaultDestTransform.isValid() )
1849  {
1850  mSettings.datumTransformStore().addEntry( ml->id(), srcAuthId, destAuthId, defaultSrcTransform.toInt(), defaultDestTransform.toInt() );
1851  mMapRenderer->addLayerCoordinateTransform( ml->id(), srcAuthId, destAuthId, defaultSrcTransform.toInt(), defaultDestTransform.toInt() );
1852  return;
1853  }
1854 
1855  const QgsCoordinateReferenceSystem& srcCRS = QgsCRSCache::instance()->crsByAuthId( srcAuthId );
1856  const QgsCoordinateReferenceSystem& destCRS = QgsCRSCache::instance()->crsByAuthId( destAuthId );
1857 
1858  if ( !s.value( "/Projections/showDatumTransformDialog", false ).toBool() )
1859  {
1860  // just use the default transform
1861  mSettings.datumTransformStore().addEntry( ml->id(), srcAuthId, destAuthId, -1, -1 );
1862  mMapRenderer->addLayerCoordinateTransform( ml->id(), srcAuthId, destAuthId, -1, -1 );
1863  return;
1864  }
1865 
1866  //get list of datum transforms
1867  QList< QList< int > > dt = QgsCoordinateTransform::datumTransformations( srcCRS, destCRS );
1868  if ( dt.size() < 2 )
1869  {
1870  return;
1871  }
1872 
1873  //if several possibilities: present dialog
1874  QgsDatumTransformDialog d( ml->name(), dt );
1875  d.setDatumTransformInfo( srcCRS.authid(), destCRS.authid() );
1876  if ( d.exec() == QDialog::Accepted )
1877  {
1878  int srcTransform = -1;
1879  int destTransform = -1;
1880  QList<int> t = d.selectedDatumTransform();
1881  if ( t.size() > 0 )
1882  {
1883  srcTransform = t.at( 0 );
1884  }
1885  if ( t.size() > 1 )
1886  {
1887  destTransform = t.at( 1 );
1888  }
1889  mSettings.datumTransformStore().addEntry( ml->id(), srcAuthId, destAuthId, srcTransform, destTransform );
1890  mMapRenderer->addLayerCoordinateTransform( ml->id(), srcAuthId, destAuthId, srcTransform, destTransform );
1891  if ( d.rememberSelection() )
1892  {
1893  s.setValue( settingsString + "_srcTransform", srcTransform );
1894  s.setValue( settingsString + "_destTransform", destTransform );
1895  }
1896  }
1897  else
1898  {
1899  mSettings.datumTransformStore().addEntry( ml->id(), srcAuthId, destAuthId, -1, -1 );
1900  mMapRenderer->addLayerCoordinateTransform( ml->id(), srcAuthId, destAuthId, -1, -1 );
1901  }
1902 }
1903 
1904 void QgsMapCanvas::zoomByFactor( double scaleFactor, const QgsPoint* center )
1905 {
1907  r.scale( scaleFactor, center );
1908  setExtent( r );
1909  refresh();
1910 }
1911 
1913 {
1914  // Find out which layer it was that sent the signal.
1915  QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
1916  emit selectionChanged( layer );
1917  refresh();
1918 }
1919 
1920 void QgsMapCanvas::dragEnterEvent( QDragEnterEvent * e )
1921 {
1922  // By default graphics view delegates the drag events to graphics items.
1923  // But we do not want that and by ignoring the drag enter we let the
1924  // parent (e.g. QgisApp) to handle drops of map layers etc.
1925  e->ignore();
1926 }
1927 
1928 void QgsMapCanvas::mapToolDestroyed()
1929 {
1930  QgsDebugMsg( "maptool destroyed" );
1931  mMapTool = 0;
1932 }
1933 
1934 #ifdef HAVE_TOUCH
1935 bool QgsMapCanvas::event( QEvent * e )
1936 {
1937  bool done = false;
1938  if ( e->type() == QEvent::Gesture )
1939  {
1940  // call handler of current map tool
1941  if ( mMapTool )
1942  {
1943  done = mMapTool->gestureEvent( static_cast<QGestureEvent*>( e ) );
1944  }
1945  }
1946  else
1947  {
1948  // pass other events to base class
1949  done = QGraphicsView::event( e );
1950  }
1951  return done;
1952 }
1953 #endif
1954 
1956 {
1957  return QSettings().value( "/qgis/canvasRotation", true ).toBool();
1958 }
1959 
1960 void QgsMapCanvas::enableRotation( bool enable )
1961 {
1962  QSettings().setValue( "/qgis/canvasRotation", enable );
1963 }