QGIS API Documentation  2.12.0-Lyon
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 
37 {
38  public:
39  explicit ItemDelegate( QStandardItemModel* model ) : mModel( model ) {}
40 
41  QSize sizeHint( const QStyleOptionViewItem& /*option*/, const QModelIndex & index ) const override
42  {
43  return mModel->item( index.row() )->icon().actualSize( QSize( 512, 512 ) );
44  }
45 
46  private:
47  QStandardItemModel* mModel;
48 
49 };
50 
51 void QgsSizeScaleWidget::setFromSymbol()
52 {
53  if ( !mSymbol )
54  {
55  return;
56  }
57 
58  QgsDataDefined ddSize = mSymbol->dataDefinedSize();
59  QgsScaleExpression expr( ddSize.expressionString() );
60  if ( expr )
61  {
62  for ( int i = 0; i < scaleMethodComboBox->count(); i++ )
63  {
64  if ( scaleMethodComboBox->itemData( i ).toInt() == int( expr.type() ) )
65  {
66  scaleMethodComboBox->setCurrentIndex( i );
67  break;
68  }
69  }
70 
71  mExpressionWidget->setField( expr.baseExpression() );
72 
73  minValueSpinBox->setValue( expr.minValue() );
74  maxValueSpinBox->setValue( expr.maxValue() );
75  minSizeSpinBox->setValue( expr.minSize() );
76  maxSizeSpinBox->setValue( expr.maxSize() );
77  nullSizeSpinBox->setValue( expr.nullSize() );
78  }
79  updatePreview();
80 }
81 
82 static QgsExpressionContext _getExpressionContext( const void* context )
83 {
84  const QgsSizeScaleWidget* widget = ( const QgsSizeScaleWidget* ) context;
85 
86  QgsExpressionContext expContext;
90 
91  if ( widget->mapCanvas() )
92  {
95  }
96  else
97  {
99  }
100 
101  if ( widget->layer() )
102  expContext << QgsExpressionContextUtils::layerScope( widget->layer() );
103 
104  return expContext;
105 }
106 
108  : mSymbol( symbol )
109  // we just use the minimumValue and maximumValue from the layer, unfortunately they are
110  // non const, so we get the layer from the registry instead
111  , mLayer( layer ? dynamic_cast<QgsVectorLayer *>( QgsMapLayerRegistry::instance()->mapLayer( layer->id() ) ) : 0 )
112  , mMapCanvas( 0 )
113 {
114  setupUi( this );
115  setWindowFlags( Qt::WindowStaysOnTopHint );
116 
117  mExpressionWidget->registerGetExpressionContextCallback( &_getExpressionContext, this );
118 
119  if ( mLayer )
120  {
121  mLayerTreeLayer = new QgsLayerTreeLayer( mLayer );
122  mRoot.addChildNode( mLayerTreeLayer ); // takes ownership
123  }
124  else
125  {
126  mLayerTreeLayer = 0;
127  }
128 
129  treeView->setModel( &mPreviewList );
130  treeView->setItemDelegate( new ItemDelegate( &mPreviewList ) );
131  treeView->setHeaderHidden( true );
132  treeView->expandAll();
133 
134  QAction* computeFromLayer = new QAction( tr( "Compute from layer" ), this );
135  connect( computeFromLayer, SIGNAL( triggered() ), this, SLOT( computeFromLayerTriggered() ) );
136 
137  QMenu* menu = new QMenu();
138  menu->addAction( computeFromLayer );
139  computeValuesButton->setMenu( menu );
140  connect( computeValuesButton, SIGNAL( clicked() ), computeValuesButton, SLOT( showMenu() ) );
141 
142  //mExpressionWidget->setFilters( QgsFieldProxyModel::Numeric | QgsFieldProxyModel::Date );
143  if ( mLayer )
144  {
145  mExpressionWidget->setLayer( mLayer );
146  }
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  minSizeSpinBox->setShowClearButton( false );
153  maxSizeSpinBox->setShowClearButton( false );
154  minValueSpinBox->setShowClearButton( false );
155  maxValueSpinBox->setShowClearButton( false );
156  nullSizeSpinBox->setShowClearButton( false );
157 
158  // setup ui from expression if any
159  setFromSymbol();
160 
161  connect( minSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
162  connect( maxSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
163  connect( minValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
164  connect( maxValueSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
165  connect( nullSizeSpinBox, SIGNAL( valueChanged( double ) ), this, SLOT( updatePreview() ) );
166  //potentially very expensive for large layers:
167  connect( mExpressionWidget, SIGNAL( fieldChanged( QString ) ), this, SLOT( computeFromLayerTriggered() ) );
168  connect( scaleMethodComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( updatePreview() ) );
169 }
170 
172 {
173  QScopedPointer<QgsScaleExpression> exp( createExpression() );
174  return QgsDataDefined( exp.data() );
175 }
176 
178 {
179  setFromSymbol();
180 }
181 
182 QgsScaleExpression *QgsSizeScaleWidget::createExpression() const
183 {
184  return new QgsScaleExpression( QgsScaleExpression::Type( scaleMethodComboBox->itemData( scaleMethodComboBox->currentIndex() ).toInt() ),
185  mExpressionWidget->currentField(),
186  minValueSpinBox->value(),
187  maxValueSpinBox->value(),
188  minSizeSpinBox->value(),
189  maxSizeSpinBox->value(),
190  nullSizeSpinBox->value() );
191 }
192 
193 void QgsSizeScaleWidget::updatePreview()
194 {
195  if ( !mSymbol || !mLayer )
196  return;
197 
198  QScopedPointer<QgsScaleExpression> expr( createExpression() );
199  QList<double> breaks = QgsSymbolLayerV2Utils::prettyBreaks( expr->minValue(), expr->maxValue(), 4 );
200 
201  treeView->setIconSize( QSize( 512, 512 ) );
202  mPreviewList.clear();
203  int widthMax = 0;
204  for ( int i = 0; i < breaks.length(); i++ )
205  {
206  QScopedPointer< QgsMarkerSymbolV2 > symbol( static_cast<QgsMarkerSymbolV2*>( mSymbol->clone() ) );
207  symbol->setDataDefinedSize( QgsDataDefined() );
208  symbol->setDataDefinedAngle( QgsDataDefined() ); // to avoid symbol not beeing drawn
209  symbol->setSize( expr->size( breaks[i] ) );
210  QgsSymbolV2LegendNode node( mLayerTreeLayer, QgsLegendSymbolItemV2( symbol.data(), QString::number( i ), 0 ) );
211  const QSize sz( node.minimumIconSize() );
212  node.setIconSize( sz );
213  QScopedPointer< QStandardItem > item( new QStandardItem( node.data( Qt::DecorationRole ).value<QPixmap>(), QString::number( breaks[i] ) ) );
214  widthMax = qMax( sz.width(), widthMax );
215  mPreviewList.appendRow( item.take() );
216  }
217 
218  // center icon and align text left by giving icons the same width
219  // @todo maybe add some space so that icons don't touch
220  for ( int i = 0; i < breaks.length(); i++ )
221  {
222  QPixmap img( mPreviewList.item( i )->icon().pixmap( mPreviewList.item( i )->icon().actualSize( QSize( 512, 512 ) ) ) );
223  QPixmap enlarged( widthMax, img.height() );
224  // fill transparent and add original image
225  enlarged.fill( Qt::transparent );
226  QPainter p( &enlarged );
227  p.drawPixmap( QPoint(( widthMax - img.width() ) / 2, 0 ), img );
228  p.end();
229  mPreviewList.item( i )->setIcon( enlarged );
230  }
231 }
232 
233 void QgsSizeScaleWidget::computeFromLayerTriggered()
234 {
235  if ( !mLayer )
236  return;
237 
238  QgsExpression expression( mExpressionWidget->currentField() );
239 
240  QgsExpressionContext context;
245 
246  if ( ! expression.prepare( &context ) )
247  return;
248 
249  QStringList lst( expression.referencedColumns() );
250 
251  QgsFeatureIterator fit = mLayer->getFeatures(
252  QgsFeatureRequest().setFlags( expression.needsGeometry()
255  .setSubsetOfAttributes( lst, mLayer->fields() ) );
256 
257  // create list of non-null attribute values
258  double min = DBL_MAX;
259  double max = -DBL_MAX;
260  QgsFeature f;
261  while ( fit.nextFeature( f ) )
262  {
263  bool ok;
264  context.setFeature( f );
265  const double value = expression.evaluate( &context ).toDouble( &ok );
266  if ( ok )
267  {
268  max = qMax( max, value );
269  min = qMin( min, value );
270  }
271  }
272  minValueSpinBox->setValue( min );
273  maxValueSpinBox->setValue( max );
274  updatePreview();
275 }
276 
Class for parsing and evaluation of expressions (formerly called "search strings").
Definition: qgsexpression.h:92
Wrapper for iterator of features from vector data provider or vector layer.
static unsigned index
void setupUi(QWidget *widget)
void setIcon(const QIcon &icon)
A container class for data source field mapping or expression.
static QList< double > prettyBreaks(double minimum, double maximum, int classes)
Computes a sequence of about 'classes' 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
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 ...
QgsDataDefined dataDefinedSize() const
Returns data defined size for whole symbol (including all symbol layers).
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:176
QString tr(const char *sourceText, const char *disambiguation, int n)
QPixmap pixmap(const QSize &size, Mode mode, State state) const
ItemDelegate(QStandardItemModel *model)
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.
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)
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
int row() const
This class wraps a request for features to a vector layer (or directly its vector data provider)...
virtual QgsSymbolV2 * clone() const override
QSize actualSize(const QSize &size, Mode mode, State state) const
QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &index) const override
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
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:429
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...
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.
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.
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)
QgsSizeScaleWidget(const QgsVectorLayer *layer, const QgsMarkerSymbolV2 *symbol)
QIcon icon() const
Layer tree node points to a map layer.