QGIS API Documentation  2.14.0-Essen
qgssizescalewidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssizescalewidget.cpp - continuous size scale assistant
3 
4  ---------------------
5  begin : March 2015
6  copyright : (C) 2015 by Vincent Mora
7  email : vincent dot mora at oslandia dot com
8  ***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include "qgssizescalewidget.h"
18 
19 #include "qgsvectorlayer.h"
20 #include "qgsmaplayerregistry.h"
21 #include "qgssymbolv2.h"
22 #include "qgslayertreelayer.h"
24 #include "qgssymbollayerv2utils.h"
25 #include "qgsscaleexpression.h"
26 #include "qgsdatadefined.h"
27 #include "qgsmapcanvas.h"
28 
29 #include <QMenu>
30 #include <QAction>
31 #include <QItemDelegate>
32 
33 #include <limits>
34 
36 
37 // RAII class to block a QObject signal until destroyed
38 struct SignalBlocker
39 {
40  SignalBlocker( QObject * object )
41  : mObject( object )
42  {
43  mObject->blockSignals( true );
44  }
45  ~SignalBlocker()
46  {
47  mObject->blockSignals( false );
48  }
49 private:
50  QObject * mObject;
51 
52  SignalBlocker( const SignalBlocker& rh );
53  SignalBlocker& operator=( const SignalBlocker& rh );
54 
55 
56 
57 };
58 
60 
61 void QgsSizeScaleWidget::setFromSymbol()
62 {
63  if ( !mSymbol )
64  {
65  return;
66  }
67 
68  QgsDataDefined ddSize;
69  if ( dynamic_cast< const QgsMarkerSymbolV2*>( mSymbol ) )
70  {
71  ddSize = static_cast< const QgsMarkerSymbolV2*>( mSymbol )->dataDefinedSize();
72  }
73  else if ( dynamic_cast< const QgsLineSymbolV2*>( mSymbol ) )
74  {
75  ddSize = dynamic_cast< const QgsLineSymbolV2*>( mSymbol )->dataDefinedWidth();
76  }
77 
78  QgsScaleExpression expr( ddSize.expressionString() );
79  if ( expr )
80  {
81  for ( int i = 0; i < scaleMethodComboBox->count(); i++ )
82  {
83  if ( scaleMethodComboBox->itemData( i ).toInt() == int( expr.type() ) )
84  {
85  ( SignalBlocker( scaleMethodComboBox ), scaleMethodComboBox->setCurrentIndex( i ) );
86  break;
87  }
88  }
89 
90  // the (,) is used to create the Blocker first, then call the setter
91  // the unamed SignalBlocker is destroyed at the end of the line (semicolumn)
92  ( SignalBlocker( mExpressionWidget ), mExpressionWidget->setField( expr.baseExpression() ) );
93  ( SignalBlocker( minValueSpinBox ), minValueSpinBox->setValue( expr.minValue() ) );
94  ( SignalBlocker( maxValueSpinBox ), maxValueSpinBox->setValue( expr.maxValue() ) );
95  ( SignalBlocker( minSizeSpinBox ), minSizeSpinBox->setValue( expr.minSize() ) );
96  ( SignalBlocker( maxSizeSpinBox ), maxSizeSpinBox->setValue( expr.maxSize() ) );
97  ( SignalBlocker( nullSizeSpinBox ), nullSizeSpinBox->setValue( expr.nullSize() ) );
98  ( SignalBlocker( exponentSpinBox ), exponentSpinBox->setValue( expr.exponent() ) );
99  }
100  updatePreview();
101 }
102 
103 static QgsExpressionContext _getExpressionContext( const void* context )
104 {
105  const QgsSizeScaleWidget* widget = ( const QgsSizeScaleWidget* ) context;
106 
107  QgsExpressionContext expContext;
111 
112  if ( widget->mapCanvas() )
113  {
116  }
117  else
118  {
120  }
121 
122  if ( widget->layer() )
123  expContext << QgsExpressionContextUtils::layerScope( widget->layer() );
124 
127 
129 
130  return expContext;
131 }
132 
134  : mSymbol( symbol )
135  // we just use the minimumValue and maximumValue from the layer, unfortunately they are
136  // non const, so we get the layer from the registry instead
137  , mLayer( layer ? dynamic_cast<QgsVectorLayer *>( QgsMapLayerRegistry::instance()->mapLayer( layer->id() ) ) : nullptr )
138  , mMapCanvas( nullptr )
139 {
140  setupUi( this );
141  setWindowFlags( Qt::WindowStaysOnTopHint );
142 
143  mExpressionWidget->registerGetExpressionContextCallback( &_getExpressionContext, this );
144 
145  if ( mLayer )
146  {
147  mLayerTreeLayer = new QgsLayerTreeLayer( mLayer );
148  mRoot.addChildNode( mLayerTreeLayer ); // takes ownership
149  }
150  else
151  {
152  mLayerTreeLayer = nullptr;
153  }
154 
155  treeView->setModel( &mPreviewList );
156  treeView->setItemDelegate( new ItemDelegate( &mPreviewList ) );
157  treeView->setHeaderHidden( true );
158  treeView->expandAll();
159 
160  QAction* computeFromLayer = new QAction( tr( "Compute from layer" ), this );
161  connect( computeFromLayer, SIGNAL( triggered() ), this, SLOT( computeFromLayerTriggered() ) );
162 
163  QMenu* menu = new QMenu();
164  menu->addAction( computeFromLayer );
165  computeValuesButton->setMenu( menu );
166  connect( computeValuesButton, SIGNAL( clicked() ), computeValuesButton, SLOT( showMenu() ) );
167 
168  //mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
169  if ( mLayer )
170  {
171  mExpressionWidget->setLayer( mLayer );
172  }
173 
174  if ( dynamic_cast<const QgsMarkerSymbolV2*>( mSymbol ) )
175  {
176  scaleMethodComboBox->addItem( tr( "Flannery" ), int( QgsScaleExpression::Flannery ) );
177  scaleMethodComboBox->addItem( tr( "Surface" ), int( QgsScaleExpression::Area ) );
178  scaleMethodComboBox->addItem( tr( "Radius" ), int( QgsScaleExpression::Linear ) );
179  }
180  else if ( dynamic_cast<const QgsLineSymbolV2*>( mSymbol ) )
181  {
182  scaleMethodComboBox->addItem( tr( "Exponential" ), int( QgsScaleExpression::Exponential ) );
183  scaleMethodComboBox->addItem( tr( "Linear" ), int( QgsScaleExpression::Linear ) );
184  }
185  else
186  {
187  Q_ASSERT( false );
188  }
189 
190 
191  minSizeSpinBox->setShowClearButton( false );
192  maxSizeSpinBox->setShowClearButton( false );
193  minValueSpinBox->setShowClearButton( false );
194  maxValueSpinBox->setShowClearButton( false );
195  nullSizeSpinBox->setShowClearButton( false );
196 
197  // setup ui from expression if any
198  setFromSymbol();
199 
200  connect( minSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
201  connect( maxSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
202  connect( minValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
203  connect( maxValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
204  connect( nullSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
205  connect( exponentSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
206  //potentially very expensive for large layers:
207  connect( mExpressionWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( computeFromLayerTriggered() ) );
208  connect( scaleMethodComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( updatePreview() ) );
209 }
210 
212 {
213  QScopedPointer<QgsScaleExpression> exp( createExpression() );
214  return QgsDataDefined( exp.data() );
215 }
216 
218 {
219  setFromSymbol();
220 }
221 
222 QgsScaleExpression *QgsSizeScaleWidget::createExpression() const
223 {
224  return new QgsScaleExpression( QgsScaleExpression::Type( scaleMethodComboBox->itemData( scaleMethodComboBox->currentIndex() ).toInt() ),
225  mExpressionWidget->asExpression(),
226  minValueSpinBox->value(),
227  maxValueSpinBox->value(),
228  minSizeSpinBox->value(),
229  maxSizeSpinBox->value(),
230  nullSizeSpinBox->value(),
231  exponentSpinBox->value() );
232 }
233 
234 void QgsSizeScaleWidget::updatePreview()
235 {
236  if ( !mSymbol || !mLayer )
237  return;
238 
239  QScopedPointer<QgsScaleExpression> expr( createExpression() );
240 
241  if ( expr->type() == QgsScaleExpression::Exponential )
242  exponentSpinBox->show();
243  else
244  exponentSpinBox->hide();
245 
246  QList<double> breaks = QgsSymbolLayerV2Utils::prettyBreaks( expr->minValue(), expr->maxValue(), 4 );
247 
248  treeView->setIconSize( QSize( 512, 512 ) );
249  mPreviewList.clear();
250  int widthMax = 0;
251  for ( int i = 0; i < breaks.length(); i++ )
252  {
254  if ( dynamic_cast<const QgsMarkerSymbolV2*>( mSymbol ) )
255  {
256  QScopedPointer< QgsMarkerSymbolV2 > symbol( static_cast<QgsMarkerSymbolV2*>( mSymbol->clone() ) );
257  symbol->setDataDefinedSize( QgsDataDefined() );
258  symbol->setDataDefinedAngle( QgsDataDefined() ); // to avoid symbol not beeing drawn
259  symbol->setSize( expr->size( breaks[i] ) );
260  node.reset( new QgsSymbolV2LegendNode( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), nullptr ) ) );
261  }
262  else if ( dynamic_cast<const QgsLineSymbolV2*>( mSymbol ) )
263  {
264  QScopedPointer< QgsLineSymbolV2 > symbol( static_cast<QgsLineSymbolV2*>( mSymbol->clone() ) );
265  symbol->setDataDefinedWidth( QgsDataDefined() );
266  symbol->setWidth( expr->size( breaks[i] ) );
267  node.reset( new QgsSymbolV2LegendNode( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), nullptr ) ) );
268 
269  }
270 
271  const QSize sz( node->minimumIconSize() );
272  node->setIconSize( sz );
273  QScopedPointer< QStandardItem > item( new QStandardItem( node->data( Qt::DecorationRole ).value<QPixmap>(), QString::number( breaks[i] ) ) );
274  widthMax = qMax( sz.width(), widthMax );
275  mPreviewList.appendRow( item.take() );
276  }
277 
278  // center icon and align text left by giving icons the same width
279  // @todo maybe add some space so that icons don't touch
280  for ( int i = 0; i < breaks.length(); i++ )
281  {
282  QPixmap img( mPreviewList.item( i )->icon().pixmap( mPreviewList.item( i )->icon().actualSize( QSize( 512, 512 ) ) ) );
283  QPixmap enlarged( widthMax, img.height() );
284  // fill transparent and add original image
285  enlarged.fill( Qt::transparent );
286  QPainter p( &enlarged );
287  p.drawPixmap( QPoint(( widthMax - img.width() ) / 2, 0 ), img );
288  p.end();
289  mPreviewList.item( i )->setIcon( enlarged );
290  }
291 }
292 
293 void QgsSizeScaleWidget::computeFromLayerTriggered()
294 {
295  if ( !mLayer )
296  return;
297 
298  QgsExpression expression( mExpressionWidget->currentField() );
299 
300  QgsExpressionContext context;
305 
306  if ( ! expression.prepare( &context ) )
307  return;
308 
309  QStringList lst( expression.referencedColumns() );
310 
311  QgsFeatureIterator fit = mLayer->getFeatures(
312  QgsFeatureRequest().setFlags( expression.needsGeometry()
315  .setSubsetOfAttributes( lst, mLayer->fields() ) );
316 
317  // create list of non-null attribute values
318  double min = DBL_MAX;
319  double max = -DBL_MAX;
320  QgsFeature f;
321  while ( fit.nextFeature( f ) )
322  {
323  bool ok;
324  context.setFeature( f );
325  const double value = expression.evaluate( &context ).toDouble( &ok );
326  if ( ok )
327  {
328  max = qMax( max, value );
329  min = qMin( min, value );
330  }
331  }
332  ( SignalBlocker( minValueSpinBox ), minValueSpinBox->setValue( min ) );
333  ( SignalBlocker( maxSizeSpinBox ), maxValueSpinBox->setValue( max ) );
334  updatePreview();
335 }
336 
Class for parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
Single variable definition for use within a QgsExpressionContextScope.
void setupUi(QWidget *widget)
void setIcon(const QIcon &icon)
A container class for data source field mapping or expression.
int width() const
bool end()
static QList< double > prettyBreaks(double minimum, double maximum, int classes)
Computes a sequence of about &#39;classes&#39; equally spaced round values which cover the range of values fr...
void fill(const QColor &color)
static QgsExpressionContextScope * atlasScope(const QgsAtlasComposition *atlas)
Creates a new scope which contains variables and functions relating to a QgsAtlasComposition.
int length() const
QgsFields fields() const
Returns the list of fields of this layer.
QgsDataDefined dataDefined() const override
virtual QgsSymbolV2 * clone() const =0
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
Class storing parameters of a scale expression, which is a subclass of QgsExpression for expressions ...
void addAction(QAction *action)
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
QString expressionString() const
Returns the expression string of this QgsDataDefined.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
QString tr(const char *sourceText, const char *disambiguation, int n)
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
QPixmap pixmap(const QSize &size, Mode mode, State state) const
void reset(T *other)
The QgsMapSettings class contains configuration for rendering of the map.
virtual void showEvent(QShowEvent *) override
double ANALYSIS_EXPORT max(double x, double y)
Returns the maximum of two doubles or the first argument if both are equal.
QgsExpressionContextScope * lastScope()
Returns the last scope added to the context.
QString number(int n, int base)
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
static QgsExpressionContext _getExpressionContext(const void *context)
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
QgsSizeScaleWidget(const QgsVectorLayer *layer, const QgsSymbolV2 *symbol)
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QSize actualSize(const QSize &size, Mode mode, State state) const
Single scope for storing variables and functions for use within a QgsExpressionContext.
const QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
This class tracks map layers that are currently loaded and provides a means to fetch a pointer to a m...
T * data() const
QStandardItem * item(int row, int column) const
int height() const
void setWindowFlags(QFlags< Qt::WindowType > type)
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object...
QgsExpressionContextScope & expressionContextScope()
Returns a reference to the expression context scope for the map canvas.
Definition: qgsmapcanvas.h:443
void addChildNode(QgsLayerTreeNode *node)
Append an existing node. The node must not have a parent yet. The node will be owned by this group...
const QgsVectorLayer * layer() const
Returns the vector layer associated with the widget.
Implementation of legend node interface for displaying preview of vector symbols and their labels and...
void setHighlightedVariables(const QStringList &variableNames)
Sets the list of variable names within the context intended to be highlighted to the user...
static QgsExpressionContextScope * projectScope()
Creates a new scope which contains variables and functions relating to the current QGIS project...
double ANALYSIS_EXPORT min(double x, double y)
Returns the minimum of two doubles or the first argument if both are equal.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Geometry is not required. It may still be returned if e.g. required for a filter condition.
static const QString EXPR_GEOMETRY_PART_NUM
Inbuilt variable name for geometry part number variable.
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Represents a vector layer which manages a vector based data sets.
static const QString EXPR_GEOMETRY_PART_COUNT
Inbuilt variable name for geometry part count variable.
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
void appendRow(const QList< QStandardItem * > &items)
QIcon icon() const
Layer tree node points to a map layer.