From be5aeac3b76a91d2f793995b3fa8fb10ce89ca46 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 5 Jul 2014 14:12:40 +1000 Subject: [PATCH] =?UTF-8?q?[composer]=20Initial=20framework=20for=20data?= =?UTF-8?q?=20defined=20properties=20in=20compositions=20and=20composer=20?= =?UTF-8?q?items.=20Funded=20by=20Canton=20of=20Neuch=C3=A2tel,=20Switzerl?= =?UTF-8?q?and?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/core/composer/qgscomposeritem.sip | 71 ++++- python/core/composer/qgscomposition.sip | 25 ++ src/app/composer/qgscomposeritemwidget.cpp | 66 +++++ src/app/composer/qgscomposeritemwidget.h | 15 + src/core/composer/qgscomposeritem.cpp | 121 ++++++++ src/core/composer/qgscomposeritem.h | 87 +++++- src/core/composer/qgscomposition.cpp | 320 +++++++++++++++++++++ src/core/composer/qgscomposition.h | 100 ++++++- 8 files changed, 794 insertions(+), 11 deletions(-) diff --git a/python/core/composer/qgscomposeritem.sip b/python/core/composer/qgscomposeritem.sip index d44ed81f3682..066c787e5a57 100644 --- a/python/core/composer/qgscomposeritem.sip +++ b/python/core/composer/qgscomposeritem.sip @@ -133,6 +133,36 @@ class QgsComposerItem : QObject, QGraphicsRectItem LowerRight }; + /** Data defined properties for different item types + */ + enum DataDefinedProperty + { + NoProperty = 0, /*< no property */ + AllProperties, /*< all properties for item */ + //composer page properties + PresetPaperSize, /*< preset paper size for composition */ + PaperWidth, /*< paper width */ + PaperHeight, /*< paper height */ + NumPages, /*< number of pages in composition */ + PaperOrientation, /*< paper orientation */ + //general composer item properties + PageNumber, /*< page number for item placement */ + PositionX, /*< x position on page */ + PositionY, /*< y position on page */ + ItemWidth, /*< width of item */ + ItemHeight, /*< height of item */ + ItemRotation, /*< rotation of item */ + Transparency, /*< item transparency */ + BlendMode, /*< item blend mode */ + //composer map + MapRotation, /*< map rotation */ + MapScale, /*< map scale */ + MapXMin, /*< map extent x minimum */ + MapYMin, /*< map extent y minimum */ + MapXMax, /*< map extent x maximum */ + MapYMax /*< map extent y maximum */ + }; + /**Constructor @param composition parent composition @param manageZValue true if the z-Value of this object should be managed by mComposition*/ @@ -481,6 +511,22 @@ class QgsComposerItem : QObject, QGraphicsRectItem */ void setCurrentExportLayer( int layerIdx = -1 ); + /**Returns a reference to the data defined settings for one of the item's data defined properties. + * @param property data defined property to return + * @note this method was added in version 2.5 + */ + QgsDataDefined* dataDefinedProperty( DataDefinedProperty property ); + + /**Sets parameters for a data defined property for the item + * @param property data defined property to set + * @param active true if data defined property is active, false if it is disabled + * @param useExpression true if the expression should be used + * @param expression expression for data defined property + * @field field name if the data defined property should take its value from a field + * @note this method was added in version 2.5 + */ + void setDataDefinedProperty( DataDefinedProperty property, bool active, bool useExpression, const QString &expression, const QString &field ); + public slots: /**Sets the item rotation * @deprecated Use setItemRotation( double rotation ) instead @@ -488,15 +534,24 @@ class QgsComposerItem : QObject, QGraphicsRectItem virtual void setRotation( double r ); /**Sets the item rotation - @param r item rotation in degrees - @param adjustPosition set to true if item should be shifted so that rotation occurs - around item center. If false, rotation occurs around item origin - @note this method was added in version 2.1 + * @param r item rotation in degrees + * @param adjustPosition set to true if item should be shifted so that rotation occurs + * around item center. If false, rotation occurs around item origin + * @note this method was added in version 2.1 */ virtual void setItemRotation( double r, bool adjustPosition = false ); void repaint(); + /**Refreshes a data defined property for the item by reevaluating the property's value + * and redrawing the item with this new value. + * @param property data defined property to refresh. If property is set to + * QgsComposerItem::AllProperties then all data defined properties for the item will be + * refreshed. + * @note this method was added in version 2.5 + */ + virtual void refreshDataDefinedProperty( DataDefinedProperty property = AllProperties ); + protected: //event handlers @@ -582,6 +637,14 @@ class QgsComposerItem : QObject, QGraphicsRectItem void deleteVAlignSnapItem(); void deleteAlignItems(); + /**Evaluate a data defined property and return the calculated value + * @returns true if data defined property could be successfully evaluated + * @param property data defined property to evaluate + * @param expressionValue QVariant for storing the evaluated value + * @note this method was added in version 2.5 + */ + bool dataDefinedEvaluate( QgsComposerItem::DataDefinedProperty property, QVariant &expressionValue ); + signals: /**Is emitted on item rotation change*/ void itemRotationChanged( double newRotation ); diff --git a/python/core/composer/qgscomposition.sip b/python/core/composer/qgscomposition.sip index da1678a8bae8..6dd1d89be562 100644 --- a/python/core/composer/qgscomposition.sip +++ b/python/core/composer/qgscomposition.sip @@ -444,6 +444,22 @@ class QgsComposition : QGraphicsScene @note added in version 2.4*/ QList< QgsPaperItem* > pages(); + /**Returns a reference to the data defined settings for one of the composition's data defined properties. + * @param property data defined property to return + * @note this method was added in version 2.5 + */ + QgsDataDefined* dataDefinedProperty( QgsComposerItem::DataDefinedProperty property ); + + /**Sets parameters for a data defined property for the composition + * @param property data defined property to set + * @param active true if data defined property is active, false if it is disabled + * @param useExpression true if the expression should be used + * @param expression expression for data defined property + * @field field name if the data defined property should take its value from a field + * @note this method was added in version 2.5 + */ + void setDataDefinedProperty( QgsComposerItem::DataDefinedProperty property, bool active, bool useExpression, const QString &expression, const QString &field ); + public slots: /**Casts object to the proper subclass type and calls corresponding itemAdded signal*/ void sendItemAddedSignal( QgsComposerItem* item ); @@ -463,6 +479,15 @@ class QgsComposition : QGraphicsScene * @note added in version 2.3*/ void setSelectedItem( QgsComposerItem* item ); + /**Refreshes a data defined property for the composition by reevaluating the property's value + * and redrawing the composition with this new value. + * @param property data defined property to refresh. If property is set to + * QgsComposerItem::AllProperties then all data defined properties for the composition will be + * refreshed. + * @note this method was added in version 2.5 + */ + void refreshDataDefinedProperty( QgsComposerItem::DataDefinedProperty property = QgsComposerItem::AllProperties ); + protected: void init(); diff --git a/src/app/composer/qgscomposeritemwidget.cpp b/src/app/composer/qgscomposeritemwidget.cpp index 140992e444dc..0f229415a34d 100644 --- a/src/app/composer/qgscomposeritemwidget.cpp +++ b/src/app/composer/qgscomposeritemwidget.cpp @@ -18,8 +18,10 @@ #include "qgscomposeritemwidget.h" #include "qgscomposeritem.h" #include "qgscomposermap.h" +#include "qgsatlascomposition.h" #include "qgscomposition.h" #include "qgspoint.h" +#include "qgsdatadefinedbutton.h" #include #include @@ -36,6 +38,44 @@ QgsComposerItemBaseWidget::~QgsComposerItemBaseWidget() } +void QgsComposerItemBaseWidget::updateDataDefinedProperty() +{ + //match data defined button to item's data defined property + QgsDataDefinedButton* ddButton = dynamic_cast( sender() ); + if ( !ddButton ) + { + return; + } + QgsComposerItem::DataDefinedProperty property = ddPropertyForWidget( ddButton ); + if ( property == QgsComposerItem::NoProperty ) + { + return; + } + + //set the data defined property and refresh the item + setDataDefinedProperty( ddButton, property ); + mItem->refreshDataDefinedProperty( property ); +} + +void QgsComposerItemBaseWidget::setDataDefinedProperty( const QgsDataDefinedButton *ddBtn, QgsComposerItem::DataDefinedProperty p ) +{ + if ( !mItem ) + { + return; + } + + const QMap< QString, QString >& map = ddBtn->definedProperty(); + mItem->setDataDefinedProperty( p, map.value( "active" ).toInt(), map.value( "useexpr" ).toInt(), map.value( "expression" ), map.value( "field" ) ); +} + +QgsComposerItem::DataDefinedProperty QgsComposerItemBaseWidget::ddPropertyForWidget( QgsDataDefinedButton *widget ) +{ + Q_UNUSED( widget ); + + //base implementation, return no property + return QgsComposerItem::NoProperty; +} + QgsAtlasComposition* QgsComposerItemBaseWidget::atlasComposition() const { if ( !mItem ) @@ -98,6 +138,17 @@ QgsComposerItemWidget::QgsComposerItemWidget( QWidget* parent, QgsComposerItem* connect( mTransparencySlider, SIGNAL( valueChanged( int ) ), mTransparencySpnBx, SLOT( setValue( int ) ) ); connect( mTransparencySpnBx, SIGNAL( valueChanged( int ) ), mTransparencySlider, SLOT( setValue( int ) ) ); + + //connect atlas signals to data defined buttons + QgsAtlasComposition* atlas = atlasComposition(); + if ( atlas ) + { + //repopulate data defined buttons if atlas layer changes + connect( atlas, SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), + this, SLOT( populateDataDefinedButtons() ) ); + connect( atlas, SIGNAL( toggled( bool ) ), this, SLOT( populateDataDefinedButtons() ) ); + } + } QgsComposerItemWidget::QgsComposerItemWidget(): QgsComposerItemBaseWidget( 0, 0 ) @@ -449,6 +500,20 @@ void QgsComposerItemWidget::setValuesForGuiNonPositionElements() mItemRotationSpinBox->blockSignals( false ); } +void QgsComposerItemWidget::populateDataDefinedButtons() +{ + //QgsVectorLayer* vl = atlasCoverageLayer(); + + //block signals from data defined buttons + + //initialise buttons to use atlas coverage layer + + //initial state of controls - disable related controls when dd buttons are active + + //unblock signals from data defined buttons + +} + void QgsComposerItemWidget::setValuesForGuiElements() { if ( !mItem ) @@ -463,6 +528,7 @@ void QgsComposerItemWidget::setValuesForGuiElements() setValuesForGuiPositionElements(); setValuesForGuiNonPositionElements(); + populateDataDefinedButtons(); } void QgsComposerItemWidget::on_mBlendModeCombo_currentIndexChanged( int index ) diff --git a/src/app/composer/qgscomposeritemwidget.h b/src/app/composer/qgscomposeritemwidget.h index d87b137dd966..492406e4e2dd 100644 --- a/src/app/composer/qgscomposeritemwidget.h +++ b/src/app/composer/qgscomposeritemwidget.h @@ -23,6 +23,7 @@ class QgsComposerItem; class QgsAtlasComposition; +class QgsDataDefinedButton; /**A base class for property widgets for composer items. All composer item widgets should inherit from * this base class. @@ -34,7 +35,17 @@ class QgsComposerItemBaseWidget: public QWidget QgsComposerItemBaseWidget( QWidget* parent, QgsComposerItem* item ); ~QgsComposerItemBaseWidget(); + protected slots: + /**Must be called when a data defined button changes*/ + void updateDataDefinedProperty(); + protected: + /**Sets a data defined property for the item from its current data defined button settings*/ + void setDataDefinedProperty( const QgsDataDefinedButton *ddBtn, QgsComposerItem::DataDefinedProperty p ); + + /**Returns the data defined property corresponding to a data defined button widget*/ + virtual QgsComposerItem::DataDefinedProperty ddPropertyForWidget( QgsDataDefinedButton* widget ); + /**Returns the current atlas coverage layer (if set)*/ QgsVectorLayer* atlasCoverageLayer() const; @@ -108,6 +119,10 @@ class QgsComposerItemWidget: public QgsComposerItemBaseWidget, private Ui::QgsCo //sets the values for all non-position related elements void setValuesForGuiNonPositionElements(); + protected slots: + /**Initializes data defined buttons to current atlas coverage layer*/ + void populateDataDefinedButtons(); + private: QgsComposerItemWidget(); // void changeItemTransparency( int value ); diff --git a/src/core/composer/qgscomposeritem.cpp b/src/core/composer/qgscomposeritem.cpp index 08b03d023558..aa9b6f98bf3c 100644 --- a/src/core/composer/qgscomposeritem.cpp +++ b/src/core/composer/qgscomposeritem.cpp @@ -30,6 +30,7 @@ #include "qgscomposition.h" #include "qgscomposeritem.h" #include "qgscomposerframe.h" +#include "qgsdatadefined.h" #include #include "qgsapplication.h" @@ -117,6 +118,23 @@ void QgsComposerItem::init( bool manageZValue ) // Setup composer effect mEffect = new QgsComposerEffect(); setGraphicsEffect( mEffect ); + + // data defined strings + + if ( mComposition ) + { + //connect to atlas toggling on/off and coverage layer and feature changes + //to update data defined values + connect( &mComposition->atlasComposition(), SIGNAL( toggled( bool ) ), this, SLOT( refreshDataDefinedProperty() ) ); + connect( &mComposition->atlasComposition(), SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), this, SLOT( refreshDataDefinedProperty() ) ); + connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshDataDefinedProperty() ) ); + //also, refreshing composition triggers a recalculation of data defined properties + connect( mComposition, SIGNAL( refreshItemsTriggered() ), this, SLOT( refreshDataDefinedProperty() ) ); + + //toggling atlas or changing coverage layer requires data defined expressions to be reprepared + connect( &mComposition->atlasComposition(), SIGNAL( toggled( bool ) ), this, SLOT( prepareDataDefinedExpressions() ) ); + connect( &mComposition->atlasComposition(), SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), this, SLOT( prepareDataDefinedExpressions() ) ); + } } QgsComposerItem::~QgsComposerItem() @@ -128,6 +146,11 @@ QgsComposerItem::~QgsComposerItem() delete mBoundingResizeRectangle; delete mEffect; + + //clear pointers to QgsDataDefined objects + //TODO - probably leaky. Check same code within labelling and fix. + mDataDefinedProperties.clear(); + deleteAlignItems(); } @@ -226,6 +249,12 @@ bool QgsComposerItem::_writeXML( QDomElement& itemElem, QDomDocument& doc ) cons //transparency composerItemElem.setAttribute( "transparency", QString::number( mTransparency ) ); + //data defined properties + if ( mComposition ) + { + mComposition->writeDataDefinedPropertyMap( composerItemElem, doc, &mDataDefinedNames, &mDataDefinedProperties ); + } + itemElem.appendChild( composerItemElem ); return true; @@ -369,6 +398,12 @@ bool QgsComposerItem::_readXML( const QDomElement& itemElem, const QDomDocument& //transparency setTransparency( itemElem.attribute( "transparency" , "0" ).toInt() ); + //data defined properties + if ( mComposition ) + { + mComposition->readDataDefinedPropertyMap( itemElem, &mDataDefinedNames, &mDataDefinedProperties ); + } + return true; } @@ -1170,11 +1205,49 @@ void QgsComposerItem::deleteAlignItems() deleteVAlignSnapItem(); } +bool QgsComposerItem::dataDefinedEvaluate( QgsComposerItem::DataDefinedProperty property, QVariant &expressionValue ) +{ + if ( !mComposition ) + { + return false; + } + return mComposition->dataDefinedEvaluate( property, expressionValue, &mDataDefinedProperties ); +} + +void QgsComposerItem::prepareDataDefinedExpressions() const +{ + //use atlas coverage layer if set + QgsVectorLayer* atlasLayer = 0; + if ( mComposition ) + { + QgsAtlasComposition* atlas = &mComposition->atlasComposition(); + if ( atlas && atlas->enabled() ) + { + atlasLayer = atlas->coverageLayer(); + } + } + + //prepare all QgsDataDefineds + QMap< DataDefinedProperty, QgsDataDefined* >::const_iterator it = mDataDefinedProperties.constBegin(); + if ( it != mDataDefinedProperties.constEnd() ) + { + it.value()->prepareExpression( atlasLayer ); + } +} + void QgsComposerItem::repaint() { update(); } +void QgsComposerItem::refreshDataDefinedProperty( QgsComposerItem::DataDefinedProperty property ) +{ + //update data defined properties and redraw item to match + + + update(); +} + void QgsComposerItem::setId( const QString& id ) { setToolTip( id ); @@ -1186,3 +1259,51 @@ void QgsComposerItem::setIsGroupMember( bool isGroupMember ) mIsGroupMember = isGroupMember; setFlag( QGraphicsItem::ItemIsSelectable, !isGroupMember ); //item in groups cannot be selected } + +QgsDataDefined *QgsComposerItem::dataDefinedProperty( QgsComposerItem::DataDefinedProperty property ) +{ + if ( property == QgsComposerItem::AllProperties || property == QgsComposerItem::NoProperty ) + { + //bad property requested, don't return anything + return 0; + } + + //find corresponding QgsDataDefined and return it + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >::const_iterator it = mDataDefinedProperties.find( property ); + if ( it != mDataDefinedProperties.constEnd() ) + { + return it.value(); + } + + //could not find matching QgsDataDefined + return 0; +} + +void QgsComposerItem::setDataDefinedProperty( QgsComposerItem::DataDefinedProperty property, bool active, bool useExpression, const QString &expression, const QString &field ) +{ + if ( property == QgsComposerItem::AllProperties || property == QgsComposerItem::NoProperty ) + { + //bad property requested + return; + } + + bool defaultVals = ( !active && !useExpression && expression.isEmpty() && field.isEmpty() ); + + if ( mDataDefinedProperties.contains( property ) ) + { + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >::const_iterator it = mDataDefinedProperties.find( property ); + if ( it != mDataDefinedProperties.constEnd() ) + { + QgsDataDefined* dd = it.value(); + dd->setActive( active ); + dd->setUseExpression( useExpression ); + dd->setExpressionString( expression ); + dd->setField( field ); + } + } + else if ( !defaultVals ) + { + QgsDataDefined* dd = new QgsDataDefined( active, useExpression, expression, field ); + mDataDefinedProperties.insert( property, dd ); + } +} diff --git a/src/core/composer/qgscomposeritem.h b/src/core/composer/qgscomposeritem.h index 7c3d0de681ef..22a1561a9861 100644 --- a/src/core/composer/qgscomposeritem.h +++ b/src/core/composer/qgscomposeritem.h @@ -23,12 +23,13 @@ #include #include -class QgsComposition; class QWidget; class QDomDocument; class QDomElement; class QGraphicsLineItem; class QgsComposerItemGroup; +class QgsDataDefined; +class QgsComposition; /** \ingroup MapComposer * A item that forms part of a map composition. @@ -87,6 +88,36 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem LowerRight }; + /** Data defined properties for different item types + */ + enum DataDefinedProperty + { + NoProperty = 0, /*< no property */ + AllProperties, /*< all properties for item */ + //composer page properties + PresetPaperSize, /*< preset paper size for composition */ + PaperWidth, /*< paper width */ + PaperHeight, /*< paper height */ + NumPages, /*< number of pages in composition */ + PaperOrientation, /*< paper orientation */ + //general composer item properties + PageNumber, /*< page number for item placement */ + PositionX, /*< x position on page */ + PositionY, /*< y position on page */ + ItemWidth, /*< width of item */ + ItemHeight, /*< height of item */ + ItemRotation, /*< rotation of item */ + Transparency, /*< item transparency */ + BlendMode, /*< item blend mode */ + //composer map + MapRotation, /*< map rotation */ + MapScale, /*< map scale */ + MapXMin, /*< map extent x minimum */ + MapYMin, /*< map extent y minimum */ + MapXMax, /*< map extent x maximum */ + MapYMax /*< map extent y maximum */ + }; + /**Constructor @param composition parent composition @param manageZValue true if the z-Value of this object should be managed by mComposition*/ @@ -436,6 +467,22 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem */ virtual void setCurrentExportLayer( int layerIdx = -1 ) { mCurrentExportLayer = layerIdx; } + /**Returns a reference to the data defined settings for one of the item's data defined properties. + * @param property data defined property to return + * @note this method was added in version 2.5 + */ + QgsDataDefined* dataDefinedProperty( DataDefinedProperty property ); + + /**Sets parameters for a data defined property for the item + * @param property data defined property to set + * @param active true if data defined property is active, false if it is disabled + * @param useExpression true if the expression should be used + * @param expression expression for data defined property + * @field field name if the data defined property should take its value from a field + * @note this method was added in version 2.5 + */ + void setDataDefinedProperty( DataDefinedProperty property, bool active, bool useExpression, const QString &expression, const QString &field ); + public slots: /**Sets the item rotation * @deprecated Use setItemRotation( double rotation ) instead @@ -443,15 +490,24 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem virtual void setRotation( double r ); /**Sets the item rotation - @param r item rotation in degrees - @param adjustPosition set to true if item should be shifted so that rotation occurs - around item center. If false, rotation occurs around item origin - @note this method was added in version 2.1 + * @param r item rotation in degrees + * @param adjustPosition set to true if item should be shifted so that rotation occurs + * around item center. If false, rotation occurs around item origin + * @note this method was added in version 2.1 */ virtual void setItemRotation( double r, bool adjustPosition = false ); void repaint(); + /**Refreshes a data defined property for the item by reevaluating the property's value + * and redrawing the item with this new value. + * @param property data defined property to refresh. If property is set to + * QgsComposerItem::AllProperties then all data defined properties for the item will be + * refreshed. + * @note this method was added in version 2.5 + */ + virtual void refreshDataDefinedProperty( DataDefinedProperty property = AllProperties ); + protected: QgsComposition* mComposition; @@ -506,6 +562,9 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem @note: this member was added in version 2.4*/ int mCurrentExportLayer; + /**Map of data defined properties for the item to string name to use when exporting item to xml*/ + QMap< QgsComposerItem::DataDefinedProperty, QString > mDataDefinedNames; + /**Draw selection boxes around item*/ virtual void drawSelectionBoxes( QPainter* p ); @@ -582,6 +641,14 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem void deleteVAlignSnapItem(); void deleteAlignItems(); + /**Evaluate a data defined property and return the calculated value + * @returns true if data defined property could be successfully evaluated + * @param property data defined property to evaluate + * @param expressionValue QVariant for storing the evaluated value + * @note this method was added in version 2.5 + */ + bool dataDefinedEvaluate( QgsComposerItem::DataDefinedProperty property, QVariant &expressionValue ); + signals: /**Is emitted on item rotation change*/ void itemRotationChanged( double newRotation ); @@ -593,6 +660,13 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem * @note: this function was introduced in version 2.2 */ void frameChanged(); + + private slots: + /**Prepares all composer item data defined expressions using the current atlas coverage layer if set. + * @note this method was added in version 2.5 + */ + void prepareDataDefinedExpressions() const; + private: // id (not unique) QString mId; @@ -601,6 +675,9 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem // name (temporary when loaded from template) QString mTemplateUuid; + /**Map of current data defined properties*/ + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* > mDataDefinedProperties; + void init( bool manageZValue ); friend class QgsComposerItemGroup; // to access mTemplateUuid diff --git a/src/core/composer/qgscomposition.cpp b/src/core/composer/qgscomposition.cpp index 6856c88118de..9b931e5266df 100644 --- a/src/core/composer/qgscomposition.cpp +++ b/src/core/composer/qgscomposition.cpp @@ -39,6 +39,7 @@ #include "qgsexpression.h" #include "qgssymbolv2.h" #include "qgssymbollayerv2utils.h" +#include "qgsdatadefined.h" #include #include @@ -96,6 +97,19 @@ void QgsComposition::init() mAtlasMode = QgsComposition::AtlasOff; mPreventCursorChange = false; + //data defined strings + + //connect to atlas toggling on/off and coverage layer and feature changes + //to update data defined values + connect( &mAtlasComposition, SIGNAL( toggled( bool ) ), this, SLOT( refreshDataDefinedProperty() ) ); + connect( &mAtlasComposition, SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), this, SLOT( refreshDataDefinedProperty() ) ); + connect( &mAtlasComposition, SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshDataDefinedProperty() ) ); + //also, refreshing composition triggers a recalculation of data defined properties + connect( this, SIGNAL( refreshItemsTriggered() ), this, SLOT( refreshDataDefinedProperty() ) ); + //toggling atlas or changing coverage layer requires data defined expressions to be reprepared + connect( &mAtlasComposition, SIGNAL( toggled( bool ) ), this, SLOT( prepareAllDataDefinedExpressions() ) ); + connect( &mAtlasComposition, SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), this, SLOT( prepareAllDataDefinedExpressions() ) ); + setBackgroundBrush( QColor( 215, 215, 215 ) ); createDefaultPageStyleSymbol(); @@ -157,6 +171,10 @@ QgsComposition::~QgsComposition() removePaperItems(); deleteAndRemoveMultiFrames(); + // clear pointers to QgsDataDefined objects + //TODO - should be qDeleteAll? Check original label code too for same leak. + mDataDefinedProperties.clear(); + // make sure that all composer items are removed before // this class is deconstructed - to avoid segfaults // when composer items access in destructor composition that isn't valid anymore @@ -192,6 +210,12 @@ void QgsComposition::setSelectedItem( QgsComposerItem *item ) emit selectedItemChanged( item ); } +void QgsComposition::refreshDataDefinedProperty( QgsComposerItem::DataDefinedProperty property ) +{ + //updates data defined properties and redraws composition to match + +} + QRectF QgsComposition::compositionBounds() const { //start with an empty rectangle @@ -692,6 +716,9 @@ bool QgsComposition::writeXML( QDomElement& composerElem, QDomDocument& doc ) } composerElem.appendChild( compositionElem ); + //data defined properties + writeDataDefinedPropertyMap( compositionElem, doc, &mDataDefinedNames, &mDataDefinedProperties ); + return true; } @@ -757,6 +784,9 @@ bool QgsComposition::readXML( const QDomElement& compositionElem, const QDomDocu mGenerateWorldFile = compositionElem.attribute( "generateWorldFile", "0" ).toInt() == 1 ? true : false; + //data defined properties + readDataDefinedPropertyMap( compositionElem, &mDataDefinedNames, &mDataDefinedProperties ); + updatePaperItems(); updateBounds(); @@ -2688,6 +2718,296 @@ bool QgsComposition::setAtlasMode( QgsComposition::AtlasMode mode ) return true; } +QgsDataDefined *QgsComposition::dataDefinedProperty( QgsComposerItem::DataDefinedProperty property ) +{ + if ( property == QgsComposerItem::AllProperties || property == QgsComposerItem::NoProperty ) + { + //invalid property + return 0; + } + + //find matching QgsDataDefined for property + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >::const_iterator it = mDataDefinedProperties.find( property ); + if ( it != mDataDefinedProperties.constEnd() ) + { + return it.value(); + } + + //not found + return 0; +} + +void QgsComposition::setDataDefinedProperty( QgsComposerItem::DataDefinedProperty property, bool active, bool useExpression, const QString &expression, const QString &field ) +{ + if ( property == QgsComposerItem::AllProperties || property == QgsComposerItem::NoProperty ) + { + //invalid property + return; + } + + bool defaultVals = ( !active && !useExpression && expression.isEmpty() && field.isEmpty() ); + + if ( mDataDefinedProperties.contains( property ) ) + { + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >::const_iterator it = mDataDefinedProperties.find( property ); + if ( it != mDataDefinedProperties.constEnd() ) + { + QgsDataDefined* dd = it.value(); + dd->setActive( active ); + dd->setUseExpression( useExpression ); + dd->setExpressionString( expression ); + dd->setField( field ); + } + } + else if ( !defaultVals ) + { + QgsDataDefined* dd = new QgsDataDefined( active, useExpression, expression, field ); + mDataDefinedProperties.insert( property, dd ); + } +} + +bool QgsComposition::dataDefinedEvaluate( QgsComposerItem::DataDefinedProperty property, QVariant &expressionValue, QMap *dataDefinedProperties ) +{ + if ( property == QgsComposerItem::NoProperty || property == QgsComposerItem::AllProperties ) + { + //invalid property + return false; + } + + //null passed-around QVariant + expressionValue.clear(); + + //get fields and feature from atlas + const QgsFeature* currentFeature = 0; + const QgsFields* layerFields = 0; + if ( mAtlasComposition.enabled() ) + { + QgsVectorLayer* atlasLayer = mAtlasComposition.coverageLayer(); + if ( atlasLayer ) + { + layerFields = &atlasLayer->pendingFields(); + } + if ( mAtlasMode != QgsComposition::AtlasOff ) + { + currentFeature = mAtlasComposition.currentFeature(); + } + } + + //evaluate data defined property using current atlas context + QVariant result = dataDefinedValue( property, currentFeature, layerFields, dataDefinedProperties ); + + if ( result.isValid() ) + { + expressionValue = result; + return true; + } + + return false; +} + +void QgsComposition::readDataDefinedPropertyMap( const QDomElement &itemElem, QMap *dataDefinedNames, QMap *dataDefinedProperties ) const +{ + QMap::const_iterator i = dataDefinedNames->constBegin(); + for ( ; i != dataDefinedNames->constEnd(); ++i ) + { + QString elemName = i.value(); + QDomNodeList ddNodeList = itemElem.elementsByTagName( elemName ); + if ( ddNodeList.size() > 0 ) + { + QDomElement ddElem = ddNodeList.at( 0 ).toElement(); + readDataDefinedProperty( i.key(), ddElem, dataDefinedProperties ); + } + } +} + +void QgsComposition::readDataDefinedProperty( QgsComposerItem::DataDefinedProperty property, const QDomElement &ddElem, QMap *dataDefinedProperties ) const +{ + if ( property == QgsComposerItem::AllProperties || property == QgsComposerItem::NoProperty ) + { + //invalid property + return; + } + + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >::const_iterator it = dataDefinedProperties->constFind( property ); + + QgsDataDefined* dd = 0; + if ( it != dataDefinedProperties->constEnd() ) + { + dd = it.value(); + } + else + { + //QgsDataDefined for property doesn't currently exist, need to add new + dd = new QgsDataDefined( ); + dataDefinedProperties->insert( property, dd ); + } + + //set values for QgsDataDefined + QString active = ddElem.attribute( "active" ); + if ( active.compare( "true", Qt::CaseInsensitive ) == 0 ) + { + dd->setActive( true ); + } + else + { + dd->setActive( false ); + } + QString useExpr = ddElem.attribute( "useExpr" ); + if ( useExpr.compare( "true", Qt::CaseInsensitive ) == 0 ) + { + dd->setUseExpression( true ); + } + else + { + dd->setUseExpression( false ); + } + dd->setField( ddElem.attribute( "field" ) ); + dd->setExpressionString( ddElem.attribute( "expr" ) ); +} + +void QgsComposition::writeDataDefinedPropertyMap( QDomElement &itemElem, QDomDocument &doc, const QMap *dataDefinedNames, const QMap *dataDefinedProperties ) const +{ + QMap::const_iterator i = dataDefinedNames->constBegin(); + for ( ; i != dataDefinedNames->constEnd(); ++i ) + { + QString newElemName = i.value(); + + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >::const_iterator it = dataDefinedProperties->find( i.key() ); + if ( it != dataDefinedProperties->constEnd() ) + { + QgsDataDefined* dd = it.value(); + if ( dd ) + { + bool active = dd->isActive(); + bool useExpr = dd->useExpression(); + QString expr = dd->expressionString(); + QString field = dd->field(); + + bool defaultVals = ( !active && !useExpr && expr.isEmpty() && field.isEmpty() ); + + if ( !defaultVals ) + { + QDomElement ddElem = doc.createElement( newElemName ); + if ( active ) + { + ddElem.setAttribute( "active", "true" ); + } + else + { + ddElem.setAttribute( "active", "false" ); + } + if ( useExpr ) + { + ddElem.setAttribute( "useExpr", "true" ); + } + else + { + ddElem.setAttribute( "useExpr", "false" ); + } + ddElem.setAttribute( "expr", expr ); + ddElem.setAttribute( "field", field ); + itemElem.appendChild( ddElem ); + } + } + } + } +} + +QVariant QgsComposition::dataDefinedValue( QgsComposerItem::DataDefinedProperty property, const QgsFeature *feature, const QgsFields *fields, QMap *dataDefinedProperties ) +{ + if ( property == QgsComposerItem::AllProperties || property == QgsComposerItem::NoProperty ) + { + //invalid property + return QVariant(); + } + if ( !dataDefinedProperties->contains( property ) ) + { + //missing property + return QVariant(); + } + + QgsDataDefined* dd = 0; + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >::const_iterator it = dataDefinedProperties->find( property ); + if ( it != dataDefinedProperties->constEnd() ) + { + dd = it.value(); + } + if ( !dd ) + { + return QVariant(); + } + + if ( !dd->isActive() ) + { + return QVariant(); + } + + QVariant result = QVariant(); + bool useExpression = dd->useExpression(); + QString field = dd->field(); + + if ( !dd->expressionIsPrepared() ) + { + prepareDataDefinedExpression( dd, dataDefinedProperties ); + } + + if ( useExpression && dd->expressionIsPrepared() ) + { + QgsExpression* expr = dd->expression(); + + result = expr->evaluate( feature ); + if ( expr->hasEvalError() ) + { + QgsDebugMsgLevel( QString( "Evaluate error:" ) + expr->evalErrorString(), 4 ); + return QVariant(); + } + } + else if ( !useExpression && !field.isEmpty() && fields ) + { + if ( !feature ) + { + return QVariant(); + } + // use direct attribute access instead of evaluating "field" expression (much faster) + int indx = fields->indexFromName( field ); + if ( indx != -1 ) + { + result = feature->attribute( indx ); + } + } + return result; +} + +void QgsComposition::prepareDataDefinedExpression( QgsDataDefined *dd, QMap *dataDefinedProperties ) const +{ + QgsVectorLayer* atlasLayer = 0; + + if ( mAtlasComposition.enabled() ) + { + atlasLayer = mAtlasComposition.coverageLayer(); + } + + //if specific QgsDataDefined passed, prepare it + //otherwise prepare all QgsDataDefineds + if ( dd ) + { + dd->prepareExpression( atlasLayer ); + } + else + { + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >::const_iterator it = dataDefinedProperties->constBegin(); + for ( ; it != dataDefinedProperties->constEnd(); ++it ) + { + it.value()->prepareExpression( atlasLayer ); + } + } +} + +void QgsComposition::prepareAllDataDefinedExpressions() +{ + prepareDataDefinedExpression( 0, &mDataDefinedProperties ); +} + void QgsComposition::relativeResizeRect( QRectF& rectToResize, const QRectF& boundsBefore, const QRectF& boundsAfter ) { //linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter diff --git a/src/core/composer/qgscomposition.h b/src/core/composer/qgscomposition.h index 8ce1c9f00c3d..433b43498a81 100644 --- a/src/core/composer/qgscomposition.h +++ b/src/core/composer/qgscomposition.h @@ -16,7 +16,6 @@ #ifndef QGSCOMPOSITION_H #define QGSCOMPOSITION_H -#include "qgscomposeritem.h" #include #include @@ -33,6 +32,7 @@ #include "qgscomposeritemcommand.h" #include "qgsatlascomposition.h" #include "qgspaperitem.h" +#include "qgscomposeritem.h" class QgisApp; class QgsComposerFrame; @@ -56,6 +56,7 @@ class QgsComposerMultiFrameCommand; class QgsVectorLayer; class QgsComposer; class QgsFillSymbolV2; +class QgsDataDefined; /** \ingroup MapComposer * Graphics scene for map printing. The class manages the paper item which always @@ -504,6 +505,22 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene @note added in version 2.4*/ QList< QgsPaperItem* > pages() { return mPages; } + /**Returns a reference to the data defined settings for one of the composition's data defined properties. + * @param property data defined property to return + * @note this method was added in version 2.5 + */ + QgsDataDefined* dataDefinedProperty( QgsComposerItem::DataDefinedProperty property ); + + /**Sets parameters for a data defined property for the composition + * @param property data defined property to set + * @param active true if data defined property is active, false if it is disabled + * @param useExpression true if the expression should be used + * @param expression expression for data defined property + * @field field name if the data defined property should take its value from a field + * @note this method was added in version 2.5 + */ + void setDataDefinedProperty( QgsComposerItem::DataDefinedProperty property, bool active, bool useExpression, const QString &expression, const QString &field ); + public slots: /**Casts object to the proper subclass type and calls corresponding itemAdded signal*/ void sendItemAddedSignal( QgsComposerItem* item ); @@ -523,6 +540,15 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene * @note added in version 2.3*/ void setSelectedItem( QgsComposerItem* item ); + /**Refreshes a data defined property for the composition by reevaluating the property's value + * and redrawing the composition with this new value. + * @param property data defined property to refresh. If property is set to + * QgsComposerItem::AllProperties then all data defined properties for the composition will be + * refreshed. + * @note this method was added in version 2.5 + */ + void refreshDataDefinedProperty( QgsComposerItem::DataDefinedProperty property = QgsComposerItem::AllProperties ); + protected: void init(); @@ -592,6 +618,13 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene QgsComposition::AtlasMode mAtlasMode; + bool mPreventCursorChange; + + /**Map of data defined properties for the composition to string name to use when exporting composition to xml*/ + QMap< QgsComposerItem::DataDefinedProperty, QString > mDataDefinedNames; + /**Map of current data defined properties to QgsDataDefined for the composition*/ + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* > mDataDefinedProperties; + QgsComposition(); //default constructor is forbidden /**Calculates the bounds of all non-gui items in the composition. Ignores snap lines and mouse handles*/ @@ -625,7 +658,68 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene //tries to return the current QGraphicsView attached to the composition QGraphicsView* graphicsView() const; - bool mPreventCursorChange; + /**Evaluate a data defined property and return the calculated value + * @returns true if data defined property could be successfully evaluated + * @param property data defined property to evaluate + * @param expressionValue QVariant for storing the evaluated value + * @param dataDefinedProperties map of data defined properties to QgsDataDefined + * @note this method was added in version 2.5 + */ + bool dataDefinedEvaluate( QgsComposerItem::DataDefinedProperty property, QVariant &expressionValue, + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >* dataDefinedProperties ); + + /**Reads all data defined properties from xml + * @param itemElem dom element containing data defined properties + * @param dataDefinedNames map of data defined property to name used within xml + * @param dataDefinedProperties map of data defined properties to QgsDataDefined in which to store properties from xml + * @note this method was added in version 2.5 + */ + void readDataDefinedPropertyMap( const QDomElement &itemElem, + QMap< QgsComposerItem::DataDefinedProperty, QString >* dataDefinedNames, + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >* dataDefinedProperties + ) const; + + /**Reads a single data defined property from xml DOM element + * @param property data defined property to read + * @param ddElem dom element containing settings for data defined property + * @param dataDefinedProperties map of data defined properties to QgsDataDefined in which to store properties from xml + * @note this method was added in version 2.5 + */ + void readDataDefinedProperty( QgsComposerItem::DataDefinedProperty property, const QDomElement &ddElem, + QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >* dataDefinedProperties ) const; + + /**Writes data defined properties to xml + * @param itemElem DOM element in which to store data defined properties + * @param doc DOM document + * @param dataDefinedNames map of data defined property to name used within xml + * @param dataDefinedProperties map of data defined properties to QgsDataDefined for storing in xml + * @note this method was added in version 2.5 + */ + void writeDataDefinedPropertyMap( QDomElement &itemElem, QDomDocument &doc, + const QMap< QgsComposerItem::DataDefinedProperty, QString >* dataDefinedNames, + const QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >* dataDefinedProperties ) const; + + /**Evaluates a data defined property and returns the calculated value. + * @param property data defined property to evaluate + * @param feature current atlas feature to evaluate property for + * @param fields fields from atlas layer + * @param dataDefinedProperties map of data defined properties to QgsDataDefined + * @note this method was added in version 2.5 + */ + QVariant dataDefinedValue( QgsComposerItem::DataDefinedProperty property, const QgsFeature *feature, const QgsFields *fields, + QMap *dataDefinedProperties ); + + + /**Prepares the expression for a data defined property, using the current atlas layer if set. + * @param dd data defined to prepare. If no data defined is set, all data defined expressions will be prepared + * @param dataDefinedProperties map of data defined properties to QgsDataDefined + * @note this method was added in version 2.5 + */ + void prepareDataDefinedExpression( QgsDataDefined *dd, QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* >* dataDefinedProperties ) const; + + private slots: + /*Prepares all data defined expressions*/ + void prepareAllDataDefinedExpressions(); signals: void paperSizeChanged(); @@ -662,6 +756,8 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene /**Is emitted when the composition has an updated status bar message for the composer window*/ void statusMsgChanged( QString message ); + + friend class QgsComposerItem; //for accessing dataDefinedEvaluate, readDataDefinedPropertyMap and writeDataDefinedPropertyMap }; template void QgsComposition::composerItems( QList& itemList )