Skip to content
Permalink
Browse files

[composer] Add a checkbox for legends to prevent automatic resizing

A new checkbox has been added to the legend settings to control
whether or not a legend should be automatically resized to fit
its contents.

If unchecked, then the legend will never resize and instead just
stick to whatever size the user has set. Any content which
doesn't fit the size is cropped out.

Refs #10556

On behalf of Faunalia, sponsored by ENEL
  • Loading branch information
nyalldawson committed Jul 13, 2016
1 parent 4f31ab6 commit 2f8c6f52073d4c9c77c39fa119f18ef82783e05d
@@ -46,6 +46,20 @@ class QgsComposerLegend : QgsComposerItem
/** Sets item box to the whole content*/
void adjustBoxSize();

/** Sets whether the legend should automatically resize to fit its contents.
* @param enabled set to false to disable automatic resizing. The legend frame will not
* be expanded to fit legend items, and items may be cropped from display.
* @see resizeToContents()
* @note added in QGIS 3.0
*/
void setResizeToContents( bool enabled );

/** Returns whether the legend should automatically resize to fit its contents.
* @see setResizeToContents()
* @note added in QGIS 3.0
*/
bool resizeToContents() const;

/** Returns pointer to the legend model*/
//! @deprecated in 2.6 - use modelV2()
QgsLegendModel* model() /Deprecated/;
@@ -137,6 +137,8 @@ void QgsComposerLegendWidget::setGuiElements()

mCheckBoxAutoUpdate->setChecked( mLegend->autoUpdateModel() );

mCheckboxResizeContents->setChecked( mLegend->resizeToContents() );

const QgsComposerMap* map = mLegend->composerMap();
mMapComboBox->setItem( map );
mFontColorButton->setColor( mLegend->fontColor() );
@@ -572,6 +574,21 @@ void QgsComposerLegendWidget::composerMapChanged( QgsComposerItem* item )
}
}

void QgsComposerLegendWidget::on_mCheckboxResizeContents_toggled( bool checked )
{
if ( !mLegend )
{
return;
}

mLegend->beginCommand( tr( "Legend resize to contents" ) );
mLegend->setResizeToContents( checked );
if ( checked )
mLegend->adjustBoxSize();
mLegend->updateItem();
mLegend->endCommand();
}

void QgsComposerLegendWidget::on_mRasterBorderGroupBox_toggled( bool state )
{
if ( !mLegend )
@@ -886,6 +903,7 @@ void QgsComposerLegendWidget::blockAllSignals( bool b )
mRasterBorderWidthSpinBox->blockSignals( b );
mWmsLegendWidthSpinBox->blockSignals( b );
mWmsLegendHeightSpinBox->blockSignals( b );
mCheckboxResizeContents->blockSignals( b );
mTitleSpaceBottomSpinBox->blockSignals( b );
}

@@ -68,6 +68,7 @@ class QgsComposerLegendWidget: public QgsComposerItemBaseWidget, private Ui::Qgs
void on_mColumnSpaceSpinBox_valueChanged( double d );
void on_mCheckBoxAutoUpdate_stateChanged( int state );
void composerMapChanged( QgsComposerItem* item );
void on_mCheckboxResizeContents_toggled( bool checked );

void on_mRasterBorderGroupBox_toggled( bool state );
void on_mRasterBorderWidthSpinBox_valueChanged( double d );
@@ -44,6 +44,7 @@ QgsComposerLegend::QgsComposerLegend( QgsComposition* composition )
, mInAtlas( false )
, mInitialMapScaleCalculated( false )
, mForceResize( false )
, mSizeToContents( true )
{
mLegendModel2 = new QgsLegendModelV2( QgsProject::instance()->layerTreeRoot() );

@@ -69,6 +70,7 @@ QgsComposerLegend::QgsComposerLegend()
, mInAtlas( false )
, mInitialMapScaleCalculated( false )
, mForceResize( false )
, mSizeToContents( true )
{

}
@@ -120,28 +122,31 @@ void QgsComposerLegend::paint( QPainter* painter, const QStyleOptionGraphicsItem
mInitialMapScaleCalculated = true;

QgsLegendRenderer legendRenderer( mLegendModel2, mSettings );
legendRenderer.setLegendSize( mForceResize ? QSize() : rect().size() );
legendRenderer.setLegendSize( mForceResize && mSizeToContents ? QSize() : rect().size() );

//adjust box if width or height is too small
QSizeF size = legendRenderer.minimumSize();
if ( mForceResize )
if ( mSizeToContents )
{
mForceResize = false;
//set new rect, respecting position mode and data defined size/position
QRectF targetRect = QRectF( pos().x(), pos().y(), size.width(), size.height() );
setSceneRect( evalItemRect( targetRect, true ) );
}
else if ( size.height() > rect().height() || size.width() > rect().width() )
{
//need to resize box
QRectF targetRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
if ( size.height() > targetRect.height() )
targetRect.setHeight( size.height() );
if ( size.width() > rect().width() )
targetRect.setWidth( size.width() );

//set new rect, respecting position mode and data defined size/position
setSceneRect( evalItemRect( targetRect, true ) );
QSizeF size = legendRenderer.minimumSize();
if ( mForceResize )
{
mForceResize = false;
//set new rect, respecting position mode and data defined size/position
QRectF targetRect = QRectF( pos().x(), pos().y(), size.width(), size.height() );
setSceneRect( evalItemRect( targetRect, true ) );
}
else if ( size.height() > rect().height() || size.width() > rect().width() )
{
//need to resize box
QRectF targetRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
if ( size.height() > targetRect.height() )
targetRect.setHeight( size.height() );
if ( size.width() > rect().width() )
targetRect.setWidth( size.width() );

//set new rect, respecting position mode and data defined size/position
setSceneRect( evalItemRect( targetRect, true ) );
}
}

drawBackground( painter );
@@ -150,6 +155,13 @@ void QgsComposerLegend::paint( QPainter* painter, const QStyleOptionGraphicsItem
painter->setRenderHint( QPainter::Antialiasing, true );
painter->setPen( QPen( QColor( 0, 0, 0 ) ) );

if ( !mSizeToContents )
{
// set a clip region to crop out parts of legend which don't fit
QRectF thisPaintRect = QRectF( 0, 0, rect().width(), rect().height() );
painter->setClipRect( thisPaintRect );
}

legendRenderer.drawLegend( painter );

painter->restore();
@@ -180,6 +192,9 @@ QSizeF QgsComposerLegend::paintAndDetermineSize( QPainter* painter )

void QgsComposerLegend::adjustBoxSize()
{
if ( !mSizeToContents )
return;

if ( !mInitialMapScaleCalculated )
{
// this is messy - but until we have painted the item we have no knowledge of the current DPI
@@ -200,6 +215,15 @@ void QgsComposerLegend::adjustBoxSize()
}
}

void QgsComposerLegend::setResizeToContents( bool enabled )
{
mSizeToContents = enabled;
}

bool QgsComposerLegend::resizeToContents() const
{
return mSizeToContents;
}

void QgsComposerLegend::setCustomLayerTree( QgsLayerTreeGroup* rootGroup )
{
@@ -362,6 +386,8 @@ bool QgsComposerLegend::writeXML( QDomElement& elem, QDomDocument & doc ) const
composerLegendElem.setAttribute( "wrapChar", mSettings.wrapChar() );
composerLegendElem.setAttribute( "fontColor", mSettings.fontColor().name() );

composerLegendElem.setAttribute( "resizeToContents", mSizeToContents );

if ( mComposerMap )
{
composerLegendElem.setAttribute( "map", mComposerMap->id() );
@@ -488,6 +514,8 @@ bool QgsComposerLegend::readXML( const QDomElement& itemElem, const QDomDocument

mSettings.setWrapChar( itemElem.attribute( "wrapChar" ) );

mSizeToContents = itemElem.attribute( "resizeToContents", "1" ) != "0";

//composer map
mLegendFilterByMap = itemElem.attribute( "legendFilterByMap", "0" ).toInt();
if ( !itemElem.attribute( "map" ).isEmpty() )
@@ -75,6 +75,20 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
/** Sets item box to the whole content*/
void adjustBoxSize();

/** Sets whether the legend should automatically resize to fit its contents.
* @param enabled set to false to disable automatic resizing. The legend frame will not
* be expanded to fit legend items, and items may be cropped from display.
* @see resizeToContents()
* @note added in QGIS 3.0
*/
void setResizeToContents( bool enabled );

/** Returns whether the legend should automatically resize to fit its contents.
* @see setResizeToContents()
* @note added in QGIS 3.0
*/
bool resizeToContents() const;

/** Returns pointer to the legend model*/
//! @deprecated in 2.6 - use modelV2()
Q_DECL_DEPRECATED QgsLegendModel* model() {return &mLegendModel;}
@@ -299,6 +313,9 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem

//! Will be true if the legend size should be totally reset at next paint
bool mForceResize;

//! Will be true if the legend should be resized automatically to fit contents
bool mSizeToContents;
};

#endif
@@ -64,8 +64,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>374</width>
<height>1293</height>
<width>375</width>
<height>1506</height>
</rect>
</property>
<layout class="QVBoxLayout" name="mainLayout">
@@ -153,6 +153,13 @@
</item>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="mCheckboxResizeContents">
<property name="text">
<string>Resize to fit contents</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -1034,6 +1041,7 @@
<tabstop>mTitleAlignCombo</tabstop>
<tabstop>mMapComboBox</tabstop>
<tabstop>mWrapCharLineEdit</tabstop>
<tabstop>mCheckboxResizeContents</tabstop>
<tabstop>mLegendItemColGroupBox</tabstop>
<tabstop>mCheckBoxAutoUpdate</tabstop>
<tabstop>mUpdateAllPushButton</tabstop>
@@ -115,6 +115,89 @@ def testResizeWithMapContent(self):

QgsMapLayerRegistry.instance().removeMapLayers([point_layer])

def testResizeDisabled(self):
"""Test that test legend does not resize if auto size is disabled"""

point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
QgsMapLayerRegistry.instance().addMapLayers([point_layer])

s = QgsMapSettings()
s.setLayers([point_layer.id()])
s.setCrsTransformEnabled(False)
composition = QgsComposition(s)
composition.setPaperSize(297, 210)

composer_map = QgsComposerMap(composition, 20, 20, 80, 80)
composer_map.setFrameEnabled(True)
composition.addComposerMap(composer_map)
composer_map.setNewExtent(point_layer.extent())

legend = QgsComposerLegend(composition)
legend.setSceneRect(QRectF(120, 20, 80, 80))
legend.setFrameEnabled(True)
legend.setFrameOutlineWidth(2)
legend.setBackgroundColor(QColor(200, 200, 200))
legend.setTitle('')
legend.setLegendFilterByMapEnabled(True)

#disable auto resizing
legend.setResizeToContents(False)

composition.addComposerLegend(legend)
legend.setComposerMap(composer_map)

composer_map.setNewExtent(QgsRectangle(-102.51, 41.16, -102.36, 41.30))

checker = QgsCompositionChecker(
'composer_legend_noresize', composition)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testComposition()
self.assertTrue(result, message)

QgsMapLayerRegistry.instance().removeMapLayers([point_layer])

def testResizeDisabledCrop(self):
"""Test that if legend resizing is disabled, and legend is too small, then content is cropped"""

point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
QgsMapLayerRegistry.instance().addMapLayers([point_layer])

s = QgsMapSettings()
s.setLayers([point_layer.id()])
s.setCrsTransformEnabled(False)
composition = QgsComposition(s)
composition.setPaperSize(297, 210)

composer_map = QgsComposerMap(composition, 20, 20, 80, 80)
composer_map.setFrameEnabled(True)
composition.addComposerMap(composer_map)
composer_map.setNewExtent(point_layer.extent())

legend = QgsComposerLegend(composition)
legend.setSceneRect(QRectF(120, 20, 20, 20))
legend.setFrameEnabled(True)
legend.setFrameOutlineWidth(2)
legend.setBackgroundColor(QColor(200, 200, 200))
legend.setTitle('')
legend.setLegendFilterByMapEnabled(True)

# disable auto resizing
legend.setResizeToContents(False)

composition.addComposerLegend(legend)
legend.setComposerMap(composer_map)

composer_map.setNewExtent(QgsRectangle(-102.51, 41.16, -102.36, 41.30))

checker = QgsCompositionChecker(
'composer_legend_noresize_crop', composition)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testComposition()
self.assertTrue(result, message)

QgsMapLayerRegistry.instance().removeMapLayers([point_layer])

if __name__ == '__main__':
unittest.main()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

1 comment on commit 2f8c6f5

@DelazJ

This comment has been minimized.

Copy link
Contributor

@DelazJ DelazJ commented on 2f8c6f5 Jul 13, 2016

@nyalldawson doesn't this need to be documented (feature tag)?

Please sign in to comment.
You can’t perform that action at this time.