Skip to content

Commit 3487471

Browse files
committedMay 20, 2015
Merge pull request #2035 from vmora/multivariate_legend
Avoid symbol cropping in legend
2 parents d978f3f + b8bc181 commit 3487471

File tree

7 files changed

+133
-22
lines changed

7 files changed

+133
-22
lines changed
 

‎python/core/effects/qgsimageoperation.sip

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,9 @@ class QgsImageOperation
136136
* @param minSize minimum size for cropped image, if desired. If the
137137
* cropped image is smaller than the minimum size, it will be centered
138138
* in the returned image.
139+
* @param center croped image will be centered on the center of the original image
139140
* @note added in QGIS 2.9
140141
*/
141-
static QImage cropTransparent( const QImage & image, const QSize& minSize = QSize() );
142+
static QImage cropTransparent( const QImage & image, const QSize& minSize = QSize(), bool center = false );
142143

143144
};

‎python/core/layertree/qgslayertreemodellegendnode.sip

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ class QgsSymbolV2LegendNode : QgsLayerTreeModelLegendNode
134134
virtual bool isScaleOK( double scale ) const;
135135

136136
virtual void invalidateMapBasedData();
137+
138+
//! Set the icon size
139+
//! @note added in 2.10
140+
void setIconSize( const QSize& sz );
141+
//! @note added in 2.10
142+
QSize iconSize() const;
143+
144+
//! Get the minimum icon size to prevent cropping
145+
//! @note added in 2.10
146+
QSize minimumIconSize() const;
137147
};
138148

139149

‎src/core/effects/qgsimageoperation.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ void QgsImageOperation::flipImage( QImage &image, QgsImageOperation::FlipType ty
793793
runLineOperation( image, flipOperation );
794794
}
795795

796-
QImage QgsImageOperation::cropTransparent( const QImage &image, const QSize &minSize )
796+
QImage QgsImageOperation::cropTransparent( const QImage &image, const QSize &minSize, bool center )
797797
{
798798
int width = image.width();
799799
int height = image.height();
@@ -828,6 +828,16 @@ QImage QgsImageOperation::cropTransparent( const QImage &image, const QSize &min
828828
ymax = ymin + minSize.height();
829829
}
830830
}
831+
if ( center )
832+
{
833+
// recompute min and max to center image
834+
const int dx = qMax( qAbs( xmax - width / 2 ), qAbs( xmin - width / 2 ) );
835+
const int dy = qMax( qAbs( ymax - height / 2 ), qAbs( ymin - height / 2 ) );
836+
xmin = qMax( 0, width / 2 - dx );
837+
xmax = qMin( width, width / 2 + dx );
838+
ymin = qMax( 0, height / 2 - dy );
839+
ymax = qMin( height, height / 2 + dy );
840+
}
831841
return image.copy( xmin, ymin, xmax - xmin, ymax - ymin );
832842
}
833843

‎src/core/effects/qgsimageoperation.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,10 @@ class CORE_EXPORT QgsImageOperation
167167
* @param minSize minimum size for cropped image, if desired. If the
168168
* cropped image is smaller than the minimum size, it will be centered
169169
* in the returned image.
170+
* @param center croped image will be centered on the center of the original image
170171
* @note added in QGIS 2.9
171172
*/
172-
static QImage cropTransparent( const QImage & image, const QSize& minSize = QSize() );
173+
static QImage cropTransparent( const QImage & image, const QSize& minSize = QSize(), bool center = false );
173174

174175
private:
175176

‎src/core/layertree/qgslayertreemodellegendnode.cpp

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "qgsrasterlayer.h"
2525
#include "qgsrendererv2.h"
2626
#include "qgssymbollayerv2utils.h"
27+
#include "qgsimageoperation.h"
2728
#include "qgsvectorlayer.h"
2829

2930

@@ -135,6 +136,7 @@ QgsSymbolV2LegendNode::QgsSymbolV2LegendNode( QgsLayerTreeLayer* nodeLayer, cons
135136
: QgsLayerTreeModelLegendNode( nodeLayer, parent )
136137
, mItem( item )
137138
, mSymbolUsesMapUnits( false )
139+
, mIconSize( 16, 16 )
138140
{
139141
updateLabel();
140142

@@ -155,6 +157,64 @@ Qt::ItemFlags QgsSymbolV2LegendNode::flags() const
155157
}
156158

157159

160+
QSize QgsSymbolV2LegendNode::minimumIconSize() const
161+
{
162+
QSize minSz;
163+
if ( mItem.symbol() && mItem.symbol()->type() == QgsSymbolV2::Marker )
164+
{
165+
QScopedPointer<QgsRenderContext> context( createTemporaryRenderContext() );
166+
QPixmap pix = QPixmap::fromImage(
167+
QgsImageOperation::cropTransparent(
168+
QgsSymbolLayerV2Utils::symbolPreviewPixmap(
169+
mItem.symbol(),
170+
QSize( 512, 512 ),
171+
context.data() ).toImage(),
172+
mIconSize,
173+
true ) );
174+
minSz = pix.size();
175+
}
176+
else if ( mItem.symbol() && mItem.symbol()->type() == QgsSymbolV2::Line )
177+
{
178+
QScopedPointer<QgsRenderContext> context( createTemporaryRenderContext() );
179+
QPixmap pix = QPixmap::fromImage(
180+
QgsImageOperation::cropTransparent(
181+
QgsSymbolLayerV2Utils::symbolPreviewPixmap(
182+
mItem.symbol(),
183+
QSize( mIconSize.width(), 512 ),
184+
context.data() ).toImage(),
185+
mIconSize,
186+
true ) );
187+
minSz = pix.size();
188+
}
189+
else
190+
{
191+
minSz = mIconSize;
192+
}
193+
194+
if ( mItem.level() != 0 && !( model() && model()->testFlag( QgsLayerTreeModel::ShowLegendAsTree ) ) )
195+
minSz.setWidth( indentSize + minSz.width() );
196+
197+
return minSz;
198+
}
199+
200+
inline
201+
QgsRenderContext * QgsSymbolV2LegendNode::createTemporaryRenderContext() const
202+
{
203+
double scale = 0.0;
204+
double mupp = 0.0;
205+
int dpi = 0;
206+
if ( model() )
207+
model()->legendMapViewData( &mupp, &dpi, &scale );
208+
bool validData = mupp != 0 && dpi != 0 && scale != 0;
209+
210+
// setup temporary render context
211+
QScopedPointer<QgsRenderContext> context( new QgsRenderContext );
212+
context->setScaleFactor( dpi / 25.4 );
213+
context->setRendererScale( scale );
214+
context->setMapToPixel( QgsMapToPixel( mupp ) ); // hope it's ok to leave out other params
215+
return validData ? context.take() : 0;
216+
}
217+
158218
QVariant QgsSymbolV2LegendNode::data( int role ) const
159219
{
160220
if ( role == Qt::DisplayRole )
@@ -167,31 +227,17 @@ QVariant QgsSymbolV2LegendNode::data( int role ) const
167227
}
168228
else if ( role == Qt::DecorationRole )
169229
{
170-
QSize iconSize( 16, 16 ); // TODO: configurable
171-
const int indentSize = 20;
172-
if ( mPixmap.isNull() )
230+
if ( mPixmap.isNull() || mPixmap.size() != mIconSize )
173231
{
174232
QPixmap pix;
175233
if ( mItem.symbol() )
176234
{
177-
double scale = 0.0;
178-
double mupp = 0.0;
179-
int dpi = 0;
180-
if ( model() )
181-
model()->legendMapViewData( &mupp, &dpi, &scale );
182-
bool validData = mupp != 0 && dpi != 0 && scale != 0;
183-
184-
// setup temporary render context
185-
QgsRenderContext context;
186-
context.setScaleFactor( dpi / 25.4 );
187-
context.setRendererScale( scale );
188-
context.setMapToPixel( QgsMapToPixel( mupp ) ); // hope it's ok to leave out other params
189-
190-
pix = QgsSymbolLayerV2Utils::symbolPreviewPixmap( mItem.symbol(), iconSize, validData ? &context : 0 );
235+
QScopedPointer<QgsRenderContext> context( createTemporaryRenderContext() );
236+
pix = QgsSymbolLayerV2Utils::symbolPreviewPixmap( mItem.symbol(), mIconSize, context.data() );
191237
}
192238
else
193239
{
194-
pix = QPixmap( iconSize );
240+
pix = QPixmap( mIconSize );
195241
pix.fill( Qt::transparent );
196242
}
197243

‎src/core/layertree/qgslayertreemodellegendnode.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class QgsLayerTreeModel;
2929
class QgsLegendSettings;
3030
class QgsMapSettings;
3131
class QgsSymbolV2;
32+
class QgsRenderContext;
3233

3334
/**
3435
* The QgsLegendRendererItem class is abstract interface for legend items
@@ -162,6 +163,16 @@ class CORE_EXPORT QgsSymbolV2LegendNode : public QgsLayerTreeModelLegendNode
162163

163164
virtual void invalidateMapBasedData() override;
164165

166+
//! Set the icon size
167+
//! @note added in 2.10
168+
void setIconSize( const QSize& sz ) { mIconSize = sz; }
169+
//! @note added in 2.10
170+
QSize iconSize() const { return mIconSize; }
171+
172+
//! Get the minimum icon size to prevent cropping
173+
//! @note added in 2.10
174+
QSize minimumIconSize() const;
175+
165176
private:
166177
void updateLabel();
167178

@@ -170,6 +181,14 @@ class CORE_EXPORT QgsSymbolV2LegendNode : public QgsLayerTreeModelLegendNode
170181
mutable QPixmap mPixmap; // cached symbol preview
171182
QString mLabel;
172183
bool mSymbolUsesMapUnits;
184+
QSize mIconSize;
185+
186+
// ident the symbol icon to make it look like a tree structure
187+
static const int indentSize = 20;
188+
189+
// return a temporary context or null if legendMapViewData are not valid
190+
QgsRenderContext * createTemporaryRenderContext() const;
191+
173192
};
174193

175194

‎src/core/qgsmaplayerlegend.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,33 @@ QList<QgsLayerTreeModelLegendNode*> QgsDefaultVectorLayerLegend::createLayerTree
199199
nodes.append( new QgsSimpleLegendNode( nodeLayer, r->legendClassificationAttribute() ) );
200200
}
201201

202+
// we have varying icon sizes, and we want icon to be centered and
203+
// text to be left aligned, so we have to compute the max width of icons
204+
//
205+
// we do that for nodes who share a common parent
206+
207+
QList<QgsSymbolV2LegendNode*> symbolNodes;
208+
QMap<QString, int> widthMax;
202209
foreach ( const QgsLegendSymbolItemV2& i, r->legendSymbolItemsV2() )
203210
{
204-
nodes.append( new QgsSymbolV2LegendNode( nodeLayer, i ) );
211+
QgsSymbolV2LegendNode * n = new QgsSymbolV2LegendNode( nodeLayer, i );
212+
nodes.append( n );
213+
if ( i.symbol() )
214+
{
215+
const QSize sz( n->minimumIconSize() );
216+
const QString parentKey( n->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString() );
217+
widthMax[parentKey] = qMax( sz.width(), widthMax.contains( parentKey ) ? widthMax[parentKey] : 0 );
218+
n->setIconSize( sz );
219+
symbolNodes.append( n );
220+
}
221+
}
222+
223+
foreach ( QgsSymbolV2LegendNode* n, symbolNodes )
224+
{
225+
const QString parentKey( n->data( QgsLayerTreeModelLegendNode::ParentRuleKeyRole ).toString() );
226+
Q_ASSERT( widthMax[parentKey] > 0 );
227+
const int twiceMarginWidth = 2; // a one pixel margin avoids hugly rendering of icon
228+
n->setIconSize( QSize( widthMax[parentKey] + twiceMarginWidth, n->iconSize().rheight() + twiceMarginWidth ) );
205229
}
206230

207231
if ( nodes.count() == 1 && nodes[0]->data( Qt::EditRole ).toString().isEmpty() )

0 commit comments

Comments
 (0)
Please sign in to comment.