[FEATURE] add expressions at the symbollist level

Size and Rotation can be defined by an expression for all symbols
composing a marker. Width can be defined by an expression for all
symbols composing a line.

For markers, a legend is generated for varying sizes. This allows
multivariate analysis legend in the case of classified/graduated colors.

The offset is now set along with size to maintain the relative position
of symbols composing a marker.

An asistant, with preview, is accessible through the data defined button
to help the user define the size expression. Three methods are
available: Frannery, Area and Radius.

Added a widget for use in categorized/classified symbology gui to set
the expression if needed. The assistant is also available from it.
This commit is contained in:
vmora 2015-05-08 16:37:15 +10:00 committed by Nyall Dawson
parent c38ff519e2
commit 1e46196937
25 changed files with 1482 additions and 82 deletions

View File

@ -172,6 +172,7 @@
%Include symbology-ng/characterwidget.sip
%Include symbology-ng/qgsdashspacedialog.sip
%Include symbology-ng/qgsdatadefinedsymboldialog.sip
%Include symbology-ng/qgssizescalewidget.sip
%Include symbology-ng/qgsstylev2exportimportdialog.sip
%Include symbology-ng/qgssvgselectorwidget.sip
%Include symbology-ng/qgsgraduatedhistogramwidget.sip

View File

@ -11,7 +11,7 @@ class QgsDataDefinedAssistant: QDialog
#include <qgsdatadefinedbutton.h>
%End
public:
virtual QgsDataDefined* dataDefined() const = 0 /Factory/;
virtual QgsDataDefined dataDefined() const = 0;
};
/** \ingroup gui

View File

@ -0,0 +1,11 @@
class QgsSizeScaleWidget : QgsDataDefinedAssistant
{
%TypeHeaderCode
#include <qgssizescalewidget.h>
%End
public:
QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsMarkerSymbolV2 * symbol );
QgsDataDefined dataDefined() const;
};

View File

@ -4,7 +4,7 @@ class QgsSymbolsListWidget : QWidget
#include <qgssymbolslistwidget.h>
%End
public:
QgsSymbolsListWidget( QgsSymbolV2* symbol, QgsStyleV2* style, QMenu* menu, QWidget* parent /TransferThis/ = 0 );
QgsSymbolsListWidget( QgsSymbolV2* symbol, QgsStyleV2* style, QMenu* menu, QWidget* parent /TransferThis/ = 0, const QgsVectorLayer * layer = 0 );
public slots:
void setSymbolFromStyle( const QModelIndex & index );

View File

@ -22,6 +22,8 @@
#include "qgspointdisplacementrenderer.h"
#include "qgsinvertedpolygonrenderer.h"
#include "qgspainteffect.h"
#include "qgsscaleexpression.h"
#include "qgsdatadefined.h"
#include "qgsfeature.h"
#include "qgsvectorlayer.h"
@ -695,6 +697,56 @@ QgsLegendSymbolList QgsCategorizedSymbolRendererV2::legendSymbolItems( double sc
return lst;
}
QgsLegendSymbolListV2 QgsCategorizedSymbolRendererV2::legendSymbolItemsV2() const
{
QgsLegendSymbolListV2 lst;
if ( mSourceSymbol.data() && mSourceSymbol->type() == QgsSymbolV2::Marker )
{
// check that all symbols that have the same size expression
QgsDataDefined ddSize;
foreach ( QgsRendererCategoryV2 category, mCategories )
{
const QgsMarkerSymbolV2 * symbol = static_cast<const QgsMarkerSymbolV2 *>( category.symbol() );
if ( !ddSize.hasDefaultValues() && symbol->dataDefinedSize() != ddSize )
{
// no common size expression
return QgsFeatureRendererV2::legendSymbolItemsV2();
}
else
{
ddSize = symbol->dataDefinedSize();
}
}
if ( !ddSize.isActive() || !ddSize.useExpression() )
{
return QgsFeatureRendererV2::legendSymbolItemsV2();
}
QgsScaleExpression exp( ddSize.expressionString() );
if ( exp.type() != QgsScaleExpression::Unknown )
{
QgsLegendSymbolItemV2 title( NULL, exp.baseExpression(), "" );
lst << title;
foreach ( double v, QgsSymbolLayerV2Utils::prettyBreaks( exp.minValue(), exp.maxValue(), 4 ) )
{
QgsLegendSymbolItemV2 si( mSourceSymbol.data(), QString::number( v ), "" );
QgsMarkerSymbolV2 * s = static_cast<QgsMarkerSymbolV2 *>( si.symbol() );
s->setColor( QColor( 0, 0, 0 ) );
s->setDataDefinedSize( QgsDataDefined() );
s->setSize( exp.size( v ) );
lst << si;
}
// now list the categorized symbols
const QgsLegendSymbolListV2 list2 = QgsFeatureRendererV2::legendSymbolItemsV2() ;
foreach ( QgsLegendSymbolItemV2 item, list2 )
lst << item;
return lst;
}
}
return QgsFeatureRendererV2::legendSymbolItemsV2();
}
QgsSymbolV2* QgsCategorizedSymbolRendererV2::sourceSymbol()
{

View File

@ -139,6 +139,9 @@ class CORE_EXPORT QgsCategorizedSymbolRendererV2 : public QgsFeatureRendererV2
//! @note not available in python bindings
virtual QgsLegendSymbolList legendSymbolItems( double scaleDenominator = -1, QString rule = QString() ) override;
//! @note added in 2.10
QgsLegendSymbolListV2 legendSymbolItemsV2() const override;
QgsSymbolV2* sourceSymbol();
void setSourceSymbol( QgsSymbolV2* sym );

View File

@ -21,6 +21,8 @@
#include "qgspointdisplacementrenderer.h"
#include "qgsinvertedpolygonrenderer.h"
#include "qgspainteffect.h"
#include "qgsscaleexpression.h"
#include "qgsdatadefined.h"
#include "qgsfeature.h"
#include "qgsvectorlayer.h"
@ -1151,6 +1153,57 @@ QgsLegendSymbologyList QgsGraduatedSymbolRendererV2::legendSymbologyItems( QSize
return lst;
}
QgsLegendSymbolListV2 QgsGraduatedSymbolRendererV2::legendSymbolItemsV2() const
{
QgsLegendSymbolListV2 list;
if ( mSourceSymbol.data() && mSourceSymbol->type() == QgsSymbolV2::Marker )
{
// check that all symbols that have the same size expression
QgsDataDefined ddSize;
foreach ( QgsRendererRangeV2 range, mRanges )
{
const QgsMarkerSymbolV2 * symbol = static_cast<const QgsMarkerSymbolV2 *>( range.symbol() );
if ( !ddSize.hasDefaultValues() && symbol->dataDefinedSize() != ddSize )
{
// no common size expression
return QgsFeatureRendererV2::legendSymbolItemsV2();
}
else
{
ddSize = symbol->dataDefinedSize();
}
}
if ( !ddSize.isActive() || !ddSize.useExpression() )
{
return QgsFeatureRendererV2::legendSymbolItemsV2();
}
QgsScaleExpression exp( ddSize.expressionString() );
if ( exp.type() != QgsScaleExpression::Unknown )
{
QgsLegendSymbolItemV2 title( NULL, exp.baseExpression(), "" );
list << title;
foreach ( double v, QgsSymbolLayerV2Utils::prettyBreaks( exp.minValue(), exp.maxValue(), 4 ) )
{
QgsLegendSymbolItemV2 si( mSourceSymbol.data(), QString::number( v ), "" );
QgsMarkerSymbolV2 * s = static_cast<QgsMarkerSymbolV2 *>( si.symbol() );
s->setColor( QColor( 0, 0, 0 ) );
s->setDataDefinedSize( QgsDataDefined() );
s->setSize( exp.size( v ) );
list << si;
}
// now list the graduated symbols
const QgsLegendSymbolListV2 list2 = QgsFeatureRendererV2::legendSymbolItemsV2() ;
foreach ( QgsLegendSymbolItemV2 item, list2 )
list << item;
return list;
}
}
return QgsFeatureRendererV2::legendSymbolItemsV2();
}
QgsLegendSymbolList QgsGraduatedSymbolRendererV2::legendSymbolItems( double scaleDenominator, QString rule )
{
Q_UNUSED( scaleDenominator );

View File

@ -239,6 +239,10 @@ class CORE_EXPORT QgsGraduatedSymbolRendererV2 : public QgsFeatureRendererV2
//! @note not available in python bindings
virtual QgsLegendSymbolList legendSymbolItems( double scaleDenominator = -1, QString rule = QString() ) override;
//! @note added in 2.10
QgsLegendSymbolListV2 legendSymbolItemsV2() const override;
QgsSymbolV2* sourceSymbol();
void setSourceSymbol( QgsSymbolV2* sym );

View File

@ -26,6 +26,8 @@
#include "qgspointdisplacementrenderer.h"
#include "qgsinvertedpolygonrenderer.h"
#include "qgspainteffect.h"
#include "qgsscaleexpression.h"
#include "qgsdatadefined.h"
#include <QDomDocument>
#include <QDomElement>
@ -384,6 +386,30 @@ QgsLegendSymbolList QgsSingleSymbolRendererV2::legendSymbolItems( double scaleDe
QgsLegendSymbolListV2 QgsSingleSymbolRendererV2::legendSymbolItemsV2() const
{
QgsLegendSymbolListV2 lst;
if ( mSymbol->type() == QgsSymbolV2::Marker )
{
const QgsMarkerSymbolV2 * symbol = static_cast<const QgsMarkerSymbolV2 *>( mSymbol.data() );
QgsDataDefined sizeDD = symbol->dataDefinedSize();
if ( sizeDD.isActive() && sizeDD.useExpression() )
{
QgsScaleExpression scaleExp( sizeDD.expressionString() );
if ( scaleExp.type() != QgsScaleExpression::Unknown )
{
QgsLegendSymbolItemV2 title( NULL, scaleExp.baseExpression(), 0 );
lst << title;
foreach ( double v, QgsSymbolLayerV2Utils::prettyBreaks( scaleExp.minValue(), scaleExp.maxValue(), 4 ) )
{
QgsLegendSymbolItemV2 si( mSymbol.data(), QString::number( v ), 0 );
QgsMarkerSymbolV2 * s = static_cast<QgsMarkerSymbolV2 *>( si.symbol() );
s->setDataDefinedSize( 0 );
s->setSize( scaleExp.size( v ) );
lst << si;
}
return lst;
}
}
}
lst << QgsLegendSymbolItemV2( mSymbol.data(), QString(), 0 );
return lst;
}

View File

@ -21,7 +21,7 @@
#endif
#define DEG2RAD(x) ((x)*M_PI/180)
#define DEFAULT_SCALE_METHOD QgsSymbolV2::ScaleArea
#define DEFAULT_SCALE_METHOD QgsSymbolV2::ScaleDiameter
#include <QColor>
#include <QMap>
@ -330,7 +330,7 @@ class CORE_EXPORT QgsMarkerSymbolLayerV2 : public QgsSymbolLayerV2
QgsSymbolV2::ScaleMethod scaleMethod() const { return mScaleMethod; }
void setOffset( QPointF offset ) { mOffset = offset; }
QPointF offset() { return mOffset; }
QPointF offset() const { return mOffset; }
virtual void toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const override;

View File

@ -28,6 +28,8 @@
#include "qgspainteffect.h"
#include "qgseffectstack.h"
#include "qgsdatadefined.h"
#include <QColor>
#include <QImage>
#include <QPainter>
@ -35,6 +37,42 @@
#include <cmath>
inline
QgsDataDefined* rotateWholeSymbol( double additionalRotation, const QgsDataDefined& dd )
{
QgsDataDefined* rotatedDD = new QgsDataDefined( dd );
rotatedDD->setUseExpression( true );
QString exprString = dd.useExpression() ? dd.expressionString() : dd.field();
rotatedDD->setExpressionString( QString::number( additionalRotation ) + " + (" + exprString + ")" );
return rotatedDD;
}
inline
QgsDataDefined* scaleWholeSymbol( double scaleFactor, const QgsDataDefined& dd )
{
QgsDataDefined* scaledDD = new QgsDataDefined( dd );
scaledDD->setUseExpression( true );
QString exprString = dd.useExpression() ? dd.expressionString() : dd.field();
scaledDD->setExpressionString( QString::number( scaleFactor ) + "*(" + exprString + ")" );
return scaledDD;
}
inline
QgsDataDefined* scaleWholeSymbol( double scaleFactorX, double scaleFactorY, const QgsDataDefined& dd )
{
QgsDataDefined* scaledDD = new QgsDataDefined( dd );
scaledDD->setUseExpression( true );
QString exprString = dd.useExpression() ? dd.expressionString() : dd.field();
scaledDD->setExpressionString(
( scaleFactorX ? "tostring(" + QString::number( scaleFactorX ) + "*(" + exprString + "))" : "'0'" ) +
"|| ',' || " +
( scaleFactorY ? "tostring(" + QString::number( scaleFactorY ) + "*(" + exprString + "))" : "'0'" ));
return scaledDD;
}
////////////////////
QgsSymbolV2::QgsSymbolV2( SymbolType type, QgsSymbolLayerV2List layers )
: mType( type )
, mLayers( layers )
@ -500,7 +538,6 @@ QgsFillSymbolV2* QgsFillSymbolV2::createSimple( const QgsStringMap& properties )
///////////////////
QgsMarkerSymbolV2::QgsMarkerSymbolV2( QgsSymbolLayerV2List layers )
: QgsSymbolV2( Marker, layers )
{
@ -519,8 +556,7 @@ void QgsMarkerSymbolV2::setAngle( double ang )
}
}
double QgsMarkerSymbolV2::angle()
double QgsMarkerSymbolV2::angle() const
{
QgsSymbolLayerV2List::const_iterator it = mLayers.begin();
@ -541,6 +577,74 @@ void QgsMarkerSymbolV2::setLineAngle( double lineAng )
}
}
void QgsMarkerSymbolV2::setDataDefinedAngle( const QgsDataDefined& dd )
{
const double symbolRotation = angle();
for ( QgsSymbolLayerV2List::iterator it = mLayers.begin(); it != mLayers.end(); ++it )
{
QgsMarkerSymbolLayerV2* layer = static_cast<QgsMarkerSymbolLayerV2 *>( *it );
if ( dd.hasDefaultValues() )
{
layer->removeDataDefinedProperty( "angle" );
}
else
{
if ( qgsDoubleNear( layer->angle(), symbolRotation ) )
{
layer->setDataDefinedProperty( "angle", new QgsDataDefined( dd ) );
}
else
{
QgsDataDefined* rotatedDD = rotateWholeSymbol( layer->angle() - symbolRotation, dd );
layer->setDataDefinedProperty( "angle", rotatedDD );
}
}
}
}
QgsDataDefined QgsMarkerSymbolV2::dataDefinedAngle() const
{
const double symbolRotation = angle();
QgsDataDefined* symbolDD = 0;
// find the base of the "en masse" pattern
for ( QgsSymbolLayerV2List::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
{
const QgsMarkerSymbolLayerV2* layer = static_cast<const QgsMarkerSymbolLayerV2 *>( *it );
if ( layer->angle() == symbolRotation && layer->getDataDefinedProperty( "angle" ) )
{
symbolDD = layer->getDataDefinedProperty( "angle" );
break;
}
}
if ( !symbolDD )
return QgsDataDefined();
// check that all layer's angle expressions match the "en masse" pattern
for ( QgsSymbolLayerV2List::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
{
const QgsMarkerSymbolLayerV2* layer = static_cast<const QgsMarkerSymbolLayerV2 *>( *it );
QgsDataDefined* layerAngleDD = layer->getDataDefinedProperty( "angle" );
if ( qgsDoubleNear( layer->angle(), symbolRotation ) )
{
if ( *layerAngleDD != *symbolDD )
return QgsDataDefined();
}
else
{
QScopedPointer< QgsDataDefined > rotatedDD( rotateWholeSymbol( layer->angle() - symbolRotation, *symbolDD ) );
if ( *layerAngleDD != *( rotatedDD.data() ) )
return QgsDataDefined();
}
}
return QgsDataDefined( *symbolDD );
}
void QgsMarkerSymbolV2::setSize( double s )
{
double origSize = size();
@ -550,16 +654,19 @@ void QgsMarkerSymbolV2::setSize( double s )
QgsMarkerSymbolLayerV2* layer = static_cast<QgsMarkerSymbolLayerV2*>( *it );
if ( layer->size() == origSize )
layer->setSize( s );
else
else if ( origSize != 0 )
{
// proportionally scale size
if ( origSize != 0 )
layer->setSize( layer->size() * s / origSize );
layer->setSize( layer->size() * s / origSize );
}
// also scale offset to maintain relative position
if ( origSize != 0 && ( layer->offset().x() || layer->offset().y() ) )
layer->setOffset( QPointF( layer->offset().x() * s / origSize,
layer->offset().y() * s / origSize ) );
}
}
double QgsMarkerSymbolV2::size()
double QgsMarkerSymbolV2::size() const
{
// return size of the largest symbol
double maxSize = 0;
@ -573,6 +680,91 @@ double QgsMarkerSymbolV2::size()
return maxSize;
}
void QgsMarkerSymbolV2::setDataDefinedSize( const QgsDataDefined &dd )
{
const double symbolSize = size();
for ( QgsSymbolLayerV2List::iterator it = mLayers.begin(); it != mLayers.end(); ++it )
{
QgsMarkerSymbolLayerV2* layer = static_cast<QgsMarkerSymbolLayerV2 *>( *it );
if ( dd.hasDefaultValues() )
{
layer->removeDataDefinedProperty( "size" );
layer->removeDataDefinedProperty( "offset" );
}
else
{
if ( symbolSize == 0 || qgsDoubleNear( layer->size(), symbolSize ) )
{
layer->setDataDefinedProperty( "size", new QgsDataDefined( dd ) );
}
else
{
layer->setDataDefinedProperty( "size", scaleWholeSymbol( layer->size() / symbolSize, dd ) );
}
if ( layer->offset().x() || layer->offset().y() )
{
layer->setDataDefinedProperty( "offset", scaleWholeSymbol(
layer->offset().x() / symbolSize,
layer->offset().y() / symbolSize, dd ) );
}
}
}
}
QgsDataDefined QgsMarkerSymbolV2::dataDefinedSize() const
{
const double symbolSize = size();
QgsDataDefined* symbolDD = 0;
// find the base of the "en masse" pattern
for ( QgsSymbolLayerV2List::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
{
const QgsMarkerSymbolLayerV2* layer = static_cast<const QgsMarkerSymbolLayerV2 *>( *it );
if ( layer->size() == symbolSize && layer->getDataDefinedProperty( "size" ) )
{
symbolDD = layer->getDataDefinedProperty( "size" );
break;
}
}
if ( !symbolDD )
return QgsDataDefined();
// check that all layers size expressions match the "en masse" pattern
for ( QgsSymbolLayerV2List::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
{
const QgsMarkerSymbolLayerV2* layer = static_cast<const QgsMarkerSymbolLayerV2 *>( *it );
QgsDataDefined* layerSizeDD = layer->getDataDefinedProperty( "size" );
QgsDataDefined* layerOffsetDD = layer->getDataDefinedProperty( "offset" );
if ( qgsDoubleNear( layer->size(), symbolSize ) )
{
if ( !layerSizeDD || *layerSizeDD != *symbolDD )
return QgsDataDefined();
}
else
{
if ( symbolSize == 0 )
return QgsDataDefined();
QScopedPointer< QgsDataDefined > scaledDD( scaleWholeSymbol( layer->size() / symbolSize, *symbolDD ) );
if ( !layerSizeDD || *layerSizeDD != *( scaledDD.data() ) )
return QgsDataDefined();
}
QScopedPointer< QgsDataDefined > scaledOffsetDD( scaleWholeSymbol( layer->offset().x() / symbolSize, layer->offset().y() / symbolSize, *symbolDD ) );
if ( layerOffsetDD && *layerOffsetDD != *( scaledOffsetDD.data() ) )
return QgsDataDefined();
}
return QgsDataDefined( *symbolDD );
}
void QgsMarkerSymbolV2::setScaleMethod( QgsSymbolV2::ScaleMethod scaleMethod )
{
@ -668,16 +860,18 @@ void QgsLineSymbolV2::setWidth( double w )
{
layer->setWidth( w );
}
else
else if ( origWidth != 0 )
{
// proportionally scale the width
if ( origWidth != 0 )
layer->setWidth( layer->width() * w / origWidth );
layer->setWidth( layer->width() * w / origWidth );
}
// also scale offset to maintain relative position
if ( origWidth != 0 && layer->offset() )
layer->setOffset( layer->offset() * w / origWidth );
}
}
double QgsLineSymbolV2::width()
double QgsLineSymbolV2::width() const
{
double maxWidth = 0;
for ( QgsSymbolLayerV2List::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
@ -690,6 +884,89 @@ double QgsLineSymbolV2::width()
return maxWidth;
}
void QgsLineSymbolV2::setDataDefinedWidth( const QgsDataDefined& dd )
{
const double symbolWidth = width();
for ( QgsSymbolLayerV2List::iterator it = mLayers.begin(); it != mLayers.end(); ++it )
{
QgsLineSymbolLayerV2* layer = static_cast<QgsLineSymbolLayerV2*>( *it );
if ( dd.hasDefaultValues() )
{
layer->removeDataDefinedProperty( "width" );
layer->removeDataDefinedProperty( "offset" );
}
else
{
if ( symbolWidth == 0 || qgsDoubleNear( layer->width(), symbolWidth ) )
{
layer->setDataDefinedProperty( "width", new QgsDataDefined( dd ) );
}
else
{
layer->setDataDefinedProperty( "width", scaleWholeSymbol( layer->width() / symbolWidth, dd ) );
}
if ( layer->offset() )
{
layer->setDataDefinedProperty( "offset", scaleWholeSymbol( layer->offset() / symbolWidth, dd ) );
}
}
}
}
QgsDataDefined QgsLineSymbolV2::dataDefinedWidth() const
{
const double symbolWidth = width();
QgsDataDefined* symbolDD = 0;
// find the base of the "en masse" pattern
for ( QgsSymbolLayerV2List::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
{
const QgsLineSymbolLayerV2* layer = static_cast<const QgsLineSymbolLayerV2*>( *it );
if ( layer->width() == symbolWidth && layer->getDataDefinedProperty( "width" ) )
{
symbolDD = layer->getDataDefinedProperty( "width" );
break;
}
}
if ( !symbolDD )
return QgsDataDefined();
// check that all layers width expressions match the "en masse" pattern
for ( QgsSymbolLayerV2List::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
{
const QgsLineSymbolLayerV2* layer = static_cast<const QgsLineSymbolLayerV2*>( *it );
QgsDataDefined* layerWidthDD = layer->getDataDefinedProperty( "width" );
QgsDataDefined* layerOffsetDD = layer->getDataDefinedProperty( "offset" );
if ( qgsDoubleNear( layer->width(), symbolWidth ) )
{
if ( !layerWidthDD || *layerWidthDD != *symbolDD )
return QgsDataDefined();
}
else
{
if ( symbolWidth == 0 )
return QgsDataDefined();
QScopedPointer< QgsDataDefined > scaledDD( scaleWholeSymbol( layer->width() / symbolWidth, *symbolDD ) );
if ( !layerWidthDD || *layerWidthDD != *( scaledDD.data() ) )
return QgsDataDefined();
}
QScopedPointer< QgsDataDefined > scaledOffsetDD( scaleWholeSymbol( layer->offset() / symbolWidth, *symbolDD ) );
if ( layerOffsetDD && *layerOffsetDD != *( scaledOffsetDD.data() ) )
return QgsDataDefined();
}
return QgsDataDefined( *symbolDD );
}
void QgsLineSymbolV2::renderPolyline( const QPolygonF& points, const QgsFeature* f, QgsRenderContext& context, int layer, bool selected )
{
//save old painter

View File

@ -41,6 +41,7 @@ class QgsPaintEffect;
class QgsMarkerSymbolLayerV2;
class QgsLineSymbolLayerV2;
class QgsFillSymbolLayerV2;
class QgsDataDefined;
typedef QList<QgsSymbolLayerV2*> QgsSymbolLayerV2List;
@ -183,6 +184,7 @@ class CORE_EXPORT QgsSymbolV2
QSet<QString> usedAttributes() const;
//! @note the layer will be NULL after stopRender
void setLayer( const QgsVectorLayer* layer ) { mLayer = layer; }
const QgsVectorLayer* layer() const { return mLayer; }
@ -280,7 +282,22 @@ class CORE_EXPORT QgsMarkerSymbolV2 : public QgsSymbolV2
QgsMarkerSymbolV2( QgsSymbolLayerV2List layers = QgsSymbolLayerV2List() );
void setAngle( double angle );
double angle();
double angle() const;
/** Set data defined angle for whole symbol (including all symbol layers).
* @param dd data defined angle
* @note added in QGIS 2.9
* @see dataDefinedAngle
*/
void setDataDefinedAngle( const QgsDataDefined& dd );
/** Returns data defined angle for whole symbol (including all symbol layers).
* @returns data defined angle, or empty data defined if angle is not set
* at the marker level
* @note added in QGIS 2.9
* @see setDataDefinedAngle
*/
QgsDataDefined dataDefinedAngle() const;
/** Sets the line angle modification for the symbol's angle. This angle is added to
* the marker's rotation and data defined rotation before rendering the symbol, and
@ -291,7 +308,22 @@ class CORE_EXPORT QgsMarkerSymbolV2 : public QgsSymbolV2
void setLineAngle( double lineAngle );
void setSize( double size );
double size();
double size() const;
/** Set data defined size for whole symbol (including all symbol layers).
* @param dd data defined size
* @note added in QGIS 2.9
* @see dataDefinedSize
*/
void setDataDefinedSize( const QgsDataDefined& dd );
/** Returns data defined size for whole symbol (including all symbol layers).
* @returns data defined size, or empty data defined if size is not set
* at the marker level
* @note added in QGIS 2.9
* @see setDataDefinedSize
*/
QgsDataDefined dataDefinedSize() const;
void setScaleMethod( QgsSymbolV2::ScaleMethod scaleMethod );
ScaleMethod scaleMethod();
@ -319,7 +351,22 @@ class CORE_EXPORT QgsLineSymbolV2 : public QgsSymbolV2
QgsLineSymbolV2( QgsSymbolLayerV2List layers = QgsSymbolLayerV2List() );
void setWidth( double width );
double width();
double width() const;
/** Set data defined width for whole symbol (including all symbol layers).
* @param dd data defined width
* @note added in QGIS 2.9
* @see dataDefinedWidth
*/
void setDataDefinedWidth( const QgsDataDefined& dd );
/** Returns data defined size for whole symbol (including all symbol layers).
* @returns data defined size, or empty data defined if size is not set
* at the line level
* @note added in QGIS 2.9
* @see setDataDefinedWidth
*/
QgsDataDefined dataDefinedWidth() const;
void renderPolyline( const QPolygonF& points, const QgsFeature* f, QgsRenderContext& context, int layer = -1, bool selected = false );

View File

@ -41,6 +41,7 @@ symbology-ng/qgssymbolslistwidget.cpp
symbology-ng/qgssvgselectorwidget.cpp
symbology-ng/qgslayerpropertieswidget.cpp
symbology-ng/qgssmartgroupeditordialog.cpp
symbology-ng/qgssizescalewidget.cpp
effects/qgseffectdrawmodecombobox.cpp
effects/qgspainteffectpropertieswidget.cpp
@ -378,6 +379,7 @@ SET(QGIS_GUI_MOC_HDRS
symbology-ng/qgsvectorfieldsymbollayerwidget.h
symbology-ng/qgsvectorgradientcolorrampv2dialog.h
symbology-ng/qgsvectorrandomcolorrampv2dialog.h
symbology-ng/qgssizescalewidget.h
attributetable/qgsattributetabledelegate.h
attributetable/qgsattributetablefiltermodel.h

View File

@ -434,13 +434,13 @@ void QgsDataDefinedButton::showAssistant()
if ( mAssistant->exec() == QDialog::Accepted )
{
QScopedPointer<QgsDataDefined> dd( mAssistant->dataDefined() );
setUseExpression( dd->useExpression() );
setActive( dd->isActive() );
if ( dd->isActive() && dd->useExpression() )
setExpression( dd->expressionString() );
else if ( dd->isActive() )
setField( dd->field() );
QgsDataDefined dd = mAssistant->dataDefined();
setUseExpression( dd.useExpression() );
setActive( dd.isActive() );
if ( dd.isActive() && dd.useExpression() )
setExpression( dd.expressionString() );
else if ( dd.isActive() )
setField( dd.field() );
updateGui();
}
activateWindow(); // reset focus to parent window

View File

@ -35,7 +35,7 @@ class QgsDataDefined;
class GUI_EXPORT QgsDataDefinedAssistant: public QDialog
{
public:
virtual QgsDataDefined* dataDefined() const = 0;
virtual QgsDataDefined dataDefined() const = 0;
};
/** \ingroup gui

View File

@ -45,6 +45,7 @@ QgsRendererV2Widget::QgsRendererV2Widget( QgsVectorLayer* layer, QgsStyleV2* sty
else if ( mLayer && mLayer->geometryType() == QGis::Point )
{
contextMenu->addAction( tr( "Change size" ), this, SLOT( changeSymbolSize() ) );
contextMenu->addAction( tr( "Change angle" ), this, SLOT( changeSymbolAngle() ) );
}
}
@ -127,17 +128,17 @@ void QgsRendererV2Widget::changeSymbolWidth()
return;
}
bool ok;
QgsLineSymbolV2* line = dynamic_cast<QgsLineSymbolV2*>( symbolList.at( 0 ) ) ;
double width = QInputDialog::getDouble( this, tr( "Width" ), tr( "Change symbol width" ), line ? line->width() : 0.0 , 0.0, 999999, 1, &ok );
if ( ok )
QgsDataDefinedWidthDialog dlg( symbolList, mLayer );
if ( QMessageBox::Ok == dlg.exec() )
{
QList<QgsSymbolV2*>::iterator symbolIt = symbolList.begin();
for ( ; symbolIt != symbolList.end(); ++symbolIt )
if ( !dlg.mDDBtn->isActive() )
{
line = dynamic_cast<QgsLineSymbolV2*>( *symbolIt );
if ( line )
line->setWidth( width );
QList<QgsSymbolV2*>::iterator symbolIt = symbolList.begin();
for ( ; symbolIt != symbolList.end(); ++symbolIt )
{
dynamic_cast<QgsLineSymbolV2*>( *symbolIt )->setWidth( dlg.mSpinBox->value() );
}
}
refreshSymbolView();
}
@ -151,18 +152,41 @@ void QgsRendererV2Widget::changeSymbolSize()
return;
}
bool ok;
QgsMarkerSymbolV2* marker = dynamic_cast<QgsMarkerSymbolV2*>( symbolList.at( 0 ) );
QgsDataDefinedSizeDialog dlg( symbolList, mLayer );
double size = QInputDialog::getDouble( this, tr( "Size" ), tr( "Change symbol size" ), marker ? marker->size() : 0.0 , 0.0, 999999, 1, &ok );
if ( ok )
if ( QMessageBox::Ok == dlg.exec() )
{
QList<QgsSymbolV2*>::iterator symbolIt = symbolList.begin();
for ( ; symbolIt != symbolList.end(); ++symbolIt )
if ( !dlg.mDDBtn->isActive() )
{
marker = dynamic_cast<QgsMarkerSymbolV2*>( *symbolIt );
if ( marker )
marker->setSize( size );
QList<QgsSymbolV2*>::iterator symbolIt = symbolList.begin();
for ( ; symbolIt != symbolList.end(); ++symbolIt )
{
dynamic_cast<QgsMarkerSymbolV2*>( *symbolIt )->setSize( dlg.mSpinBox->value() );
}
}
refreshSymbolView();
}
}
void QgsRendererV2Widget::changeSymbolAngle()
{
QList<QgsSymbolV2*> symbolList = selectedSymbols();
if ( symbolList.size() < 1 )
{
return;
}
QgsDataDefinedRotationDialog dlg( symbolList, mLayer );
if ( QMessageBox::Ok == dlg.exec() )
{
if ( !dlg.mDDBtn->isActive() )
{
QList<QgsSymbolV2*>::iterator symbolIt = symbolList.begin();
for ( ; symbolIt != symbolList.end(); ++symbolIt )
{
dynamic_cast<QgsMarkerSymbolV2*>( *symbolIt )->setAngle( dlg.mSpinBox->value() );
}
}
refreshSymbolView();
}
@ -355,3 +379,84 @@ void QgsRendererV2DataDefinedMenus::updateMenu( QActionGroup* actionGroup, QStri
}
}
#endif
QgsDataDefinedValueDialog::QgsDataDefinedValueDialog( const QList<QgsSymbolV2*>& symbolList, QgsVectorLayer * layer, const QString & label )
: mSymbolList( symbolList )
, mLayer( layer )
{
setupUi( this );
setWindowFlags( Qt::WindowStaysOnTopHint );
mLabel->setText( label );
connect( mDDBtn, SIGNAL( dataDefinedChanged( const QString& ) ), this, SLOT( dataDefinedChanged() ) );
connect( mDDBtn, SIGNAL( dataDefinedActivated( bool ) ), this, SLOT( dataDefinedChanged() ) );
}
void QgsDataDefinedValueDialog::init( const QString & description )
{
QgsDataDefined dd = symbolDataDefined();
mDDBtn->init( mLayer, &dd, QgsDataDefinedButton::Double, description );
mSpinBox->setValue( value( mSymbolList.back() ) );
mSpinBox->setEnabled( !mDDBtn->isActive() );
}
QgsDataDefined QgsDataDefinedValueDialog::symbolDataDefined() const
{
// check that all symbols share the same size expression
QgsDataDefined dd = symbolDataDefined( mSymbolList.back() );
foreach ( QgsSymbolV2 * it, mSymbolList )
{
if ( symbolDataDefined( it ) != dd ) return QgsDataDefined();
}
return dd;
}
void QgsDataDefinedValueDialog::dataDefinedChanged()
{
QgsDataDefined dd = mDDBtn->currentDataDefined();
mSpinBox->setEnabled( !dd.isActive() );
if ( // shall we remove datadefined expressions for layers ?
( symbolDataDefined().isActive() && !dd.isActive() )
// shall we set the "en masse" expression for properties ?
|| dd.isActive() )
{
foreach ( QgsSymbolV2 * it, mSymbolList )
setDataDefined( it, dd );
}
}
QgsDataDefined QgsDataDefinedSizeDialog::symbolDataDefined( const QgsSymbolV2 *symbol ) const
{
const QgsMarkerSymbolV2* marker = static_cast<const QgsMarkerSymbolV2*>( symbol );
return marker->dataDefinedSize();
}
void QgsDataDefinedSizeDialog::setDataDefined( QgsSymbolV2* symbol, const QgsDataDefined& dd )
{
static_cast<QgsMarkerSymbolV2*>( symbol )->setDataDefinedSize( dd );
}
QgsDataDefined QgsDataDefinedRotationDialog::symbolDataDefined( const QgsSymbolV2 *symbol ) const
{
const QgsMarkerSymbolV2* marker = static_cast<const QgsMarkerSymbolV2*>( symbol );
return marker->dataDefinedAngle();
}
void QgsDataDefinedRotationDialog::setDataDefined( QgsSymbolV2 *symbol, const QgsDataDefined &dd )
{
static_cast<QgsMarkerSymbolV2*>( symbol )->setDataDefinedAngle( dd );
}
QgsDataDefined QgsDataDefinedWidthDialog::symbolDataDefined( const QgsSymbolV2 *symbol ) const
{
const QgsLineSymbolV2* line = static_cast<const QgsLineSymbolV2*>( symbol );
return line->dataDefinedWidth();
}
void QgsDataDefinedWidthDialog::setDataDefined( QgsSymbolV2 *symbol, const QgsDataDefined &dd )
{
static_cast<QgsLineSymbolV2*>( symbol )->setDataDefinedWidth( dd );
}

View File

@ -18,6 +18,7 @@
#include <QWidget>
#include <QMenu>
#include "qgssymbolv2.h"
#include "qgsdatadefined.h"
class QgsVectorLayer;
class QgsStyleV2;
@ -73,6 +74,8 @@ class GUI_EXPORT QgsRendererV2Widget : public QWidget
void changeSymbolWidth();
/**Change marker sizes of selected symbols*/
void changeSymbolSize();
/**Change marker angles of selected symbols*/
void changeSymbolAngle();
virtual void copy() {}
virtual void paste() {}
@ -126,4 +129,98 @@ class QgsRendererV2DataDefinedMenus : public QObject
QgsVectorLayer* mLayer;
};
////////////
#include "ui_widget_set_dd_value.h"
#include "qgssizescalewidget.h"
/**
Utility classes for "en masse" size definition
*/
class GUI_EXPORT QgsDataDefinedValueDialog : public QDialog, public Ui::QgsDataDefinedValueDialog
{
Q_OBJECT
public:
/** Constructor
* @param symbolList must not be empty
* @param layer must not be null
*/
QgsDataDefinedValueDialog( const QList<QgsSymbolV2*>& symbolList, QgsVectorLayer * layer, const QString & label );
virtual ~QgsDataDefinedValueDialog() {}
public slots:
void dataDefinedChanged();
protected:
QgsDataDefined symbolDataDefined() const;
void init( const QString & description ); // needed in children ctor to call virtual
virtual QgsDataDefined symbolDataDefined( const QgsSymbolV2 * ) const = 0;
virtual double value( const QgsSymbolV2 * ) const = 0;
virtual void setDataDefined( QgsSymbolV2* symbol, const QgsDataDefined& dd ) = 0;
QList<QgsSymbolV2*> mSymbolList;
QgsVectorLayer* mLayer;
};
class GUI_EXPORT QgsDataDefinedSizeDialog : public QgsDataDefinedValueDialog
{
Q_OBJECT
public:
QgsDataDefinedSizeDialog( const QList<QgsSymbolV2*>& symbolList, QgsVectorLayer * layer )
: QgsDataDefinedValueDialog( symbolList, layer, tr( "Size" ) )
{
init( tr( "Symbol size" ) );
if ( symbolList.length() )
mDDBtn->setAssistant( new QgsSizeScaleWidget( mLayer, static_cast<const QgsMarkerSymbolV2*>( symbolList[0] ) ) );
}
protected:
QgsDataDefined symbolDataDefined( const QgsSymbolV2 * symbol ) const override;
double value( const QgsSymbolV2 * symbol ) const override { return static_cast<const QgsMarkerSymbolV2*>( symbol )->size(); }
void setDataDefined( QgsSymbolV2* symbol, const QgsDataDefined& dd ) override;
};
class GUI_EXPORT QgsDataDefinedRotationDialog : public QgsDataDefinedValueDialog
{
Q_OBJECT
public:
QgsDataDefinedRotationDialog( const QList<QgsSymbolV2*>& symbolList, QgsVectorLayer * layer )
: QgsDataDefinedValueDialog( symbolList, layer, tr( "Rotation" ) )
{
init( tr( "Symbol rotation" ) );
}
protected:
QgsDataDefined symbolDataDefined( const QgsSymbolV2 * symbol ) const override;
double value( const QgsSymbolV2 * symbol ) const override { return static_cast<const QgsMarkerSymbolV2*>( symbol )->angle(); }
void setDataDefined( QgsSymbolV2* symbol, const QgsDataDefined& dd ) override;
};
class GUI_EXPORT QgsDataDefinedWidthDialog : public QgsDataDefinedValueDialog
{
Q_OBJECT
public:
QgsDataDefinedWidthDialog( const QList<QgsSymbolV2*>& symbolList, QgsVectorLayer * layer )
: QgsDataDefinedValueDialog( symbolList, layer, tr( "Width" ) )
{
init( tr( "Symbol width" ) );
}
protected:
QgsDataDefined symbolDataDefined( const QgsSymbolV2 * symbol ) const override;
double value( const QgsSymbolV2 * symbol ) const override { return static_cast<const QgsLineSymbolV2*>( symbol )->width(); }
void setDataDefined( QgsSymbolV2* symbol, const QgsDataDefined& dd ) override;
};
#endif // QGSRENDERERV2WIDGET_H

View File

@ -0,0 +1,204 @@
/***************************************************************************
qgssizescalewidget.cpp - continuous size scale assistant
---------------------
begin : March 2015
copyright : (C) 2015 by Vincent Mora
email : vincent dot mora at oslandia dot com
***************************************************************************
* *
* 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 "qgssizescalewidget.h"
#include "qgsvectorlayer.h"
#include "qgsmaplayerregistry.h"
#include "qgssymbolv2.h"
#include "qgslayertreelayer.h"
#include "qgslayertreemodellegendnode.h"
#include "qgssymbollayerv2utils.h"
#include "qgsscaleexpression.h"
#include "qgsdatadefined.h"
#include <QMenu>
#include <QAction>
#include <QItemDelegate>
#include <limits>
class ItemDelegate : public QItemDelegate
{
public:
ItemDelegate( QStandardItemModel* model ): mModel( model ) {}
QSize sizeHint( const QStyleOptionViewItem& /*option*/, const QModelIndex & index ) const override
{
return mModel->item( index.row() )->icon().actualSize( QSize( 512, 512 ) );
}
private:
QStandardItemModel* mModel;
};
QgsSizeScaleWidget::QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsMarkerSymbolV2 * symbol )
: mSymbol( symbol )
// we just use the minimumValue and maximumValue from the layer, unfortunately they are
// non const, so we get the layer from the registry instead
, mLayer( dynamic_cast<QgsVectorLayer *>( QgsMapLayerRegistry::instance()->mapLayer( layer->id() ) ) )
{
setupUi( this );
setWindowFlags( Qt::WindowStaysOnTopHint );
mLayerTreeLayer = new QgsLayerTreeLayer( mLayer );
mRoot.addChildNode( mLayerTreeLayer ); // takes ownership
treeView->setModel( &mPreviewList );
treeView->setItemDelegate( new ItemDelegate( &mPreviewList ) );
treeView->setHeaderHidden( true );
treeView->expandAll();
QAction* computeFromLayer = new QAction( tr( "Compute from layer" ), this );
connect( computeFromLayer, SIGNAL( triggered() ), this, SLOT( computeFromLayerTriggered() ) );
QMenu* menu = new QMenu();
menu->addAction( computeFromLayer );
computeValuesButton->setMenu( menu );
connect( computeValuesButton, SIGNAL( clicked() ), computeValuesButton, SLOT( showMenu() ) );
//mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
mExpressionWidget->setLayer( mLayer );
scaleMethodComboBox->addItem( tr( "Flannery" ), int( QgsScaleExpression::Flannery ) );
scaleMethodComboBox->addItem( tr( "Surface" ), int( QgsScaleExpression::Area ) );
scaleMethodComboBox->addItem( tr( "Radius" ), int( QgsScaleExpression::Linear ) );
minSizeSpinBox->setShowClearButton( false );
maxSizeSpinBox->setShowClearButton( false );
minValueSpinBox->setShowClearButton( false );
maxValueSpinBox->setShowClearButton( false );
// setup ui from expression if any
QgsDataDefined ddSize = mSymbol->dataDefinedSize();
QgsScaleExpression expr( ddSize.expressionString() );
if ( expr )
{
for ( int i = 0; i < scaleMethodComboBox->count(); i++ )
{
if ( scaleMethodComboBox->itemData( i ).toInt() == int( expr.type() ) )
{
scaleMethodComboBox->setCurrentIndex( i );
break;
}
}
mExpressionWidget->setField( expr.baseExpression() );
minValueSpinBox->setValue( expr.minValue() );
maxValueSpinBox->setValue( expr.maxValue() );
minSizeSpinBox->setValue( expr.minSize() );
maxSizeSpinBox->setValue( expr.maxSize() );
updatePreview();
}
connect( minSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
connect( maxSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
connect( minValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
connect( maxValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
//potentially very expensive for large layers:
connect( mExpressionWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( computeFromLayerTriggered() ) );
connect( scaleMethodComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( updatePreview() ) );
}
QgsDataDefined QgsSizeScaleWidget::dataDefined() const
{
QScopedPointer<QgsScaleExpression> exp( createExpression() );
return QgsDataDefined( exp.data() );
}
QgsScaleExpression *QgsSizeScaleWidget::createExpression() const
{
return new QgsScaleExpression( QgsScaleExpression::Type( scaleMethodComboBox->itemData( scaleMethodComboBox->currentIndex() ).toInt() ),
mExpressionWidget->currentField(),
minValueSpinBox->value(),
maxValueSpinBox->value(),
minSizeSpinBox->value(),
maxSizeSpinBox->value() );
}
void QgsSizeScaleWidget::updatePreview()
{
QScopedPointer<QgsScaleExpression> expr( createExpression() );
QList<double> breaks = QgsSymbolLayerV2Utils::prettyBreaks( expr->minValue(), expr->maxValue(), 4 );
treeView->setIconSize( QSize( 512, 512 ) );
mPreviewList.clear();
int widthMax = 0;
for ( int i = 0; i < breaks.length(); i++ )
{
QScopedPointer< QgsMarkerSymbolV2 > symbol( dynamic_cast<QgsMarkerSymbolV2*>( mSymbol->clone() ) );
symbol->setDataDefinedSize( QgsDataDefined() );
symbol->setDataDefinedAngle( "" ); // to avoid symbol not beeing drawn
symbol->setSize( expr->size( breaks[i] ) );
QgsSymbolV2LegendNode node( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), 0 ) );
const QSize sz( node.minimumIconSize() );
node.setIconSize( sz );
QScopedPointer< QStandardItem > item( new QStandardItem( node.data( Qt::DecorationRole ).value<QPixmap>(), QString::number( breaks[i] ) ) );
widthMax = qMax( sz.width(), widthMax );
mPreviewList.appendRow( item.take() );
}
// center icon and align text left by giving icons the same width
// @todo maybe add some space so that icons don't touch
for ( int i = 0; i < breaks.length(); i++ )
{
QPixmap img( mPreviewList.item( i )->icon().pixmap( mPreviewList.item( i )->icon().actualSize( QSize( 512, 512 ) ) ) );
QPixmap enlarged( widthMax, img.height() );
// fill transparent and add original image
enlarged.fill( Qt::transparent );
QPainter p( &enlarged );
p.drawPixmap( QPoint(( widthMax - img.width() ) / 2, 0 ), img );
p.end();
mPreviewList.item( i )->setIcon( enlarged );
}
}
void QgsSizeScaleWidget::computeFromLayerTriggered()
{
QgsExpression expression( mExpressionWidget->currentField() );
if ( ! expression.prepare( mLayer->pendingFields() ) )
return;
QStringList lst( expression.referencedColumns() );
QgsFeatureIterator fit = mLayer->getFeatures(
QgsFeatureRequest().setFlags( expression.needsGeometry()
? QgsFeatureRequest::NoFlags
: QgsFeatureRequest::NoGeometry )
.setSubsetOfAttributes( lst, mLayer->pendingFields() ) );
// create list of non-null attribute values
double min = DBL_MAX;
double max = -DBL_MAX;
QgsFeature f;
while ( fit.nextFeature( f ) )
{
bool ok;
const double value = expression.evaluate( f ).toDouble( &ok );
if ( ok )
{
max = qMax( max, value );
min = qMin( min, value );
}
}
minValueSpinBox->setValue( min );
maxValueSpinBox->setValue( max );
updatePreview();
}

View File

@ -0,0 +1,56 @@
/***************************************************************************
qgssizescalewidget.h - continuous size scale assistant
---------------------
begin : March 2015
copyright : (C) 2015 by Vincent Mora
email : vincent dot mora at oslandia dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSSIZESCALEWIDGET_H
#define QGSSIZESCALEWIDGET_H
#include "qgslayertreegroup.h"
#include "qgslayertreemodel.h"
#include "qgsdatadefinedbutton.h"
#include "ui_widget_size_scale.h"
#include <QStandardItemModel>
class QgsVectorLayer;
class QgsMarkerSymbolV2;
class QgsLayerTreeLayer;
class QgsScaleExpression;
class QgsDataDefined;
class GUI_EXPORT QgsSizeScaleWidget : public QgsDataDefinedAssistant, private Ui_SizeScaleBase
{
Q_OBJECT
public:
QgsSizeScaleWidget( const QgsVectorLayer * layer, const QgsMarkerSymbolV2 * symbol );
QgsDataDefined dataDefined() const override;
private slots:
void computeFromLayerTriggered();
void updatePreview();
private:
const QgsMarkerSymbolV2* mSymbol;
QgsVectorLayer* mLayer;
QgsLayerTreeLayer* mLayerTreeLayer;
QgsLayerTreeGroup mRoot;
QStandardItemModel mPreviewList;
QgsScaleExpression* createExpression() const;
};
#endif //QGSSIZESCALEWIDGET_H

View File

@ -16,11 +16,15 @@
#include "qgssymbolslistwidget.h"
#include "qgssizescalewidget.h"
#include "qgsstylev2managerdialog.h"
#include "qgsdatadefined.h"
#include "qgssymbolv2.h"
#include "qgsstylev2.h"
#include "qgssymbollayerv2utils.h"
#include "qgsmarkersymbollayerv2.h"
#include "qgsapplication.h"
@ -33,16 +37,17 @@
#include <QInputDialog>
#include <QMessageBox>
#include <QMenu>
#include <QScopedPointer>
QgsSymbolsListWidget::QgsSymbolsListWidget( QgsSymbolV2* symbol, QgsStyleV2* style, QMenu* menu, QWidget* parent )
QgsSymbolsListWidget::QgsSymbolsListWidget( QgsSymbolV2* symbol, QgsStyleV2* style, QMenu* menu, QWidget* parent, const QgsVectorLayer * layer )
: QWidget( parent )
, mSymbol( symbol )
, mStyle( style )
, mAdvancedMenu( 0 )
, mClipFeaturesAction( 0 )
, mLayer( layer )
{
mSymbol = symbol;
mStyle = style;
setupUi( this );
mSymbolUnitWidget->setUnits( QgsSymbolV2::OutputUnitList() << QgsSymbolV2::MM << QgsSymbolV2::MapUnit );
@ -94,6 +99,16 @@ QgsSymbolsListWidget::QgsSymbolsListWidget( QgsSymbolV2* symbol, QgsStyleV2* sty
connect( spinSize, SIGNAL( valueChanged( double ) ), this, SLOT( setMarkerSize( double ) ) );
connect( spinWidth, SIGNAL( valueChanged( double ) ), this, SLOT( setLineWidth( double ) ) );
connect( mRotationDDBtn, SIGNAL( dataDefinedChanged( const QString& ) ), this, SLOT( updateDataDefinedMarkerAngle() ) );
connect( mRotationDDBtn, SIGNAL( dataDefinedActivated( bool ) ), this, SLOT( updateDataDefinedMarkerAngle() ) );
connect( mSizeDDBtn, SIGNAL( dataDefinedChanged( const QString& ) ), this, SLOT( updateDataDefinedMarkerSize() ) );
connect( mSizeDDBtn, SIGNAL( dataDefinedActivated( bool ) ), this, SLOT( updateDataDefinedMarkerSize() ) );
connect( mWidthDDBtn, SIGNAL( dataDefinedChanged( const QString& ) ), this, SLOT( updateDataDefinedLineWidth() ) );
connect( mWidthDDBtn, SIGNAL( dataDefinedActivated( bool ) ), this, SLOT( updateDataDefinedLineWidth() ) );
if ( mSymbol->type() == QgsSymbolV2::Marker && mLayer )
mSizeDDBtn->setAssistant( new QgsSizeScaleWidget( mLayer, static_cast<const QgsMarkerSymbolV2*>( mSymbol ) ) );
// Live color updates are not undoable to child symbol layers
btnColor->setAcceptLiveUpdates( false );
btnColor->setAllowAlpha( true );
@ -198,6 +213,23 @@ void QgsSymbolsListWidget::setMarkerAngle( double angle )
emit changed();
}
void QgsSymbolsListWidget::updateDataDefinedMarkerAngle()
{
QgsMarkerSymbolV2* markerSymbol = static_cast<QgsMarkerSymbolV2*>( mSymbol );
QgsDataDefined dd = mRotationDDBtn->currentDataDefined();
bool isDefault = dd.hasDefaultValues();
if ( // shall we remove datadefined expressions for layers ?
( markerSymbol->dataDefinedAngle().hasDefaultValues() && isDefault )
// shall we set the "en masse" expression for properties ?
|| !isDefault )
{
markerSymbol->setDataDefinedAngle( dd );
emit changed();
}
}
void QgsSymbolsListWidget::setMarkerSize( double size )
{
QgsMarkerSymbolV2* markerSymbol = static_cast<QgsMarkerSymbolV2*>( mSymbol );
@ -207,6 +239,23 @@ void QgsSymbolsListWidget::setMarkerSize( double size )
emit changed();
}
void QgsSymbolsListWidget::updateDataDefinedMarkerSize()
{
QgsMarkerSymbolV2* markerSymbol = static_cast<QgsMarkerSymbolV2*>( mSymbol );
QgsDataDefined dd = mSizeDDBtn->currentDataDefined();
bool isDefault = dd.hasDefaultValues();
if ( // shall we remove datadefined expressions for layers ?
( !markerSymbol->dataDefinedSize().hasDefaultValues() && isDefault )
// shall we set the "en masse" expression for properties ?
|| !isDefault )
{
markerSymbol->setDataDefinedSize( dd );
emit changed();
}
}
void QgsSymbolsListWidget::setLineWidth( double width )
{
QgsLineSymbolV2* lineSymbol = static_cast<QgsLineSymbolV2*>( mSymbol );
@ -216,6 +265,23 @@ void QgsSymbolsListWidget::setLineWidth( double width )
emit changed();
}
void QgsSymbolsListWidget::updateDataDefinedLineWidth()
{
QgsLineSymbolV2* lineSymbol = static_cast<QgsLineSymbolV2*>( mSymbol );
QgsDataDefined dd = mWidthDDBtn->currentDataDefined();
bool isDefault = dd.hasDefaultValues();
if ( // shall we remove datadefined expressions for layers ?
( !lineSymbol->dataDefinedWidth().hasDefaultValues() && isDefault )
// shall we set the "en masse" expression for properties ?
|| !isDefault )
{
lineSymbol->setDataDefinedWidth( dd );
emit changed();
}
}
void QgsSymbolsListWidget::symbolAddedToStyle( QString name, QgsSymbolV2* symbol )
{
Q_UNUSED( name );
@ -297,11 +363,37 @@ void QgsSymbolsListWidget::updateSymbolInfo()
QgsMarkerSymbolV2* markerSymbol = static_cast<QgsMarkerSymbolV2*>( mSymbol );
spinSize->setValue( markerSymbol->size() );
spinAngle->setValue( markerSymbol->angle() );
if ( mLayer )
{
QgsDataDefined ddSize = markerSymbol->dataDefinedSize();
mSizeDDBtn->init( mLayer, &ddSize, QgsDataDefinedButton::AnyType, QgsDataDefinedButton::doublePosDesc() );
spinSize->setEnabled( !mSizeDDBtn->isActive() );
QgsDataDefined ddAngle( markerSymbol->dataDefinedAngle() );
mRotationDDBtn->init( mLayer, &ddAngle, QgsDataDefinedButton::AnyType, QgsDataDefinedButton::doubleDesc() );
spinAngle->setEnabled( !mRotationDDBtn->isActive() );
}
else
{
mSizeDDBtn->setEnabled( false );
mRotationDDBtn->setEnabled( false );
}
}
else if ( mSymbol->type() == QgsSymbolV2::Line )
{
QgsLineSymbolV2* lineSymbol = static_cast<QgsLineSymbolV2*>( mSymbol );
spinWidth->setValue( lineSymbol->width() );
if ( mLayer )
{
QgsDataDefined dd( lineSymbol->dataDefinedWidth() );
mWidthDDBtn->init( mLayer, &dd, QgsDataDefinedButton::AnyType, QgsDataDefinedButton::doubleDesc() );
spinWidth->setEnabled( !mWidthDDBtn->isActive() );
}
else
{
mWidthDDBtn->setEnabled( false );
}
}
mSymbolUnitWidget->blockSignals( true );

View File

@ -30,7 +30,7 @@ class GUI_EXPORT QgsSymbolsListWidget : public QWidget, private Ui::SymbolsListW
Q_OBJECT
public:
QgsSymbolsListWidget( QgsSymbolV2* symbol, QgsStyleV2* style, QMenu* menu, QWidget* parent );
QgsSymbolsListWidget( QgsSymbolV2* symbol, QgsStyleV2* style, QMenu* menu, QWidget* parent, const QgsVectorLayer * layer = 0 );
public slots:
void setSymbolFromStyle( const QModelIndex & index );
@ -49,6 +49,10 @@ class GUI_EXPORT QgsSymbolsListWidget : public QWidget, private Ui::SymbolsListW
void openStyleManager();
void clipFeaturesToggled( bool checked );
void updateDataDefinedMarkerSize();
void updateDataDefinedMarkerAngle();
void updateDataDefinedLineWidth();
signals:
void changed();
@ -57,6 +61,7 @@ class GUI_EXPORT QgsSymbolsListWidget : public QWidget, private Ui::SymbolsListW
QgsStyleV2* mStyle;
QMenu* mAdvancedMenu;
QAction* mClipFeaturesAction;
const QgsVectorLayer* mLayer;
void populateSymbolView();
void populateSymbols( QStringList symbols );

View File

@ -333,8 +333,10 @@ void QgsSymbolV2SelectorDialog::layerChanged()
else
{
// then it must be a symbol
currentItem->symbol()->setLayer( mVectorLayer );
// Now populate symbols of that type using the symbols list widget:
QWidget *symbolsList = new QgsSymbolsListWidget( currentItem->symbol(), mStyle, mAdvancedMenu, this );
QWidget *symbolsList = new QgsSymbolsListWidget( currentItem->symbol(), mStyle, mAdvancedMenu, this, mVectorLayer );
setWidget( symbolsList );
connect( symbolsList, SIGNAL( changed() ), this, SLOT( symbolChanged() ) );
}

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsDataDefinedValueDialog</class>
<widget class="QDialog" name="QgsDataDefinedValueDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>99</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="mLabel">
<property name="text">
<string>Label</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QgsDoubleSpinBox" name="mSpinBox"/>
</item>
<item row="0" column="2">
<widget class="QgsDataDefinedButton" name="mDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsDoubleSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>qgsdoublespinbox.h</header>
</customwidget>
<customwidget>
<class>QgsDataDefinedButton</class>
<extends>QToolButton</extends>
<header>qgsdatadefinedbutton.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>QgsDataDefinedValueDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>QgsDataDefinedValueDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,240 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SizeScaleBase</class>
<widget class="QDialog" name="SizeScaleBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>576</width>
<height>343</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Field</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Scale method</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Size from</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QgsDoubleSpinBox" name="minSizeSpinBox">
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>to</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QgsDoubleSpinBox" name="maxSizeSpinBox">
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
<property name="value">
<double>10.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Values from</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QgsDoubleSpinBox" name="minValueSpinBox">
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-99999999.000000000000000</double>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>to</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QgsDoubleSpinBox" name="maxValueSpinBox">
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-99999999.000000000000000</double>
</property>
<property name="maximum">
<double>99999999.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QToolButton" name="computeValuesButton">
<property name="maximumSize">
<size>
<width>20</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QgsFieldExpressionWidget" name="mExpressionWidget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QComboBox" name="scaleMethodComboBox"/>
</item>
</layout>
</item>
<item row="0" column="1" rowspan="2">
<widget class="QTreeView" name="treeView"/>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>154</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsFieldExpressionWidget</class>
<extends>QWidget</extends>
<header location="global">qgsfieldexpressionwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsDoubleSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>qgsdoublespinbox.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mExpressionWidget</tabstop>
<tabstop>scaleMethodComboBox</tabstop>
<tabstop>minSizeSpinBox</tabstop>
<tabstop>maxSizeSpinBox</tabstop>
<tabstop>minValueSpinBox</tabstop>
<tabstop>maxValueSpinBox</tabstop>
<tabstop>computeValuesButton</tabstop>
<tabstop>buttonBox</tabstop>
<tabstop>treeView</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SizeScaleBase</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SizeScaleBase</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -84,6 +84,7 @@
<property name="text">
<string/>
</property>
<zorder>stackedWidget</zorder>
</widget>
</item>
<item row="0" column="1">
@ -104,7 +105,51 @@
</property>
<widget class="QWidget" name="pageMarker">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Size</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QgsDataDefinedButton" name="mSizeDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QgsDataDefinedButton" name="mRotationDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QgsDoubleSpinBox" name="spinAngle">
<property name="suffix">
<string> °</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="singleStep">
<double>0.500000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Rotation</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QgsDoubleSpinBox" name="spinSize">
<property name="decimals">
<number>5</number>
@ -123,36 +168,6 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Size</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Rotation</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QgsDoubleSpinBox" name="spinAngle">
<property name="suffix">
<string> °</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="singleStep">
<double>0.500000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="pageLine">
@ -176,6 +191,13 @@
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QgsDataDefinedButton" name="mWidthDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
@ -334,17 +356,25 @@
<header>qgsunitselectionwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsDataDefinedButton</class>
<extends>QToolButton</extends>
<header>qgsdatadefinedbutton.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mTransparencySlider</tabstop>
<tabstop>spinSize</tabstop>
<tabstop>mSizeDDBtn</tabstop>
<tabstop>spinWidth</tabstop>
<tabstop>mWidthDDBtn</tabstop>
<tabstop>btnColor</tabstop>
<tabstop>spinAngle</tabstop>
<tabstop>mRotationDDBtn</tabstop>
<tabstop>groupsCombo</tabstop>
<tabstop>openStyleManagerButton</tabstop>
<tabstop>viewSymbols</tabstop>
<tabstop>btnAdvanced</tabstop>
<tabstop>spinWidth</tabstop>
</tabstops>
<resources/>
<connections/>