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