mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
262 lines
9.2 KiB
C++
262 lines
9.2 KiB
C++
/***************************************************************************
|
|
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;
|
|
|
|
};
|
|
|
|
void QgsSizeScaleWidget::setFromSymbol()
|
|
{
|
|
if ( !mSymbol )
|
|
{
|
|
return;
|
|
}
|
|
|
|
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() );
|
|
nullSizeSpinBox->setValue( expr.nullSize() );
|
|
}
|
|
updatePreview();
|
|
}
|
|
|
|
static QgsExpressionContext _getExpressionContext( const void* context )
|
|
{
|
|
QgsExpressionContext expContext;
|
|
expContext << QgsExpressionContextUtils::globalScope()
|
|
<< QgsExpressionContextUtils::projectScope();
|
|
|
|
const QgsVectorLayer* layer = ( const QgsVectorLayer* ) context;
|
|
if ( layer )
|
|
expContext << QgsExpressionContextUtils::layerScope( layer );
|
|
|
|
return expContext;
|
|
}
|
|
|
|
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( layer ? dynamic_cast<QgsVectorLayer *>( QgsMapLayerRegistry::instance()->mapLayer( layer->id() ) ) : 0 )
|
|
{
|
|
setupUi( this );
|
|
setWindowFlags( Qt::WindowStaysOnTopHint );
|
|
|
|
mExpressionWidget->registerGetExpressionContextCallback( &_getExpressionContext, mLayer );
|
|
|
|
if ( mLayer )
|
|
{
|
|
mLayerTreeLayer = new QgsLayerTreeLayer( mLayer );
|
|
mRoot.addChildNode( mLayerTreeLayer ); // takes ownership
|
|
}
|
|
else
|
|
{
|
|
mLayerTreeLayer = 0;
|
|
}
|
|
|
|
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 );
|
|
if ( mLayer )
|
|
{
|
|
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 );
|
|
nullSizeSpinBox->setShowClearButton( false );
|
|
|
|
// setup ui from expression if any
|
|
setFromSymbol();
|
|
|
|
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() ) );
|
|
connect( nullSizeSpinBox, 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() );
|
|
}
|
|
|
|
void QgsSizeScaleWidget::showEvent( QShowEvent* )
|
|
{
|
|
setFromSymbol();
|
|
}
|
|
|
|
QgsScaleExpression *QgsSizeScaleWidget::createExpression() const
|
|
{
|
|
return new QgsScaleExpression( QgsScaleExpression::Type( scaleMethodComboBox->itemData( scaleMethodComboBox->currentIndex() ).toInt() ),
|
|
mExpressionWidget->currentField(),
|
|
minValueSpinBox->value(),
|
|
maxValueSpinBox->value(),
|
|
minSizeSpinBox->value(),
|
|
maxSizeSpinBox->value(),
|
|
nullSizeSpinBox->value() );
|
|
}
|
|
|
|
void QgsSizeScaleWidget::updatePreview()
|
|
{
|
|
if ( !mSymbol || !mLayer )
|
|
return;
|
|
|
|
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( QgsDataDefined() ); // 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()
|
|
{
|
|
if ( !mLayer )
|
|
return;
|
|
|
|
QgsExpression expression( mExpressionWidget->currentField() );
|
|
|
|
QgsExpressionContext context;
|
|
context << QgsExpressionContextUtils::globalScope()
|
|
<< QgsExpressionContextUtils::projectScope()
|
|
<< QgsExpressionContextUtils::layerScope( mLayer );
|
|
|
|
if ( ! expression.prepare( &context ) )
|
|
return;
|
|
|
|
QStringList lst( expression.referencedColumns() );
|
|
|
|
QgsFeatureIterator fit = mLayer->getFeatures(
|
|
QgsFeatureRequest().setFlags( expression.needsGeometry()
|
|
? QgsFeatureRequest::NoFlags
|
|
: QgsFeatureRequest::NoGeometry )
|
|
.setSubsetOfAttributes( lst, mLayer->fields() ) );
|
|
|
|
// create list of non-null attribute values
|
|
double min = DBL_MAX;
|
|
double max = -DBL_MAX;
|
|
QgsFeature f;
|
|
while ( fit.nextFeature( f ) )
|
|
{
|
|
bool ok;
|
|
context.setFeature( f );
|
|
const double value = expression.evaluate( &context ).toDouble( &ok );
|
|
if ( ok )
|
|
{
|
|
max = qMax( max, value );
|
|
min = qMin( min, value );
|
|
}
|
|
}
|
|
minValueSpinBox->setValue( min );
|
|
maxValueSpinBox->setValue( max );
|
|
updatePreview();
|
|
}
|
|
|