QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 
35 void QgsSizeScaleWidget::setFromSymbol()
36 {
37  if ( !mSymbol )
38  {
39  return;
40  }
41 
42  QgsDataDefined ddSize;
43  if ( dynamic_cast< const QgsMarkerSymbolV2*>( mSymbol ) )
44  {
45  ddSize = static_cast< const QgsMarkerSymbolV2*>( mSymbol )->dataDefinedSize();
46  }
47  else if ( dynamic_cast< const QgsLineSymbolV2*>( mSymbol ) )
48  {
49  ddSize = dynamic_cast< const QgsLineSymbolV2*>( mSymbol )->dataDefinedWidth();
50  }
51 
52  QgsScaleExpression expr( ddSize.expressionString() );
53  if ( expr )
54  {
55  for ( int i = 0; i < scaleMethodComboBox->count(); i++ )
56  {
57  if ( scaleMethodComboBox->itemData( i ).toInt() == int( expr.type() ) )
58  {
59  whileBlocking( scaleMethodComboBox )->setCurrentIndex( i );
60  break;
61  }
62  }
63 
64  whileBlocking( mExpressionWidget )->setField( expr.baseExpression() );
65  whileBlocking( minValueSpinBox )->setValue( expr.minValue() );
66  whileBlocking( maxValueSpinBox )->setValue( expr.maxValue() );
67  whileBlocking( minSizeSpinBox )->setValue( expr.minSize() );
68  whileBlocking( maxSizeSpinBox )->setValue( expr.maxSize() );
69  whileBlocking( nullSizeSpinBox )->setValue( expr.nullSize() );
70  whileBlocking( exponentSpinBox )->setValue( expr.exponent() );
71  }
72  updatePreview();
73 }
74 
75 static QgsExpressionContext _getExpressionContext( const void* context )
76 {
77  const QgsSizeScaleWidget* widget = ( const QgsSizeScaleWidget* ) context;
78 
79  QgsExpressionContext expContext;
83 
84  if ( widget->mapCanvas() )
85  {
88  }
89  else
90  {
92  }
93 
94  if ( widget->layer() )
95  expContext << QgsExpressionContextUtils::layerScope( widget->layer() );
96 
99 
101 
102  return expContext;
103 }
104 
106  : mSymbol( symbol )
107  // we just use the minimumValue and maximumValue from the layer, unfortunately they are
108  // non const, so we get the layer from the registry instead
109  , mLayer( layer ? dynamic_cast<QgsVectorLayer *>( QgsMapLayerRegistry::instance()->mapLayer( layer->id() ) ) : nullptr )
110  , mMapCanvas( nullptr )
111 {
112  setupUi( this );
113  setWindowFlags( Qt::WindowStaysOnTopHint );
114 
115  mExpressionWidget->registerGetExpressionContextCallback( &_getExpressionContext, this );
116 
117  if ( mLayer )
118  {
119  mLayerTreeLayer = new QgsLayerTreeLayer( mLayer );
120  mRoot.addChildNode( mLayerTreeLayer ); // takes ownership
121  }
122  else
123  {
124  mLayerTreeLayer = nullptr;
125  }
126 
127  treeView->setModel( &mPreviewList );
128  treeView->setItemDelegate( new ItemDelegate( &mPreviewList ) );
129  treeView->setHeaderHidden( true );
130  treeView->expandAll();
131 
132  QAction* computeFromLayer = new QAction( tr( "Compute from layer" ), this );
133  connect( computeFromLayer, SIGNAL( triggered() ), this, SLOT( computeFromLayerTriggered() ) );
134 
135  QMenu* menu = new QMenu();
136  menu->addAction( computeFromLayer );
137  computeValuesButton->setMenu( menu );
138  connect( computeValuesButton, SIGNAL( clicked() ), computeValuesButton, SLOT( showMenu() ) );
139 
140  //mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
141  if ( mLayer )
142  {
143  mExpressionWidget->setLayer( mLayer );
144  }
145 
146  if ( dynamic_cast<const QgsMarkerSymbolV2*>( mSymbol ) )
147  {
148  scaleMethodComboBox->addItem( tr( "Flannery" ), int( QgsScaleExpression::Flannery ) );
149  scaleMethodComboBox->addItem( tr( "Surface" ), int( QgsScaleExpression::Area ) );
150  scaleMethodComboBox->addItem( tr( "Radius" ), int( QgsScaleExpression::Linear ) );
151  }
152  else if ( dynamic_cast<const QgsLineSymbolV2*>( mSymbol ) )
153  {
154  scaleMethodComboBox->addItem( tr( "Exponential" ), int( QgsScaleExpression::Exponential ) );
155  scaleMethodComboBox->addItem( tr( "Linear" ), int( QgsScaleExpression::Linear ) );
156  }
157  else
158  {
159  Q_ASSERT( false );
160  }
161 
162 
163  minSizeSpinBox->setShowClearButton( false );
164  maxSizeSpinBox->setShowClearButton( false );
165  minValueSpinBox->setShowClearButton( false );
166  maxValueSpinBox->setShowClearButton( false );
167  nullSizeSpinBox->setShowClearButton( false );
168 
169  // setup ui from expression if any
170  setFromSymbol();
171 
172  connect( minSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
173  connect( maxSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
174  connect( minValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
175  connect( maxValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
176  connect( nullSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
177  connect( exponentSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
178  //potentially very expensive for large layers:
179  connect( mExpressionWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( computeFromLayerTriggered() ) );
180  connect( scaleMethodComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( updatePreview() ) );
181 }
182 
184 {
185  QScopedPointer<QgsScaleExpression> exp( createExpression() );
186  return QgsDataDefined( exp.data() );
187 }
188 
190 {
191  setFromSymbol();
192 }
193 
194 QgsScaleExpression *QgsSizeScaleWidget::createExpression() const
195 {
196  return new QgsScaleExpression( QgsScaleExpression::Type( scaleMethodComboBox->itemData( scaleMethodComboBox->currentIndex() ).toInt() ),
197  mExpressionWidget->asExpression(),
198  minValueSpinBox->value(),
199  maxValueSpinBox->value(),
200  minSizeSpinBox->value(),
201  maxSizeSpinBox->value(),
202  nullSizeSpinBox->value(),
203  exponentSpinBox->value() );
204 }
205 
206 void QgsSizeScaleWidget::updatePreview()
207 {
208  if ( !mSymbol || !mLayer )
209  return;
210 
211  QScopedPointer<QgsScaleExpression> expr( createExpression() );
212 
213  if ( expr->type() == QgsScaleExpression::Exponential )
214  exponentSpinBox->show();
215  else
216  exponentSpinBox->hide();
217 
218  QList<double> breaks = QgsSymbolLayerV2Utils::prettyBreaks( expr->minValue(), expr->maxValue(), 4 );
219 
220  treeView->setIconSize( QSize( 512, 512 ) );
221  mPreviewList.clear();
222  int widthMax = 0;
223  for ( int i = 0; i < breaks.length(); i++ )
224  {
226  if ( dynamic_cast<const QgsMarkerSymbolV2*>( mSymbol ) )
227  {
228  QScopedPointer< QgsMarkerSymbolV2 > symbol( static_cast<QgsMarkerSymbolV2*>( mSymbol->clone() ) );
229  symbol->setDataDefinedSize( QgsDataDefined() );
230  symbol->setDataDefinedAngle( QgsDataDefined() ); // to avoid symbol not beeing drawn
231  symbol->setSize( expr->size( breaks[i] ) );
232  node.reset( new QgsSymbolV2LegendNode( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), nullptr ) ) );
233  }
234  else if ( dynamic_cast<const QgsLineSymbolV2*>( mSymbol ) )
235  {
236  QScopedPointer< QgsLineSymbolV2 > symbol( static_cast<QgsLineSymbolV2*>( mSymbol->clone() ) );
237  symbol->setDataDefinedWidth( QgsDataDefined() );
238  symbol->setWidth( expr->size( breaks[i] ) );
239  node.reset( new QgsSymbolV2LegendNode( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), nullptr ) ) );
240 
241  }
242 
243  const QSize sz( node->minimumIconSize() );
244  node->setIconSize( sz );
245  QScopedPointer< QStandardItem > item( new QStandardItem( node->data( Qt::DecorationRole ).value<QPixmap>(), QString::number( breaks[i] ) ) );
246  widthMax = qMax( sz.width(), widthMax );
247  mPreviewList.appendRow( item.take() );
248  }
249 
250  // center icon and align text left by giving icons the same width
251  // @todo maybe add some space so that icons don't touch
252  for ( int i = 0; i < breaks.length(); i++ )
253  {
254  QPixmap img( mPreviewList.item( i )->icon().pixmap( mPreviewList.item( i )->icon().actualSize( QSize( 512, 512 ) ) ) );
255  QPixmap enlarged( widthMax, img.height() );
256  // fill transparent and add original image
257  enlarged.fill( Qt::transparent );
258  QPainter p( &enlarged );
259  p.drawPixmap( QPoint(( widthMax - img.width() ) / 2, 0 ), img );
260  p.end();
261  mPreviewList.item( i )->setIcon( enlarged );
262  }
263 }
264 
265 void QgsSizeScaleWidget::computeFromLayerTriggered()
266 {
267  if ( !mLayer )
268  return;
269 
270  QgsExpression expression( mExpressionWidget->currentField() );
271 
272  QgsExpressionContext context;
277 
278  if ( ! expression.prepare( &context ) )
279  return;
280 
281  QStringList lst( expression.referencedColumns() );
282 
283  QgsFeatureIterator fit = mLayer->getFeatures(
284  QgsFeatureRequest().setFlags( expression.needsGeometry()
287  .setSubsetOfAttributes( lst, mLayer->fields() ) );
288 
289  // create list of non-null attribute values
290  double min = DBL_MAX;
291  double max = -DBL_MAX;
292  QgsFeature f;
293  while ( fit.nextFeature( f ) )
294  {
295  bool ok;
296  context.setFeature( f );
297  const double value = expression.evaluate( &context ).toDouble( &ok );
298  if ( ok )
299  {
300  max = qMax( max, value );
301  min = qMin( min, value );
302  }
303  }
304  whileBlocking( minValueSpinBox )->setValue( min );
305  whileBlocking( maxValueSpinBox )->setValue( max );
306  updatePreview();
307 }
308 
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
const QgsVectorLayer * layer() const
Returns the vector layer associated with the widget.
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 ...
const QgsMapCanvas * mapCanvas() const
Returns the map canvas associated with the widget.
void addAction(QAction *action)
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)
QgsFields fields() const
Returns the list of fields of this layer.
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)
QString expressionString() const
Returns the expression string of this QgsDataDefined.
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.
This class tracks map layers that are currently loaded and provides various methods to retrieve match...
T * data() const
QStandardItem * item(int row, int column) const
int height() const
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
void setWindowFlags(QFlags< Qt::WindowType > type)
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:333
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:455
void addChildNode(QgsLayerTreeNode *node)
Append an existing node. The node must not have a parent yet. The node will be owned by this group...
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.