QGIS API Documentation  2.2.0-Valmiera
 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"
47 #include "qgsmaplayer.h"
48 #include "qgsmaplayerregistry.h"
49 #include "qgsmaptoolpan.h"
50 #include "qgsmaptoolzoom.h"
51 #include "qgsmaptopixel.h"
52 #include "qgsmapoverviewcanvas.h"
53 #include "qgsmaprenderer.h"
54 #include "qgsmessagelog.h"
55 #include "qgsmessageviewer.h"
56 #include "qgsproject.h"
57 #include "qgsrubberband.h"
58 #include "qgsvectorlayer.h"
59 #include <math.h>
60 
63 {
64  public:
65 
67 
70 
72  QPoint mouseLastXY;
73 
76 
79 
80 };
81 
82 
83 
84 QgsMapCanvas::QgsMapCanvas( QWidget * parent, const char *name )
85  : QGraphicsView( parent )
86  , mCanvasProperties( new CanvasProperties )
87  , mNewSize( QSize() )
88  , mPainting( false )
89  , mAntiAliasing( false )
90 {
91  setObjectName( name );
92  mScene = new QGraphicsScene();
93  setScene( mScene );
94  setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
95  setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
96  mLastExtentIndex = -1;
97  mCurrentLayer = NULL;
98  mMapOverview = NULL;
99  mMapTool = NULL;
100  mLastNonZoomMapTool = NULL;
101 
102  mBackbufferEnabled = true;
103  mDrawing = false;
104  mFrozen = false;
105  mDirty = true;
106 
108 
109  // by default, the canvas is rendered
110  mRenderFlag = true;
111 
112  setMouseTracking( true );
113  setFocusPolicy( Qt::StrongFocus );
114 
116  connect( mMapRenderer, SIGNAL( datumTransformInfoRequested( const QgsMapLayer*, const QString&, const QString& ) ),
117  this, SLOT( getDatumTransformInfo( const QgsMapLayer*, const QString& , const QString& ) ) );
118 
119  mResizeTimer = new QTimer( this );
120  connect( mResizeTimer, SIGNAL( timeout() ), this, SLOT( refresh() ) );
121 
122  // create map canvas item which will show the map
123  mMap = new QgsMapCanvasMap( this );
124  mScene->addItem( mMap );
125  mScene->update(); // porting??
126 
127  moveCanvasContents( true );
128 
129  connect( mMapRenderer, SIGNAL( drawError( QgsMapLayer* ) ), this, SLOT( showError( QgsMapLayer* ) ) );
130  connect( mMapRenderer, SIGNAL( hasCrsTransformEnabledChanged( bool ) ), this, SLOT( crsTransformEnabled( bool ) ) );
131 
133 
134  // project handling
135  connect( QgsProject::instance(), SIGNAL( readProject( const QDomDocument & ) ),
136  this, SLOT( readProject( const QDomDocument & ) ) );
137  connect( QgsProject::instance(), SIGNAL( writeProject( QDomDocument & ) ),
138  this, SLOT( writeProject( QDomDocument & ) ) );
139  mMap->resize( size() );
140 
141 #ifdef Q_OS_WIN
142  // Enable touch event on Windows.
143  // Qt on Windows needs to be told it can take touch events or else it ignores them.
144  grabGesture( Qt::PinchGesture );
145  viewport()->setAttribute( Qt::WA_AcceptTouchEvents );
146 #endif
147 } // QgsMapCanvas ctor
148 
149 
151 {
152  if ( mMapTool )
153  {
154  mMapTool->deactivate();
155  mMapTool = NULL;
156  }
157  mLastNonZoomMapTool = NULL;
158 
159  // delete canvas items prior to deleteing the canvas
160  // because they might try to update canvas when it's
161  // already being destructed, ends with segfault
162  QList<QGraphicsItem*> list = mScene->items();
163  QList<QGraphicsItem*>::iterator it = list.begin();
164  while ( it != list.end() )
165  {
166  QGraphicsItem* item = *it;
167  delete item;
168  ++it;
169  }
170 
171  mScene->deleteLater(); // crashes in python tests on windows
172 
173  delete mMapRenderer;
174  // mCanvasProperties auto-deleted via std::auto_ptr
175  // CanvasProperties struct has its own dtor for freeing resources
176 
177 } // dtor
178 
180 {
181  mAntiAliasing = theFlag;
182  mMap->enableAntiAliasing( theFlag );
183  if ( mMapOverview )
184  mMapOverview->enableAntiAliasing( theFlag );
185 } // anti aliasing
186 
187 void QgsMapCanvas::useImageToRender( bool theFlag )
188 {
189  mMap->useImageToRender( theFlag );
190  refresh(); // redraw the map on change - prevents black map view
191 }
192 
194 {
195  return mMap;
196 }
197 
199 {
200  return mMapRenderer;
201 }
202 
203 
205 {
206  QStringList& layers = mMapRenderer->layerSet();
207  if ( index >= 0 && index < ( int ) layers.size() )
208  return QgsMapLayerRegistry::instance()->mapLayer( layers[index] );
209  else
210  return NULL;
211 }
212 
213 
215 {
217 }
218 
220 {
221  return mMapRenderer->scale();
222 } // scale
223 
224 void QgsMapCanvas::setDirty( bool dirty )
225 {
226  mDirty = dirty;
227 }
228 
230 {
231  return mDirty;
232 }
233 
234 
235 
237 {
238  return mDrawing;
239 } // isDrawing
240 
241 
242 // return the current coordinate transform based on the extents and
243 // device size
245 {
247 }
248 
249 void QgsMapCanvas::setLayerSet( QList<QgsMapCanvasLayer> &layers )
250 {
251  if ( mDrawing )
252  {
253  QgsDebugMsg( "NOT updating layer set while drawing" );
254  return;
255  }
256 
257  // create layer set
258  QStringList layerSet, layerSetOverview;
259 
260  int i;
261  for ( i = 0; i < layers.size(); i++ )
262  {
263  QgsMapCanvasLayer &lyr = layers[i];
264  if ( !lyr.layer() )
265  {
266  continue;
267  }
268 
269  if ( lyr.isVisible() )
270  {
271  layerSet.push_back( lyr.layer()->id() );
272  }
273 
274  if ( lyr.isInOverview() )
275  {
276  layerSetOverview.push_back( lyr.layer()->id() );
277  }
278  }
279 
280  QStringList& layerSetOld = mMapRenderer->layerSet();
281 
282  bool layerSetChanged = layerSetOld != layerSet;
283 
284  // update only if needed
285  if ( layerSetChanged )
286  {
287  QgsDebugMsg( "Layers changed to: " + layerSet.join( ", " ) );
288 
289  for ( i = 0; i < layerCount(); i++ )
290  {
291  // Add check if vector layer when disconnecting from selectionChanged slot
292  // Ticket #811 - racicot
294  disconnect( currentLayer, SIGNAL( repaintRequested() ), this, SLOT( refresh() ) );
295  disconnect( currentLayer, SIGNAL( screenUpdateRequested() ), this, SLOT( updateMap() ) );
296  QgsVectorLayer *isVectLyr = qobject_cast<QgsVectorLayer *>( currentLayer );
297  if ( isVectLyr )
298  {
299  disconnect( currentLayer, SIGNAL( selectionChanged() ), this, SLOT( selectionChangedSlot() ) );
300  }
301  }
302 
303  mMapRenderer->setLayerSet( layerSet );
304 
305  for ( i = 0; i < layerCount(); i++ )
306  {
307  // Add check if vector layer when connecting to selectionChanged slot
308  // Ticket #811 - racicot
310  connect( currentLayer, SIGNAL( repaintRequested() ), this, SLOT( refresh() ) );
311  connect( currentLayer, SIGNAL( screenUpdateRequested() ), this, SLOT( updateMap() ) );
312  QgsVectorLayer *isVectLyr = qobject_cast<QgsVectorLayer *>( currentLayer );
313  if ( isVectLyr )
314  {
315  connect( currentLayer, SIGNAL( selectionChanged() ), this, SLOT( selectionChangedSlot() ) );
316  }
317  }
318 
319  QgsDebugMsg( "Layers have changed, refreshing" );
320  emit layersChanged();
321 
322  refresh();
323  }
324 
325  if ( mMapOverview )
326  {
327  QStringList& layerSetOvOld = mMapOverview->layerSet();
328  if ( layerSetOvOld != layerSetOverview )
329  {
330  mMapOverview->setLayerSet( layerSetOverview );
331  }
332 
333  // refresh overview maplayers even if layer set is the same
334  // because full extent might have changed
335  updateOverview();
336  }
337 } // setLayerSet
338 
340 {
341  if ( mMapOverview )
342  {
343  // disconnect old map overview if exists
344  disconnect( mMapRenderer, SIGNAL( hasCrsTransformEnabledChanged( bool ) ),
345  mMapOverview, SLOT( hasCrsTransformEnabled( bool ) ) );
346  disconnect( mMapRenderer, SIGNAL( destinationSrsChanged() ),
347  mMapOverview, SLOT( destinationSrsChanged() ) );
348 
349  // map overview is not owned by map canvas so don't delete it...
350  }
351 
352  mMapOverview = overview;
353 
354  if ( overview )
355  {
356  // connect to the map render to copy its projection settings
357  connect( mMapRenderer, SIGNAL( hasCrsTransformEnabledChanged( bool ) ),
358  overview, SLOT( hasCrsTransformEnabled( bool ) ) );
359  connect( mMapRenderer, SIGNAL( destinationSrsChanged() ),
360  overview, SLOT( destinationSrsChanged() ) );
361  }
362 }
363 
364 
366 {
367  // redraw overview
368  if ( mMapOverview )
369  {
371  }
372 }
373 
374 
376 {
377  return mCurrentLayer;
378 }
379 
380 
382 {
383  mResizeTimer->stop();
384 
385  // we can't draw again if already drawing...
386  if ( mDrawing )
387  return;
388 
389  QSettings settings;
390  bool logRefresh = settings.value( "/Map/logCanvasRefreshEvent", false ).toBool();
391  QTime t;
392  if ( logRefresh )
393  {
394  t.start();
395  }
396 
397 #ifdef Q_WS_X11
398  bool enableBackbufferSetting = settings.value( "/Map/enableBackbuffer", 1 ).toBool();
399 #endif
400 
401 #ifdef Q_WS_X11
402 #ifndef ANDROID
403  // disable the update that leads to the resize crash on X11 systems
404  if ( viewport() )
405  {
406  if ( enableBackbufferSetting != mBackbufferEnabled )
407  {
408  qDebug() << "Enable back buffering: " << enableBackbufferSetting;
409  if ( enableBackbufferSetting )
410  {
411  viewport()->setAttribute( Qt::WA_PaintOnScreen, false );
412  }
413  else
414  {
415  viewport()->setAttribute( Qt::WA_PaintOnScreen, true );
416  }
417  mBackbufferEnabled = enableBackbufferSetting;
418  }
419  }
420 #endif // ANDROID
421 #endif // Q_WS_X11
422 
423  mDrawing = true;
424 
425  //update $map variable to canvas
426  QgsExpression::setSpecialColumn( "$map", tr( "canvas" ) );
427 
428  if ( mRenderFlag && !mFrozen )
429  {
430  clear();
431 
432  // Tell the user we're going to be a while
433  QApplication::setOverrideCursor( Qt::WaitCursor );
434 
435  emit renderStarting();
436 
437  mMap->render();
438 
439  mDirty = false;
440 
441  // notify any listeners that rendering is complete
442  QPainter p;
443  p.begin( &mMap->paintDevice() );
444  emit renderComplete( &p );
445  p.end();
446 
447  // notifies current map tool
448  if ( mMapTool )
450 
451  // Tell the user we've finished going to be a while
452  QApplication::restoreOverrideCursor();
453  }
454 
455  mDrawing = false;
456 
457  // Done refreshing
458  emit mapCanvasRefreshed();
459 
460  if ( logRefresh )
461  {
462  QString logMsg = tr( "Canvas refresh: %1 ms" ).arg( t.elapsed() );
463  QObject* senderObj = QObject::sender();
464  if ( senderObj )
465  {
466  logMsg += tr( ", sender '%1'" ).arg( senderObj->metaObject()->className() );
467  }
468  QgsMessageLog::logMessage( logMsg, tr( "Rendering" ) );
469  }
470 
471 } // refresh
472 
474 {
475  if ( mMap )
476  {
477  mMap->updateContents();
478  }
479 }
480 
481 //the format defaults to "PNG" if not specified
482 void QgsMapCanvas::saveAsImage( QString theFileName, QPixmap * theQPixmap, QString theFormat )
483 {
484  //
485  //check if the optional QPaintDevice was supplied
486  //
487  if ( theQPixmap != NULL )
488  {
489  // render
490  QPainter painter;
491  painter.begin( theQPixmap );
492  mMapRenderer->render( &painter );
493  emit renderComplete( &painter );
494  painter.end();
495 
496  theQPixmap->save( theFileName, theFormat.toLocal8Bit().data() );
497  }
498  else //use the map view
499  {
500  QPixmap *pixmap = dynamic_cast<QPixmap *>( &mMap->paintDevice() );
501  if ( !pixmap )
502  return;
503 
504  pixmap->save( theFileName, theFormat.toLocal8Bit().data() );
505  }
506  //create a world file to go with the image...
507  QgsRectangle myRect = mMapRenderer->extent();
508  QString myHeader;
509  // note: use 17 places of precision for all numbers output
510  //Pixel XDim
511  myHeader += qgsDoubleToString( mapUnitsPerPixel() ) + "\r\n";
512  //Rotation on y axis - hard coded
513  myHeader += "0 \r\n";
514  //Rotation on x axis - hard coded
515  myHeader += "0 \r\n";
516  //Pixel YDim - almost always negative - see
517  //http://en.wikipedia.org/wiki/World_file#cite_note-2
518  myHeader += "-" + qgsDoubleToString( mapUnitsPerPixel() ) + "\r\n";
519  //Origin X (center of top left cell)
520  myHeader += qgsDoubleToString( myRect.xMinimum() + ( mapUnitsPerPixel() / 2 ) ) + "\r\n";
521  //Origin Y (center of top left cell)
522  myHeader += qgsDoubleToString( myRect.yMaximum() - ( mapUnitsPerPixel() / 2 ) ) + "\r\n";
523  QFileInfo myInfo = QFileInfo( theFileName );
524  // allow dotted names
525  QString myWorldFileName = myInfo.absolutePath() + "/" + myInfo.completeBaseName() + "." + theFormat + "w";
526  QFile myWorldFile( myWorldFileName );
527  if ( !myWorldFile.open( QIODevice::WriteOnly ) ) //don't use QIODevice::Text
528  {
529  return;
530  }
531  QTextStream myStream( &myWorldFile );
532  myStream << myHeader;
533 } // saveAsImage
534 
535 
536 
538 {
539  return mMapRenderer->extent();
540 } // extent
541 
543 {
544  return mMapRenderer->fullExtent();
545 } // extent
546 
548 {
549  // projection settings have changed
550 
551  QgsDebugMsg( "updating full extent" );
552 
554  refresh();
555 }
556 
558 {
559  if ( mDrawing )
560  {
561  return;
562  }
563 
564  QgsRectangle current = extent();
565 
566  if ( r.isEmpty() )
567  {
568  QgsDebugMsg( "Empty extent - keeping old extent with new center!" );
569  QgsRectangle e( QgsPoint( r.center().x() - current.width() / 2.0, r.center().y() - current.height() / 2.0 ),
570  QgsPoint( r.center().x() + current.width() / 2.0, r.center().y() + current.height() / 2.0 ) );
571  mMapRenderer->setExtent( e );
572  }
573  else
574  {
575  mMapRenderer->setExtent( r );
576  }
577  emit extentsChanged();
578  updateScale();
579  if ( mMapOverview )
581  if ( mLastExtent.size() > 20 )
582  mLastExtent.removeAt( 0 );
583 
584  //clear all extent items after current index
585  for ( int i = mLastExtent.size() - 1; i > mLastExtentIndex; i-- )
586  {
587  mLastExtent.removeAt( i );
588  }
589 
590  mLastExtent.append( extent() ) ;
591 
592  // adjust history to no more than 20
593  if ( mLastExtent.size() > 20 )
594  {
595  mLastExtent.removeAt( 0 );
596  }
597 
598  // the last item is the current extent
599  mLastExtentIndex = mLastExtent.size() - 1;
600 
601  // update controls' enabled state
602  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
603  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
604  // notify canvas items of change
606 
607 } // setExtent
608 
609 
611 {
612  double scale = mMapRenderer->scale();
613 
614  emit scaleChanged( scale );
615 }
616 
617 
619 {
620  // Indicate to the next paint event that we need to rebuild the canvas contents
621  setDirty( true );
622 
623 } // clear
624 
625 
626 
628 {
629  if ( mDrawing )
630  {
631  return;
632  }
633 
635  // If the full extent is an empty set, don't do the zoom
636  if ( !extent.isEmpty() )
637  {
638  // Add a 5% margin around the full extent
639  extent.scale( 1.05 );
640  setExtent( extent );
641  }
642  refresh();
643 
644 } // zoomToFullExtent
645 
646 
647 
649 {
650  if ( mDrawing )
651  {
652  return;
653  }
654 
655  if ( mLastExtentIndex > 0 )
656  {
659  emit extentsChanged();
660  updateScale();
661  if ( mMapOverview )
663  refresh();
664  // update controls' enabled state
665  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
666  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
667  // notify canvas items of change
669  }
670 
671 } // zoomToPreviousExtent
672 
674 {
675  if ( mDrawing )
676  {
677  return;
678  }
679  if ( mLastExtentIndex < mLastExtent.size() - 1 )
680  {
683  emit extentsChanged();
684  updateScale();
685  if ( mMapOverview )
687  refresh();
688  // update controls' enabled state
689  emit zoomLastStatusChanged( mLastExtentIndex > 0 );
690  emit zoomNextStatusChanged( mLastExtentIndex < mLastExtent.size() - 1 );
691  // notify canvas items of change
693  }
694 }// zoomToNextExtent
695 
697 {
698  mLastExtent.clear(); // clear the zoom history list
699  mLastExtent.append( extent() ) ; // set the current extent in the list
700  mLastExtentIndex = mLastExtent.size() - 1;
701  // update controls' enabled state
704 }// clearExtentHistory
705 
706 
708 {
710 }
711 
713 {
714  // We assume that if the map units have changed, the changed value
715  // will be accessible from QgsMapRenderer
716 
717  // And then force a redraw of the scale number in the status bar
718  updateScale();
719 
720  // And then redraw the map to force the scale bar to update
721  // itself. This is less than ideal as the entire map gets redrawn
722  // just to get the scale bar to redraw itself. If we ask the scale
723  // bar to redraw itself without redrawing the map, the existing
724  // scale bar is not removed, and we end up with two scale bars in
725  // the same location. This can perhaps be fixed when/if the scale
726  // bar is done as a transparent layer on top of the map canvas.
727  refresh();
728 }
729 
731 {
732  if ( mDrawing )
733  {
734  return;
735  }
736 
737  if ( layer == NULL )
738  {
739  // use current layer by default
740  layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
741  }
742 
743  if ( layer == NULL )
744  {
745  return;
746  }
747 
748  if ( layer->selectedFeatureCount() == 0 )
749  {
750  return;
751  }
752 
754 
755  // no selected features, only one selected point feature
756  //or two point features with the same x- or y-coordinates
757  if ( rect.isEmpty() )
758  {
759  // zoom in
760  QgsPoint c = rect.center();
761  rect = extent();
762  rect.scale( 1.0, &c );
763  }
764  //zoom to an area
765  else
766  {
767  // Expand rect to give a bit of space around the selected
768  // objects so as to keep them clear of the map boundaries
769  // The same 5% should apply to all margins.
770  rect.scale( 1.05 );
771  }
772 
773  setExtent( rect );
774  refresh();
775 } // zoomToSelected
776 
778 {
779  if ( mDrawing )
780  {
781  return;
782  }
783 
784  if ( layer == NULL )
785  {
786  // use current layer by default
787  layer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
788  }
789 
790  if ( layer == NULL )
791  {
792  return;
793  }
794 
795  if ( layer->selectedFeatureCount() == 0 )
796  {
797  return;
798  }
799 
801  setExtent( QgsRectangle( rect.center(), rect.center() ) );
802  refresh();
803 } // panToSelected
804 
805 void QgsMapCanvas::keyPressEvent( QKeyEvent * e )
806 {
807 
808  if ( mDrawing )
809  {
810  e->ignore();
811  }
812 
813  if ( mCanvasProperties->mouseButtonDown || mCanvasProperties->panSelectorDown )
814  {
815  emit keyPressed( e );
816  return;
817  }
818 
819  QPainter paint;
820  QPen pen( Qt::gray );
821  QgsPoint ll, ur;
822 
823  if ( ! mCanvasProperties->mouseButtonDown )
824  {
825  // Don't want to interfer with mouse events
826 
827  QgsRectangle currentExtent = mMapRenderer->extent();
828  double dx = qAbs(( currentExtent.xMaximum() - currentExtent.xMinimum() ) / 4 );
829  double dy = qAbs(( currentExtent.yMaximum() - currentExtent.yMinimum() ) / 4 );
830 
831  switch ( e->key() )
832  {
833  case Qt::Key_Left:
834  QgsDebugMsg( "Pan left" );
835 
836  currentExtent.setXMinimum( currentExtent.xMinimum() - dx );
837  currentExtent.setXMaximum( currentExtent.xMaximum() - dx );
838  setExtent( currentExtent );
839  refresh();
840  break;
841 
842  case Qt::Key_Right:
843  QgsDebugMsg( "Pan right" );
844 
845  currentExtent.setXMinimum( currentExtent.xMinimum() + dx );
846  currentExtent.setXMaximum( currentExtent.xMaximum() + dx );
847  setExtent( currentExtent );
848  refresh();
849  break;
850 
851  case Qt::Key_Up:
852  QgsDebugMsg( "Pan up" );
853 
854  currentExtent.setYMaximum( currentExtent.yMaximum() + dy );
855  currentExtent.setYMinimum( currentExtent.yMinimum() + dy );
856  setExtent( currentExtent );
857  refresh();
858  break;
859 
860  case Qt::Key_Down:
861  QgsDebugMsg( "Pan down" );
862 
863  currentExtent.setYMaximum( currentExtent.yMaximum() - dy );
864  currentExtent.setYMinimum( currentExtent.yMinimum() - dy );
865  setExtent( currentExtent );
866  refresh();
867  break;
868 
869 
870 
871  case Qt::Key_Space:
872  QgsDebugMsg( "Pressing pan selector" );
873 
874  //mCanvasProperties->dragging = true;
875  if ( ! e->isAutoRepeat() )
876  {
877  mCanvasProperties->panSelectorDown = true;
878  mCanvasProperties->rubberStartPoint = mCanvasProperties->mouseLastXY;
879  }
880  break;
881 
882  case Qt::Key_PageUp:
883  QgsDebugMsg( "Zoom in" );
884  zoomIn();
885  break;
886 
887  case Qt::Key_PageDown:
888  QgsDebugMsg( "Zoom out" );
889  zoomOut();
890  break;
891 
892  default:
893  // Pass it on
894  if ( mMapTool )
895  {
896  mMapTool->keyPressEvent( e );
897  }
898  else e->ignore();
899 
900  QgsDebugMsg( "Ignoring key: " + QString::number( e->key() ) );
901  }
902  }
903 
904  emit keyPressed( e );
905 
906 } //keyPressEvent()
907 
908 void QgsMapCanvas::keyReleaseEvent( QKeyEvent * e )
909 {
910  QgsDebugMsg( "keyRelease event" );
911 
912  if ( mDrawing )
913  {
914  return;
915  }
916 
917  switch ( e->key() )
918  {
919  case Qt::Key_Space:
920  if ( !e->isAutoRepeat() && mCanvasProperties->panSelectorDown )
921  {
922  QgsDebugMsg( "Releasing pan selector" );
923 
924  mCanvasProperties->panSelectorDown = false;
925  panActionEnd( mCanvasProperties->mouseLastXY );
926  }
927  break;
928 
929  default:
930  // Pass it on
931  if ( mMapTool )
932  {
934  }
935  else e->ignore();
936 
937  QgsDebugMsg( "Ignoring key release: " + QString::number( e->key() ) );
938  }
939 
940  emit keyReleased( e );
941 
942 } //keyReleaseEvent()
943 
944 
945 void QgsMapCanvas::mouseDoubleClickEvent( QMouseEvent * e )
946 {
947  if ( mDrawing )
948  {
949  return;
950  }
951 
952  // call handler of current map tool
953  if ( mMapTool )
955 } // mouseDoubleClickEvent
956 
957 
958 void QgsMapCanvas::mousePressEvent( QMouseEvent * e )
959 {
960  if ( mDrawing )
961  {
962  return;
963  }
964 
965  //use middle mouse button for panning, map tools won't receive any events in that case
966  if ( e->button() == Qt::MidButton )
967  {
968  mCanvasProperties->panSelectorDown = true;
969  mCanvasProperties->rubberStartPoint = mCanvasProperties->mouseLastXY;
970  }
971  else
972  {
973 
974  // call handler of current map tool
975  if ( mMapTool )
977  }
978 
979  if ( mCanvasProperties->panSelectorDown )
980  {
981  return;
982  }
983 
984  mCanvasProperties->mouseButtonDown = true;
985  mCanvasProperties->rubberStartPoint = e->pos();
986 
987 } // mousePressEvent
988 
989 
990 void QgsMapCanvas::mouseReleaseEvent( QMouseEvent * e )
991 {
992  if ( mDrawing )
993  {
994  return;
995  }
996 
997  //use middle mouse button for panning, map tools won't receive any events in that case
998  if ( e->button() == Qt::MidButton )
999  {
1000  mCanvasProperties->panSelectorDown = false;
1001  panActionEnd( mCanvasProperties->mouseLastXY );
1002  }
1003  else
1004  {
1005  // call handler of current map tool
1006  if ( mMapTool )
1007  {
1008  // right button was pressed in zoom tool? return to previous non zoom tool
1009  if ( e->button() == Qt::RightButton && mMapTool->isTransient() )
1010  {
1011  QgsDebugMsg( "Right click in map tool zoom or pan, last tool is " +
1012  QString( mLastNonZoomMapTool ? "not null." : "null." ) );
1013 
1014  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCurrentLayer );
1015 
1016  // change to older non-zoom tool
1017  if ( mLastNonZoomMapTool
1018  && ( !mLastNonZoomMapTool->isEditTool() || ( vlayer && vlayer->isEditable() ) ) )
1019  {
1021  mLastNonZoomMapTool = NULL;
1022  setMapTool( t );
1023  }
1024  return;
1025  }
1027  }
1028  }
1029 
1030 
1031  mCanvasProperties->mouseButtonDown = false;
1032 
1033  if ( mCanvasProperties->panSelectorDown )
1034  return;
1035 
1036 } // mouseReleaseEvent
1037 
1038 void QgsMapCanvas::resizeEvent( QResizeEvent * e )
1039 {
1040  mNewSize = e->size();
1041  mResizeTimer->start( 500 );
1042 }
1043 
1044 void QgsMapCanvas::paintEvent( QPaintEvent *e )
1045 {
1046  if ( mNewSize.isValid() )
1047  {
1048  if ( mPainting || mDrawing || mResizeTimer->isActive() )
1049  {
1050  //cancel current render progress
1051  if ( mMapRenderer )
1052  {
1053  QgsRenderContext* theRenderContext = mMapRenderer->rendererContext();
1054  if ( theRenderContext )
1055  {
1056  theRenderContext->setRenderingStopped( true );
1057  }
1058  }
1059  return;
1060  }
1061 
1062  mPainting = true;
1063 
1064  while ( mNewSize.isValid() )
1065  {
1066  QSize lastSize = mNewSize;
1067  mNewSize = QSize();
1068 
1069  //set map size before scene size helps keep scene indexes updated properly
1070  // this was the cause of rubberband artifacts
1071  mMap->resize( lastSize );
1072  mScene->setSceneRect( QRectF( 0, 0, lastSize.width(), lastSize.height() ) );
1073 
1074  // notify canvas items of change
1076 
1077  updateScale();
1078 
1079  refresh();
1080 
1081  emit extentsChanged();
1082  }
1083 
1084  mPainting = false;
1085  }
1086 
1088 } // paintEvent
1089 
1091 {
1092  QList<QGraphicsItem*> list = mScene->items();
1093  QList<QGraphicsItem*>::iterator it = list.begin();
1094  while ( it != list.end() )
1095  {
1096  QgsMapCanvasItem* item = dynamic_cast<QgsMapCanvasItem *>( *it );
1097 
1098  if ( item )
1099  {
1100  item->updatePosition();
1101  }
1102 
1103  ++it;
1104  }
1105 }
1106 
1107 
1108 void QgsMapCanvas::wheelEvent( QWheelEvent *e )
1109 {
1110  // Zoom the map canvas in response to a mouse wheel event. Moving the
1111  // wheel forward (away) from the user zooms in
1112 
1113  QgsDebugMsg( "Wheel event delta " + QString::number( e->delta() ) );
1114 
1115  if ( mDrawing )
1116  {
1117  return;
1118  }
1119 
1120  if ( mMapTool )
1121  {
1122  mMapTool->wheelEvent( e );
1123  }
1124 
1125  if ( QgsApplication::keyboardModifiers() )
1126  {
1127  // leave the wheel for map tools if any modifier pressed
1128  return;
1129  }
1130 
1131  switch ( mWheelAction )
1132  {
1133  case WheelZoom:
1134  // zoom without changing extent
1135  if ( e->delta() > 0 )
1136  zoomIn();
1137  else
1138  zoomOut();
1139  break;
1140 
1141  case WheelZoomAndRecenter:
1142  // zoom and don't change extent
1143  zoomWithCenter( e->x(), e->y(), e->delta() > 0 );
1144  break;
1145 
1147  {
1148  // zoom map to mouse cursor
1149  double scaleFactor = e->delta() > 0 ? 1 / mWheelZoomFactor : mWheelZoomFactor;
1150 
1151  QgsPoint oldCenter( mMapRenderer->extent().center() );
1152  QgsPoint mousePos( getCoordinateTransform()->toMapPoint( e->x(), e->y() ) );
1153  QgsPoint newCenter( mousePos.x() + (( oldCenter.x() - mousePos.x() ) * scaleFactor ),
1154  mousePos.y() + (( oldCenter.y() - mousePos.y() ) * scaleFactor ) );
1155 
1156  // same as zoomWithCenter (no coordinate transformations are needed)
1158  extent.scale( scaleFactor, &newCenter );
1159  setExtent( extent );
1160  refresh();
1161  break;
1162  }
1163 
1164  case WheelNothing:
1165  // well, nothing!
1166  break;
1167  }
1168 }
1169 
1170 void QgsMapCanvas::setWheelAction( WheelAction action, double factor )
1171 {
1172  mWheelAction = action;
1173  mWheelZoomFactor = factor;
1174 }
1175 
1177 {
1179 }
1180 
1182 {
1184 }
1185 
1186 void QgsMapCanvas::zoomScale( double newScale )
1187 {
1188  zoomByFactor( newScale / scale() );
1189 }
1190 
1191 void QgsMapCanvas::zoomWithCenter( int x, int y, bool zoomIn )
1192 {
1193  if ( mDrawing )
1194  {
1195  return;
1196  }
1197 
1198  double scaleFactor = ( zoomIn ? 1 / mWheelZoomFactor : mWheelZoomFactor );
1199 
1200  // transform the mouse pos to map coordinates
1201  QgsPoint center = getCoordinateTransform()->toMapPoint( x, y );
1203  r.scale( scaleFactor, &center );
1204  setExtent( r );
1205  refresh();
1206 }
1207 
1208 void QgsMapCanvas::mouseMoveEvent( QMouseEvent * e )
1209 {
1210  if ( mDrawing )
1211  {
1212  return;
1213  }
1214 
1215  mCanvasProperties->mouseLastXY = e->pos();
1216 
1217  if ( mCanvasProperties->panSelectorDown )
1218  {
1219  panAction( e );
1220  }
1221  else
1222  {
1223  // call handler of current map tool
1224  if ( mMapTool )
1225  mMapTool->canvasMoveEvent( e );
1226  }
1227 
1228  // show x y on status bar
1229  QPoint xy = e->pos();
1231  emit xyCoordinates( coord );
1232 } // mouseMoveEvent
1233 
1234 
1235 
1238 {
1239  if ( !tool )
1240  return;
1241 
1242  if ( mMapTool )
1243  {
1244  disconnect( mMapTool, SIGNAL( destroyed() ), this, SLOT( mapToolDestroyed() ) );
1245  mMapTool->deactivate();
1246  }
1247 
1248  if ( tool->isTransient() && mMapTool && !mMapTool->isTransient() )
1249  {
1250  // if zoom or pan tool will be active, save old tool
1251  // to bring it back on right click
1252  // (but only if it wasn't also zoom or pan tool)
1254  }
1255  else
1256  {
1257  mLastNonZoomMapTool = NULL;
1258  }
1259 
1260  // set new map tool and activate it
1261  mMapTool = tool;
1262  if ( mMapTool )
1263  {
1264  connect( mMapTool, SIGNAL( destroyed() ), this, SLOT( mapToolDestroyed() ) );
1265  mMapTool->activate();
1266  }
1267 
1268  emit mapToolSet( mMapTool );
1269 } // setMapTool
1270 
1272 {
1273  if ( mMapTool && mMapTool == tool )
1274  {
1275  mMapTool->deactivate();
1276  mMapTool = NULL;
1277  emit mapToolSet( NULL );
1278  setCursor( Qt::ArrowCursor );
1279  }
1280 
1281  if ( mLastNonZoomMapTool && mLastNonZoomMapTool == tool )
1282  {
1283  mLastNonZoomMapTool = NULL;
1284  }
1285 }
1286 
1288 void QgsMapCanvas::setCanvasColor( const QColor & theColor )
1289 {
1290  // background of map's pixmap
1291  mMap->setBackgroundColor( theColor );
1292 
1293  // background of the QGraphicsView
1294  QBrush bgBrush( theColor );
1295  setBackgroundBrush( bgBrush );
1296 #if 0
1297  QPalette palette;
1298  palette.setColor( backgroundRole(), theColor );
1299  setPalette( palette );
1300 #endif
1301 
1302  // background of QGraphicsScene
1303  mScene->setBackgroundBrush( bgBrush );
1304 } // setBackgroundColor
1305 
1307 {
1308  return mScene->backgroundBrush().color();
1309 }
1310 
1312 {
1313  return mMapRenderer->layerSet().size();
1314 } // layerCount
1315 
1316 
1317 QList<QgsMapLayer*> QgsMapCanvas::layers() const
1318 {
1319  QList<QgsMapLayer*> lst;
1320  foreach ( QString layerID, mMapRenderer->layerSet() )
1321  {
1323  if ( layer )
1324  lst.append( layer );
1325  }
1326  return lst;
1327 }
1328 
1329 
1331 {
1332  // called when a layer has changed visibility setting
1333 
1334  refresh();
1335 
1336 } // layerStateChange
1337 
1338 
1339 
1340 void QgsMapCanvas::freeze( bool frz )
1341 {
1342  mFrozen = frz;
1343 } // freeze
1344 
1346 {
1347  return mFrozen;
1348 } // freeze
1349 
1350 
1352 {
1353  return mMap->paintDevice();
1354 }
1355 
1357 {
1358  return mMapRenderer->mapUnitsPerPixel();
1359 } // mapUnitsPerPixel
1360 
1361 
1363 {
1364  QgsDebugMsg( "Setting map units to " + QString::number( static_cast<int>( u ) ) );
1365  mMapRenderer->setMapUnits( u );
1366 }
1367 
1368 
1370 {
1371  return mMapRenderer->mapUnits();
1372 }
1373 
1374 
1375 void QgsMapCanvas::setRenderFlag( bool theFlag )
1376 {
1377  mRenderFlag = theFlag;
1378  if ( mMapRenderer )
1379  {
1381  if ( rc )
1382  {
1383  rc->setRenderingStopped( !theFlag );
1384  }
1385  }
1386 
1387  if ( mRenderFlag )
1388  {
1389  refresh();
1390  }
1391 }
1392 
1393 void QgsMapCanvas::connectNotify( const char * signal )
1394 {
1395  Q_UNUSED( signal );
1396  QgsDebugMsg( "QgsMapCanvas connected to " + QString( signal ) );
1397 } //connectNotify
1398 
1399 
1400 
1402 {
1403  return mMapTool;
1404 }
1405 
1406 void QgsMapCanvas::panActionEnd( QPoint releasePoint )
1407 {
1408  if ( mDrawing )
1409  {
1410  return;
1411  }
1412 
1413  // move map image and other items to standard position
1414  moveCanvasContents( true ); // true means reset
1415 
1416  // use start and end box points to calculate the extent
1417  QgsPoint start = getCoordinateTransform()->toMapCoordinates( mCanvasProperties->rubberStartPoint );
1418  QgsPoint end = getCoordinateTransform()->toMapCoordinates( releasePoint );
1419 
1420  double dx = qAbs( end.x() - start.x() );
1421  double dy = qAbs( end.y() - start.y() );
1422 
1423  // modify the extent
1425 
1426  if ( end.x() < start.x() )
1427  {
1428  r.setXMinimum( r.xMinimum() + dx );
1429  r.setXMaximum( r.xMaximum() + dx );
1430  }
1431  else
1432  {
1433  r.setXMinimum( r.xMinimum() - dx );
1434  r.setXMaximum( r.xMaximum() - dx );
1435  }
1436 
1437  if ( end.y() < start.y() )
1438  {
1439  r.setYMaximum( r.yMaximum() + dy );
1440  r.setYMinimum( r.yMinimum() + dy );
1441 
1442  }
1443  else
1444  {
1445  r.setYMaximum( r.yMaximum() - dy );
1446  r.setYMinimum( r.yMinimum() - dy );
1447 
1448  }
1449 
1450  setExtent( r );
1451  refresh();
1452 }
1453 
1454 void QgsMapCanvas::panAction( QMouseEvent * e )
1455 {
1456  Q_UNUSED( e );
1457 
1458  if ( mDrawing )
1459  {
1460  return;
1461  }
1462 
1463  // move all map canvas items
1465 
1466  // update canvas
1467  //updateContents(); // TODO: need to update?
1468 }
1469 
1471 {
1472  if ( mDrawing )
1473  {
1474  return;
1475  }
1476 
1477  QPoint pnt( 0, 0 );
1478  if ( !reset )
1479  pnt += mCanvasProperties->mouseLastXY - mCanvasProperties->rubberStartPoint;
1480 
1481  mMap->setPanningOffset( pnt );
1482 
1483  QList<QGraphicsItem*> list = mScene->items();
1484  QList<QGraphicsItem*>::iterator it = list.begin();
1485  while ( it != list.end() )
1486  {
1487  QGraphicsItem* item = *it;
1488 
1489  if ( item != mMap )
1490  {
1491  // this tells map canvas item to draw with offset
1492  QgsMapCanvasItem* canvasItem = dynamic_cast<QgsMapCanvasItem *>( item );
1493  if ( canvasItem )
1494  canvasItem->setPanningOffset( pnt );
1495  }
1496 
1497  ++it;
1498  }
1499 
1500  // show items
1502 
1503 }
1504 
1506 {
1507 #if 0
1508  QMessageBox::warning(
1509  this,
1510  mapLayer->lastErrorTitle(),
1511  tr( "Could not draw %1 because:\n%2", "COMMENTED OUT" ).arg( mapLayer->name() ).arg( mapLayer->lastError() )
1512  );
1513 #endif
1514 
1515  QgsMessageViewer * mv = new QgsMessageViewer( this );
1516  mv->setWindowTitle( mapLayer->lastErrorTitle() );
1517  mv->setMessageAsPlainText( tr( "Could not draw %1 because:\n%2" )
1518  .arg( mapLayer->name() ).arg( mapLayer->lastError() ) );
1519  mv->exec();
1520  //MH
1521  //QgsMessageViewer automatically sets delete on close flag
1522  //so deleting mv would lead to a segfault
1523 }
1524 
1526 {
1527  return mCanvasProperties->mouseLastXY;
1528 }
1529 
1530 void QgsMapCanvas::readProject( const QDomDocument & doc )
1531 {
1532  QDomNodeList nodes = doc.elementsByTagName( "mapcanvas" );
1533  if ( nodes.count() )
1534  {
1535  QDomNode node = nodes.item( 0 );
1536  mMapRenderer->readXML( node );
1537  clearExtentHistory(); // clear the extent history on project load
1538  }
1539  else
1540  {
1541  QgsDebugMsg( "Couldn't read mapcanvas information from project" );
1542  }
1543 }
1544 
1545 void QgsMapCanvas::writeProject( QDomDocument & doc )
1546 {
1547  // create node "mapcanvas" and call mMapRenderer->writeXML()
1548 
1549  QDomNodeList nl = doc.elementsByTagName( "qgis" );
1550  if ( !nl.count() )
1551  {
1552  QgsDebugMsg( "Unable to find qgis element in project file" );
1553  return;
1554  }
1555  QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element ok
1556 
1557  QDomElement mapcanvasNode = doc.createElement( "mapcanvas" );
1558  qgisNode.appendChild( mapcanvasNode );
1559  mMapRenderer->writeXML( mapcanvasNode, doc );
1560 }
1561 
1563 void QgsMapCanvas::getDatumTransformInfo( const QgsMapLayer* ml, const QString& srcAuthId, const QString& destAuthId )
1564 {
1565  if ( !ml )
1566  {
1567  return;
1568  }
1569 
1570  //check if default datum transformation available
1571  QSettings s;
1572  QString settingsString = "/Projections/" + srcAuthId + "//" + destAuthId;
1573  QVariant defaultSrcTransform = s.value( settingsString + "_srcTransform" );
1574  QVariant defaultDestTransform = s.value( settingsString + "_destTransform" );
1575  if ( defaultSrcTransform.isValid() && defaultDestTransform.isValid() )
1576  {
1577  mMapRenderer->addLayerCoordinateTransform( ml->id(), srcAuthId, destAuthId, defaultSrcTransform.toInt(), defaultDestTransform.toInt() );
1578  return;
1579  }
1580 
1581  const QgsCoordinateReferenceSystem& srcCRS = QgsCRSCache::instance()->crsByAuthId( srcAuthId );
1582  const QgsCoordinateReferenceSystem& destCRS = QgsCRSCache::instance()->crsByAuthId( destAuthId );
1583 
1584  if ( !s.value( "/Projections/showDatumTransformDialog", false ).toBool() )
1585  {
1586  // just use the default transform
1587  mMapRenderer->addLayerCoordinateTransform( ml->id(), srcAuthId, destAuthId, -1, -1 );
1588  return;
1589  }
1590 
1591  //get list of datum transforms
1592  QList< QList< int > > dt = QgsCoordinateTransform::datumTransformations( srcCRS, destCRS );
1593  if ( dt.size() < 2 )
1594  {
1595  return;
1596  }
1597 
1598  //if several possibilities: present dialog
1599  QgsDatumTransformDialog d( ml->name(), dt );
1600  if ( mMapRenderer && ( d.exec() == QDialog::Accepted ) )
1601  {
1602  int srcTransform = -1;
1603  int destTransform = -1;
1604  QList<int> t = d.selectedDatumTransform();
1605  if ( t.size() > 0 )
1606  {
1607  srcTransform = t.at( 0 );
1608  }
1609  if ( t.size() > 1 )
1610  {
1611  destTransform = t.at( 1 );
1612  }
1613  mMapRenderer->addLayerCoordinateTransform( ml->id(), srcAuthId, destAuthId, srcTransform, destTransform );
1614  if ( d.rememberSelection() )
1615  {
1616  s.setValue( settingsString + "_srcTransform", srcTransform );
1617  s.setValue( settingsString + "_destTransform", destTransform );
1618  }
1619  }
1620  else
1621  {
1622  mMapRenderer->addLayerCoordinateTransform( ml->id(), srcAuthId, destAuthId, -1, -1 );
1623  }
1624 }
1625 
1626 void QgsMapCanvas::zoomByFactor( double scaleFactor )
1627 {
1628  if ( mDrawing )
1629  {
1630  return;
1631  }
1632 
1634  r.scale( scaleFactor );
1635  setExtent( r );
1636  refresh();
1637 }
1638 
1640 {
1641  // Find out which layer it was that sent the signal.
1642  QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
1643  emit selectionChanged( layer );
1644  refresh();
1645 }
1646 
1647 void QgsMapCanvas::dragEnterEvent( QDragEnterEvent * e )
1648 {
1649  // By default graphics view delegates the drag events to graphics items.
1650  // But we do not want that and by ignoring the drag enter we let the
1651  // parent (e.g. QgisApp) to handle drops of map layers etc.
1652  e->ignore();
1653 }
1654 
1656 {
1657  if ( enabled )
1658  {
1659  QgsDebugMsg( "refreshing after reprojection was enabled" );
1660  refresh();
1661  connect( mMapRenderer, SIGNAL( destinationSrsChanged() ), this, SLOT( refresh() ) );
1662  }
1663  else
1664  disconnect( mMapRenderer, SIGNAL( destinationSrsChanged() ), this, SLOT( refresh() ) );
1665 }
1666 
1668 {
1669  QgsDebugMsg( "maptool destroyed" );
1670  mMapTool = 0;
1671 }
1672 
1673 #ifdef HAVE_TOUCH
1674 bool QgsMapCanvas::event( QEvent * e )
1675 {
1676  bool done = false;
1677  if ( mDrawing )
1678  {
1679  return done;
1680  }
1681  if ( e->type() == QEvent::Gesture )
1682  {
1683  // call handler of current map tool
1684  if ( mMapTool )
1685  {
1686  done = mMapTool->gestureEvent( static_cast<QGestureEvent*>( e ) );
1687  }
1688  }
1689  else
1690  {
1691  // pass other events to base class
1692  done = QGraphicsView::event( e );
1693  }
1694  return done;
1695 }
1696 #endif