/*************************************************************************** qgsvectorlayer.cpp -------------------- begin : Oct 29, 2003 copyright : (C) 2003 by Gary E.Sherman email : sherman at mrcc.com This class implements a generic means to display vector layers. The features and attributes are read from the data store using a "data provider" plugin. QgsVectorLayer can be used with any data store for which an appropriate plugin is available. ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include <limits> #include <QImage> #include <QPainter> #include <QPainterPath> #include <QPolygonF> #include <QProgressDialog> #include <QSettings> #include <QString> #include <QDomNode> #include "qgsvectorlayer.h" // renderers #include "qgscontinuouscolorrenderer.h" #include "qgsgraduatedsymbolrenderer.h" #include "qgsrenderer.h" #include "qgssinglesymbolrenderer.h" #include "qgsuniquevaluerenderer.h" #include "qgsattributeaction.h" #include "qgis.h" //for globals #include "qgsapplication.h" #include "qgscoordinatetransform.h" #include "qgsfeature.h" #include "qgsfeaturerequest.h" #include "qgsfield.h" #include "qgsgeometry.h" #include "qgslabel.h" #include "qgslogger.h" #include "qgsmessagelog.h" #include "qgsmaptopixel.h" #include "qgspoint.h" #include "qgsproviderregistry.h" #include "qgsrectangle.h" #include "qgsrendercontext.h" #include "qgscoordinatereferencesystem.h" #include "qgsvectordataprovider.h" #include "qgsvectorlayercache.h" #include "qgsvectorlayereditbuffer.h" #include "qgsvectorlayereditutils.h" #include "qgsvectorlayerfeatureiterator.h" #include "qgsvectorlayerjoinbuffer.h" #include "qgsvectorlayerundocommand.h" #include "qgsvectoroverlay.h" #include "qgsmaplayerregistry.h" #include "qgsclipper.h" #include "qgsproject.h" #include "qgsrendererv2.h" #include "qgssymbolv2.h" #include "qgssymbollayerv2.h" #include "qgssinglesymbolrendererv2.h" #include "qgsdiagramrendererv2.h" #include "qgsstylev2.h" #ifdef TESTPROVIDERLIB #include <dlfcn.h> #endif QgsVectorLayer::QgsVectorLayer( QString vectorLayerPath, QString baseName, QString providerKey, bool loadDefaultStyleFlag ) : QgsMapLayer( VectorLayer, baseName, vectorLayerPath ) , mUpdateThreshold( 0 ) // XXX better default value? , mDataProvider( NULL ) , mProviderKey( providerKey ) , mReadOnly( false ) , mRenderer( 0 ) , mRendererV2( NULL ) , mUsingRendererV2( false ) , mLabel( 0 ) , mLabelOn( false ) , mVertexMarkerOnlyForSelection( false ) , mCache( new QgsVectorLayerCache(this) ) , mEditBuffer( 0 ) , mJoinBuffer( 0 ) , mDiagramRenderer( 0 ) , mDiagramLayerSettings( 0 ) , mValidExtent( false ) , mSymbolFeatureCounted( false ) { mActions = new QgsAttributeAction( this ); // if we're given a provider type, try to create and bind one to this layer if ( ! mProviderKey.isEmpty() ) { setDataProvider( mProviderKey ); } if ( mValid ) { // Always set crs setCoordinateSystem(); mJoinBuffer = new QgsVectorLayerJoinBuffer(); updateFields(); QSettings settings; //Changed to default to true as of QGIS 1.7 //TODO: remove hack when http://hub.qgis.org/issues/5170 is fixed #ifdef ANDROID bool use_symbology_ng_default = false; #else bool use_symbology_ng_default = true; #endif if ( settings.value( "/qgis/use_symbology_ng", use_symbology_ng_default ).toBool() && hasGeometryType() ) { // using symbology-ng! setUsingRendererV2( true ); } // check if there is a default style / propertysheet defined // for this layer and if so apply it bool defaultLoadedFlag = false; if ( loadDefaultStyleFlag ) { loadDefaultStyle( defaultLoadedFlag ); } // if the default style failed to load or was disabled use some very basic defaults if ( !defaultLoadedFlag && hasGeometryType() ) { // add single symbol renderer if ( mUsingRendererV2 ) { setRendererV2( QgsFeatureRendererV2::defaultRenderer( geometryType() ) ); } else { QgsSingleSymbolRenderer *renderer = new QgsSingleSymbolRenderer( geometryType() ); setRenderer( renderer ); } } connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( checkJoinLayerRemove( QString ) ) ); // Get the update threshold from user settings. We // do this only on construction to avoid the penality of // fetching this each time the layer is drawn. If the user // changes the threshold from the preferences dialog, it will // have no effect on existing layers // TODO: load this setting somewhere else [MD] //QSettings settings; //mUpdateThreshold = settings.readNumEntry("Map/updateThreshold", 1000); } } // QgsVectorLayer ctor QgsVectorLayer::~QgsVectorLayer() { QgsDebugMsg( "entered." ); emit layerDeleted(); mValid = false; delete mRenderer; delete mDataProvider; delete mEditBuffer; delete mJoinBuffer; delete mCache; delete mLabel; delete mDiagramLayerSettings; delete mActions; //delete remaining overlays QList<QgsVectorOverlay*>::iterator overlayIt = mOverlays.begin(); for ( ; overlayIt != mOverlays.end(); ++overlayIt ) { delete *overlayIt; } } QString QgsVectorLayer::storageType() const { if ( mDataProvider ) { return mDataProvider->storageType(); } return 0; } QString QgsVectorLayer::capabilitiesString() const { if ( mDataProvider ) { return mDataProvider->capabilitiesString(); } return 0; } QString QgsVectorLayer::dataComment() const { if ( mDataProvider ) { return mDataProvider->dataComment(); } return QString(); } QString QgsVectorLayer::providerType() const { return mProviderKey; } /** * sets the preferred display field based on some fuzzy logic */ void QgsVectorLayer::setDisplayField( QString fldName ) { if ( !hasGeometryType() ) return; // If fldName is provided, use it as the display field, otherwise // determine the field index for the feature column of the identify // dialog. We look for fields containing "name" first and second for // fields containing "id". If neither are found, the first field // is used as the node. QString idxName = ""; QString idxId = ""; if ( !fldName.isEmpty() ) { mDisplayField = fldName; } else { const QgsFields &fields = pendingFields(); int fieldsSize = fields.size(); for ( int idx = 0; idx < fields.count(); ++idx ) { QString fldName = fields[idx].name(); QgsDebugMsg( "Checking field " + fldName + " of " + QString::number( fieldsSize ) + " total" ); // Check the fields and keep the first one that matches. // We assume that the user has organized the data with the // more "interesting" field names first. As such, name should // be selected before oldname, othername, etc. if ( fldName.indexOf( "name", false ) > -1 ) { if ( idxName.isEmpty() ) { idxName = fldName; } } if ( fldName.indexOf( "descrip", false ) > -1 ) { if ( idxName.isEmpty() ) { idxName = fldName; } } if ( fldName.indexOf( "id", false ) > -1 ) { if ( idxId.isEmpty() ) { idxId = fldName; } } } //if there were no fields in the dbf just return - otherwise qgis segfaults! if ( fieldsSize == 0 ) return; if ( idxName.length() > 0 ) { mDisplayField = idxName; } else { if ( idxId.length() > 0 ) { mDisplayField = idxId; } else { mDisplayField = fields[0].name(); } } } } // NOTE this is a temporary method added by Tim to prevent label clipping // which was occurring when labeller was called in the main draw loop // This method will probably be removed again in the near future! void QgsVectorLayer::drawLabels( QgsRenderContext& rendererContext ) { if ( !hasGeometryType() ) return; QgsDebugMsg( "Starting draw of labels: " + id() ); if (( mRenderer || mRendererV2 ) && mLabelOn && ( !mLabel->scaleBasedVisibility() || ( mLabel->minScale() <= rendererContext.rendererScale() && rendererContext.rendererScale() <= mLabel->maxScale() ) ) ) { QgsAttributeList attributes; if ( mRenderer ) { attributes = mRenderer->classificationAttributes(); } else if ( mRendererV2 ) { foreach ( QString attrName, mRendererV2->usedAttributes() ) { int attrNum = fieldNameIndex( attrName ); attributes.append( attrNum ); } // make sure the renderer is ready for classification ("symbolForFeature") mRendererV2->startRender( rendererContext, this ); } // Add fields required for labels mLabel->addRequiredFields( attributes ); QgsDebugMsg( "Selecting features based on view extent" ); int featureCount = 0; try { // select the records in the extent. The provider sets a spatial filter // and sets up the selection set for retrieval select( attributes, rendererContext.extent() ); QgsFeature fet; while ( nextFeature( fet ) ) { if (( mRenderer && mRenderer->willRenderFeature( &fet ) ) || ( mRendererV2 && mRendererV2->willRenderFeature( fet ) ) ) { bool sel = mSelectedFeatureIds.contains( fet.id() ); mLabel->renderLabel( rendererContext, fet, sel, 0 ); } featureCount++; } } catch ( QgsCsException &e ) { Q_UNUSED( e ); QgsDebugMsg( "Error projecting label locations" ); } if ( mRendererV2 ) { mRendererV2->stopRender( rendererContext ); } QgsDebugMsg( QString( "Total features processed %1" ).arg( featureCount ) ); // XXX Something in our draw event is triggering an additional draw event when resizing [TE 01/26/06] // XXX Calling this will begin processing the next draw event causing image havoc and recursion crashes. //qApp->processEvents(); } } unsigned char *QgsVectorLayer::drawLineString( unsigned char *feature, QgsRenderContext &renderContext ) { QPainter *p = renderContext.painter(); unsigned char *ptr = feature + 5; unsigned int wkbType = *(( int* )( feature + 1 ) ); int nPoints = *(( int* )ptr ); ptr = feature + 9; bool hasZValue = ( wkbType == QGis::WKBLineString25D ); QVector<double> x( nPoints ); QVector<double> y( nPoints ); QVector<double> z( nPoints, 0.0 ); // Extract the points from the WKB format into the x and y vectors. for ( int i = 0; i < nPoints; ++i ) { x[i] = *(( double * ) ptr ); ptr += sizeof( double ); y[i] = *(( double * ) ptr ); ptr += sizeof( double ); if ( hasZValue ) // ignore Z value ptr += sizeof( double ); } // Transform the points into map coordinates (and reproject if // necessary) transformPoints( x, y, z, renderContext ); // Work around a +/- 32768 limitation on coordinates // Look through the x and y coordinates and see if there are any // that need trimming. If one is found, there's no need to look at // the rest of them so end the loop at that point. for ( int i = 0; i < nPoints; ++i ) { if ( qAbs( x.at( i ) ) > QgsClipper::MAX_X || qAbs( y.at( i ) ) > QgsClipper::MAX_Y ) { QgsClipper::trimFeature( x, y, true ); // true = polyline nPoints = x.size(); // trimming may change nPoints. break; } } // set up QPolygonF class with transformed points QPolygonF pa( nPoints ); for ( int i = 0; i < nPoints; ++i ) { pa[i].setX( x.at( i ) ); pa[i].setY( y.at( i ) ); } // The default pen gives bevelled joins between segements of the // polyline, which is good enough for the moment. //preserve a copy of the pen before we start fiddling with it QPen pen = p->pen(); // to be kept original // // experimental alpha transparency // 255 = opaque // QPen myTransparentPen = p->pen(); // store current pen QColor myColor = myTransparentPen.color(); //only set transparency from layer level if renderer does not provide //transparency on class level if ( !mRenderer->usesTransparency() ) { myColor.setAlpha( mTransparencyLevel ); } myTransparentPen.setColor( myColor ); p->setPen( myTransparentPen ); p->drawPolyline( pa ); // draw vertex markers if in editing mode, but only to the main canvas if ( mEditBuffer && renderContext.drawEditingInformation() ) { QVector<double>::const_iterator xIt; QVector<double>::const_iterator yIt; for ( xIt = x.begin(), yIt = y.begin(); xIt != x.end(); ++xIt, ++yIt ) { drawVertexMarker( *xIt, *yIt, *p, mCurrentVertexMarkerType, mCurrentVertexMarkerSize ); } } //restore the pen p->setPen( pen ); return ptr; } unsigned char *QgsVectorLayer::drawPolygon( unsigned char *feature, QgsRenderContext &renderContext ) { QPainter *p = renderContext.painter(); typedef QPair<QVector<double>, QVector<double> > ringType; typedef ringType* ringTypePtr; typedef QVector<ringTypePtr> ringsType; // get number of rings in the polygon unsigned int numRings = *(( int* )( feature + 1 + sizeof( int ) ) ); if ( numRings == 0 ) // sanity check for zero rings in polygon return feature + 9; unsigned int wkbType = *(( int* )( feature + 1 ) ); bool hasZValue = ( wkbType == QGis::WKBPolygon25D ); int total_points = 0; // A vector containing a pointer to a pair of double vectors.The // first vector in the pair contains the x coordinates, and the // second the y coordinates. ringsType rings; // Set pointer to the first ring unsigned char* ptr = feature + 1 + 2 * sizeof( int ); for ( register unsigned int idx = 0; idx < numRings; idx++ ) { int nPoints = *(( int* )ptr ); ringTypePtr ring = new ringType( QVector<double>( nPoints ), QVector<double>( nPoints ) ); ptr += 4; // create a dummy vector for the z coordinate QVector<double> zVector( nPoints, 0.0 ); // Extract the points from the WKB and store in a pair of // vectors. for ( int jdx = 0; jdx < nPoints; jdx++ ) { ring->first[jdx] = *(( double * ) ptr ); ptr += sizeof( double ); ring->second[jdx] = *(( double * ) ptr ); ptr += sizeof( double ); if ( hasZValue ) ptr += sizeof( double ); } // If ring has fewer than two points, what is it then? // Anyway, this check prevents a crash if ( nPoints < 1 ) { QgsDebugMsg( "Ring has only " + QString::number( nPoints ) + " points! Skipping this ring." ); continue; } transformPoints( ring->first, ring->second, zVector, renderContext ); // Work around a +/- 32768 limitation on coordinates // Look through the x and y coordinates and see if there are any // that need trimming. If one is found, there's no need to look at // the rest of them so end the loop at that point. for ( int i = 0; i < nPoints; ++i ) { if ( qAbs( ring->first[i] ) > QgsClipper::MAX_X || qAbs( ring->second[i] ) > QgsClipper::MAX_Y ) { QgsClipper::trimFeature( ring->first, ring->second, false ); break; } } // Don't bother keeping the ring if it has been trimmed out of // existence. if ( ring->first.size() == 0 ) delete ring; else { rings.push_back( ring ); total_points += ring->first.size(); } } // Now we draw the polygons // use painter paths for drawing polygons with holes // when adding polygon to the path they invert the area // this means that adding inner rings to the path creates // holes in outer ring QPainterPath path; // OddEven fill rule by default // Only try to draw polygons if there is something to draw if ( total_points > 0 ) { //preserve a copy of the brush and pen before we start fiddling with it QBrush brush = p->brush(); //to be kept as original QPen pen = p->pen(); // to be kept original // // experimental alpha transparency // 255 = opaque // QBrush myTransparentBrush = p->brush(); QColor myColor = brush.color(); //only set transparency from layer level if renderer does not provide //transparency on class level if ( !mRenderer->usesTransparency() ) { myColor.setAlpha( mTransparencyLevel ); } myTransparentBrush.setColor( myColor ); QPen myTransparentPen = p->pen(); // store current pen myColor = myTransparentPen.color(); //only set transparency from layer level if renderer does not provide //transparency on class level if ( !mRenderer->usesTransparency() ) { myColor.setAlpha( mTransparencyLevel ); } myTransparentPen.setColor( myColor ); p->setBrush( myTransparentBrush ); p->setPen( myTransparentPen ); if ( numRings == 1 ) { ringTypePtr r = rings[0]; unsigned ringSize = r->first.size(); QPolygonF pa( ringSize ); for ( register unsigned int j = 0; j != ringSize; ++j ) { pa[j].setX( r->first[j] ); pa[j].setY( r->second[j] ); } p->drawPolygon( pa ); // draw vertex markers if in editing mode, but only to the main canvas if ( mEditBuffer && renderContext.drawEditingInformation() ) { for ( register unsigned int j = 0; j != ringSize; ++j ) { drawVertexMarker( r->first[j], r->second[j], *p, mCurrentVertexMarkerType, mCurrentVertexMarkerSize ); } } delete rings[0]; } else { // Store size here and use it in the loop to avoid penalty of // multiple calls to size() int numRings = rings.size(); for ( register int i = 0; i < numRings; ++i ) { // Store the pointer in a variable with a short name so as to make // the following code easier to type and read. ringTypePtr r = rings[i]; // only do this once to avoid penalty of additional calls unsigned ringSize = r->first.size(); // Transfer points to the array of QPointF QPolygonF pa( ringSize ); for ( register unsigned int j = 0; j != ringSize; ++j ) { pa[j].setX( r->first[j] ); pa[j].setY( r->second[j] ); } path.addPolygon( pa ); // Tidy up the pointed to pairs of vectors as we finish with them delete rings[i]; } #if 0 // A bit of code to aid in working out what values of // QgsClipper::minX, etc cause the X11 zoom bug. int largestX = -std::numeric_limits<int>::max(); int smallestX = std::numeric_limits<int>::max(); int largestY = -std::numeric_limits<int>::max(); int smallestY = std::numeric_limits<int>::max(); for ( int i = 0; i < pa.size(); ++i ) { largestX = qMax( largestX, pa.point( i ).x() ); smallestX = qMin( smallestX, pa.point( i ).x() ); largestY = qMax( largestY, pa.point( i ).y() ); smallestY = qMin( smallestY, pa.point( i ).y() ); } QgsDebugMsg( QString( "Largest X coordinate was %1" ).arg( largestX ) ); QgsDebugMsg( QString( "Smallest X coordinate was %1" ).arg( smallestX ) ); QgsDebugMsg( QString( "Largest Y coordinate was %1" ).arg( largestY ) ); QgsDebugMsg( QString( "Smallest Y coordinate was %1" ).arg( smallestY ) ); #endif // // draw the polygon // p->drawPath( path ); // draw vertex markers if in editing mode, but only to the main canvas if ( mEditBuffer && renderContext.drawEditingInformation() ) { for ( int i = 0; i < path.elementCount(); ++i ) { const QPainterPath::Element & e = path.elementAt( i ); drawVertexMarker( e.x, e.y, *p, mCurrentVertexMarkerType, mCurrentVertexMarkerSize ); } } } // //restore brush and pen to original // p->setBrush( brush ); p->setPen( pen ); } // totalPoints > 0 return ptr; } void QgsVectorLayer::drawRendererV2( QgsRenderContext& rendererContext, bool labeling ) { if ( !hasGeometryType() ) return; QSettings settings; bool vertexMarkerOnlyForSelection = settings.value( "/qgis/digitizing/marker_only_for_selected", false ).toBool(); mRendererV2->startRender( rendererContext, this ); #ifndef Q_WS_MAC int featureCount = 0; #endif //Q_WS_MAC QgsFeature fet; while ( nextFeature( fet ) ) { try { if ( !fet.geometry() ) continue; // skip features without geometry if ( rendererContext.renderingStopped() ) { break; } #ifndef Q_WS_MAC //MH: disable this on Mac for now to avoid problems with resizing #ifdef Q_WS_X11 if ( !mEnableBackbuffer ) // do not handle events, as we're already inside a paint event { #endif // Q_WS_X11 if ( mUpdateThreshold > 0 && 0 == featureCount % mUpdateThreshold ) { emit screenUpdateRequested(); // emit drawingProgress( featureCount, totalFeatures ); qApp->processEvents(); } else if ( featureCount % 1000 == 0 ) { // emit drawingProgress( featureCount, totalFeatures ); qApp->processEvents(); } #ifdef Q_WS_X11 } #endif // Q_WS_X11 #endif // Q_WS_MAC bool sel = mSelectedFeatureIds.contains( fet.id() ); bool drawMarker = ( mEditBuffer && ( !vertexMarkerOnlyForSelection || sel ) ); // render feature bool rendered = mRendererV2->renderFeature( fet, rendererContext, -1, sel, drawMarker ); if ( mEditBuffer ) { // Cache this for the use of (e.g.) modifying the feature's uncommitted geometry. mCache->cacheGeometry( fet.id(), *fet.geometry() ); } // labeling - register feature if ( rendered && rendererContext.labelingEngine() ) { if ( labeling ) { rendererContext.labelingEngine()->registerFeature( this, fet, rendererContext ); } if ( mDiagramRenderer ) { rendererContext.labelingEngine()->registerDiagramFeature( this, fet, rendererContext ); } } } catch ( const QgsCsException &cse ) { Q_UNUSED( cse ); QgsDebugMsg( QString( "Failed to transform a point while drawing a feature with ID '%1'. Ignoring this feature. %2" ) .arg( fet.id() ).arg( cse.what() ) ); } #ifndef Q_WS_MAC ++featureCount; #endif //Q_WS_MAC } stopRendererV2( rendererContext, NULL ); #ifndef Q_WS_MAC QgsDebugMsg( QString( "Total features processed %1" ).arg( featureCount ) ); #endif } void QgsVectorLayer::drawRendererV2Levels( QgsRenderContext& rendererContext, bool labeling ) { if ( !hasGeometryType() ) return; QHash< QgsSymbolV2*, QList<QgsFeature> > features; // key = symbol, value = array of features QSettings settings; bool vertexMarkerOnlyForSelection = settings.value( "/qgis/digitizing/marker_only_for_selected", false ).toBool(); // startRender must be called before symbolForFeature() calls to make sure renderer is ready mRendererV2->startRender( rendererContext, this ); QgsSingleSymbolRendererV2* selRenderer = NULL; if ( !mSelectedFeatureIds.isEmpty() ) { selRenderer = new QgsSingleSymbolRendererV2( QgsSymbolV2::defaultSymbol( geometryType() ) ); selRenderer->symbol()->setColor( QgsRenderer::selectionColor() ); selRenderer->setVertexMarkerAppearance( currentVertexMarkerType(), currentVertexMarkerSize() ); selRenderer->startRender( rendererContext, this ); } // 1. fetch features QgsFeature fet; #ifndef Q_WS_MAC int featureCount = 0; #endif //Q_WS_MAC while ( nextFeature( fet ) ) { if ( !fet.geometry() ) continue; // skip features without geometry if ( rendererContext.renderingStopped() ) { stopRendererV2( rendererContext, selRenderer ); return; } #ifndef Q_WS_MAC if ( featureCount % 1000 == 0 ) { qApp->processEvents(); } #endif //Q_WS_MAC QgsSymbolV2* sym = mRendererV2->symbolForFeature( fet ); if ( !sym ) { continue; } if ( !features.contains( sym ) ) { features.insert( sym, QList<QgsFeature>() ); } features[sym].append( fet ); if ( mEditBuffer ) { // Cache this for the use of (e.g.) modifying the feature's uncommitted geometry. mCache->cacheGeometry( fet.id(), *fet.geometry() ); } if ( sym && rendererContext.labelingEngine() ) { if ( labeling ) { rendererContext.labelingEngine()->registerFeature( this, fet, rendererContext ); } if ( mDiagramRenderer ) { rendererContext.labelingEngine()->registerDiagramFeature( this, fet, rendererContext ); } } #ifndef Q_WS_MAC ++featureCount; #endif //Q_WS_MAC } // find out the order QgsSymbolV2LevelOrder levels; QgsSymbolV2List symbols = mRendererV2->symbols(); for ( int i = 0; i < symbols.count(); i++ ) { QgsSymbolV2* sym = symbols[i]; for ( int j = 0; j < sym->symbolLayerCount(); j++ ) { int level = sym->symbolLayer( j )->renderingPass(); if ( level < 0 || level >= 1000 ) // ignore invalid levels continue; QgsSymbolV2LevelItem item( sym, j ); while ( level >= levels.count() ) // append new empty levels levels.append( QgsSymbolV2Level() ); levels[level].append( item ); } } // 2. draw features in correct order for ( int l = 0; l < levels.count(); l++ ) { QgsSymbolV2Level& level = levels[l]; for ( int i = 0; i < level.count(); i++ ) { QgsSymbolV2LevelItem& item = level[i]; if ( !features.contains( item.symbol() ) ) { QgsDebugMsg( "level item's symbol not found!" ); continue; } int layer = item.layer(); QList<QgsFeature>& lst = features[item.symbol()]; QList<QgsFeature>::iterator fit; #ifndef Q_WS_MAC featureCount = 0; #endif //Q_WS_MAC for ( fit = lst.begin(); fit != lst.end(); ++fit ) { if ( rendererContext.renderingStopped() ) { stopRendererV2( rendererContext, selRenderer ); return; } #ifndef Q_WS_MAC if ( featureCount % 1000 == 0 ) { qApp->processEvents(); } #endif //Q_WS_MAC bool sel = mSelectedFeatureIds.contains( fit->id() ); // maybe vertex markers should be drawn only during the last pass... bool drawMarker = ( mEditBuffer && ( !vertexMarkerOnlyForSelection || sel ) ); try { mRendererV2->renderFeature( *fit, rendererContext, layer, sel, drawMarker ); } catch ( const QgsCsException &cse ) { Q_UNUSED( cse ); QgsDebugMsg( QString( "Failed to transform a point while drawing a feature with ID '%1'. Ignoring this feature. %2" ) .arg( fet.id() ).arg( cse.what() ) ); } #ifndef Q_WS_MAC ++featureCount; #endif //Q_WS_MAC } } } stopRendererV2( rendererContext, selRenderer ); } void QgsVectorLayer::reload() { if ( mDataProvider ) { mDataProvider->reloadData(); } } bool QgsVectorLayer::draw( QgsRenderContext& rendererContext ) { if ( !hasGeometryType() ) return true; //set update threshold before each draw to make sure the current setting is picked up QSettings settings; mUpdateThreshold = settings.value( "Map/updateThreshold", 0 ).toInt(); #ifdef Q_WS_X11 mEnableBackbuffer = settings.value( "/Map/enableBackbuffer", 1 ).toBool(); #endif if ( mUsingRendererV2 ) { if ( !mRendererV2 ) return false; QgsDebugMsg( "rendering v2:\n" + mRendererV2->dump() ); if ( mEditBuffer ) { // Destroy all cached geometries and clear the references to them mCache->deleteCachedGeometries(); mCache->setCachedGeometriesRect( rendererContext.extent() ); // set editing vertex markers style mRendererV2->setVertexMarkerAppearance( currentVertexMarkerType(), currentVertexMarkerSize() ); } QgsAttributeList attributes; foreach ( QString attrName, mRendererV2->usedAttributes() ) { int attrNum = fieldNameIndex( attrName ); attributes.append( attrNum ); QgsDebugMsg( "attrs: " + attrName + " - " + QString::number( attrNum ) ); } bool labeling = false; //register label and diagram layer to the labeling engine prepareLabelingAndDiagrams( rendererContext, attributes, labeling ); select( attributes, rendererContext.extent() ); if (( mRendererV2->capabilities() & QgsFeatureRendererV2::SymbolLevels ) && mRendererV2->usingSymbolLevels() ) drawRendererV2Levels( rendererContext, labeling ); else drawRendererV2( rendererContext, labeling ); return true; } //draw ( p, viewExtent, theMapToPixelTransform, ct, drawingToEditingCanvas, 1., 1.); if ( mRenderer ) { // painter is active (begin has been called /* Steps to draw the layer 1. get the features in the view extent by SQL query 2. read WKB for a feature 3. transform 4. draw */ QPen pen; /*Pointer to a marker image*/ QImage marker; //vertex marker type for selection QgsVectorLayer::VertexMarkerType vertexMarker = QgsVectorLayer::NoMarker; int vertexMarkerSize = 7; if ( mEditBuffer ) { // Destroy all cached geometries and clear the references to them mCache->deleteCachedGeometries(); mCache->setCachedGeometriesRect( rendererContext.extent() ); vertexMarker = currentVertexMarkerType(); vertexMarkerSize = currentVertexMarkerSize(); mVertexMarkerOnlyForSelection = settings.value( "/qgis/digitizing/marker_only_for_selected", false ).toBool(); } // int totalFeatures = pendingFeatureCount(); int featureCount = 0; QgsFeature fet; QgsAttributeList attributes = mRenderer->classificationAttributes(); bool labeling = false; prepareLabelingAndDiagrams( rendererContext, attributes, labeling ); select( attributes, rendererContext.extent() ); try { while ( nextFeature( fet ) ) { if ( !fet.geometry() ) continue; // skip features without geometry if ( rendererContext.renderingStopped() ) { break; } #ifndef Q_WS_MAC //MH: disable this on Mac for now to avoid problems with resizing if ( mUpdateThreshold > 0 && 0 == featureCount % mUpdateThreshold ) { emit screenUpdateRequested(); // emit drawingProgress( featureCount, totalFeatures ); qApp->processEvents(); } else if ( featureCount % 1000 == 0 ) { // emit drawingProgress( featureCount, totalFeatures ); qApp->processEvents(); } // #else // Q_UNUSED( totalFeatures ); #endif //Q_WS_MAC // check if feature is selected // only show selections of the current layer // TODO: create a mechanism to let layer know whether it's current layer or not [MD] bool sel = mSelectedFeatureIds.contains( fet.id() ); mCurrentVertexMarkerType = QgsVectorLayer::NoMarker; mCurrentVertexMarkerSize = 7; if ( mEditBuffer ) { // Cache this for the use of (e.g.) modifying the feature's uncommitted geometry. mCache->cacheGeometry( fet.id(), *fet.geometry() ); if ( !mVertexMarkerOnlyForSelection || sel ) { mCurrentVertexMarkerType = vertexMarker; mCurrentVertexMarkerSize = vertexMarkerSize; } } //QgsDebugMsg(QString("markerScale before renderFeature(): %1").arg(markerScaleFactor)); // markerScalerFactore reflects the wanted scaling of the marker double opacity = 1.0; if ( !mRenderer->usesTransparency() ) { opacity = ( mTransparencyLevel * 1.0 ) / 255.0; } mRenderer->renderFeature( rendererContext, fet, &marker, sel, opacity ); // markerScalerFactore now reflects the actual scaling of the marker that the render performed. //QgsDebugMsg(QString("markerScale after renderFeature(): %1").arg(markerScaleFactor)); //double scale = rendererContext.scaleFactor() / markerScaleFactor; drawFeature( rendererContext, fet, &marker ); if ( mRenderer->willRenderFeature( &fet ) && rendererContext.labelingEngine() ) { if ( labeling ) { rendererContext.labelingEngine()->registerFeature( this, fet, rendererContext ); } if ( mDiagramRenderer ) { rendererContext.labelingEngine()->registerDiagramFeature( this, fet, rendererContext ); } } ++featureCount; } } catch ( QgsCsException &cse ) { Q_UNUSED( cse ); QgsDebugMsg( QString( "Failed to transform a point while drawing a feature with ID '%1'. Rendering stopped. %2" ) .arg( fet.id() ).arg( cse.what() ) ); return false; } QgsDebugMsg( QString( "Total features processed %1" ).arg( featureCount ) ); } else { QgsDebugMsg( "QgsRenderer is null" ); } if ( mEditBuffer ) { QgsDebugMsg( QString( "Cached %1 geometries." ).arg( mCache->cachedGeometries().count() ) ); } return true; // Assume success always } void QgsVectorLayer::drawVertexMarker( double x, double y, QPainter& p, QgsVectorLayer::VertexMarkerType type, int m ) { if ( type == QgsVectorLayer::SemiTransparentCircle ) { p.setPen( QColor( 50, 100, 120, 200 ) ); p.setBrush( QColor( 200, 200, 210, 120 ) ); p.drawEllipse( x - m, y - m, m * 2 + 1, m * 2 + 1 ); } else if ( type == QgsVectorLayer::Cross ) { p.setPen( QColor( 255, 0, 0 ) ); p.drawLine( x - m, y + m, x + m, y - m ); p.drawLine( x - m, y - m, x + m, y + m ); } } void QgsVectorLayer::select( QgsFeatureId fid, bool emitSignal ) { mSelectedFeatureIds.insert( fid ); if ( emitSignal ) { // invalidate cache setCacheImage( 0 ); emit selectionChanged(); } } void QgsVectorLayer::deselect( QgsFeatureId fid, bool emitSignal ) { mSelectedFeatureIds.remove( fid ); if ( emitSignal ) { // invalidate cache setCacheImage( 0 ); emit selectionChanged(); } } void QgsVectorLayer::select( QgsRectangle & rect, bool lock ) { // normalize the rectangle rect.normalize(); if ( !lock ) { removeSelection( false ); // don't emit signal } //select all the elements select( QgsAttributeList(), rect, false, true ); QgsFeature f; while ( nextFeature( f ) ) { select( f.id(), false ); // don't emit signal (not to redraw it everytime) } // invalidate cache setCacheImage( 0 ); emit selectionChanged(); // now emit signal to redraw layer } void QgsVectorLayer::invertSelection() { // copy the ids of selected features to tmp QgsFeatureIds tmp = mSelectedFeatureIds; removeSelection( false ); // don't emit signal select( QgsAttributeList(), QgsRectangle(), false ); QgsFeature fet; while ( nextFeature( fet ) ) { select( fet.id(), false ); // don't emit signal } for ( QgsFeatureIds::iterator iter = tmp.begin(); iter != tmp.end(); ++iter ) { mSelectedFeatureIds.remove( *iter ); } // invalidate cache setCacheImage( 0 ); emit selectionChanged(); } void QgsVectorLayer::invertSelectionInRectangle( QgsRectangle & rect ) { // normalize the rectangle rect.normalize(); select( QgsAttributeList(), rect, false, true ); QgsFeature fet; while ( nextFeature( fet ) ) { if ( mSelectedFeatureIds.contains( fet.id() ) ) { deselect( fet.id(), false ); // don't emit signal } else { select( fet.id(), false ); // don't emit signal } } // invalidate cache setCacheImage( 0 ); emit selectionChanged(); } void QgsVectorLayer::removeSelection( bool emitSignal ) { if ( mSelectedFeatureIds.size() == 0 ) return; mSelectedFeatureIds.clear(); if ( emitSignal ) { // invalidate cache setCacheImage( 0 ); emit selectionChanged(); } } void QgsVectorLayer::triggerRepaint() { emit repaintRequested(); } QgsVectorDataProvider* QgsVectorLayer::dataProvider() { return mDataProvider; } const QgsVectorDataProvider* QgsVectorLayer::dataProvider() const { return mDataProvider; } void QgsVectorLayer::setProviderEncoding( const QString& encoding ) { if ( mDataProvider ) { mDataProvider->setEncoding( encoding ); } } const QgsRenderer* QgsVectorLayer::renderer() const { return mRenderer; } void QgsVectorLayer::setRenderer( QgsRenderer *r ) { if ( !hasGeometryType() ) return; if ( r != mRenderer ) { if ( r ) setUsingRendererV2( false ); delete mRenderer; mRenderer = r; } } void QgsVectorLayer::setDiagramRenderer( QgsDiagramRendererV2* r ) { delete mDiagramRenderer; mDiagramRenderer = r; } QGis::GeometryType QgsVectorLayer::geometryType() const { if ( mDataProvider ) { int type = mDataProvider->geometryType(); switch ( type ) { case QGis::WKBPoint: case QGis::WKBPoint25D: return QGis::Point; case QGis::WKBLineString: case QGis::WKBLineString25D: return QGis::Line; case QGis::WKBPolygon: case QGis::WKBPolygon25D: return QGis::Polygon; case QGis::WKBMultiPoint: case QGis::WKBMultiPoint25D: return QGis::Point; case QGis::WKBMultiLineString: case QGis::WKBMultiLineString25D: return QGis::Line; case QGis::WKBMultiPolygon: case QGis::WKBMultiPolygon25D: return QGis::Polygon; case QGis::WKBNoGeometry: return QGis::NoGeometry; } QgsDebugMsg( QString( "Data Provider Geometry type is not recognised, is %1" ).arg( type ) ); } else { QgsDebugMsg( "pointer to mDataProvider is null" ); } // We shouldn't get here, and if we have, other things are likely to // go wrong. Code that uses the type() return value should be // rewritten to cope with a value of QGis::Unknown. To make this // need known, the following message is printed every time we get // here. QgsDebugMsg( "WARNING: This code should never be reached. Problems may occur..." ); return QGis::UnknownGeometry; } bool QgsVectorLayer::hasGeometryType() const { QGis::GeometryType t = geometryType(); return ( t != QGis::NoGeometry && t != QGis::UnknownGeometry ); } QGis::WkbType QgsVectorLayer::wkbType() const { return ( QGis::WkbType )( mWkbType ); } QgsRectangle QgsVectorLayer::boundingBoxOfSelected() { if ( mSelectedFeatureIds.size() == 0 ) //no selected features { return QgsRectangle( 0, 0, 0, 0 ); } QgsRectangle r, retval; retval.setMinimal(); QgsFeature fet; if ( mDataProvider->capabilities() & QgsVectorDataProvider::SelectAtId ) { foreach ( QgsFeatureId fid, mSelectedFeatureIds ) { if ( featureAtId( fid, fet, true, false ) && fet.geometry() ) { r = fet.geometry()->boundingBox(); retval.combineExtentWith( &r ); } } } else { select( QgsAttributeList(), QgsRectangle(), true ); while ( nextFeature( fet ) ) { if ( mSelectedFeatureIds.contains( fet.id() ) ) { if ( fet.geometry() ) { r = fet.geometry()->boundingBox(); retval.combineExtentWith( &r ); } } } } if ( retval.width() == 0.0 || retval.height() == 0.0 ) { // If all of the features are at the one point, buffer the // rectangle a bit. If they are all at zero, do something a bit // more crude. if ( retval.xMinimum() == 0.0 && retval.xMaximum() == 0.0 && retval.yMinimum() == 0.0 && retval.yMaximum() == 0.0 ) { retval.set( -1.0, -1.0, 1.0, 1.0 ); } } return retval; } long QgsVectorLayer::featureCount() const { if ( !mDataProvider ) { QgsDebugMsg( "invoked with null mDataProvider" ); return 0; } return mDataProvider->featureCount(); } long QgsVectorLayer::featureCount( QgsSymbolV2* symbol ) { if ( !mSymbolFeatureCounted ) return -1; return mSymbolFeatureCountMap.value( symbol ); } bool QgsVectorLayer::countSymbolFeatures( bool showProgress ) { if ( mSymbolFeatureCounted ) return true; mSymbolFeatureCountMap.clear(); if ( !mDataProvider ) { QgsDebugMsg( "invoked with null mDataProvider" ); return false; } if ( !mRendererV2 ) { QgsDebugMsg( "invoked with null mRendererV2" ); return false; } QgsLegendSymbolList symbolList = mRendererV2->legendSymbolItems(); QgsLegendSymbolList::const_iterator symbolIt = symbolList.constBegin(); for ( ; symbolIt != symbolList.constEnd(); ++symbolIt ) { mSymbolFeatureCountMap.insert( symbolIt->second, 0 ); } long nFeatures = pendingFeatureCount(); QProgressDialog progressDialog( tr( "Updating feature count for layer %1" ).arg( name() ), tr( "Abort" ), 0, nFeatures ); progressDialog.setWindowModality( Qt::WindowModal ); int featuresCounted = 0; select( pendingAllAttributesList(), QgsRectangle(), false, false ); QgsFeature f; // Renderer (rule based) may depend on context scale, with scale is ignored if 0 QgsRenderContext renderContext; renderContext.setRendererScale( 0 ); mRendererV2->startRender( renderContext, this ); while ( nextFeature( f ) ) { QgsSymbolV2List featureSymbolList = mRendererV2->symbolsForFeature( f ); for ( QgsSymbolV2List::iterator symbolIt = featureSymbolList.begin(); symbolIt != featureSymbolList.end(); ++symbolIt ) { mSymbolFeatureCountMap[*symbolIt] += 1; } ++featuresCounted; if ( showProgress ) { if ( featuresCounted % 50 == 0 ) { if ( featuresCounted > nFeatures ) //sometimes the feature count is not correct { progressDialog.setMaximum( 0 ); } progressDialog.setValue( featuresCounted ); if ( progressDialog.wasCanceled() ) { mSymbolFeatureCountMap.clear(); mRendererV2->stopRender( renderContext ); return false; } } } } mRendererV2->stopRender( renderContext ); progressDialog.setValue( nFeatures ); mSymbolFeatureCounted = true; return true; } long QgsVectorLayer::updateFeatureCount() const { return -1; } void QgsVectorLayer::updateExtents() { mValidExtent = false; } void QgsVectorLayer::setExtent( const QgsRectangle &r ) { QgsMapLayer::setExtent( r ); mValidExtent = true; } QgsRectangle QgsVectorLayer::extent() { if ( mValidExtent ) return QgsMapLayer::extent(); QgsRectangle rect; rect.setMinimal(); if ( !hasGeometryType() ) return rect; if ( !mDataProvider ) { QgsDebugMsg( "invoked with null mDataProvider" ); } if ( mEditBuffer && mEditBuffer->mDeletedFeatureIds.isEmpty() && mEditBuffer->mChangedGeometries.isEmpty() ) { mDataProvider->updateExtents(); // get the extent of the layer from the provider // but only when there are some features already if ( mDataProvider->featureCount() != 0 ) { QgsRectangle r = mDataProvider->extent(); rect.combineExtentWith( &r ); } for ( QgsFeatureMap::iterator it = mEditBuffer->mAddedFeatures.begin(); it != mEditBuffer->mAddedFeatures.end(); it++ ) { QgsRectangle r = it->geometry()->boundingBox(); rect.combineExtentWith( &r ); } } else { select( QgsAttributeList(), QgsRectangle(), true ); QgsFeature fet; while ( nextFeature( fet ) ) { if ( fet.geometry() ) { QgsRectangle bb = fet.geometry()->boundingBox(); rect.combineExtentWith( &bb ); } } } if ( rect.xMinimum() > rect.xMaximum() && rect.yMinimum() > rect.yMaximum() ) { // special case when there are no features in provider nor any added rect = QgsRectangle(); // use rectangle with zero coordinates } setExtent( rect ); // Send this (hopefully) up the chain to the map canvas emit recalculateExtents(); return rect; } QString QgsVectorLayer::subsetString() { if ( ! mDataProvider ) { QgsDebugMsg( "invoked with null mDataProvider" ); return 0; } return mDataProvider->subsetString(); } bool QgsVectorLayer::setSubsetString( QString subset ) { if ( ! mDataProvider ) { QgsDebugMsg( "invoked with null mDataProvider" ); return false; } bool res = mDataProvider->setSubsetString( subset ); // get the updated data source string from the provider mDataSource = mDataProvider->dataSourceUri(); updateExtents(); if ( res ) setCacheImage( 0 ); return res; } #if 0 void QgsVectorLayer::addJoinedAttributes( QgsFeature& f, bool all ) { if ( all || ( mFetchAttributes.size() > 0 && mJoinBuffer->containsFetchJoins() ) ) { mJoinBuffer->updateFeatureAttributes( f, f.attributes().count(), all ); } } #endif void QgsVectorLayer::select( QgsAttributeList attributes, QgsRectangle rect, bool fetchGeometries, bool useIntersect ) { #if 0 if ( !mDataProvider ) return; mFetching = true; mFetchRect = rect; mFetchAttributes = attributes; mFetchGeometry = fetchGeometries; mFetchConsidered = mEditBuffer ? mEditBuffer->mDeletedFeatureIds : QSet<QgsFeatureId>(); QgsAttributeList targetJoinFieldList; if ( mEditBuffer ) { mFetchAddedFeaturesIt = mEditBuffer->mAddedFeatures.begin(); mFetchChangedGeomIt = mEditBuffer->mChangedGeometries.begin(); } //look in the normal features of the provider if ( mFetchAttributes.size() > 0 ) { if ( mEditBuffer || mJoinBuffer->containsJoins() ) { QgsAttributeList joinFields; mJoinBuffer->select( mFetchAttributes, joinFields, mUpdatedFields ); QgsAttributeList::const_iterator joinFieldIt = joinFields.constBegin(); for ( ; joinFieldIt != joinFields.constEnd(); ++joinFieldIt ) { if ( !mFetchAttributes.contains( *joinFieldIt ) ) { mFetchAttributes.append( *joinFieldIt ); } } //detect which fields are from the provider mFetchProvAttributes.clear(); const QgsFields& provFields = mDataProvider->fields(); for ( QgsAttributeList::iterator it = mFetchAttributes.begin(); it != mFetchAttributes.end(); it++ ) { if ( *it < provFields.count() ) { mFetchProvAttributes << *it; } } mProviderIterator = mDataProvider->select( mFetchProvAttributes, rect, fetchGeometries, useIntersect ); } else { mProviderIterator = mDataProvider->select( mFetchAttributes, rect, fetchGeometries, useIntersect ); } } else //we don't need any attributes at all { mProviderIterator = mDataProvider->select( QgsAttributeList(), rect, fetchGeometries, useIntersect ); } #endif QgsFeatureRequest request; if ( !rect.isEmpty() ) request.setFilterRect( rect ); if ( !fetchGeometries ) request.setFlags( QgsFeatureRequest::NoGeometry ); if ( useIntersect ) request.setFlags( request.flags() | QgsFeatureRequest::ExactIntersect ); if ( attributes != pendingAllAttributesList() ) request.setSubsetOfAttributes( attributes ); mLayerIterator = getFeatures( request ); } #if 0 void QgsVectorLayer::select( const QgsFeatureRequest& request ) { mLayerIterator = getFeatures( request ); /* QgsAttributeList attrs; if ( !( request.flags() & QgsFeatureRequest::SubsetOfAttributes ) ) attrs = request.subsetOfAttributes(); else attrs = pendingAllAttributesList(); bool fetchGeom = !( request.flags() & QgsFeatureRequest::NoGeometry ); bool exactIntersect = ( request.flags() & QgsFeatureRequest::ExactIntersect ); select( attrs, request.filterRect(), fetchGeom, exactIntersect ); */ } #endif QgsFeatureIterator QgsVectorLayer::getFeatures( const QgsFeatureRequest& request ) { if ( !mDataProvider ) return QgsFeatureIterator(); return QgsFeatureIterator( new QgsVectorLayerFeatureIterator(this, request) ); } bool QgsVectorLayer::nextFeature( QgsFeature &f ) { return mLayerIterator.nextFeature( f ); #if 0 if ( !mFetching ) return false; if ( mEditBuffer ) { if ( !mFetchRect.isEmpty() ) { // check if changed geometries are in rectangle for ( ; mFetchChangedGeomIt != mEditBuffer->mChangedGeometries.end(); mFetchChangedGeomIt++ ) { QgsFeatureId fid = mFetchChangedGeomIt.key(); if ( mFetchConsidered.contains( fid ) ) // skip deleted features continue; mFetchConsidered << fid; if ( !mFetchChangedGeomIt->intersects( mFetchRect ) ) // skip changed geometries not in rectangle and don't check again continue; f.setFeatureId( fid ); f.setValid( true ); if ( mFetchGeometry ) f.setGeometry( mFetchChangedGeomIt.value() ); if ( mFetchAttributes.size() > 0 ) { if ( fid < 0 ) { // fid<0 => in mAddedFeatures bool found = false; for ( QgsFeatureList::iterator it = mEditBuffer->mAddedFeatures.begin(); it != mEditBuffer->mAddedFeatures.end(); it++ ) { if ( fid == it->id() ) { found = true; f.setAttributes( it->attributes() ); // no need to update (always up-to-date) updateFeatureAttributes( f ); break; } } if ( !found ) { QgsDebugMsg( QString( "No attributes for the added feature %1 found" ).arg( f.id() ) ); } } else { // retrieve attributes from provider QgsFeature tmp; //mDataProvider->featureAtId( fid, tmp, false, mFetchProvAttributes ); QgsFeatureRequest request; request.setFilterFid( fid ).setSubsetOfAttributes( mFetchProvAttributes ); QgsFeatureIterator fi = mDataProvider->getFeatures( request ); if ( fi.nextFeature( tmp ) ) { if (mEditBuffer) mEditBuffer->updateChangedAttributes( tmp ); f.setAttributes( tmp.attributes() ); } } } addJoinedAttributes( f ); // return complete feature mFetchChangedGeomIt++; return true; } // no more changed geometries } for ( ; mFetchAddedFeaturesIt != mEditBuffer->mAddedFeatures.end(); mFetchAddedFeaturesIt++ ) { QgsFeatureId fid = mFetchAddedFeaturesIt->id(); if ( mFetchConsidered.contains( fid ) ) // must have changed geometry outside rectangle continue; if ( !mFetchRect.isEmpty() && mFetchAddedFeaturesIt->geometry() && !mFetchAddedFeaturesIt->geometry()->intersects( mFetchRect ) ) // skip added features not in rectangle continue; f.setFeatureId( fid ); f.setValid( true ); if ( mFetchGeometry ) f.setGeometry( *mFetchAddedFeaturesIt->geometry() ); if ( mFetchAttributes.size() > 0 ) { f.setAttributes( mFetchAddedFeaturesIt->attributes() ); // no need to do this (always up-to-date) updateFeatureAttributes( f ); } addJoinedAttributes( f ); mFetchAddedFeaturesIt++; return true; } // no more added features } while ( mProviderIterator.nextFeature( f ) ) { if ( mFetchConsidered.contains( f.id() ) ) { continue; } if ( mFetchAttributes.size() > 0 ) { if (mEditBuffer) mEditBuffer->updateChangedAttributes( f ); //check changed attributes addJoinedAttributes( f ); // check joined attributes } if ( mEditBuffer && mFetchGeometry ) { mEditBuffer->updateFeatureGeometry( f ); } return true; } mFetching = false; return false; #endif } bool QgsVectorLayer::featureAtId( QgsFeatureId featureId, QgsFeature& f, bool fetchGeometries, bool fetchAttributes ) { QgsFeatureRequest request; request.setFilterFid( featureId ); if ( !fetchGeometries ) request.setFlags( QgsFeatureRequest::NoGeometry ); if ( !fetchAttributes ) request.setSubsetOfAttributes( QgsAttributeList() ); QgsFeatureIterator fi = getFeatures( request ); return fi.nextFeature( f ); #if 0 if ( !mDataProvider ) return false; if ( mEditBuffer && mEditBuffer->mDeletedFeatureIds.contains( featureId ) ) return false; if ( fetchGeometries && mEditBuffer->mChangedGeometries.contains( featureId ) ) { f.setFeatureId( featureId ); f.setValid( true ); f.setGeometry( mEditBuffer->mChangedGeometries[featureId] ); if ( fetchAttributes ) { if ( featureId < 0 && mEditBuffer ) { // featureId<0 => in mAddedFeatures bool found = false; for ( QgsFeatureList::iterator it = mEditBuffer->mAddedFeatures.begin(); it != mEditBuffer->mAddedFeatures.end(); it++ ) { if ( featureId != it->id() ) { found = true; f.setAttributes( it->attributes() ); break; } } if ( !found ) { QgsDebugMsg( QString( "No attributes for the added feature %1 found" ).arg( f.id() ) ); } } else { // retrieve attributes from provider QgsFeature tmp; //mDataProvider->featureAtId( featureId, tmp, false, mDataProvider->attributeIndexes() ); QgsFeatureRequest request; request.setFilterFid( featureId ).setFlags( QgsFeatureRequest::NoGeometry ); QgsFeatureIterator fi = mDataProvider->getFeatures( request ); if ( fi.nextFeature( tmp ) ) { f.setAttributes( tmp.attributes() ); if (mEditBuffer) mEditBuffer->updateChangedAttributes( f ); } } addJoinedAttributes( f, true ); } return true; } //added features if (mEditBuffer) { for ( QgsFeatureList::iterator iter = mEditBuffer->mAddedFeatures.begin(); iter != mEditBuffer->mAddedFeatures.end(); ++iter ) { if ( iter->id() == featureId ) { f.setFeatureId( iter->id() ); f.setValid( true ); if ( fetchGeometries ) f.setGeometry( *iter->geometry() ); if ( fetchAttributes ) f.setAttributes( iter->attributes() ); return true; } } } // regular features QgsFeatureRequest request; request.setFilterFid( featureId ); if ( !fetchGeometries ) request.setFlags( QgsFeatureRequest::NoGeometry ); if ( !fetchAttributes ) request.setSubsetOfAttributes( QgsAttributeList() ); QgsFeatureIterator fi = mDataProvider->getFeatures( request ); if ( fi.nextFeature( f ) ) { if (mEditBuffer) mEditBuffer->updateChangedAttributes( f ); addJoinedAttributes( f, true ); return true; } return false; #endif } bool QgsVectorLayer::addFeature( QgsFeature& f, bool alsoUpdateExtent ) { if ( !mEditBuffer || !mDataProvider ) return false; // TODO[MD]: alsoUpdateExtent return mEditBuffer->addFeature(f); } bool QgsVectorLayer::updateFeature( QgsFeature &f ) { QgsFeature current; if ( !featureAtId( f.id(), current, f.geometry(), !f.attributes().isEmpty() ) ) { QgsDebugMsg( QString( "feature %1 could not be retrieved" ).arg( f.id() ) ); return false; } if ( f.geometry() && current.geometry() && f.geometry() != current.geometry() && !f.geometry()->isGeosEqual( *current.geometry() ) ) { if ( !changeGeometry( f.id(), f.geometry() ) ) { QgsDebugMsg( QString( "geometry of feature %1 could not be changed." ).arg( f.id() ) ); return false; } } const QgsAttributes &fa = f.attributes(); const QgsAttributes &ca = current.attributes(); for ( int attr = 0; attr < fa.count(); ++attr ) { if ( fa[attr] != ca[attr] ) { if ( !changeAttributeValue( f.id(), attr, fa[attr] ) ) { QgsDebugMsg( QString( "attribute %1 of feature %2 could not be changed." ).arg( attr ).arg( f.id() ) ); return false; } } } return true; } bool QgsVectorLayer::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex ) { if ( !mEditBuffer || !mDataProvider ) return false; QgsVectorLayerEditUtils utils(this); return utils.insertVertex( x, y, atFeatureId, beforeVertex ); } bool QgsVectorLayer::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex ) { if ( !mEditBuffer || !mDataProvider ) return false; QgsVectorLayerEditUtils utils(this); return utils.moveVertex( x, y, atFeatureId, atVertex ); } bool QgsVectorLayer::deleteVertex( QgsFeatureId atFeatureId, int atVertex ) { if ( !mEditBuffer || !mDataProvider ) return false; QgsVectorLayerEditUtils utils(this); return utils.deleteVertex( atFeatureId, atVertex ); } bool QgsVectorLayer::deleteSelectedFeatures() { if ( !( mDataProvider->capabilities() & QgsVectorDataProvider::DeleteFeatures ) ) { return false; } if ( !isEditable() ) { return false; } if ( mSelectedFeatureIds.size() == 0 ) return true; while ( mSelectedFeatureIds.size() > 0 ) { QgsFeatureId fid = *mSelectedFeatureIds.begin(); deleteFeature( fid ); // removes from selection } // invalidate cache setCacheImage( 0 ); emit selectionChanged(); triggerRepaint(); updateExtents(); return true; } int QgsVectorLayer::addRing( const QList<QgsPoint>& ring ) { if ( !mEditBuffer || !mDataProvider ) return 6; QgsVectorLayerEditUtils utils(this); return utils.addRing( ring ); } int QgsVectorLayer::addPart( const QList<QgsPoint> &points ) { if ( !mEditBuffer || !mDataProvider ) return 7; //number of selected features must be 1 if ( mSelectedFeatureIds.size() < 1 ) { QgsDebugMsg( "Number of selected features <1" ); return 4; } else if ( mSelectedFeatureIds.size() > 1 ) { QgsDebugMsg( "Number of selected features >1" ); return 5; } QgsVectorLayerEditUtils utils(this); return utils.addPart( points, *mSelectedFeatureIds.constBegin() ); } int QgsVectorLayer::translateFeature( QgsFeatureId featureId, double dx, double dy ) { if (!mEditBuffer || !mDataProvider) return -1; QgsVectorLayerEditUtils utils(this); return utils.translateFeature( featureId, dx, dy ); } int QgsVectorLayer::splitFeatures( const QList<QgsPoint>& splitLine, bool topologicalEditing ) { if (!mEditBuffer || !mDataProvider) return -1; QgsVectorLayerEditUtils utils(this); return utils.splitFeatures( splitLine, topologicalEditing ); } int QgsVectorLayer::removePolygonIntersections( QgsGeometry* geom, QgsFeatureIds ignoreFeatures ) { if ( !hasGeometryType() ) return 1; int returnValue = 0; //first test if geom really has type polygon or multipolygon if ( geom->type() != QGis::Polygon ) { return 1; } //get bounding box of geom QgsRectangle geomBBox = geom->boundingBox(); //get list of features that intersect this bounding box select( QgsAttributeList(), geomBBox, true, true ); QgsFeature f; while ( nextFeature( f ) ) { if ( ignoreFeatures.contains( f.id() ) ) { continue; } //call geometry->makeDifference for each feature QgsGeometry *currentGeom = f.geometry(); if ( currentGeom ) { if ( geom->makeDifference( currentGeom ) != 0 ) { returnValue = 2; } } } return returnValue; } int QgsVectorLayer::addTopologicalPoints( QgsGeometry* geom ) { if (!mEditBuffer || !mDataProvider) return -1; QgsVectorLayerEditUtils utils(this); return utils.addTopologicalPoints( geom ); } int QgsVectorLayer::addTopologicalPoints( const QgsPoint& p ) { if (!mEditBuffer || !mDataProvider) return -1; QgsVectorLayerEditUtils utils(this); return utils.addTopologicalPoints( p ); } QgsLabel *QgsVectorLayer::label() { return mLabel; } const QgsLabel *QgsVectorLayer::label() const { return mLabel; } void QgsVectorLayer::enableLabels( bool on ) { mLabelOn = on; } bool QgsVectorLayer::hasLabelsEnabled( void ) const { return mLabelOn; } bool QgsVectorLayer::startEditing() { if ( !mDataProvider ) { return false; } // allow editing if provider supports any of the capabilities if ( !( mDataProvider->capabilities() & QgsVectorDataProvider::EditingCapabilities ) ) { return false; } if ( mReadOnly ) { return false; } if ( mEditBuffer ) { // editing already underway return false; } mEditBuffer = new QgsVectorLayerEditBuffer(this); // forward signals connect(mEditBuffer, SIGNAL(layerModified()), this, SIGNAL(layerModified())); // TODO[MD]: necessary? connect(mEditBuffer, SIGNAL(layerModified()), this, SLOT(triggerRepaint())); // TODO[MD]: works well? connect(mEditBuffer, SIGNAL(featureAdded(QgsFeatureId)), this, SIGNAL(featureAdded(QgsFeatureId))); connect(mEditBuffer, SIGNAL(featureDeleted(QgsFeatureId)), this, SIGNAL(featureDeleted(QgsFeatureId))); connect(mEditBuffer, SIGNAL(geometryChanged(QgsFeatureId,QgsGeometry&)), this, SIGNAL(geometryChanged(QgsFeatureId,QgsGeometry&))); connect(mEditBuffer, SIGNAL(attributeValueChanged(QgsFeatureId,int,QVariant)), this, SIGNAL(attributeValueChanged(QgsFeatureId,int,QVariant))); connect(mEditBuffer, SIGNAL(attributeAdded(int)), this, SIGNAL(attributeAdded(int))); connect(mEditBuffer, SIGNAL(attributeDeleted(int)), this, SIGNAL(attributeDeleted(int))); updateFields(); emit editingStarted(); return true; } bool QgsVectorLayer::readXml( const QDomNode& layer_node ) { QgsDebugMsg( QString( "Datasource in QgsVectorLayer::readXml: " ) + mDataSource.toLocal8Bit().data() ); //process provider key QDomNode pkeyNode = layer_node.namedItem( "provider" ); if ( pkeyNode.isNull() ) { mProviderKey = ""; } else { QDomElement pkeyElt = pkeyNode.toElement(); mProviderKey = pkeyElt.text(); } // determine type of vector layer if ( ! mProviderKey.isNull() ) { // if the provider string isn't empty, then we successfully // got the stored provider } else if ( mDataSource.contains( "dbname=" ) ) { mProviderKey = "postgres"; } else { mProviderKey = "ogr"; } if ( ! setDataProvider( mProviderKey ) ) { return false; } QDomElement pkeyElem = pkeyNode.toElement(); if ( !pkeyElem.isNull() ) { QString encodingString = pkeyElem.attribute( "encoding" ); if ( !encodingString.isEmpty() ) { mDataProvider->setEncoding( encodingString ); } } //load vector joins if ( !mJoinBuffer ) { mJoinBuffer = new QgsVectorLayerJoinBuffer(); } mJoinBuffer->readXml( layer_node ); updateFields(); connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( checkJoinLayerRemove( QString ) ) ); QString errorMsg; if ( !readSymbology( layer_node, errorMsg ) ) { return false; } return mValid; // should be true if read successfully } // void QgsVectorLayer::readXml bool QgsVectorLayer::setDataProvider( QString const & provider ) { // XXX should I check for and possibly delete any pre-existing providers? // XXX How often will that scenario occur? mProviderKey = provider; // XXX is this necessary? Usually already set // XXX when execution gets here. //XXX - This was a dynamic cast but that kills the Windows // version big-time with an abnormal termination error mDataProvider = ( QgsVectorDataProvider* )( QgsProviderRegistry::instance()->provider( provider, mDataSource ) ); if ( mDataProvider ) { QgsDebugMsg( "Instantiated the data provider plugin" ); mValid = mDataProvider->isValid(); if ( mValid ) { // TODO: Check if the provider has the capability to send fullExtentCalculated connect( mDataProvider, SIGNAL( fullExtentCalculated() ), this, SLOT( updateExtents() ) ); // get the extent QgsRectangle mbr = mDataProvider->extent(); // show the extent QString s = mbr.toString(); QgsDebugMsg( "Extent of layer: " + s ); // store the extent setExtent( mbr ); // get and store the feature type mWkbType = mDataProvider->geometryType(); // look at the fields in the layer and set the primary // display field using some real fuzzy logic setDisplayField(); if ( mProviderKey == "postgres" ) { QgsDebugMsg( "Beautifying layer name " + name() ); // adjust the display name for postgres layers QRegExp reg( "\"[^\"]+\"\\.\"([^\"]+)\"( \\([^)]+\\))?" ); if ( reg.indexIn( name() ) >= 0 ) { QStringList stuff = reg.capturedTexts(); QString lName = stuff[1]; const QMap<QString, QgsMapLayer*> &layers = QgsMapLayerRegistry::instance()->mapLayers(); QMap<QString, QgsMapLayer*>::const_iterator it; for ( it = layers.constBegin(); it != layers.constEnd() && ( *it )->name() != lName; it++ ) ; if ( it != layers.constEnd() && stuff.size() > 2 ) { lName += "." + stuff[2].mid( 2, stuff[2].length() - 3 ); } if ( !lName.isEmpty() ) setLayerName( lName ); } QgsDebugMsg( "Beautified layer name " + name() ); // deal with unnecessary schema qualification to make v.in.ogr happy mDataSource = mDataProvider->dataSourceUri(); } else if ( mProviderKey == "osm" ) { // make sure that the "observer" has been removed from URI to avoid crashes mDataSource = mDataProvider->dataSourceUri(); } else if ( provider == "ogr" ) { // make sure that the /vsigzip or /vsizip is added to uri, if applicable mDataSource = mDataProvider->dataSourceUri(); if ( mDataSource.right( 10 ) == "|layerid=0" ) mDataSource.chop( 10 ); } // label mLabel = new QgsLabel( mDataProvider->fields() ); mLabelOn = false; } else { QgsDebugMsg( "Invalid provider plugin " + QString( mDataSource.toUtf8() ) ); return false; } } else { QgsDebugMsg( " unable to get data provider" ); return false; } return true; } // QgsVectorLayer:: setDataProvider /* virtual */ bool QgsVectorLayer::writeXml( QDomNode & layer_node, QDomDocument & document ) { // first get the layer element so that we can append the type attribute QDomElement mapLayerNode = layer_node.toElement(); if ( mapLayerNode.isNull() || ( "maplayer" != mapLayerNode.nodeName() ) ) { QgsDebugMsg( "can't find <maplayer>" ); return false; } mapLayerNode.setAttribute( "type", "vector" ); // set the geometry type mapLayerNode.setAttribute( "geometry", QGis::qgisVectorGeometryType[geometryType()] ); // add provider node if ( mDataProvider ) { QDomElement provider = document.createElement( "provider" ); provider.setAttribute( "encoding", mDataProvider->encoding() ); QDomText providerText = document.createTextNode( providerType() ); provider.appendChild( providerText ); layer_node.appendChild( provider ); } //save joins mJoinBuffer->writeXml( layer_node, document ); // renderer specific settings QString errorMsg; return writeSymbology( layer_node, document, errorMsg ); } // bool QgsVectorLayer::writeXml bool QgsVectorLayer::readSymbology( const QDomNode& node, QString& errorMessage ) { if ( hasGeometryType() ) { // try renderer v2 first QDomElement rendererElement = node.firstChildElement( RENDERER_TAG_NAME ); if ( !rendererElement.isNull() ) { // using renderer v2 setUsingRendererV2( true ); QgsFeatureRendererV2* r = QgsFeatureRendererV2::load( rendererElement ); if ( !r ) return false; setRendererV2( r ); } else { // using renderer v1 setUsingRendererV2( false ); // create and bind a renderer to this layer QDomNode singlenode = node.namedItem( "singlesymbol" ); QDomNode graduatednode = node.namedItem( "graduatedsymbol" ); QDomNode continuousnode = node.namedItem( "continuoussymbol" ); QDomNode uniquevaluenode = node.namedItem( "uniquevalue" ); QgsRenderer * renderer = 0; int returnCode = 1; if ( !singlenode.isNull() ) { renderer = new QgsSingleSymbolRenderer( geometryType() ); returnCode = renderer->readXML( singlenode, *this ); } else if ( !graduatednode.isNull() ) { renderer = new QgsGraduatedSymbolRenderer( geometryType() ); returnCode = renderer->readXML( graduatednode, *this ); } else if ( !continuousnode.isNull() ) { renderer = new QgsContinuousColorRenderer( geometryType() ); returnCode = renderer->readXML( continuousnode, *this ); } else if ( !uniquevaluenode.isNull() ) { renderer = new QgsUniqueValueRenderer( geometryType() ); returnCode = renderer->readXML( uniquevaluenode, *this ); } if ( !renderer ) { errorMessage = tr( "Unknown renderer" ); return false; } if ( returnCode == 1 ) { errorMessage = tr( "No renderer object" ); delete renderer; return false; } else if ( returnCode == 2 ) { errorMessage = tr( "Classification field not found" ); delete renderer; return false; } mRenderer = renderer; } // get and set the display field if it exists. QDomNode displayFieldNode = node.namedItem( "displayfield" ); if ( !displayFieldNode.isNull() ) { QDomElement e = displayFieldNode.toElement(); setDisplayField( e.text() ); } // use scale dependent visibility flag QDomElement e = node.toElement(); mLabel->setScaleBasedVisibility( e.attribute( "scaleBasedLabelVisibilityFlag", "0" ) == "1" ); mLabel->setMinScale( e.attribute( "minLabelScale", "1" ).toFloat() ); mLabel->setMaxScale( e.attribute( "maxLabelScale", "100000000" ).toFloat() ); //also restore custom properties (for labeling-ng) readCustomProperties( node, "labeling" ); // tab display QDomNode editorLayoutNode = node.namedItem( "editorlayout" ); if ( editorLayoutNode.isNull() ) { mEditorLayout = GeneratedLayout; } else { if ( editorLayoutNode.toElement().text() == "uifilelayout" ) { mEditorLayout = UiFileLayout; } else if ( editorLayoutNode.toElement().text() == "tablayout" ) { mEditorLayout = TabLayout; } else { mEditorLayout = GeneratedLayout; } } // Test if labeling is on or off QDomNode labelnode = node.namedItem( "label" ); QDomElement element = labelnode.toElement(); int hasLabelsEnabled = element.text().toInt(); if ( hasLabelsEnabled < 1 ) { enableLabels( false ); } else { enableLabels( true ); } QDomNode labelattributesnode = node.namedItem( "labelattributes" ); if ( !labelattributesnode.isNull() ) { QgsDebugMsg( "calling readXML" ); mLabel->readXML( labelattributesnode ); } //diagram renderer and diagram layer settings delete mDiagramRenderer; mDiagramRenderer = 0; QDomElement singleCatDiagramElem = node.firstChildElement( "SingleCategoryDiagramRenderer" ); if ( !singleCatDiagramElem.isNull() ) { mDiagramRenderer = new QgsSingleCategoryDiagramRenderer(); mDiagramRenderer->readXML( singleCatDiagramElem ); } QDomElement linearDiagramElem = node.firstChildElement( "LinearlyInterpolatedDiagramRenderer" ); if ( !linearDiagramElem.isNull() ) { mDiagramRenderer = new QgsLinearlyInterpolatedDiagramRenderer(); mDiagramRenderer->readXML( linearDiagramElem ); } if ( mDiagramRenderer ) { QDomElement diagramSettingsElem = node.firstChildElement( "DiagramLayerSettings" ); if ( !diagramSettingsElem.isNull() ) { mDiagramLayerSettings = new QgsDiagramLayerSettings(); mDiagramLayerSettings->readXML( diagramSettingsElem ); } } } // process the attribute actions mActions->readXML( node ); mEditTypes.clear(); QDomNode editTypesNode = node.namedItem( "edittypes" ); if ( !editTypesNode.isNull() ) { QDomNodeList editTypeNodes = editTypesNode.childNodes(); for ( int i = 0; i < editTypeNodes.size(); i++ ) { QDomNode editTypeNode = editTypeNodes.at( i ); QDomElement editTypeElement = editTypeNode.toElement(); QString name = editTypeElement.attribute( "name" ); if ( fieldNameIndex( name ) < -1 ) continue; EditType editType = ( EditType ) editTypeElement.attribute( "type" ).toInt(); mEditTypes.insert( name, editType ); switch ( editType ) { case ValueMap: if ( editTypeNode.hasChildNodes() ) { mValueMaps.insert( name, QMap<QString, QVariant>() ); QDomNodeList valueMapNodes = editTypeNode.childNodes(); for ( int j = 0; j < valueMapNodes.size(); j++ ) { QDomElement value = valueMapNodes.at( j ).toElement(); mValueMaps[ name ].insert( value.attribute( "key" ), value.attribute( "value" ) ); } } break; case EditRange: case SliderRange: case DialRange: { QVariant min = editTypeElement.attribute( "min" ); QVariant max = editTypeElement.attribute( "max" ); QVariant step = editTypeElement.attribute( "step" ); mRanges[ name ] = RangeData( min, max, step ); } break; case CheckBox: mCheckedStates[ name ] = QPair<QString, QString>( editTypeElement.attribute( "checked" ), editTypeElement.attribute( "unchecked" ) ); break; case ValueRelation: { QString id = editTypeElement.attribute( "layer" ); QString key = editTypeElement.attribute( "key" ); QString value = editTypeElement.attribute( "value" ); bool allowNull = editTypeElement.attribute( "allowNull" ) == "true"; bool orderByValue = editTypeElement.attribute( "orderByValue" ) == "true"; bool allowMulti = editTypeElement.attribute( "allowMulti", "false" ) == "true"; QString filterAttributeColumn = editTypeElement.attribute( "filterAttributeColumn", QString::null ); QString filterAttributeValue = editTypeElement.attribute( "filterAttributeValue", QString::null ); mValueRelations[ name ] = ValueRelationData( id, key, value, allowNull, orderByValue, allowMulti, filterAttributeColumn, filterAttributeValue ); } break; case Classification: case FileName: case Immutable: case Hidden: case LineEdit: case TextEdit: case Calendar: case Enumeration: case UniqueValues: case UniqueValuesEditable: case UuidGenerator: break; } } } QDomNode editFormNode = node.namedItem( "editform" ); if ( !editFormNode.isNull() ) { QDomElement e = editFormNode.toElement(); mEditForm = QgsProject::instance()->readPath( e.text() ); } QDomNode editFormInitNode = node.namedItem( "editforminit" ); if ( !editFormInitNode.isNull() ) { mEditFormInit = editFormInitNode.toElement().text(); } QDomNode annotationFormNode = node.namedItem( "annotationform" ); if ( !annotationFormNode.isNull() ) { QDomElement e = annotationFormNode.toElement(); mAnnotationForm = QgsProject::instance()->readPath( e.text() ); } mAttributeAliasMap.clear(); QDomNode aliasesNode = node.namedItem( "aliases" ); if ( !aliasesNode.isNull() ) { QDomElement aliasElem; QString name; QDomNodeList aliasNodeList = aliasesNode.toElement().elementsByTagName( "alias" ); for ( int i = 0; i < aliasNodeList.size(); ++i ) { aliasElem = aliasNodeList.at( i ).toElement(); QString field; if ( aliasElem.hasAttribute( "field" ) ) { field = aliasElem.attribute( "field" ); } else { int index = aliasElem.attribute( "index" ).toInt(); if ( index >= 0 && index < pendingFields().count() ) field = pendingFields()[ index ].name(); } mAttributeAliasMap.insert( field, aliasElem.attribute( "name" ) ); } } //Attributes excluded from WMS and WFS mExcludeAttributesWMS.clear(); QDomNode excludeWMSNode = node.namedItem( "excludeAttributesWMS" ); if ( !excludeWMSNode.isNull() ) { QDomNodeList attributeNodeList = excludeWMSNode.toElement().elementsByTagName( "attribute" ); for ( int i = 0; i < attributeNodeList.size(); ++i ) { mExcludeAttributesWMS.insert( attributeNodeList.at( i ).toElement().text() ); } } mExcludeAttributesWFS.clear(); QDomNode excludeWFSNode = node.namedItem( "excludeAttributesWFS" ); if ( !excludeWFSNode.isNull() ) { QDomNodeList attributeNodeList = excludeWFSNode.toElement().elementsByTagName( "attribute" ); for ( int i = 0; i < attributeNodeList.size(); ++i ) { mExcludeAttributesWFS.insert( attributeNodeList.at( i ).toElement().text() ); } } // tabs and groups display info mAttributeEditorElements.clear(); QDomNode attributeEditorFormNode = node.namedItem( "attributeEditorForm" ); QDomNodeList attributeEditorFormNodeList = attributeEditorFormNode.toElement().childNodes(); for ( int i = 0; i < attributeEditorFormNodeList.size(); i++ ) { QDomElement elem = attributeEditorFormNodeList.at( i ).toElement(); QgsAttributeEditorElement *attributeEditorWidget = attributeEditorElementFromDomElement( elem, this ); mAttributeEditorElements.append( attributeEditorWidget ); } return true; } QgsAttributeEditorElement* QgsVectorLayer::attributeEditorElementFromDomElement( QDomElement &elem, QObject* parent ) { QgsAttributeEditorElement* newElement = NULL; if ( elem.tagName() == "attributeEditorContainer" ) { QgsAttributeEditorContainer* container = new QgsAttributeEditorContainer( elem.attribute( "name" ), parent ); QDomNodeList childNodeList = elem.childNodes(); for ( int i = 0; i < childNodeList.size(); i++ ) { QDomElement childElem = childNodeList.at( i ).toElement(); QgsAttributeEditorElement* myElem = attributeEditorElementFromDomElement( childElem, container ); container->addChildElement( myElem ); } newElement = container; } else if ( elem.tagName() == "attributeEditorField" ) { newElement = new QgsAttributeEditorField( elem.attribute( "name" ), elem.attribute( "idx" ).toInt(), parent ); } return newElement; } bool QgsVectorLayer::writeSymbology( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const { QDomElement mapLayerNode = node.toElement(); if ( hasGeometryType() ) { if ( mUsingRendererV2 ) { QDomElement rendererElement = mRendererV2->save( doc ); node.appendChild( rendererElement ); } else { // use scale dependent visibility flag mapLayerNode.setAttribute( "scaleBasedLabelVisibilityFlag", mLabel->scaleBasedVisibility() ? 1 : 0 ); mapLayerNode.setAttribute( "minLabelScale", QString::number( mLabel->minScale() ) ); mapLayerNode.setAttribute( "maxLabelScale", QString::number( mLabel->maxScale() ) ); //classification field(s) QgsAttributeList attributes = mRenderer->classificationAttributes(); const QgsFields& providerFields = mDataProvider->fields(); for ( QgsAttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it ) { QDomElement classificationElement = doc.createElement( "classificationattribute" ); QDomText classificationText = doc.createTextNode( providerFields[*it].name() ); classificationElement.appendChild( classificationText ); node.appendChild( classificationElement ); } // renderer settings const QgsRenderer * myRenderer = renderer(); if ( myRenderer ) { if ( !myRenderer->writeXML( node, doc, *this ) ) { errorMessage = tr( "renderer failed to save" ); return false; } } else { QgsDebugMsg( "no renderer" ); errorMessage = tr( "no renderer" ); return false; } } //save customproperties (for labeling ng) writeCustomProperties( node, doc ); // tab display QDomElement editorLayoutElem = doc.createElement( "editorlayout" ); switch ( mEditorLayout ) { case UiFileLayout: editorLayoutElem.appendChild( doc.createTextNode( "uifilelayout" ) ); break; case TabLayout: editorLayoutElem.appendChild( doc.createTextNode( "tablayout" ) ); break; case GeneratedLayout: default: editorLayoutElem.appendChild( doc.createTextNode( "generatedlayout" ) ); break; } node.appendChild( editorLayoutElem ); // add the display field QDomElement dField = doc.createElement( "displayfield" ); QDomText dFieldText = doc.createTextNode( displayField() ); dField.appendChild( dFieldText ); node.appendChild( dField ); // add label node QDomElement labelElem = doc.createElement( "label" ); QDomText labelText = doc.createTextNode( "" ); if ( hasLabelsEnabled() ) { labelText.setData( "1" ); } else { labelText.setData( "0" ); } labelElem.appendChild( labelText ); node.appendChild( labelElem ); // Now we get to do all that all over again for QgsLabel QString fieldname = mLabel->labelField( QgsLabel::Text ); if ( fieldname != "" ) { dField = doc.createElement( "labelfield" ); dFieldText = doc.createTextNode( fieldname ); dField.appendChild( dFieldText ); node.appendChild( dField ); } mLabel->writeXML( node, doc ); if ( mDiagramRenderer ) { mDiagramRenderer->writeXML( mapLayerNode, doc ); if ( mDiagramLayerSettings ) mDiagramLayerSettings->writeXML( mapLayerNode, doc ); } } //edit types if ( mEditTypes.size() > 0 ) { QDomElement editTypesElement = doc.createElement( "edittypes" ); for ( QMap<QString, EditType>::const_iterator it = mEditTypes.begin(); it != mEditTypes.end(); ++it ) { QDomElement editTypeElement = doc.createElement( "edittype" ); editTypeElement.setAttribute( "name", it.key() ); editTypeElement.setAttribute( "type", it.value() ); switch (( EditType ) it.value() ) { case ValueMap: if ( mValueMaps.contains( it.key() ) ) { const QMap<QString, QVariant> &map = mValueMaps[ it.key()]; for ( QMap<QString, QVariant>::const_iterator vmit = map.begin(); vmit != map.end(); vmit++ ) { QDomElement value = doc.createElement( "valuepair" ); value.setAttribute( "key", vmit.key() ); value.setAttribute( "value", vmit.value().toString() ); editTypeElement.appendChild( value ); } } break; case EditRange: case SliderRange: case DialRange: if ( mRanges.contains( it.key() ) ) { editTypeElement.setAttribute( "min", mRanges[ it.key()].mMin.toString() ); editTypeElement.setAttribute( "max", mRanges[ it.key()].mMax.toString() ); editTypeElement.setAttribute( "step", mRanges[ it.key()].mStep.toString() ); } break; case CheckBox: if ( mCheckedStates.contains( it.key() ) ) { editTypeElement.setAttribute( "checked", mCheckedStates[ it.key()].first ); editTypeElement.setAttribute( "unchecked", mCheckedStates[ it.key()].second ); } break; case ValueRelation: if ( mValueRelations.contains( it.key() ) ) { const ValueRelationData &data = mValueRelations[ it.key()]; editTypeElement.setAttribute( "layer", data.mLayer ); editTypeElement.setAttribute( "key", data.mKey ); editTypeElement.setAttribute( "value", data.mValue ); editTypeElement.setAttribute( "allowNull", data.mAllowNull ? "true" : "false" ); editTypeElement.setAttribute( "orderByValue", data.mOrderByValue ? "true" : "false" ); editTypeElement.setAttribute( "allowMulti", data.mAllowMulti ? "true" : "false" ); if ( !data.mFilterAttributeColumn.isNull() ) editTypeElement.setAttribute( "filterAttributeColumn", data.mFilterAttributeColumn ); if ( !data.mFilterAttributeValue.isNull() ) editTypeElement.setAttribute( "filterAttributeValue", data.mFilterAttributeValue ); } break; case LineEdit: case UniqueValues: case UniqueValuesEditable: case Classification: case FileName: case Hidden: case TextEdit: case Calendar: case Enumeration: case Immutable: case UuidGenerator: break; } editTypesElement.appendChild( editTypeElement ); } node.appendChild( editTypesElement ); } QDomElement efField = doc.createElement( "editform" ); QDomText efText = doc.createTextNode( QgsProject::instance()->writePath( mEditForm ) ); efField.appendChild( efText ); node.appendChild( efField ); QDomElement efiField = doc.createElement( "editforminit" ); QDomText efiText = doc.createTextNode( mEditFormInit ); efiField.appendChild( efiText ); node.appendChild( efiField ); QDomElement afField = doc.createElement( "annotationform" ); QDomText afText = doc.createTextNode( QgsProject::instance()->writePath( mAnnotationForm ) ); afField.appendChild( afText ); node.appendChild( afField ); //attribute aliases if ( mAttributeAliasMap.size() > 0 ) { QDomElement aliasElem = doc.createElement( "aliases" ); QMap<QString, QString>::const_iterator a_it = mAttributeAliasMap.constBegin(); for ( ; a_it != mAttributeAliasMap.constEnd(); ++a_it ) { int idx = fieldNameIndex( a_it.key() ); if ( idx < 0 ) continue; QDomElement aliasEntryElem = doc.createElement( "alias" ); aliasEntryElem.setAttribute( "field", a_it.key() ); aliasEntryElem.setAttribute( "index", idx ); aliasEntryElem.setAttribute( "name", a_it.value() ); aliasElem.appendChild( aliasEntryElem ); } node.appendChild( aliasElem ); } //exclude attributes WMS QDomElement excludeWMSElem = doc.createElement( "excludeAttributesWMS" ); QSet<QString>::const_iterator attWMSIt = mExcludeAttributesWMS.constBegin(); for ( ; attWMSIt != mExcludeAttributesWMS.constEnd(); ++attWMSIt ) { QDomElement attrElem = doc.createElement( "attribute" ); QDomText attrText = doc.createTextNode( *attWMSIt ); attrElem.appendChild( attrText ); excludeWMSElem.appendChild( attrElem ); } node.appendChild( excludeWMSElem ); //exclude attributes WFS QDomElement excludeWFSElem = doc.createElement( "excludeAttributesWFS" ); QSet<QString>::const_iterator attWFSIt = mExcludeAttributesWFS.constBegin(); for ( ; attWFSIt != mExcludeAttributesWFS.constEnd(); ++attWFSIt ) { QDomElement attrElem = doc.createElement( "attribute" ); QDomText attrText = doc.createTextNode( *attWFSIt ); attrElem.appendChild( attrText ); excludeWFSElem.appendChild( attrElem ); } node.appendChild( excludeWFSElem ); // tabs and groups of edit form if ( mAttributeEditorElements.size() > 0 ) { QDomElement tabsElem = doc.createElement( "attributeEditorForm" ); for ( QList< QgsAttributeEditorElement* >::const_iterator it = mAttributeEditorElements.begin(); it != mAttributeEditorElements.end(); it++ ) { QDomElement attributeEditorWidgetElem = ( *it )->toDomElement( doc ); tabsElem.appendChild( attributeEditorWidgetElem ); } node.appendChild( tabsElem ); } // add attribute actions mActions->writeXML( node, doc ); //save vector overlays (e.g. diagrams) QList<QgsVectorOverlay*>::const_iterator overlay_it = mOverlays.constBegin(); for ( ; overlay_it != mOverlays.constEnd(); ++overlay_it ) { if ( *overlay_it ) { ( *overlay_it )->writeXML( mapLayerNode, doc ); } } return true; } bool QgsVectorLayer::readSld( const QDomNode& node, QString& errorMessage ) { // get the Name element QDomElement nameElem = node.firstChildElement( "Name" ); if ( nameElem.isNull() ) { errorMessage = "Warning: Name element not found within NamedLayer while it's required."; } if ( hasGeometryType() ) { setUsingRendererV2( true ); QgsFeatureRendererV2* r = QgsFeatureRendererV2::loadSld( node, geometryType(), errorMessage ); if ( !r ) return false; setRendererV2( r ); } return true; } bool QgsVectorLayer::writeSld( QDomNode& node, QDomDocument& doc, QString& errorMessage ) const { Q_UNUSED( errorMessage ); // store the Name element QDomElement nameNode = doc.createElement( "se:Name" ); nameNode.appendChild( doc.createTextNode( name() ) ); node.appendChild( nameNode ); if ( hasGeometryType() ) { if ( mUsingRendererV2 ) { node.appendChild( mRendererV2->writeSld( doc, *this ) ); } else { node.appendChild( doc.createComment( "Old Renderer not supported yet" ) ); return false; } } return true; } bool QgsVectorLayer::changeGeometry( QgsFeatureId fid, QgsGeometry* geom ) { if ( !mEditBuffer || !mDataProvider ) { return false; } return mEditBuffer->changeGeometry( fid, geom ); } bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, QVariant value, bool emitSignal ) { if ( !mEditBuffer || !mDataProvider ) return false; // TODO[MD]: emitSignal return mEditBuffer->changeAttributeValue( fid, field, value ); } bool QgsVectorLayer::addAttribute( const QgsField &field ) { if ( !mEditBuffer || !mDataProvider ) return false; return mEditBuffer->addAttribute( field ); } bool QgsVectorLayer::addAttribute( QString name, QString type ) { const QList< QgsVectorDataProvider::NativeType > &types = mDataProvider->nativeTypes(); int i; for ( i = 0; i < types.size() && types[i].mTypeName != type; i++ ) ; return i < types.size() && addAttribute( QgsField( name, types[i].mType, type ) ); } void QgsVectorLayer::addAttributeAlias( int attIndex, QString aliasString ) { if ( attIndex < 0 || attIndex >= pendingFields().count() ) return; QString name = pendingFields()[ attIndex ].name(); mAttributeAliasMap.insert( name, aliasString ); emit layerModified(); // TODO[MD]: should have a different signal? } void QgsVectorLayer::addAttributeEditorWidget( QgsAttributeEditorElement* data ) { mAttributeEditorElements.append( data ); } QString QgsVectorLayer::attributeAlias( int attributeIndex ) const { if ( attributeIndex < 0 || attributeIndex >= pendingFields().count() ) return ""; QString name = pendingFields()[ attributeIndex ].name(); return mAttributeAliasMap.value( name, "" ); } QString QgsVectorLayer::attributeDisplayName( int attributeIndex ) const { QString displayName = attributeAlias( attributeIndex ); if ( displayName.isEmpty() ) { const QgsFields& fields = pendingFields(); if ( attributeIndex >= 0 && attributeIndex < fields.count() ) { displayName = fields[attributeIndex].name(); } } return displayName; } bool QgsVectorLayer::deleteAttribute( int index ) { if ( !mEditBuffer || !mDataProvider ) return false; return mEditBuffer->deleteAttribute( index ); } bool QgsVectorLayer::deleteFeature( QgsFeatureId fid ) { if ( !mEditBuffer ) return false; bool res = mEditBuffer->deleteFeature(fid); if (res) mSelectedFeatureIds.remove( fid ); // remove it from selection return res; } const QgsFields &QgsVectorLayer::pendingFields() const { return mUpdatedFields; } QgsAttributeList QgsVectorLayer::pendingAllAttributesList() { QgsAttributeList lst; for ( int i = 0; i < mUpdatedFields.count(); ++i ) lst.append( i ); return lst; } int QgsVectorLayer::pendingFeatureCount() { return mDataProvider->featureCount() + (mEditBuffer ? mEditBuffer->mAddedFeatures.size() - mEditBuffer->mDeletedFeatureIds.size() : 0 ); } bool QgsVectorLayer::commitChanges() { mCommitErrors.clear(); if ( !mDataProvider ) { mCommitErrors << tr( "ERROR: no provider" ); return false; } if ( !mEditBuffer ) { mCommitErrors << tr( "ERROR: layer not editable" ); return false; } bool success = mEditBuffer->commitChanges( mCommitErrors ); if ( success ) { delete mEditBuffer; mEditBuffer = 0; undoStack()->clear(); emit editingStopped(); } else { QgsMessageLog::logMessage( tr( "Commit errors:\n %1" ).arg( mCommitErrors.join( "\n " ) ) ); } updateFields(); mDataProvider->updateExtents(); //clear the cache image so markers don't appear anymore on next draw setCacheImage( 0 ); return success; } const QStringList &QgsVectorLayer::commitErrors() { return mCommitErrors; } bool QgsVectorLayer::rollBack() { if ( !mEditBuffer ) { return false; } mEditBuffer->rollBack(); if ( isModified() ) { // new undo stack roll back method // old method of calling every undo could cause many canvas refreshes undoStack()->setIndex( 0 ); } updateFields(); delete mEditBuffer; mEditBuffer = 0; emit editingStopped(); // invalidate the cache so the layer updates properly to show its original // after the rollback setCacheImage( 0 ); return true; } void QgsVectorLayer::setSelectedFeatures( const QgsFeatureIds& ids ) { // TODO: check whether features with these ID exist mSelectedFeatureIds = ids; // invalidate cache setCacheImage( 0 ); emit selectionChanged(); } int QgsVectorLayer::selectedFeatureCount() { return mSelectedFeatureIds.size(); } const QgsFeatureIds& QgsVectorLayer::selectedFeaturesIds() const { return mSelectedFeatureIds; } QgsFeatureList QgsVectorLayer::selectedFeatures() { QgsFeatureList features; foreach ( QgsFeatureId fid, mSelectedFeatureIds ) { features.push_back( QgsFeature() ); featureAtId( fid, features.back(), geometryType() != QGis::NoGeometry, true ); } return features; } bool QgsVectorLayer::addFeatures( QgsFeatureList features, bool makeSelected ) { if ( !mEditBuffer || !mDataProvider ) return false; bool res = mEditBuffer->addFeatures( features ); if ( makeSelected ) { mSelectedFeatureIds.clear(); for ( QgsFeatureList::iterator iter = features.begin(); iter != features.end(); ++iter ) mSelectedFeatureIds.insert( iter->id() ); // invalidate cache setCacheImage( 0 ); emit selectionChanged(); } return res; } bool QgsVectorLayer::snapPoint( QgsPoint& point, double tolerance ) { if ( !hasGeometryType() ) return false; QMultiMap<double, QgsSnappingResult> snapResults; int result = snapWithContext( point, tolerance, snapResults, QgsSnapper::SnapToVertex ); if ( result != 0 ) { return false; } if ( snapResults.size() < 1 ) { return false; } QMultiMap<double, QgsSnappingResult>::const_iterator snap_it = snapResults.constBegin(); point.setX( snap_it.value().snappedVertex.x() ); point.setY( snap_it.value().snappedVertex.y() ); return true; } int QgsVectorLayer::snapWithContext( const QgsPoint& startPoint, double snappingTolerance, QMultiMap<double, QgsSnappingResult>& snappingResults, QgsSnapper::SnappingType snap_to ) { if ( !hasGeometryType() ) return 1; if ( snappingTolerance <= 0 || !mDataProvider ) { return 1; } QList<QgsFeature> featureList; QgsRectangle searchRect( startPoint.x() - snappingTolerance, startPoint.y() - snappingTolerance, startPoint.x() + snappingTolerance, startPoint.y() + snappingTolerance ); double sqrSnappingTolerance = snappingTolerance * snappingTolerance; int n = 0; QgsFeature f; if ( mCache->cachedGeometriesRect().contains( searchRect ) ) { QgsGeometryMap& cachedGeometries = mCache->cachedGeometries(); for ( QgsGeometryMap::iterator it = cachedGeometries.begin(); it != cachedGeometries.end() ; ++it ) { QgsGeometry* g = &( it.value() ); if ( g->boundingBox().intersects( searchRect ) ) { snapToGeometry( startPoint, it.key(), g, sqrSnappingTolerance, snappingResults, snap_to ); ++n; } } } else { // snapping outside cached area select( QgsAttributeList(), searchRect, true, true ); while ( nextFeature( f ) ) { snapToGeometry( startPoint, f.id(), f.geometry(), sqrSnappingTolerance, snappingResults, snap_to ); ++n; } } return n == 0 ? 2 : 0; } void QgsVectorLayer::snapToGeometry( const QgsPoint& startPoint, QgsFeatureId featureId, QgsGeometry* geom, double sqrSnappingTolerance, QMultiMap<double, QgsSnappingResult>& snappingResults, QgsSnapper::SnappingType snap_to ) const { if ( !geom ) { return; } int atVertex, beforeVertex, afterVertex; double sqrDistVertexSnap, sqrDistSegmentSnap; QgsPoint snappedPoint; QgsSnappingResult snappingResultVertex; QgsSnappingResult snappingResultSegment; if ( snap_to == QgsSnapper::SnapToVertex || snap_to == QgsSnapper::SnapToVertexAndSegment ) { snappedPoint = geom->closestVertex( startPoint, atVertex, beforeVertex, afterVertex, sqrDistVertexSnap ); if ( sqrDistVertexSnap < sqrSnappingTolerance ) { snappingResultVertex.snappedVertex = snappedPoint; snappingResultVertex.snappedVertexNr = atVertex; snappingResultVertex.beforeVertexNr = beforeVertex; if ( beforeVertex != -1 ) // make sure the vertex is valid { snappingResultVertex.beforeVertex = geom->vertexAt( beforeVertex ); } snappingResultVertex.afterVertexNr = afterVertex; if ( afterVertex != -1 ) // make sure the vertex is valid { snappingResultVertex.afterVertex = geom->vertexAt( afterVertex ); } snappingResultVertex.snappedAtGeometry = featureId; snappingResultVertex.layer = this; snappingResults.insert( sqrt( sqrDistVertexSnap ), snappingResultVertex ); return; } } if ( snap_to == QgsSnapper::SnapToSegment || snap_to == QgsSnapper::SnapToVertexAndSegment ) // snap to segment { if ( geometryType() != QGis::Point ) // cannot snap to segment for points/multipoints { sqrDistSegmentSnap = geom->closestSegmentWithContext( startPoint, snappedPoint, afterVertex, NULL, crs().geographicFlag() ? 1e-12 : 1e-8 ); if ( sqrDistSegmentSnap < sqrSnappingTolerance ) { snappingResultSegment.snappedVertex = snappedPoint; snappingResultSegment.snappedVertexNr = -1; snappingResultSegment.beforeVertexNr = afterVertex - 1; snappingResultSegment.afterVertexNr = afterVertex; snappingResultSegment.snappedAtGeometry = featureId; snappingResultSegment.beforeVertex = geom->vertexAt( afterVertex - 1 ); snappingResultSegment.afterVertex = geom->vertexAt( afterVertex ); snappingResultSegment.layer = this; snappingResults.insert( sqrt( sqrDistSegmentSnap ), snappingResultSegment ); } } } } int QgsVectorLayer::insertSegmentVerticesForSnap( const QList<QgsSnappingResult>& snapResults ) { QgsVectorLayerEditUtils utils(this); return utils.insertSegmentVerticesForSnap( snapResults ); } QgsVectorLayer::VertexMarkerType QgsVectorLayer::currentVertexMarkerType() { QSettings settings; QString markerTypeString = settings.value( "/qgis/digitizing/marker_style", "Cross" ).toString(); if ( markerTypeString == "Cross" ) { return QgsVectorLayer::Cross; } else if ( markerTypeString == "SemiTransparentCircle" ) { return QgsVectorLayer::SemiTransparentCircle; } else { return QgsVectorLayer::NoMarker; } } int QgsVectorLayer::currentVertexMarkerSize() { QSettings settings; return settings.value( "/qgis/digitizing/marker_size", 3 ).toInt(); } void QgsVectorLayer::drawFeature( QgsRenderContext &renderContext, QgsFeature& fet, QImage * marker ) { QPainter *p = renderContext.painter(); // Only have variables, etc outside the switch() statement that are // used in all cases of the statement (otherwise they may get // executed, but never used, in a bit of code where performance is // critical). if ( ! fet.isValid() ) { return; } QgsGeometry* geom = fet.geometry(); if ( !geom ) { return; } unsigned char* feature = geom->asWkb(); QGis::WkbType wkbType = geom->wkbType(); switch ( wkbType ) { case QGis::WKBPoint: case QGis::WKBPoint25D: { double x = *(( double * )( feature + 5 ) ); double y = *(( double * )( feature + 5 + sizeof( double ) ) ); transformPoint( x, y, &renderContext.mapToPixel(), renderContext.coordinateTransform() ); if ( qAbs( x ) > QgsClipper::MAX_X || qAbs( y ) > QgsClipper::MAX_Y ) { break; } //QPointF pt(x - (marker->width()/2), y - (marker->height()/2)); QPointF pt( x * renderContext.rasterScaleFactor() - ( marker->width() / 2 ), y * renderContext.rasterScaleFactor() - ( marker->height() / 2 ) ); p->save(); //p->scale(markerScaleFactor,markerScaleFactor); p->scale( 1.0 / renderContext.rasterScaleFactor(), 1.0 / renderContext.rasterScaleFactor() ); p->drawImage( pt, *marker ); p->restore(); break; } case QGis::WKBMultiPoint: case QGis::WKBMultiPoint25D: { unsigned char *ptr = feature + 5; unsigned int nPoints = *(( int* )ptr ); ptr += 4; p->save(); //p->scale(markerScaleFactor, markerScaleFactor); p->scale( 1.0 / renderContext.rasterScaleFactor(), 1.0 / renderContext.rasterScaleFactor() ); for ( register unsigned int i = 0; i < nPoints; ++i ) { ptr += 5; double x = *(( double * ) ptr ); ptr += sizeof( double ); double y = *(( double * ) ptr ); ptr += sizeof( double ); if ( wkbType == QGis::WKBMultiPoint25D ) // ignore Z value ptr += sizeof( double ); transformPoint( x, y, &renderContext.mapToPixel(), renderContext.coordinateTransform() ); //QPointF pt(x - (marker->width()/2), y - (marker->height()/2)); //QPointF pt(x/markerScaleFactor - (marker->width()/2), y/markerScaleFactor - (marker->height()/2)); QPointF pt( x * renderContext.rasterScaleFactor() - ( marker->width() / 2 ), y * renderContext.rasterScaleFactor() - ( marker->height() / 2 ) ); //QPointF pt( x, y ); // Work around a +/- 32768 limitation on coordinates if ( qAbs( x ) <= QgsClipper::MAX_X && qAbs( y ) <= QgsClipper::MAX_Y ) p->drawImage( pt, *marker ); } p->restore(); break; } case QGis::WKBLineString: case QGis::WKBLineString25D: { drawLineString( feature, renderContext ); break; } case QGis::WKBMultiLineString: case QGis::WKBMultiLineString25D: { unsigned char* ptr = feature + 5; unsigned int numLineStrings = *(( int* )ptr ); ptr = feature + 9; for ( register unsigned int jdx = 0; jdx < numLineStrings; jdx++ ) { ptr = drawLineString( ptr, renderContext ); } break; } case QGis::WKBPolygon: case QGis::WKBPolygon25D: { drawPolygon( feature, renderContext ); break; } case QGis::WKBMultiPolygon: case QGis::WKBMultiPolygon25D: { unsigned char *ptr = feature + 5; unsigned int numPolygons = *(( int* )ptr ); ptr = feature + 9; for ( register unsigned int kdx = 0; kdx < numPolygons; kdx++ ) ptr = drawPolygon( ptr, renderContext ); break; } default: QgsDebugMsg( "Unknown WkbType ENCOUNTERED" ); break; } } void QgsVectorLayer::setCoordinateSystem() { QgsDebugMsg( "----- Computing Coordinate System" ); // // Get the layers project info and set up the QgsCoordinateTransform // for this layer // if ( hasGeometryType() ) { // get CRS directly from provider setCrs( mDataProvider->crs() ); } else { setCrs( QgsCoordinateReferenceSystem( GEO_EPSG_CRS_AUTHID ) ); } } // Convenience function to transform the given point inline void QgsVectorLayer::transformPoint( double& x, double& y, const QgsMapToPixel* mtp, const QgsCoordinateTransform* ct ) { // transform the point if ( ct ) { double z = 0; ct->transformInPlace( x, y, z ); } // transform from projected coordinate system to pixel // position on map canvas mtp->transformInPlace( x, y ); } inline void QgsVectorLayer::transformPoints( QVector<double>& x, QVector<double>& y, QVector<double>& z, QgsRenderContext &renderContext ) { // transform the point if ( renderContext.coordinateTransform() ) renderContext.coordinateTransform()->transformInPlace( x, y, z ); // transform from projected coordinate system to pixel // position on map canvas renderContext.mapToPixel().transformInPlace( x, y ); } const QString QgsVectorLayer::displayField() const { return mDisplayField; } bool QgsVectorLayer::isEditable() const { return ( mEditBuffer && mDataProvider ); } bool QgsVectorLayer::isReadOnly() const { return mReadOnly; } bool QgsVectorLayer::setReadOnly( bool readonly ) { // exit if the layer is in editing mode if ( readonly && mEditBuffer ) return false; mReadOnly = readonly; return true; } bool QgsVectorLayer::isModified() const { return mEditBuffer && mEditBuffer->isModified(); } QgsVectorLayer::EditType QgsVectorLayer::editType( int idx ) { const QgsFields &fields = pendingFields(); if ( idx >= 0 && idx < fields.count() && mEditTypes.contains( fields[idx].name() ) ) return mEditTypes[ fields[idx].name()]; else return LineEdit; } void QgsVectorLayer::setEditType( int idx, EditType type ) { const QgsFields &fields = pendingFields(); if ( idx >= 0 && idx < fields.count() ) mEditTypes[ fields[idx].name()] = type; } QgsVectorLayer::EditorLayout QgsVectorLayer::editorLayout() { return mEditorLayout; } void QgsVectorLayer::setEditorLayout( EditorLayout editorLayout ) { mEditorLayout = editorLayout; } QString QgsVectorLayer::editForm() { return mEditForm; } void QgsVectorLayer::setEditForm( QString ui ) { mEditForm = ui; } void QgsVectorLayer::setAnnotationForm( const QString& ui ) { mAnnotationForm = ui; } QString QgsVectorLayer::editFormInit() { return mEditFormInit; } void QgsVectorLayer::setEditFormInit( QString function ) { mEditFormInit = function; } QMap< QString, QVariant > &QgsVectorLayer::valueMap( int idx ) { const QgsFields &fields = pendingFields(); // FIXME: throw an exception!? static QMap< QString, QVariant > invalidMap; if ( idx < 0 || idx >= fields.count() ) { QgsDebugMsg( QString( "field %1 not found" ).arg( idx ) ); return invalidMap; } QString fieldName = fields[idx].name(); if ( !mValueMaps.contains( fieldName ) ) mValueMaps[fieldName] = QMap<QString, QVariant>(); return mValueMaps[fieldName]; } QgsVectorLayer::RangeData &QgsVectorLayer::range( int idx ) { const QgsFields &fields = pendingFields(); // FIXME: throw an exception!? static QgsVectorLayer::RangeData invalidRange; if ( idx < 0 || idx >= fields.count() ) { QgsDebugMsg( QString( "field %1 not found" ).arg( idx ) ); return invalidRange; } QString fieldName = fields[idx].name(); if ( !mRanges.contains( fieldName ) ) mRanges[fieldName] = RangeData(); return mRanges[fieldName]; } void QgsVectorLayer::addOverlay( QgsVectorOverlay* overlay ) { mOverlays.push_back( overlay ); } void QgsVectorLayer::removeOverlay( const QString& typeName ) { for ( int i = mOverlays.size() - 1; i >= 0; --i ) { if ( mOverlays.at( i )->typeName() == typeName ) { mOverlays.removeAt( i ); } } } void QgsVectorLayer::vectorOverlays( QList<QgsVectorOverlay*>& overlayList ) { overlayList = mOverlays; } QgsVectorOverlay* QgsVectorLayer::findOverlayByType( const QString& typeName ) { QList<QgsVectorOverlay*>::iterator it = mOverlays.begin(); for ( ; it != mOverlays.end(); ++it ) { if (( *it )->typeName() == typeName ) { return *it; } } return 0; //not found } QgsFeatureRendererV2* QgsVectorLayer::rendererV2() { return mRendererV2; } void QgsVectorLayer::setRendererV2( QgsFeatureRendererV2 *r ) { if ( !hasGeometryType() ) return; if ( r != mRendererV2 ) { if ( r ) setUsingRendererV2( true ); delete mRendererV2; mRendererV2 = r; mSymbolFeatureCounted = false; mSymbolFeatureCountMap.clear(); } } bool QgsVectorLayer::isUsingRendererV2() { return mUsingRendererV2; } void QgsVectorLayer::setUsingRendererV2( bool usingRendererV2 ) { if ( !hasGeometryType() ) return; mUsingRendererV2 = usingRendererV2; } void QgsVectorLayer::beginEditCommand( QString text ) { undoStack()->beginMacro(text); } void QgsVectorLayer::endEditCommand() { undoStack()->endMacro(); } void QgsVectorLayer::destroyEditCommand() { undoStack()->endMacro(); undoStack()->undo(); } void QgsVectorLayer::setCheckedState( int idx, QString checked, QString unchecked ) { const QgsFields &fields = pendingFields(); if ( idx >= 0 && idx < fields.count() ) mCheckedStates[ fields[idx].name()] = QPair<QString, QString>( checked, unchecked ); } QPair<QString, QString> QgsVectorLayer::checkedState( int idx ) { const QgsFields &fields = pendingFields(); if ( idx >= 0 && idx < fields.count() && mCheckedStates.contains( fields[idx].name() ) ) return mCheckedStates[ fields[idx].name()]; else return QPair<QString, QString>( "1", "0" ); } int QgsVectorLayer::fieldNameIndex( const QString& fieldName ) const { const QgsFields &theFields = pendingFields(); for ( int idx = 0; idx < theFields.count(); ++idx ) { if ( QString::compare( theFields[idx].name(), fieldName, Qt::CaseInsensitive ) == 0 ) { return idx; } } return -1; } void QgsVectorLayer::addJoin( const QgsVectorJoinInfo& joinInfo ) { mJoinBuffer->addJoin( joinInfo ); updateFields(); } void QgsVectorLayer::checkJoinLayerRemove( QString theLayerId ) { removeJoin( theLayerId ); } void QgsVectorLayer::removeJoin( const QString& joinLayerId ) { mJoinBuffer->removeJoin( joinLayerId ); updateFields(); } const QList< QgsVectorJoinInfo >& QgsVectorLayer::vectorJoins() const { return mJoinBuffer->vectorJoins(); } void QgsVectorLayer::updateFields() { if ( !mDataProvider ) return; mUpdatedFields = mDataProvider->fields(); // added / removed fields if ( mEditBuffer ) mEditBuffer->updateFields( mUpdatedFields ); // joined fields if ( mJoinBuffer->containsJoins() ) mJoinBuffer->updateFields( mUpdatedFields ); } void QgsVectorLayer::createJoinCaches() { if ( mJoinBuffer->containsJoins() ) { mJoinBuffer->createJoinCaches(); } } void QgsVectorLayer::uniqueValues( int index, QList<QVariant> &uniqueValues, int limit ) { uniqueValues.clear(); if ( !mDataProvider ) { return; } QgsFields::FieldOrigin origin = mUpdatedFields.fieldOrigin(index); if ( origin == QgsFields::OriginProvider ) //a provider field { return mDataProvider->uniqueValues( index, uniqueValues, limit ); } else if ( origin == QgsFields::OriginJoin ) { int sourceLayerIndex; const QgsVectorJoinInfo* join = mJoinBuffer->joinForFieldIndex( index, mUpdatedFields, sourceLayerIndex ); Q_ASSERT( join ); QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( join->joinLayerId ) ); Q_ASSERT( vl ); return vl->dataProvider()->uniqueValues( sourceLayerIndex, uniqueValues, limit ); } else if ( origin == QgsFields::OriginEdit ) { // the layer is editable, but in certain cases it can still be avoided going through all features if ( mEditBuffer->mDeletedFeatureIds.isEmpty() && mEditBuffer->mAddedFeatures.isEmpty() && !mEditBuffer->mDeletedAttributeIds.contains( index ) && mEditBuffer->mChangedAttributeValues.isEmpty() ) { return mDataProvider->uniqueValues( index, uniqueValues, limit ); } // we need to go through each feature QgsAttributeList attList; attList << index; select( attList, QgsRectangle(), false, false ); QgsFeature f; QVariant currentValue; QHash<QString, QVariant> val; while ( nextFeature( f ) ) { currentValue = f.attribute( index ); val.insert( currentValue.toString(), currentValue ); if ( limit >= 0 && val.size() >= limit ) { break; } } uniqueValues = val.values(); return; } Q_ASSERT_X(false, "QgsVectorLayer::uniqueValues()", "Unknown source of the field!"); } QVariant QgsVectorLayer::minimumValue( int index ) { if ( !mDataProvider ) { return QVariant(); } QgsFields::FieldOrigin origin = mUpdatedFields.fieldOrigin(index); if ( origin == QgsFields::OriginProvider ) //a provider field { return mDataProvider->minimumValue( index ); } else if ( origin == QgsFields::OriginJoin ) { int sourceLayerIndex; const QgsVectorJoinInfo* join = mJoinBuffer->joinForFieldIndex( index, mUpdatedFields, sourceLayerIndex ); Q_ASSERT( join ); QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( join->joinLayerId ) ); Q_ASSERT( vl ); return vl->minimumValue( sourceLayerIndex ); } else if ( origin == QgsFields::OriginEdit ) { // the layer is editable, but in certain cases it can still be avoided going through all features if ( mEditBuffer->mDeletedFeatureIds.isEmpty() && mEditBuffer->mAddedFeatures.isEmpty() && !mEditBuffer->mDeletedAttributeIds.contains( index ) && mEditBuffer->mChangedAttributeValues.isEmpty() ) { return mDataProvider->minimumValue( index ); } // we need to go through each feature QgsAttributeList attList; attList << index; select( attList, QgsRectangle(), false, false ); QgsFeature f; double minimumValue = std::numeric_limits<double>::max(); double currentValue = 0; while ( nextFeature( f ) ) { currentValue = f.attribute( index ).toDouble(); if ( currentValue < minimumValue ) { minimumValue = currentValue; } } return QVariant( minimumValue ); } Q_ASSERT_X(false, "QgsVectorLayer::minimumValue()", "Unknown source of the field!"); return QVariant(); } QVariant QgsVectorLayer::maximumValue( int index ) { if ( !mDataProvider ) { return QVariant(); } QgsFields::FieldOrigin origin = mUpdatedFields.fieldOrigin(index); if ( origin == QgsFields::OriginProvider ) //a provider field { return mDataProvider->maximumValue( index ); } else if ( origin == QgsFields::OriginJoin ) { int sourceLayerIndex; const QgsVectorJoinInfo* join = mJoinBuffer->joinForFieldIndex( index, mUpdatedFields, sourceLayerIndex ); Q_ASSERT( join ); QgsVectorLayer* vl = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( join->joinLayerId ) ); Q_ASSERT( vl ); return vl->maximumValue( index ); } else if ( origin == QgsFields::OriginEdit ) { // the layer is editable, but in certain cases it can still be avoided going through all features if ( mEditBuffer->mDeletedFeatureIds.isEmpty() && mEditBuffer->mAddedFeatures.isEmpty() && !mEditBuffer->mDeletedAttributeIds.contains( index ) && mEditBuffer->mChangedAttributeValues.isEmpty() ) { return mDataProvider->maximumValue( index ); } // we need to go through each feature QgsAttributeList attList; attList << index; select( attList, QgsRectangle(), false, false ); QgsFeature f; double maximumValue = -std::numeric_limits<double>::max(); double currentValue = 0; while ( nextFeature( f ) ) { currentValue = f.attribute( index ).toDouble(); if ( currentValue > maximumValue ) { maximumValue = currentValue; } } return QVariant( maximumValue ); } Q_ASSERT_X(false, "QgsVectorLayer::maximumValue()", "Unknown source of the field!"); return QVariant(); } void QgsVectorLayer::stopRendererV2( QgsRenderContext& rendererContext, QgsSingleSymbolRendererV2* selRenderer ) { mRendererV2->stopRender( rendererContext ); if ( selRenderer ) { selRenderer->stopRender( rendererContext ); delete selRenderer; } } void QgsVectorLayer::prepareLabelingAndDiagrams( QgsRenderContext& rendererContext, QgsAttributeList& attributes, bool& labeling ) { if ( !rendererContext.labelingEngine() ) return; QSet<int> attrIndex; if ( rendererContext.labelingEngine()->prepareLayer( this, attrIndex, rendererContext ) ) { QSet<int>::const_iterator attIt = attrIndex.constBegin(); for ( ; attIt != attrIndex.constEnd(); ++attIt ) { if ( !attributes.contains( *attIt ) ) { attributes << *attIt; } } labeling = true; } if ( labeling ) { // see if feature count limit is set for labeling QgsPalLayerSettings& palyr = rendererContext.labelingEngine()->layer( this->id() ); if ( palyr.limitNumLabels && palyr.maxNumLabels > 0 ) { select( QgsAttributeList(), rendererContext.extent() ); // total number of features that may be labeled QgsFeature ftr; int nFeatsToLabel = 0; while ( nextFeature( ftr ) ) { nFeatsToLabel += 1; } palyr.mFeaturesToLabel = nFeatsToLabel; } } //register diagram layers if ( mDiagramRenderer && mDiagramLayerSettings ) { mDiagramLayerSettings->renderer = mDiagramRenderer; rendererContext.labelingEngine()->addDiagramLayer( this, mDiagramLayerSettings ); //add attributes needed by the diagram renderer QList<int> att = mDiagramRenderer->diagramAttributes(); QList<int>::const_iterator attIt = att.constBegin(); for ( ; attIt != att.constEnd(); ++attIt ) { if ( !attributes.contains( *attIt ) ) { attributes << *attIt; } } //and the ones needed for data defined diagram positions if ( mDiagramLayerSettings->xPosColumn >= 0 && !attributes.contains( mDiagramLayerSettings->xPosColumn ) ) { attributes << mDiagramLayerSettings->xPosColumn; } if ( mDiagramLayerSettings->yPosColumn >= 0 && !attributes.contains( mDiagramLayerSettings->yPosColumn ) ) { attributes << mDiagramLayerSettings->yPosColumn; } } } void QgsVectorLayer::setDiagramLayerSettings( const QgsDiagramLayerSettings& s ) { if ( !mDiagramLayerSettings ) mDiagramLayerSettings = new QgsDiagramLayerSettings(); *mDiagramLayerSettings = s; } QString QgsVectorLayer::metadata() { QString myMetadata = "<html><body>"; myMetadata += "<table width=\"100%\">"; //------------- myMetadata += "<tr class=\"glossy\"><td>"; myMetadata += tr( "General:" ); myMetadata += "</td></tr>"; // data comment if ( !( dataComment().isEmpty() ) ) { myMetadata += "<tr><td>"; myMetadata += tr( "Layer comment: %1" ).arg( dataComment() ); myMetadata += "</td></tr>"; } //storage type myMetadata += "<tr><td>"; myMetadata += tr( "Storage type of this layer: %1" ).arg( storageType() ); myMetadata += "</td></tr>"; // data source myMetadata += "<tr><td>"; myMetadata += tr( "Source for this layer: %1" ).arg( publicSource() ); myMetadata += "</td></tr>"; //geom type QGis::GeometryType type = geometryType(); if ( type < 0 || type > QGis::NoGeometry ) { QgsDebugMsg( "Invalid vector type" ); } else { QString typeString( QGis::qgisVectorGeometryType[geometryType()] ); myMetadata += "<tr><td>"; myMetadata += tr( "Geometry type of the features in this layer: %1" ).arg( typeString ); myMetadata += "</td></tr>"; } //feature count myMetadata += "<tr><td>"; myMetadata += tr( "The number of features in this layer: %1" ).arg( featureCount() ); myMetadata += "</td></tr>"; //capabilities myMetadata += "<tr><td>"; myMetadata += tr( "Editing capabilities of this layer: %1" ).arg( capabilitiesString() ); myMetadata += "</td></tr>"; //------------- QgsRectangle myExtent = extent(); myMetadata += "<tr class=\"glossy\"><td>"; myMetadata += tr( "Extents:" ); myMetadata += "</td></tr>"; //extents in layer cs TODO...maybe make a little nested table to improve layout... myMetadata += "<tr><td>" + tr( "In layer spatial reference system units : " ); // Try to be a bit clever over what number format we use for the // extents. Some people don't like it using scientific notation when the // numbers get large, but for small numbers this is the more practical // option (so we can't force the format to 'f' for all values). // The scheme: // - for all numbers with more than 5 digits, force non-scientific notation // and 2 digits after the decimal point. // - for all smaller numbers let the OS decide which format to use (it will // generally use non-scientific unless the number gets much less than 1). if ( !myExtent.isEmpty() ) { QString xMin, yMin, xMax, yMax; double changeoverValue = 99999; // The 'largest' 5 digit number if ( qAbs( myExtent.xMinimum() ) > changeoverValue ) { xMin = QString( "%1" ).arg( myExtent.xMinimum(), 0, 'f', 2 ); } else { xMin = QString( "%1" ).arg( myExtent.xMinimum() ); } if ( qAbs( myExtent.yMinimum() ) > changeoverValue ) { yMin = QString( "%1" ).arg( myExtent.yMinimum(), 0, 'f', 2 ); } else { yMin = QString( "%1" ).arg( myExtent.yMinimum() ); } if ( qAbs( myExtent.xMaximum() ) > changeoverValue ) { xMax = QString( "%1" ).arg( myExtent.xMaximum(), 0, 'f', 2 ); } else { xMax = QString( "%1" ).arg( myExtent.xMaximum() ); } if ( qAbs( myExtent.yMaximum() ) > changeoverValue ) { yMax = QString( "%1" ).arg( myExtent.yMaximum(), 0, 'f', 2 ); } else { yMax = QString( "%1" ).arg( myExtent.yMaximum() ); } myMetadata += tr( "xMin,yMin %1,%2 : xMax,yMax %3,%4" ) .arg( xMin ).arg( yMin ).arg( xMax ).arg( yMax ); } else { myMetadata += tr( "unknown extent" ); } myMetadata += "</td></tr>"; //extents in project cs try { #if 0 // TODO: currently disabled, will revisit later [MD] QgsRectangle myProjectedExtent = coordinateTransform->transformBoundingBox( extent() ); myMetadata += "<tr><td>"; myMetadata += tr( "In project spatial reference system units : " ) + tr( "xMin,yMin %1,%2 : xMax,yMax %3,%4" ) .arg( myProjectedExtent.xMinimum() ) .arg( myProjectedExtent.yMinimum() ) .arg( myProjectedExtent.xMaximum() ) .arg( myProjectedExtent.yMaximum() ); myMetadata += "</td></tr>"; #endif // // Display layer spatial ref system // myMetadata += "<tr class=\"glossy\"><td>"; myMetadata += tr( "Layer Spatial Reference System:" ); myMetadata += "</td></tr>"; myMetadata += "<tr><td>"; myMetadata += crs().toProj4().replace( QRegExp( "\"" ), " \"" ); myMetadata += "</td></tr>"; // // Display project (output) spatial ref system // #if 0 // TODO: disabled for now, will revisit later [MD] myMetadata += "<tr><td bgcolor=\"gray\">"; myMetadata += tr( "Project (Output) Spatial Reference System:" ); myMetadata += "</td></tr>"; myMetadata += "<tr><td>"; myMetadata += coordinateTransform->destCRS().toProj4().replace( QRegExp( "\"" ), " \"" ); myMetadata += "</td></tr>"; #endif } catch ( QgsCsException &cse ) { Q_UNUSED( cse ); QgsDebugMsg( cse.what() ); myMetadata += "<tr><td>"; myMetadata += tr( "In project spatial reference system units : " ) + tr( "(Invalid transformation of layer extents)" ); myMetadata += "</td></tr>"; } #if 0 // // Add the info about each field in the attribute table // myMetadata += "<tr class=\"glossy\"><td>"; myMetadata += tr( "Attribute field info:" ); myMetadata += "</td></tr>"; myMetadata += "<tr><td>"; // Start a nested table in this trow myMetadata += "<table width=\"100%\">"; myMetadata += "<tr><th>"; myMetadata += tr( "Field" ); myMetadata += "</th>"; myMetadata += "<th>"; myMetadata += tr( "Type" ); myMetadata += "</th>"; myMetadata += "<th>"; myMetadata += tr( "Length" ); myMetadata += "</th>"; myMetadata += "<th>"; myMetadata += tr( "Precision" ); myMetadata += "</th>"; myMetadata += "<th>"; myMetadata += tr( "Comment" ); myMetadata += "</th>"; //get info for each field by looping through them const QgsFieldMap& myFields = pendingFields(); for ( QgsFieldMap::const_iterator it = myFields.begin(); it != myFields.end(); ++it ) { const QgsField& myField = *it; myMetadata += "<tr><td>"; myMetadata += myField.name(); myMetadata += "</td>"; myMetadata += "<td>"; myMetadata += myField.typeName(); myMetadata += "</td>"; myMetadata += "<td>"; myMetadata += QString( "%1" ).arg( myField.length() ); myMetadata += "</td>"; myMetadata += "<td>"; myMetadata += QString( "%1" ).arg( myField.precision() ); myMetadata += "</td>"; myMetadata += "<td>"; myMetadata += QString( "%1" ).arg( myField.comment() ); myMetadata += "</td></tr>"; } //close field list myMetadata += "</table>"; //end of nested table #endif myMetadata += "</td></tr>"; //end of stats container table row // // Close the table // myMetadata += "</table>"; myMetadata += "</body></html>"; return myMetadata; } QgsVectorLayer::ValueRelationData &QgsVectorLayer::valueRelation( int idx ) { const QgsFields &fields = pendingFields(); // FIXME: throw an exception!? static QgsVectorLayer::ValueRelationData invalidData; if ( idx < 0 || idx >= fields.count() ) { QgsDebugMsg( QString( "field %1 not found" ).arg( idx ) ); return invalidData; } QString fieldName = fields[idx].name(); if ( !mValueRelations.contains( fieldName ) ) { mValueRelations[fieldName] = ValueRelationData(); } return mValueRelations[fieldName]; } QList<QgsAttributeEditorElement*> &QgsVectorLayer::attributeEditorElements() { return mAttributeEditorElements; } void QgsVectorLayer::clearAttributeEditorWidgets() { mAttributeEditorElements.clear(); } QDomElement QgsAttributeEditorContainer::toDomElement( QDomDocument& doc ) const { QDomElement elem = doc.createElement( "attributeEditorContainer" ); elem.setAttribute( "name", mName ); for ( QList< QgsAttributeEditorElement* >::const_iterator it = mChildren.begin(); it != mChildren.end(); ++it ) { elem.appendChild(( *it )->toDomElement( doc ) ); } return elem; } void QgsAttributeEditorContainer::addChildElement( QgsAttributeEditorElement *widget ) { mChildren.append( widget ); } QDomElement QgsAttributeEditorField::toDomElement( QDomDocument& doc ) const { QDomElement elem = doc.createElement( "attributeEditorField" ); elem.setAttribute( "name", mName ); elem.setAttribute( "index", mIdx ); return elem; }