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