Port legend item

This commit is contained in:
Nyall Dawson 2017-10-24 17:50:58 +10:00
parent 585a8b70ba
commit d3430e4d42
31 changed files with 4729 additions and 121 deletions

View File

@ -11,31 +11,6 @@
class QgsLegendModel : QgsLayerTreeModel
{
%Docstring
Item model implementation based on layer tree model for composer legend.
Overrides some functionality of QgsLayerTreeModel to better fit the needs of composer legend.
.. versionadded:: 2.6
%End
%TypeHeaderCode
#include "qgscomposerlegend.h"
%End
public:
QgsLegendModel( QgsLayerTree *rootNode, QObject *parent /TransferThis/ = 0 );
%Docstring
Construct the model based on the given layer tree
%End
virtual QVariant data( const QModelIndex &index, int role ) const;
virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
};
class QgsComposerLegend : QgsComposerItem
{
@ -48,6 +23,7 @@ class QgsComposerLegend : QgsComposerItem
%End
public:
QgsComposerLegend( QgsComposition *composition /TransferThis/ );
~QgsComposerLegend();
virtual int type() const;
%Docstring

View File

@ -408,6 +408,7 @@
%Include layout/qgslayoutitem.sip
%Include layout/qgslayoutitemgroup.sip
%Include layout/qgslayoutitemlabel.sip
%Include layout/qgslayoutitemlegend.sip
%Include layout/qgslayoutitemmap.sip
%Include layout/qgslayoutitemmapgrid.sip
%Include layout/qgslayoutitemmapitem.sip

View File

@ -84,6 +84,23 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt
UndoLabelFont,
UndoLabelMargin,
UndoLabelFontColor,
UndoLegendText,
UndoLegendColumnCount,
UndoLegendSymbolWidth,
UndoLegendSymbolHeight,
UndoLegendWmsLegendWidth,
UndoLegendWmsLegendHeight,
UndoLegendTitleSpaceBottom,
UndoLegendGroupSpace,
UndoLegendLayerSpace,
UndoLegendSymbolSpace,
UndoLegendIconSymbolSpace,
UndoLegendFontColor,
UndoLegendBoxSpace,
UndoLegendColumnSpace,
UndoLegendLineSpacing,
UndoLegendRasterStrokeWidth,
UndoLegendRasterStrokeColor,
UndoCustomCommand,
};

View File

@ -0,0 +1,474 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/layout/qgslayoutitemlegend.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsLegendModel : QgsLayerTreeModel
{
%Docstring
Item model implementation based on layer tree model for layout legend.
Overrides some functionality of QgsLayerTreeModel to better fit the needs of layout legends.
.. versionadded:: 2.6
%End
%TypeHeaderCode
#include "qgslayoutitemlegend.h"
%End
public:
QgsLegendModel( QgsLayerTree *rootNode, QObject *parent /TransferThis/ = 0 );
%Docstring
Construct the model based on the given layer tree
%End
virtual QVariant data( const QModelIndex &index, int role ) const;
virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
};
class QgsLayoutItemLegend : QgsLayoutItem
{
%Docstring
A layout item subclass for map legends.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslayoutitemlegend.h"
%End
public:
QgsLayoutItemLegend( QgsLayout *layout );
%Docstring
Constructor for QgsLayoutItemLegend, with the specified parent ``layout``.
%End
static QgsLayoutItemLegend *create( QgsLayout *layout ) /Factory/;
%Docstring
Returns a new legend item for the specified ``layout``.
The caller takes responsibility for deleting the returned object.
:rtype: QgsLayoutItemLegend
%End
virtual int type() const;
virtual QString stringType() const;
virtual QString displayName() const;
void adjustBoxSize();
%Docstring
Sets the legend's item bounds to fit the whole legend content.
%End
void setResizeToContents( bool enabled );
%Docstring
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.
.. seealso:: resizeToContents()
%End
bool resizeToContents() const;
%Docstring
Returns whether the legend should automatically resize to fit its contents.
.. seealso:: setResizeToContents()
:rtype: bool
%End
QgsLegendModel *model();
%Docstring
Returns the legend model.
:rtype: QgsLegendModel
%End
void setAutoUpdateModel( bool autoUpdate );
%Docstring
Sets whether the legend content should auto update to reflect changes in the project's
layer tree.
.. seealso:: autoUpdateModel()
%End
bool autoUpdateModel() const;
%Docstring
Returns whether the legend content should auto update to reflect changes in the project's
layer tree.
.. seealso:: setAutoUpdateModel()
:rtype: bool
%End
void setLegendFilterByMapEnabled( bool enabled );
%Docstring
Set whether legend items should be filtered to show just the ones visible in the associated map.
.. seealso:: legendFilterByMapEnabled()
%End
bool legendFilterByMapEnabled() const;
%Docstring
Find out whether legend items are filtered to show just the ones visible in the associated map
.. seealso:: setLegendFilterByMapEnabled()
:rtype: bool
%End
void setLegendFilterOutAtlas( bool doFilter );
%Docstring
When set to true, during an atlas rendering, it will filter out legend elements
where features are outside the current atlas feature.
.. seealso:: legendFilterOutAtlas()
%End
bool legendFilterOutAtlas() const;
%Docstring
Returns whether to filter out legend elements outside of the current atlas feature.
.. seealso:: setLegendFilterOutAtlas()
:rtype: bool
%End
void setTitle( const QString &title );
%Docstring
Sets the legend ``title``.
.. seealso:: title()
%End
QString title() const;
%Docstring
Returns the legend title.
.. seealso:: setTitle()
:rtype: str
%End
Qt::AlignmentFlag titleAlignment() const;
%Docstring
Returns the alignment of the legend title.
.. seealso:: setTitleAlignment()
:rtype: Qt.AlignmentFlag
%End
void setTitleAlignment( Qt::AlignmentFlag alignment );
%Docstring
Sets the ``alignment`` of the legend title.
.. seealso:: titleAlignment()
%End
QgsLegendStyle &rstyle( QgsLegendStyle::Style s );
%Docstring
Returns reference to modifiable legend style.
:rtype: QgsLegendStyle
%End
QgsLegendStyle style( QgsLegendStyle::Style s ) const;
%Docstring
Returns legend style.
:rtype: QgsLegendStyle
%End
void setStyle( QgsLegendStyle::Style component, const QgsLegendStyle &style );
%Docstring
Sets the style of ``component`` to ``style`` for the legend.
%End
QFont styleFont( QgsLegendStyle::Style component ) const;
%Docstring
Returns the font settings for a legend ``component``.
.. seealso:: setStyleFont()
:rtype: QFont
%End
void setStyleFont( QgsLegendStyle::Style component, const QFont &font );
%Docstring
Sets the style ``font`` for a legend ``component``.
.. seealso:: styleFont()
%End
void setStyleMargin( QgsLegendStyle::Style component, double margin );
%Docstring
Set the ``margin`` for a legend ``component``.
%End
void setStyleMargin( QgsLegendStyle::Style component, QgsLegendStyle::Side side, double margin );
%Docstring
Set the ``margin`` for a particular ``side`` of a legend ``component``.
%End
double lineSpacing() const;
%Docstring
Returns the spacing in-between lines in layout units.
.. seealso:: setLineSpacing()
:rtype: float
%End
void setLineSpacing( double spacing );
%Docstring
Sets the ``spacing`` in-between multiple lines.
.. seealso:: lineSpacing()
%End
double boxSpace() const;
%Docstring
Returns the legend box space.
.. seealso:: setBoxSpace()
:rtype: float
%End
void setBoxSpace( double space );
%Docstring
Sets the legend box ``space``.
.. seealso:: boxSpace()
%End
double columnSpace() const;
%Docstring
Returns the legend column spacing.
.. seealso:: setColumnSpace()
:rtype: float
%End
void setColumnSpace( double spacing );
%Docstring
Sets the legend column ``spacing``.
.. seealso:: columnSpace()
%End
QColor fontColor() const;
%Docstring
Returns the legend font color.
.. seealso:: setFontColor()
:rtype: QColor
%End
void setFontColor( const QColor &color );
%Docstring
Sets the legend font ``color``.
.. seealso:: fontColor()
%End
double symbolWidth() const;
%Docstring
Returns the legend symbol width.
.. seealso:: setSymbolWidth()
:rtype: float
%End
void setSymbolWidth( double width );
%Docstring
Sets the legend symbol ``width``.
.. seealso:: symbolWidth()
%End
double symbolHeight() const;
%Docstring
Returns the legend symbol height.
.. seealso:: setSymbolHeight()
:rtype: float
%End
void setSymbolHeight( double height );
%Docstring
Sets the legend symbol ``height``.
.. seealso:: symbolHeight()
%End
double wmsLegendWidth() const;
%Docstring
Returns the WMS legend width.
.. seealso:: setWmsLegendWidth()
:rtype: float
%End
void setWmsLegendWidth( double width );
%Docstring
Sets the WMS legend ``width``.
.. seealso:: wmsLegendWidth()
%End
double wmsLegendHeight() const;
%Docstring
Returns the WMS legend height.
.. seealso:: setWmsLegendHeight()
:rtype: float
%End
void setWmsLegendHeight( double height );
%Docstring
Sets the WMS legend ``height``.
.. seealso:: wmsLegendHeight()
%End
void setWrapString( const QString &string );
%Docstring
Sets the legend text wrapping ``string``.
.. seealso:: wrapString()
%End
QString wrapString() const;
%Docstring
Returns the legend text wrapping string.
.. seealso:: setWrapString()
:rtype: str
%End
int columnCount() const;
%Docstring
Returns the legend column count.
.. seealso:: setColumnCount()
:rtype: int
%End
void setColumnCount( int count );
%Docstring
Sets the legend column ``count``.
.. seealso:: columnCount()
%End
bool splitLayer() const;
%Docstring
Returns whether the legend items from a single layer can be split
over multiple columns.
.. seealso:: setSplitLayer()
:rtype: bool
%End
void setSplitLayer( bool enabled );
%Docstring
Sets whether the legend items from a single layer can be split
over multiple columns.
.. seealso:: splitLayer()
%End
bool equalColumnWidth() const;
%Docstring
Returns whether column widths should be equalized.
.. seealso:: setEqualColumnWidth()
:rtype: bool
%End
void setEqualColumnWidth( bool equalize );
%Docstring
Sets whether column widths should be equalized.
.. seealso:: equalColumnWidth()
%End
bool drawRasterStroke() const;
%Docstring
Returns whether a stroke will be drawn around raster symbol items.
.. seealso:: setDrawRasterStroke()
.. seealso:: rasterStrokeColor()
.. seealso:: rasterStrokeWidth()
:rtype: bool
%End
void setDrawRasterStroke( bool enabled );
%Docstring
Sets whether a stroke will be drawn around raster symbol items.
\param enabled set to true to draw borders
.. seealso:: drawRasterStroke()
.. seealso:: setRasterStrokeColor()
.. seealso:: setRasterStrokeWidth()
%End
QColor rasterStrokeColor() const;
%Docstring
Returns the stroke color for the stroke drawn around raster symbol items. The stroke is
only drawn if drawRasterStroke() is true.
.. seealso:: setRasterStrokeColor()
.. seealso:: drawRasterStroke()
.. seealso:: rasterStrokeWidth()
:rtype: QColor
%End
void setRasterStrokeColor( const QColor &color );
%Docstring
Sets the stroke ``color`` for the stroke drawn around raster symbol items. The stroke is
only drawn if drawRasterStroke() is true.
.. seealso:: rasterStrokeColor()
.. seealso:: setDrawRasterStroke()
.. seealso:: setRasterStrokeWidth()
%End
double rasterStrokeWidth() const;
%Docstring
Returns the stroke width (in layout units) for the stroke drawn around raster symbol items. The stroke is
only drawn if drawRasterStroke() is true.
.. seealso:: setRasterStrokeWidth()
.. seealso:: drawRasterStroke()
.. seealso:: rasterStrokeColor()
:rtype: float
%End
void setRasterStrokeWidth( double width );
%Docstring
Sets the stroke width for the stroke drawn around raster symbol items. The stroke is
only drawn if drawRasterStroke() is true.
.. seealso:: rasterStrokeWidth()
.. seealso:: setDrawRasterStroke()
.. seealso:: setRasterStrokeColor()
%End
void setMap( QgsLayoutItemMap *map );
%Docstring
Sets the ``map`` to associate with the legend.
.. seealso:: map()
%End
QgsLayoutItemMap *map() const;
%Docstring
Returns the associated map.
.. seealso:: setMap()
:rtype: QgsLayoutItemMap
%End
void updateLegend();
%Docstring
Updates the model and all legend entries.
%End
void updateFilterByMap( bool redraw = true );
%Docstring
Updates the legend content when filtered by map.
%End
const QgsLegendSettings &legendSettings() const;
%Docstring
Returns the legend's renderer settings object.
:rtype: QgsLegendSettings
%End
virtual void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget );
public slots:
virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );
protected:
virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/layout/qgslayoutitemlegend.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -405,6 +405,20 @@ Returns true if the map contains layers with blend modes or flattened layers for
virtual QgsExpressionContext createExpressionContext() const;
double mapUnitsToLayoutUnits() const;
%Docstring
Returns the conversion factor from map units to layout units.
This is calculated using the width of the map item and the width of the
current visible map extent.
:rtype: float
%End
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
%Docstring
Return map settings that will be used for drawing of the map.
:rtype: QgsMapSettings
%End
protected:
virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 );
@ -414,12 +428,6 @@ Returns true if the map contains layers with blend modes or flattened layers for
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
%Docstring
Return map settings that will be used for drawing of the map.
:rtype: QgsMapSettings
%End
bool isDrawing() const;
%Docstring
True if a draw is already in progress
@ -430,14 +438,6 @@ True if a draw is already in progress
virtual QRectF boundingRect() const;
double mapUnitsToLayoutUnits() const;
%Docstring
Returns the conversion factor from map units to layout units.
This is calculated using the width of the map item and the width of the
current visible map extent.
:rtype: float
%End
QPolygonF transformedMapPolygon() const;
%Docstring
Returns extent that considers rotation and shift with mOffsetX / mOffsetY

View File

@ -105,6 +105,7 @@ class QgsLayoutItemRegistry : QObject
LayoutMap,
LayoutPicture,
LayoutLabel,
LayoutLegend,
LayoutShape,
LayoutPolygon,
LayoutPolyline,

View File

@ -180,6 +180,7 @@ SET(QGIS_APP_SRCS
layout/qgslayoutitemslistview.cpp
layout/qgslayoutappmenuprovider.cpp
layout/qgslayoutlabelwidget.cpp
layout/qgslayoutlegendwidget.cpp
layout/qgslayoutmapwidget.cpp
layout/qgslayoutmapgridwidget.cpp
layout/qgslayoutpagepropertieswidget.cpp
@ -383,6 +384,7 @@ SET (QGIS_APP_MOC_HDRS
layout/qgslayoutguidewidget.h
layout/qgslayoutitemslistview.h
layout/qgslayoutlabelwidget.h
layout/qgslayoutlegendwidget.h
layout/qgslayoutmapwidget.h
layout/qgslayoutmapgridwidget.h
layout/qgslayoutpagepropertieswidget.h

View File

@ -34,6 +34,7 @@
#include "qgsmaplayerlegend.h"
#include "qgsproject.h"
#include "qgsvectorlayer.h"
#include "qgslayoutitemlegend.h"
#include <QMessageBox>
#include <QInputDialog>

View File

@ -31,6 +31,8 @@
#include "qgslayoutitempicture.h"
#include "qgslayoutitemlabel.h"
#include "qgslayoutlabelwidget.h"
#include "qgslayoutitemlegend.h"
#include "qgslayoutlegendwidget.h"
#include "qgisapp.h"
#include "qgsmapcanvas.h"
@ -110,6 +112,32 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
registry->addLayoutItemGuiMetadata( labelItemMetadata.release() );
// legend item
auto legendItemMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >( QgsLayoutItemRegistry::LayoutLegend, QObject::tr( "Legend" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLegend.svg" ) ),
[ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
{
return new QgsLayoutLegendWidget( qobject_cast< QgsLayoutItemLegend * >( item ) );
}, createRubberBand );
legendItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
{
QgsLayoutItemLegend *legend = qobject_cast< QgsLayoutItemLegend * >( item );
Q_ASSERT( legend );
QList<QgsLayoutItemMap *> mapItems;
legend->layout()->layoutItems( mapItems );
if ( !mapItems.isEmpty() )
{
legend->setMap( mapItems.at( 0 ) );
}
legend->updateLegend();
} );
registry->addLayoutItemGuiMetadata( legendItemMetadata.release() );
// shape items
auto createShapeWidget =

View File

@ -48,7 +48,6 @@ QgsLayoutLabelWidget::QgsLayoutLabelWidget( QgsLayoutItemLabel *label )
//add widget for general composer item properties
mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, label );
//shapes don't use background or frame, since the symbol style is set through a QgsSymbolSelectorWidget
mainLayout->addWidget( mItemPropertiesWidget );
mFontColorButton->setColorDialogTitle( tr( "Select Font Color" ) );

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,135 @@
/***************************************************************************
qgslayoutlegendwidget.h
-----------------------
begin : October 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail 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. *
* *
***************************************************************************/
#ifndef QGSLAYOUTLEGENDWIDGET_H
#define QGSLAYOUTLEGENDWIDGET_H
#include "ui_qgslayoutlegendwidgetbase.h"
#include "qgslayoutitemwidget.h"
#include <QWidget>
#include <QItemDelegate>
class QgsLayoutItemLegend;
/**
* \ingroup app
* A widget for setting properties relating to a layout legend.
*/
class QgsLayoutLegendWidget: public QgsLayoutItemBaseWidget, private Ui::QgsLayoutLegendWidgetBase
{
Q_OBJECT
public:
explicit QgsLayoutLegendWidget( QgsLayoutItemLegend *legend );
//! Updates the legend layers and groups
void updateLegend();
QgsLayoutItemLegend *legend() { return mLegend; }
protected:
bool setNewItem( QgsLayoutItem *item ) override;
public slots:
void mWrapCharLineEdit_textChanged( const QString &text );
void mTitleLineEdit_textChanged( const QString &text );
void mTitleAlignCombo_currentIndexChanged( int index );
void mColumnCountSpinBox_valueChanged( int c );
void mSplitLayerCheckBox_toggled( bool checked );
void mEqualColumnWidthCheckBox_toggled( bool checked );
void mSymbolWidthSpinBox_valueChanged( double d );
void mSymbolHeightSpinBox_valueChanged( double d );
void mWmsLegendWidthSpinBox_valueChanged( double d );
void mWmsLegendHeightSpinBox_valueChanged( double d );
void mTitleSpaceBottomSpinBox_valueChanged( double d );
void mGroupSpaceSpinBox_valueChanged( double d );
void mLayerSpaceSpinBox_valueChanged( double d );
void mSymbolSpaceSpinBox_valueChanged( double d );
void mIconLabelSpaceSpinBox_valueChanged( double d );
void mFontColorButton_colorChanged( const QColor &newFontColor );
void mBoxSpaceSpinBox_valueChanged( double d );
void mColumnSpaceSpinBox_valueChanged( double d );
void mLineSpacingSpinBox_valueChanged( double d );
void mCheckBoxAutoUpdate_stateChanged( int state );
void composerMapChanged( QgsLayoutItem *item );
void mCheckboxResizeContents_toggled( bool checked );
void mRasterStrokeGroupBox_toggled( bool state );
void mRasterStrokeWidthSpinBox_valueChanged( double d );
void mRasterStrokeColorButton_colorChanged( const QColor &newColor );
//item manipulation
void mMoveDownToolButton_clicked();
void mMoveUpToolButton_clicked();
void mRemoveToolButton_clicked();
void mAddToolButton_clicked();
void mEditPushButton_clicked();
void mCountToolButton_clicked( bool checked );
void mExpressionFilterButton_toggled( bool checked );
void mFilterByMapToolButton_toggled( bool checked );
void resetLayerNodeToDefaults();
void mUpdateAllPushButton_clicked();
void mAddGroupToolButton_clicked();
void mFilterLegendByAtlasCheckBox_toggled( bool checked );
void selectedChanged( const QModelIndex &current, const QModelIndex &previous );
void setCurrentNodeStyleFromAction();
private slots:
//! Sets GUI according to state of mLegend
void setGuiElements();
//! Update the enabling state of the filter by atlas button
void updateFilterLegendByAtlasButton();
void mItemTreeView_doubleClicked( const QModelIndex &index );
void titleFontChanged();
void groupFontChanged();
void layerFontChanged();
void itemFontChanged();
private:
QgsLayoutLegendWidget() = delete;
void blockAllSignals( bool b );
QgsLayoutItemLegend *mLegend = nullptr;
QgsLayoutItemPropertiesWidget *mItemPropertiesWidget = nullptr;
};
class QgsLayoutLegendMenuProvider : public QgsLayerTreeViewMenuProvider
{
public:
QgsLayoutLegendMenuProvider( QgsLayerTreeView *view, QgsLayoutLegendWidget *w );
virtual QMenu *createContextMenu() override;
protected:
QgsLayerTreeView *mView = nullptr;
QgsLayoutLegendWidget *mWidget = nullptr;
};
#endif //QGSLAYOUTLEGENDWIDGET_H

View File

@ -370,6 +370,7 @@ SET(QGIS_CORE_SRCS
layout/qgslayoutitemgroup.cpp
layout/qgslayoutitemgroupundocommand.cpp
layout/qgslayoutitemlabel.cpp
layout/qgslayoutitemlegend.cpp
layout/qgslayoutitemmap.cpp
layout/qgslayoutitemmapgrid.cpp
layout/qgslayoutitemmapitem.cpp
@ -730,6 +731,7 @@ SET(QGIS_CORE_MOC_HDRS
layout/qgslayoutitemgroup.h
layout/qgslayoutitemgroupundocommand.h
layout/qgslayoutitemlabel.h
layout/qgslayoutitemlegend.h
layout/qgslayoutitemmap.h
layout/qgslayoutitemmapgrid.h
layout/qgslayoutitemmapitem.h

View File

@ -32,6 +32,7 @@
#include <QDomDocument>
#include <QDomElement>
#include <QPainter>
#include "qgslayoutitemlegend.h"
QgsComposerLegend::QgsComposerLegend( QgsComposition *composition )
: QgsComposerItem( composition )
@ -45,6 +46,11 @@ QgsComposerLegend::QgsComposerLegend( QgsComposition *composition )
connect( mComposition->project()->layerTreeRoot(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsComposerLegend::nodeCustomPropertyChanged );
}
QgsComposerLegend::~QgsComposerLegend()
{
delete mLegendModel;
}
QgsComposerLegend::QgsComposerLegend()
: QgsComposerItem( nullptr )
{
@ -91,7 +97,7 @@ void QgsComposerLegend::paint( QPainter *painter, const QStyleOptionGraphicsItem
}
mInitialMapScaleCalculated = true;
QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
QgsLegendRenderer legendRenderer( mLegendModel, mSettings );
legendRenderer.setLegendSize( mForceResize && mSizeToContents ? QSize() : rect().size() );
//adjust box if width or height is too small
@ -152,7 +158,7 @@ QSizeF QgsComposerLegend::paintAndDetermineSize( QPainter *painter )
doUpdateFilterByMap();
}
QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
QgsLegendRenderer legendRenderer( mLegendModel, mSettings );
QSizeF size = legendRenderer.minimumSize();
if ( painter )
legendRenderer.drawLegend( painter );
@ -174,7 +180,7 @@ void QgsComposerLegend::adjustBoxSize()
return;
}
QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
QgsLegendRenderer legendRenderer( mLegendModel, mSettings );
QSizeF size = legendRenderer.minimumSize();
QgsDebugMsg( QString( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) );
if ( size.isValid() )
@ -195,6 +201,11 @@ bool QgsComposerLegend::resizeToContents() const
return mSizeToContents;
}
QgsLegendModel *QgsComposerLegend::model()
{
return mLegendModel;
}
void QgsComposerLegend::setCustomLayerTree( QgsLayerTree *rootGroup )
{
mLegendModel->setRootGroup( rootGroup ? rootGroup : ( mComposition ? mComposition->project()->layerTreeRoot() : nullptr ) );
@ -717,45 +728,3 @@ void QgsComposerLegend::onAtlasEnded()
mInAtlas = false;
updateFilterByMap();
}
// -------------------------------------------------------------------------
#include "qgslayertreemodellegendnode.h"
#include "qgsvectorlayer.h"
QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent )
: QgsLayerTreeModel( rootNode, parent )
{
setFlag( QgsLayerTreeModel::AllowLegendChangeState, false );
setFlag( QgsLayerTreeModel::AllowNodeReorder, true );
}
QVariant QgsLegendModel::data( const QModelIndex &index, int role ) const
{
// handle custom layer node labels
if ( QgsLayerTreeNode *node = index2node( index ) )
{
if ( QgsLayerTree::isLayer( node ) && ( role == Qt::DisplayRole || role == Qt::EditRole ) && !node->customProperty( QStringLiteral( "legend/title-label" ) ).isNull() )
{
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
QString name = node->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() && role == Qt::DisplayRole )
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
if ( vlayer && vlayer->featureCount() >= 0 )
name += QStringLiteral( " [%1]" ).arg( vlayer->featureCount() );
}
return name;
}
}
return QgsLayerTreeModel::data( index, role );
}
Qt::ItemFlags QgsLegendModel::flags( const QModelIndex &index ) const
{
// make the legend nodes selectable even if they are not by default
if ( index2legendNode( index ) )
return QgsLayerTreeModel::flags( index ) | Qt::ItemIsSelectable;
return QgsLayerTreeModel::flags( index );
}

View File

@ -25,31 +25,12 @@
#include "qgslegendsettings.h"
#include "qgslayertreegroup.h"
class QgsLayerTreeModel;
class QgsSymbol;
class QgsComposerMap;
class QgsLegendRenderer;
/**
* \ingroup core
* Item model implementation based on layer tree model for composer legend.
* Overrides some functionality of QgsLayerTreeModel to better fit the needs of composer legend.
*
* \since QGIS 2.6
*/
class CORE_EXPORT QgsLegendModel : public QgsLayerTreeModel
{
Q_OBJECT
public:
//! Construct the model based on the given layer tree
QgsLegendModel( QgsLayerTree *rootNode, QObject *parent SIP_TRANSFERTHIS = 0 );
QVariant data( const QModelIndex &index, int role ) const override;
Qt::ItemFlags flags( const QModelIndex &index ) const override;
};
class QgsLegendModel;
/**
@ -62,6 +43,7 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
public:
QgsComposerLegend( QgsComposition *composition SIP_TRANSFERTHIS );
~QgsComposerLegend();
//! Return correct graphics item type.
virtual int type() const override { return ComposerLegend; }
@ -95,7 +77,7 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
/**
* Returns the legend model
*/
QgsLegendModel *model() { return mLegendModel.get(); }
QgsLegendModel *model();
//! \since QGIS 2.6
void setAutoUpdateModel( bool autoUpdate );
@ -336,7 +318,7 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem
//! use new custom layer tree and update model. if new root is null pointer, will use project's tree
void setCustomLayerTree( QgsLayerTree *rootGroup );
std::unique_ptr< QgsLegendModel > mLegendModel;
QgsLegendModel *mLegendModel = nullptr;
std::unique_ptr< QgsLayerTreeGroup > mCustomLayerTree;
QgsLegendSettings mSettings;

View File

@ -117,6 +117,23 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
UndoLabelFont, //!< Label font
UndoLabelMargin, //!< Label margin
UndoLabelFontColor, //!< Label color
UndoLegendText, //!< Legend text
UndoLegendColumnCount, //!< Legend column count
UndoLegendSymbolWidth, //!< Legend symbol width
UndoLegendSymbolHeight, //!< Legend symbol height
UndoLegendWmsLegendWidth, //!< Legend WMS width
UndoLegendWmsLegendHeight, //!< Legend WMS height
UndoLegendTitleSpaceBottom, //!< Legend title space
UndoLegendGroupSpace, //!< Legend group spacing
UndoLegendLayerSpace, //!< Legend layer spacing
UndoLegendSymbolSpace, //!< Legend symbol spacing
UndoLegendIconSymbolSpace, //!< Legend icon symbol space
UndoLegendFontColor, //!< Legend font color
UndoLegendBoxSpace, //!< Legend box space
UndoLegendColumnSpace, //!< Legend column space
UndoLegendLineSpacing, //!< Legend line spacing
UndoLegendRasterStrokeWidth, //!< Legend raster stroke width
UndoLegendRasterStrokeColor, //!< Legend raster stroke color
UndoCustomCommand, //!< Base id for plugin based item undo commands
};

View File

@ -0,0 +1,884 @@
/***************************************************************************
qgslayoutitemlegend.cpp
-----------------------
begin : October 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail 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 <limits>
#include "qgslayoutitemlegend.h"
#include "qgslayoutitemregistry.h"
#include "qgslayoutitemmap.h"
#include "qgslayout.h"
#include "qgslayoutmodel.h"
#include "qgslayertree.h"
#include "qgslayertreemodel.h"
#include "qgslegendrenderer.h"
#include "qgslegendstyle.h"
#include "qgslogger.h"
#include "qgsmapsettings.h"
#include "qgsproject.h"
#include "qgssymbollayerutils.h"
#include "qgslayertreeutils.h"
#include <QDomDocument>
#include <QDomElement>
#include <QPainter>
QgsLayoutItemLegend::QgsLayoutItemLegend( QgsLayout *layout )
: QgsLayoutItem( layout )
, mLegendModel( new QgsLegendModel( layout->project()->layerTreeRoot() ) )
{
#if 0 //TODO
connect( &layout->atlasComposition(), &QgsAtlasComposition::renderEnded, this, &QgsLayoutItemLegend::onAtlasEnded );
connect( &layout->atlasComposition(), &QgsAtlasComposition::featureChanged, this, &QgsLayoutItemLegend::onAtlasFeature );
#endif
// Connect to the main layertreeroot.
// It serves in "auto update mode" as a medium between the main app legend and this one
connect( mLayout->project()->layerTreeRoot(), &QgsLayerTreeNode::customPropertyChanged, this, &QgsLayoutItemLegend::nodeCustomPropertyChanged );
}
QgsLayoutItemLegend *QgsLayoutItemLegend::create( QgsLayout *layout )
{
return new QgsLayoutItemLegend( layout );
}
int QgsLayoutItemLegend::type() const
{
return QgsLayoutItemRegistry::LayoutLegend;
}
QString QgsLayoutItemLegend::stringType() const
{
return QStringLiteral( "ItemLegend" );
}
void QgsLayoutItemLegend::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
{
if ( !painter )
return;
if ( mFilterAskedForUpdate )
{
mFilterAskedForUpdate = false;
doUpdateFilterByMap();
}
int dpi = painter->device()->logicalDpiX();
double dotsPerMM = dpi / 25.4;
if ( mLayout )
{
mSettings.setUseAdvancedEffects( mLayout->context().flags() & QgsLayoutContext::FlagUseAdvancedEffects );
mSettings.setDpi( dpi );
}
if ( mMap && mLayout )
{
mSettings.setMmPerMapUnit( mLayout->convertFromLayoutUnits( mMap->mapUnitsToLayoutUnits(), QgsUnitTypes::LayoutMillimeters ).length() );
// use a temporary QgsMapSettings to find out real map scale
QSizeF mapSizePixels = QSizeF( mMap->rect().width() * dotsPerMM, mMap->rect().height() * dotsPerMM );
QgsRectangle mapExtent = mMap->extent();
QgsMapSettings ms = mMap->mapSettings( mapExtent, mapSizePixels, dpi );
mSettings.setMapScale( ms.scale() );
}
mInitialMapScaleCalculated = true;
QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
legendRenderer.setLegendSize( mForceResize && mSizeToContents ? QSize() : rect().size() );
//adjust box if width or height is too small
if ( mSizeToContents )
{
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() );
attemptSetSceneRect( targetRect );
}
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
attemptSetSceneRect( targetRect );
}
}
QgsLayoutItem::paint( painter, itemStyle, pWidget );
}
void QgsLayoutItemLegend::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem * )
{
QPainter *painter = context.painter();
painter->save();
// painter is scaled to dots, so scale back to layout units
painter->scale( context.scaleFactor(), context.scaleFactor() );
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 );
}
QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
legendRenderer.setLegendSize( mForceResize && mSizeToContents ? QSize() : rect().size() );
legendRenderer.drawLegend( painter );
painter->restore();
}
void QgsLayoutItemLegend::adjustBoxSize()
{
if ( !mSizeToContents )
return;
if ( !mInitialMapScaleCalculated )
{
// this is messy - but until we have painted the item we have no knowledge of the current DPI
// and so cannot correctly calculate the map scale. This results in incorrect size calculations
// for marker symbols with size in map units, causing the legends to initially expand to huge
// sizes if we attempt to calculate the box size first.
return;
}
QgsLegendRenderer legendRenderer( mLegendModel.get(), mSettings );
QSizeF size = legendRenderer.minimumSize();
QgsDebugMsg( QString( "width = %1 height = %2" ).arg( size.width() ).arg( size.height() ) );
if ( size.isValid() )
{
QRectF targetRect = QRectF( pos().x(), pos().y(), size.width(), size.height() );
//set new rect, respecting position mode and data defined size/position
attemptSetSceneRect( targetRect );
}
}
void QgsLayoutItemLegend::setResizeToContents( bool enabled )
{
mSizeToContents = enabled;
}
bool QgsLayoutItemLegend::resizeToContents() const
{
return mSizeToContents;
}
void QgsLayoutItemLegend::setCustomLayerTree( QgsLayerTree *rootGroup )
{
mLegendModel->setRootGroup( rootGroup ? rootGroup : ( mLayout ? mLayout->project()->layerTreeRoot() : nullptr ) );
mCustomLayerTree.reset( rootGroup );
}
void QgsLayoutItemLegend::setAutoUpdateModel( bool autoUpdate )
{
if ( autoUpdate == autoUpdateModel() )
return;
setCustomLayerTree( autoUpdate ? nullptr : mLayout->project()->layerTreeRoot()->clone() );
adjustBoxSize();
updateFilterByMap( false );
}
void QgsLayoutItemLegend::nodeCustomPropertyChanged( QgsLayerTreeNode *, const QString & )
{
if ( autoUpdateModel() )
{
// in "auto update" mode, some parameters on the main app legend may have been changed (expression filtering)
// we must then call updateItem to reflect the changes
updateFilterByMap( false );
}
}
bool QgsLayoutItemLegend::autoUpdateModel() const
{
return !mCustomLayerTree;
}
void QgsLayoutItemLegend::setLegendFilterByMapEnabled( bool enabled )
{
mLegendFilterByMap = enabled;
updateFilterByMap( false );
}
void QgsLayoutItemLegend::setTitle( const QString &t )
{
mTitle = t;
mSettings.setTitle( t );
if ( mLayout && id().isEmpty() )
{
//notify the model that the display name has changed
mLayout->itemsModel()->updateItemDisplayName( this );
}
}
QString QgsLayoutItemLegend::title() const
{
return mTitle;
}
Qt::AlignmentFlag QgsLayoutItemLegend::titleAlignment() const
{
return mSettings.titleAlignment();
}
void QgsLayoutItemLegend::setTitleAlignment( Qt::AlignmentFlag alignment )
{
mSettings.setTitleAlignment( alignment );
}
QgsLegendStyle &QgsLayoutItemLegend::rstyle( QgsLegendStyle::Style s )
{
return mSettings.rstyle( s );
}
QgsLegendStyle QgsLayoutItemLegend::style( QgsLegendStyle::Style s ) const
{
return mSettings.style( s );
}
void QgsLayoutItemLegend::setStyle( QgsLegendStyle::Style s, const QgsLegendStyle &style )
{
mSettings.setStyle( s, style );
}
QFont QgsLayoutItemLegend::styleFont( QgsLegendStyle::Style s ) const
{
return mSettings.style( s ).font();
}
void QgsLayoutItemLegend::setStyleFont( QgsLegendStyle::Style s, const QFont &f )
{
rstyle( s ).setFont( f );
}
void QgsLayoutItemLegend::setStyleMargin( QgsLegendStyle::Style s, double margin )
{
rstyle( s ).setMargin( margin );
}
void QgsLayoutItemLegend::setStyleMargin( QgsLegendStyle::Style s, QgsLegendStyle::Side side, double margin )
{
rstyle( s ).setMargin( side, margin );
}
double QgsLayoutItemLegend::lineSpacing() const
{
return mSettings.lineSpacing();
}
void QgsLayoutItemLegend::setLineSpacing( double spacing )
{
mSettings.setLineSpacing( spacing );
}
double QgsLayoutItemLegend::boxSpace() const
{
return mSettings.boxSpace();
}
void QgsLayoutItemLegend::setBoxSpace( double s )
{
mSettings.setBoxSpace( s );
}
double QgsLayoutItemLegend::columnSpace() const
{
return mSettings.columnSpace();
}
void QgsLayoutItemLegend::setColumnSpace( double s )
{
mSettings.setColumnSpace( s );
}
QColor QgsLayoutItemLegend::fontColor() const
{
return mSettings.fontColor();
}
void QgsLayoutItemLegend::setFontColor( const QColor &c )
{
mSettings.setFontColor( c );
}
double QgsLayoutItemLegend::symbolWidth() const
{
return mSettings.symbolSize().width();
}
void QgsLayoutItemLegend::setSymbolWidth( double w )
{
mSettings.setSymbolSize( QSizeF( w, mSettings.symbolSize().height() ) );
}
double QgsLayoutItemLegend::symbolHeight() const
{
return mSettings.symbolSize().height();
}
void QgsLayoutItemLegend::setSymbolHeight( double h )
{
mSettings.setSymbolSize( QSizeF( mSettings.symbolSize().width(), h ) );
}
double QgsLayoutItemLegend::wmsLegendWidth() const
{
return mSettings.wmsLegendSize().width();
}
void QgsLayoutItemLegend::setWmsLegendWidth( double w )
{
mSettings.setWmsLegendSize( QSizeF( w, mSettings.wmsLegendSize().height() ) );
}
double QgsLayoutItemLegend::wmsLegendHeight() const
{
return mSettings.wmsLegendSize().height();
}
void QgsLayoutItemLegend::setWmsLegendHeight( double h )
{
mSettings.setWmsLegendSize( QSizeF( mSettings.wmsLegendSize().width(), h ) );
}
void QgsLayoutItemLegend::setWrapString( const QString &t )
{
mSettings.setWrapChar( t );
}
QString QgsLayoutItemLegend::wrapString() const
{
return mSettings.wrapChar();
}
int QgsLayoutItemLegend::columnCount() const
{
return mColumnCount;
}
void QgsLayoutItemLegend::setColumnCount( int c )
{
mColumnCount = c;
mSettings.setColumnCount( c );
}
bool QgsLayoutItemLegend::splitLayer() const
{
return mSettings.splitLayer();
}
void QgsLayoutItemLegend::setSplitLayer( bool s )
{
mSettings.setSplitLayer( s );
}
bool QgsLayoutItemLegend::equalColumnWidth() const
{
return mSettings.equalColumnWidth();
}
void QgsLayoutItemLegend::setEqualColumnWidth( bool s )
{
mSettings.setEqualColumnWidth( s );
}
bool QgsLayoutItemLegend::drawRasterStroke() const
{
return mSettings.drawRasterStroke();
}
void QgsLayoutItemLegend::setDrawRasterStroke( bool enabled )
{
mSettings.setDrawRasterStroke( enabled );
}
QColor QgsLayoutItemLegend::rasterStrokeColor() const
{
return mSettings.rasterStrokeColor();
}
void QgsLayoutItemLegend::setRasterStrokeColor( const QColor &color )
{
mSettings.setRasterStrokeColor( color );
}
double QgsLayoutItemLegend::rasterStrokeWidth() const
{
return mSettings.rasterStrokeWidth();
}
void QgsLayoutItemLegend::setRasterStrokeWidth( double width )
{
mSettings.setRasterStrokeWidth( width );
}
void QgsLayoutItemLegend::synchronizeWithModel()
{
adjustBoxSize();
updateFilterByMap( false );
}
void QgsLayoutItemLegend::updateLegend()
{
adjustBoxSize();
updateFilterByMap( false );
}
#if 0//TODO
bool QgsLayoutItemLegend::writeXml( QDomElement &elem, QDomDocument &doc ) const
{
if ( elem.isNull() )
{
return false;
}
QDomElement composerLegendElem = doc.createElement( QStringLiteral( "ComposerLegend" ) );
elem.appendChild( composerLegendElem );
//write general properties
composerLegendElem.setAttribute( QStringLiteral( "title" ), mTitle );
composerLegendElem.setAttribute( QStringLiteral( "titleAlignment" ), QString::number( static_cast< int >( mSettings.titleAlignment() ) ) );
composerLegendElem.setAttribute( QStringLiteral( "columnCount" ), QString::number( mColumnCount ) );
composerLegendElem.setAttribute( QStringLiteral( "splitLayer" ), QString::number( mSettings.splitLayer() ) );
composerLegendElem.setAttribute( QStringLiteral( "equalColumnWidth" ), QString::number( mSettings.equalColumnWidth() ) );
composerLegendElem.setAttribute( QStringLiteral( "boxSpace" ), QString::number( mSettings.boxSpace() ) );
composerLegendElem.setAttribute( QStringLiteral( "columnSpace" ), QString::number( mSettings.columnSpace() ) );
composerLegendElem.setAttribute( QStringLiteral( "symbolWidth" ), QString::number( mSettings.symbolSize().width() ) );
composerLegendElem.setAttribute( QStringLiteral( "symbolHeight" ), QString::number( mSettings.symbolSize().height() ) );
composerLegendElem.setAttribute( QStringLiteral( "lineSpacing" ), QString::number( mSettings.lineSpacing() ) );
composerLegendElem.setAttribute( QStringLiteral( "rasterBorder" ), mSettings.drawRasterStroke() );
composerLegendElem.setAttribute( QStringLiteral( "rasterBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSettings.rasterStrokeColor() ) );
composerLegendElem.setAttribute( QStringLiteral( "rasterBorderWidth" ), QString::number( mSettings.rasterStrokeWidth() ) );
composerLegendElem.setAttribute( QStringLiteral( "wmsLegendWidth" ), QString::number( mSettings.wmsLegendSize().width() ) );
composerLegendElem.setAttribute( QStringLiteral( "wmsLegendHeight" ), QString::number( mSettings.wmsLegendSize().height() ) );
composerLegendElem.setAttribute( QStringLiteral( "wrapChar" ), mSettings.wrapChar() );
composerLegendElem.setAttribute( QStringLiteral( "fontColor" ), mSettings.fontColor().name() );
composerLegendElem.setAttribute( QStringLiteral( "resizeToContents" ), mSizeToContents );
if ( mMap )
{
composerLegendElem.setAttribute( QStringLiteral( "map" ), mMap->id() );
}
QDomElement composerLegendStyles = doc.createElement( QStringLiteral( "styles" ) );
composerLegendElem.appendChild( composerLegendStyles );
style( QgsLegendStyle::Title ).writeXml( QStringLiteral( "title" ), composerLegendStyles, doc );
style( QgsLegendStyle::Group ).writeXml( QStringLiteral( "group" ), composerLegendStyles, doc );
style( QgsLegendStyle::Subgroup ).writeXml( QStringLiteral( "subgroup" ), composerLegendStyles, doc );
style( QgsLegendStyle::Symbol ).writeXml( QStringLiteral( "symbol" ), composerLegendStyles, doc );
style( QgsLegendStyle::SymbolLabel ).writeXml( QStringLiteral( "symbolLabel" ), composerLegendStyles, doc );
if ( mCustomLayerTree )
{
// if not using auto-update - store the custom layer tree
mCustomLayerTree->writeXml( composerLegendElem );
}
if ( mLegendFilterByMap )
{
composerLegendElem.setAttribute( QStringLiteral( "legendFilterByMap" ), QStringLiteral( "1" ) );
}
composerLegendElem.setAttribute( QStringLiteral( "legendFilterByAtlas" ), mFilterOutAtlas ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
return _writeXml( composerLegendElem, doc );
}
bool QgsLayoutItemLegend::readXml( const QDomElement &itemElem, const QDomDocument &doc )
{
if ( itemElem.isNull() )
{
return false;
}
//read general properties
mTitle = itemElem.attribute( QStringLiteral( "title" ) );
mSettings.setTitle( mTitle );
if ( !itemElem.attribute( QStringLiteral( "titleAlignment" ) ).isEmpty() )
{
mSettings.setTitleAlignment( static_cast< Qt::AlignmentFlag >( itemElem.attribute( QStringLiteral( "titleAlignment" ) ).toInt() ) );
}
int colCount = itemElem.attribute( QStringLiteral( "columnCount" ), QStringLiteral( "1" ) ).toInt();
if ( colCount < 1 ) colCount = 1;
mColumnCount = colCount;
mSettings.setColumnCount( mColumnCount );
mSettings.setSplitLayer( itemElem.attribute( QStringLiteral( "splitLayer" ), QStringLiteral( "0" ) ).toInt() == 1 );
mSettings.setEqualColumnWidth( itemElem.attribute( QStringLiteral( "equalColumnWidth" ), QStringLiteral( "0" ) ).toInt() == 1 );
QDomNodeList stylesNodeList = itemElem.elementsByTagName( QStringLiteral( "styles" ) );
if ( !stylesNodeList.isEmpty() )
{
QDomNode stylesNode = stylesNodeList.at( 0 );
for ( int i = 0; i < stylesNode.childNodes().size(); i++ )
{
QDomElement styleElem = stylesNode.childNodes().at( i ).toElement();
QgsLegendStyle style;
style.readXml( styleElem, doc );
QString name = styleElem.attribute( QStringLiteral( "name" ) );
QgsLegendStyle::Style s;
if ( name == QLatin1String( "title" ) ) s = QgsLegendStyle::Title;
else if ( name == QLatin1String( "group" ) ) s = QgsLegendStyle::Group;
else if ( name == QLatin1String( "subgroup" ) ) s = QgsLegendStyle::Subgroup;
else if ( name == QLatin1String( "symbol" ) ) s = QgsLegendStyle::Symbol;
else if ( name == QLatin1String( "symbolLabel" ) ) s = QgsLegendStyle::SymbolLabel;
else continue;
setStyle( s, style );
}
}
//font color
QColor fontClr;
fontClr.setNamedColor( itemElem.attribute( QStringLiteral( "fontColor" ), QStringLiteral( "#000000" ) ) );
mSettings.setFontColor( fontClr );
//spaces
mSettings.setBoxSpace( itemElem.attribute( QStringLiteral( "boxSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
mSettings.setColumnSpace( itemElem.attribute( QStringLiteral( "columnSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
mSettings.setSymbolSize( QSizeF( itemElem.attribute( QStringLiteral( "symbolWidth" ), QStringLiteral( "7.0" ) ).toDouble(), itemElem.attribute( QStringLiteral( "symbolHeight" ), QStringLiteral( "14.0" ) ).toDouble() ) );
mSettings.setWmsLegendSize( QSizeF( itemElem.attribute( QStringLiteral( "wmsLegendWidth" ), QStringLiteral( "50" ) ).toDouble(), itemElem.attribute( QStringLiteral( "wmsLegendHeight" ), QStringLiteral( "25" ) ).toDouble() ) );
mSettings.setLineSpacing( itemElem.attribute( QStringLiteral( "lineSpacing" ), QStringLiteral( "1.0" ) ).toDouble() );
mSettings.setDrawRasterStroke( itemElem.attribute( QStringLiteral( "rasterBorder" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) );
mSettings.setRasterStrokeColor( QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "rasterBorderColor" ), QStringLiteral( "0,0,0" ) ) ) );
mSettings.setRasterStrokeWidth( itemElem.attribute( QStringLiteral( "rasterBorderWidth" ), QStringLiteral( "0" ) ).toDouble() );
mSettings.setWrapChar( itemElem.attribute( QStringLiteral( "wrapChar" ) ) );
mSizeToContents = itemElem.attribute( QStringLiteral( "resizeToContents" ), QStringLiteral( "1" ) ) != QLatin1String( "0" );
//composer map
mLegendFilterByMap = itemElem.attribute( QStringLiteral( "legendFilterByMap" ), QStringLiteral( "0" ) ).toInt();
if ( !itemElem.attribute( QStringLiteral( "map" ) ).isEmpty() )
{
setMap( mComposition->getComposerMapById( itemElem.attribute( QStringLiteral( "map" ) ).toInt() ) );
}
mFilterOutAtlas = itemElem.attribute( QStringLiteral( "legendFilterByAtlas" ), QStringLiteral( "0" ) ).toInt();
// QGIS >= 2.6
QDomElement layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree" ) );
if ( layerTreeElem.isNull() )
layerTreeElem = itemElem.firstChildElement( QStringLiteral( "layer-tree-group" ) );
if ( !layerTreeElem.isNull() )
{
std::unique_ptr< QgsLayerTree > tree( QgsLayerTree::readXml( layerTreeElem ) );
if ( mComposition )
tree->resolveReferences( mComposition->project(), true );
setCustomLayerTree( tree.release() );
}
else
setCustomLayerTree( nullptr );
//restore general composer item properties
QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
if ( !composerItemList.isEmpty() )
{
QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
_readXml( composerItemElem, doc );
}
// < 2.0 projects backward compatibility >>>>>
//title font
QString titleFontString = itemElem.attribute( QStringLiteral( "titleFont" ) );
if ( !titleFontString.isEmpty() )
{
rstyle( QgsLegendStyle::Title ).rfont().fromString( titleFontString );
}
//group font
QString groupFontString = itemElem.attribute( QStringLiteral( "groupFont" ) );
if ( !groupFontString.isEmpty() )
{
rstyle( QgsLegendStyle::Group ).rfont().fromString( groupFontString );
}
//layer font
QString layerFontString = itemElem.attribute( QStringLiteral( "layerFont" ) );
if ( !layerFontString.isEmpty() )
{
rstyle( QgsLegendStyle::Subgroup ).rfont().fromString( layerFontString );
}
//item font
QString itemFontString = itemElem.attribute( QStringLiteral( "itemFont" ) );
if ( !itemFontString.isEmpty() )
{
rstyle( QgsLegendStyle::SymbolLabel ).rfont().fromString( itemFontString );
}
if ( !itemElem.attribute( QStringLiteral( "groupSpace" ) ).isEmpty() )
{
rstyle( QgsLegendStyle::Group ).setMargin( QgsLegendStyle::Top, itemElem.attribute( QStringLiteral( "groupSpace" ), QStringLiteral( "3.0" ) ).toDouble() );
}
if ( !itemElem.attribute( QStringLiteral( "layerSpace" ) ).isEmpty() )
{
rstyle( QgsLegendStyle::Subgroup ).setMargin( QgsLegendStyle::Top, itemElem.attribute( QStringLiteral( "layerSpace" ), QStringLiteral( "3.0" ) ).toDouble() );
}
if ( !itemElem.attribute( QStringLiteral( "symbolSpace" ) ).isEmpty() )
{
rstyle( QgsLegendStyle::Symbol ).setMargin( QgsLegendStyle::Top, itemElem.attribute( QStringLiteral( "symbolSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
rstyle( QgsLegendStyle::SymbolLabel ).setMargin( QgsLegendStyle::Top, itemElem.attribute( QStringLiteral( "symbolSpace" ), QStringLiteral( "2.0" ) ).toDouble() );
}
// <<<<<<< < 2.0 projects backward compatibility
emit itemChanged();
return true;
}
#endif
QString QgsLayoutItemLegend::displayName() const
{
if ( !id().isEmpty() )
{
return id();
}
//if no id, default to portion of title text
QString text = mSettings.title();
if ( text.isEmpty() )
{
return tr( "<legend>" );
}
if ( text.length() > 25 )
{
return QString( tr( "%1..." ) ).arg( text.left( 25 ) );
}
else
{
return text;
}
}
void QgsLayoutItemLegend::setMap( QgsLayoutItemMap *map )
{
if ( mMap )
{
disconnect( mMap, &QObject::destroyed, this, &QgsLayoutItemLegend::invalidateCurrentMap );
disconnect( mMap, &QgsLayoutObject::changed, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
disconnect( mMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
disconnect( mMap, &QgsLayoutItemMap::layerStyleOverridesChanged, this, &QgsLayoutItemLegend::mapLayerStyleOverridesChanged );
}
mMap = map;
if ( map )
{
connect( map, &QObject::destroyed, this, &QgsLayoutItemLegend::invalidateCurrentMap );
connect( map, &QgsLayoutObject::changed, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
connect( map, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemLegend::updateFilterByMapAndRedraw );
connect( map, &QgsLayoutItemMap::layerStyleOverridesChanged, this, &QgsLayoutItemLegend::mapLayerStyleOverridesChanged );
}
updateFilterByMap();
}
void QgsLayoutItemLegend::invalidateCurrentMap()
{
setMap( nullptr );
}
void QgsLayoutItemLegend::refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property )
{
QgsExpressionContext context = createExpressionContext();
bool forceUpdate = false;
//updates data defined properties and redraws item to match
if ( property == QgsLayoutObject::LegendTitle || property == QgsLayoutObject::AllProperties )
{
bool ok = false;
QString t = mDataDefinedProperties.valueAsString( QgsLayoutObject::LegendTitle, context, mTitle, &ok );
if ( ok )
{
mSettings.setTitle( t );
forceUpdate = true;
}
}
if ( property == QgsLayoutObject::LegendColumnCount || property == QgsLayoutObject::AllProperties )
{
bool ok = false;
int cols = mDataDefinedProperties.valueAsInt( QgsLayoutObject::LegendColumnCount, context, mColumnCount, &ok );
if ( ok && cols >= 0 )
{
mSettings.setColumnCount( cols );
forceUpdate = true;
}
}
if ( forceUpdate )
{
adjustBoxSize();
update();
}
QgsLayoutItem::refreshDataDefinedProperty( property );
}
void QgsLayoutItemLegend::updateFilterByMapAndRedraw()
{
updateFilterByMap( true );
}
void QgsLayoutItemLegend::mapLayerStyleOverridesChanged()
{
if ( !mMap )
return;
// map's style has been changed, so make sure to update the legend here
if ( mLegendFilterByMap )
{
// legend is being filtered by map, so we need to re run the hit test too
// as the style overrides may also have affected the visible symbols
updateFilterByMap( false );
}
else
{
mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, mLegendModel->rootGroup()->findLayers() )
mLegendModel->refreshLayerLegend( nodeLayer );
}
adjustBoxSize();
updateFilterByMap( false );
}
void QgsLayoutItemLegend::updateFilterByMap( bool redraw )
{
// ask for update
// the actual update will take place before the redraw.
// This is to avoid multiple calls to the filter
mFilterAskedForUpdate = true;
if ( redraw )
update();
}
void QgsLayoutItemLegend::doUpdateFilterByMap()
{
if ( mMap )
mLegendModel->setLayerStyleOverrides( mMap->layerStyleOverrides() );
else
mLegendModel->setLayerStyleOverrides( QMap<QString, QString>() );
bool filterByExpression = QgsLayerTreeUtils::hasLegendFilterExpression( *( mCustomLayerTree ? mCustomLayerTree.get() : mLayout->project()->layerTreeRoot() ) );
if ( mMap && ( mLegendFilterByMap || filterByExpression || mInAtlas ) )
{
int dpi = mLayout->context().dpi();
QgsRectangle requestRectangle = mMap->requestedExtent();
QSizeF size( requestRectangle.width(), requestRectangle.height() );
size *= mLayout->convertFromLayoutUnits( mMap->mapUnitsToLayoutUnits(), QgsUnitTypes::LayoutMillimeters ).length() * dpi / 25.4;
QgsMapSettings ms = mMap->mapSettings( requestRectangle, size, dpi );
QgsGeometry filterPolygon;
if ( mInAtlas )
{
#if 0 //TODO
filterPolygon = composition()->atlasComposition().currentGeometry( mMap->crs() );
#endif
}
mLegendModel->setLegendFilter( &ms, /* useExtent */ mInAtlas || mLegendFilterByMap, filterPolygon, /* useExpressions */ true );
}
else
mLegendModel->setLegendFilterByMap( nullptr );
mForceResize = true;
}
void QgsLayoutItemLegend::setLegendFilterOutAtlas( bool doFilter )
{
mFilterOutAtlas = doFilter;
}
bool QgsLayoutItemLegend::legendFilterOutAtlas() const
{
return mFilterOutAtlas;
}
void QgsLayoutItemLegend::onAtlasFeature( QgsFeature *feat )
{
if ( !feat )
return;
mInAtlas = mFilterOutAtlas;
updateFilterByMap();
}
void QgsLayoutItemLegend::onAtlasEnded()
{
mInAtlas = false;
updateFilterByMap();
}
// -------------------------------------------------------------------------
#include "qgslayertreemodellegendnode.h"
#include "qgsvectorlayer.h"
QgsLegendModel::QgsLegendModel( QgsLayerTree *rootNode, QObject *parent )
: QgsLayerTreeModel( rootNode, parent )
{
setFlag( QgsLayerTreeModel::AllowLegendChangeState, false );
setFlag( QgsLayerTreeModel::AllowNodeReorder, true );
}
QVariant QgsLegendModel::data( const QModelIndex &index, int role ) const
{
// handle custom layer node labels
if ( QgsLayerTreeNode *node = index2node( index ) )
{
if ( QgsLayerTree::isLayer( node ) && ( role == Qt::DisplayRole || role == Qt::EditRole ) && !node->customProperty( QStringLiteral( "legend/title-label" ) ).isNull() )
{
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
QString name = node->customProperty( QStringLiteral( "legend/title-label" ) ).toString();
if ( nodeLayer->customProperty( QStringLiteral( "showFeatureCount" ), 0 ).toInt() && role == Qt::DisplayRole )
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
if ( vlayer && vlayer->featureCount() >= 0 )
name += QStringLiteral( " [%1]" ).arg( vlayer->featureCount() );
}
return name;
}
}
return QgsLayerTreeModel::data( index, role );
}
Qt::ItemFlags QgsLegendModel::flags( const QModelIndex &index ) const
{
// make the legend nodes selectable even if they are not by default
if ( index2legendNode( index ) )
return QgsLayerTreeModel::flags( index ) | Qt::ItemIsSelectable;
return QgsLayerTreeModel::flags( index );
}

View File

@ -0,0 +1,529 @@
/***************************************************************************
qgslayoutitemlegend.h
---------------------
begin : October 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail 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. *
* *
***************************************************************************/
#ifndef QGSLAYOUTITEMLEGEND_H
#define QGSLAYOUTITEMLEGEND_H
#include "qgis_core.h"
#include "qgis.h"
#include "qgslayoutitem.h"
#include "qgslayertreemodel.h"
#include "qgslegendsettings.h"
#include "qgslayertreegroup.h"
class QgsLayerTreeModel;
class QgsSymbol;
class QgsLayoutItemMap;
class QgsLegendRenderer;
/**
* \ingroup core
* Item model implementation based on layer tree model for layout legend.
*
* Overrides some functionality of QgsLayerTreeModel to better fit the needs of layout legends.
*
* \since QGIS 2.6
*/
class CORE_EXPORT QgsLegendModel : public QgsLayerTreeModel
{
Q_OBJECT
public:
//! Construct the model based on the given layer tree
QgsLegendModel( QgsLayerTree *rootNode, QObject *parent SIP_TRANSFERTHIS = 0 );
QVariant data( const QModelIndex &index, int role ) const override;
Qt::ItemFlags flags( const QModelIndex &index ) const override;
};
/**
* \ingroup core
* A layout item subclass for map legends.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLayoutItemLegend : public QgsLayoutItem
{
Q_OBJECT
public:
/**
* Constructor for QgsLayoutItemLegend, with the specified parent \a layout.
*/
QgsLayoutItemLegend( QgsLayout *layout );
/**
* Returns a new legend item for the specified \a layout.
*
* The caller takes responsibility for deleting the returned object.
*/
static QgsLayoutItemLegend *create( QgsLayout *layout ) SIP_FACTORY;
int type() const override;
QString stringType() const override;
//Overridden to show legend title
QString displayName() const override;
/**
* Sets the legend's item bounds to fit the whole legend 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()
*/
void setResizeToContents( bool enabled );
/**
* Returns whether the legend should automatically resize to fit its contents.
* \see setResizeToContents()
*/
bool resizeToContents() const;
/**
* Returns the legend model.
*/
QgsLegendModel *model() { return mLegendModel.get(); }
/**
* Sets whether the legend content should auto update to reflect changes in the project's
* layer tree.
* \see autoUpdateModel()
*/
void setAutoUpdateModel( bool autoUpdate );
/**
* Returns whether the legend content should auto update to reflect changes in the project's
* layer tree.
* \see setAutoUpdateModel()
*/
bool autoUpdateModel() const;
/**
* Set whether legend items should be filtered to show just the ones visible in the associated map.
* \see legendFilterByMapEnabled()
*/
void setLegendFilterByMapEnabled( bool enabled );
/**
* Find out whether legend items are filtered to show just the ones visible in the associated map
* \see setLegendFilterByMapEnabled()
*/
bool legendFilterByMapEnabled() const { return mLegendFilterByMap; }
#if 0//TODO
/**
* Update() overloading. Use it rather than update()
*/
virtual void updateItem() override;
#endif
/**
* When set to true, during an atlas rendering, it will filter out legend elements
* where features are outside the current atlas feature.
* \see legendFilterOutAtlas()
*/
void setLegendFilterOutAtlas( bool doFilter );
/**
* Returns whether to filter out legend elements outside of the current atlas feature.
* \see setLegendFilterOutAtlas()
*/
bool legendFilterOutAtlas() const;
/**
* Sets the legend \a title.
* \see title()
*/
void setTitle( const QString &title );
/**
* Returns the legend title.
* \see setTitle()
*/
QString title() const;
/**
* Returns the alignment of the legend title.
* \see setTitleAlignment()
*/
Qt::AlignmentFlag titleAlignment() const;
/**
* Sets the \a alignment of the legend title.
* \see titleAlignment()
*/
void setTitleAlignment( Qt::AlignmentFlag alignment );
/**
* Returns reference to modifiable legend style.
*/
QgsLegendStyle &rstyle( QgsLegendStyle::Style s );
/**
* Returns legend style.
*/
QgsLegendStyle style( QgsLegendStyle::Style s ) const;
/**
* Sets the style of \a component to \a style for the legend.
*/
void setStyle( QgsLegendStyle::Style component, const QgsLegendStyle &style );
/**
* Returns the font settings for a legend \a component.
* \see setStyleFont()
*/
QFont styleFont( QgsLegendStyle::Style component ) const;
/**
* Sets the style \a font for a legend \a component.
* \see styleFont()
*/
void setStyleFont( QgsLegendStyle::Style component, const QFont &font );
/**
* Set the \a margin for a legend \a component.
*/
void setStyleMargin( QgsLegendStyle::Style component, double margin );
/**
* Set the \a margin for a particular \a side of a legend \a component.
*/
void setStyleMargin( QgsLegendStyle::Style component, QgsLegendStyle::Side side, double margin );
/**
* Returns the spacing in-between lines in layout units.
* \see setLineSpacing()
*/
double lineSpacing() const;
/**
* Sets the \a spacing in-between multiple lines.
* \see lineSpacing()
*/
void setLineSpacing( double spacing );
/**
* Returns the legend box space.
* \see setBoxSpace()
*/
double boxSpace() const;
/**
* Sets the legend box \a space.
* \see boxSpace()
*/
void setBoxSpace( double space );
/**
* Returns the legend column spacing.
* \see setColumnSpace()
*/
double columnSpace() const;
/**
* Sets the legend column \a spacing.
* \see columnSpace()
*/
void setColumnSpace( double spacing );
/**
* Returns the legend font color.
* \see setFontColor()
*/
QColor fontColor() const;
/**
* Sets the legend font \a color.
* \see fontColor()
*/
void setFontColor( const QColor &color );
/**
* Returns the legend symbol width.
* \see setSymbolWidth()
*/
double symbolWidth() const;
/**
* Sets the legend symbol \a width.
* \see symbolWidth()
*/
void setSymbolWidth( double width );
/**
* Returns the legend symbol height.
* \see setSymbolHeight()
*/
double symbolHeight() const;
/**
* Sets the legend symbol \a height.
* \see symbolHeight()
*/
void setSymbolHeight( double height );
/**
* Returns the WMS legend width.
* \see setWmsLegendWidth()
*/
double wmsLegendWidth() const;
/**
* Sets the WMS legend \a width.
* \see wmsLegendWidth()
*/
void setWmsLegendWidth( double width );
/**
* Returns the WMS legend height.
* \see setWmsLegendHeight()
*/
double wmsLegendHeight() const;
/**
* Sets the WMS legend \a height.
* \see wmsLegendHeight()
*/
void setWmsLegendHeight( double height );
/**
* Sets the legend text wrapping \a string.
* \see wrapString()
*/
void setWrapString( const QString &string );
/**
* Returns the legend text wrapping string.
* \see setWrapString()
*/
QString wrapString() const;
/**
* Returns the legend column count.
* \see setColumnCount()
*/
int columnCount() const;
/**
* Sets the legend column \a count.
* \see columnCount()
*/
void setColumnCount( int count );
/**
* Returns whether the legend items from a single layer can be split
* over multiple columns.
* \see setSplitLayer()
*/
bool splitLayer() const;
/**
* Sets whether the legend items from a single layer can be split
* over multiple columns.
* \see splitLayer()
*/
void setSplitLayer( bool enabled );
/**
* Returns whether column widths should be equalized.
* \see setEqualColumnWidth()
*/
bool equalColumnWidth() const;
/**
* Sets whether column widths should be equalized.
* \see equalColumnWidth()
*/
void setEqualColumnWidth( bool equalize );
/**
* Returns whether a stroke will be drawn around raster symbol items.
* \see setDrawRasterStroke()
* \see rasterStrokeColor()
* \see rasterStrokeWidth()
*/
bool drawRasterStroke() const;
/**
* Sets whether a stroke will be drawn around raster symbol items.
* \param enabled set to true to draw borders
* \see drawRasterStroke()
* \see setRasterStrokeColor()
* \see setRasterStrokeWidth()
*/
void setDrawRasterStroke( bool enabled );
/**
* Returns the stroke color for the stroke drawn around raster symbol items. The stroke is
* only drawn if drawRasterStroke() is true.
* \see setRasterStrokeColor()
* \see drawRasterStroke()
* \see rasterStrokeWidth()
*/
QColor rasterStrokeColor() const;
/**
* Sets the stroke \a color for the stroke drawn around raster symbol items. The stroke is
* only drawn if drawRasterStroke() is true.
* \see rasterStrokeColor()
* \see setDrawRasterStroke()
* \see setRasterStrokeWidth()
*/
void setRasterStrokeColor( const QColor &color );
/**
* Returns the stroke width (in layout units) for the stroke drawn around raster symbol items. The stroke is
* only drawn if drawRasterStroke() is true.
* \see setRasterStrokeWidth()
* \see drawRasterStroke()
* \see rasterStrokeColor()
*/
double rasterStrokeWidth() const;
/**
* Sets the stroke width for the stroke drawn around raster symbol items. The stroke is
* only drawn if drawRasterStroke() is true.
* \see rasterStrokeWidth()
* \see setDrawRasterStroke()
* \see setRasterStrokeColor()
*/
void setRasterStrokeWidth( double width );
/**
* Sets the \a map to associate with the legend.
* \see map()
*/
void setMap( QgsLayoutItemMap *map );
/**
* Returns the associated map.
* \see setMap()
*/
QgsLayoutItemMap *map() const { return mMap; }
/**
* Updates the model and all legend entries.
*/
void updateLegend();
/**
* Updates the legend content when filtered by map.
*/
void updateFilterByMap( bool redraw = true );
#if 0//TODO
/**
* Stores state in Dom node
* \param elem is Dom element corresponding to 'Composer' tag
* \param doc Dom document
*/
bool writeXml( QDomElement &elem, QDomDocument &doc ) const override;
/**
* Sets state from Dom document
* \param itemElem is Dom node corresponding to item tag
* \param doc is Dom document
*/
bool readXml( const QDomElement &itemElem, const QDomDocument &doc ) override;
#endif
/**
* Returns the legend's renderer settings object.
*/
const QgsLegendSettings &legendSettings() const { return mSettings; }
void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) override;
public slots:
void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ) override;
protected:
void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override;
private slots:
//! Data changed
void synchronizeWithModel();
//! Removes the associated map if the map is deleted.
void invalidateCurrentMap();
void updateFilterByMapAndRedraw();
//! update legend in case style of associated map has changed
void mapLayerStyleOverridesChanged();
//! react to atlas
void onAtlasEnded();
void onAtlasFeature( QgsFeature * );
void nodeCustomPropertyChanged( QgsLayerTreeNode *node, const QString &key );
private:
QgsLayoutItemLegend() = delete;
//! use new custom layer tree and update model. if new root is null pointer, will use project's tree
void setCustomLayerTree( QgsLayerTree *rootGroup );
std::unique_ptr< QgsLegendModel > mLegendModel;
std::unique_ptr< QgsLayerTreeGroup > mCustomLayerTree;
QgsLegendSettings mSettings;
QString mTitle;
int mColumnCount = 1;
QgsLayoutItemMap *mMap = nullptr;
bool mLegendFilterByMap = false;
bool mLegendFilterByExpression = false;
//! whether to filter out legend elements outside of the atlas feature
bool mFilterOutAtlas = false;
//! tag for update request
bool mFilterAskedForUpdate = false;
//! actual filter update
void doUpdateFilterByMap();
bool mInAtlas = false;
//! Will be false until the associated map scale and DPI have been calculated
bool mInitialMapScaleCalculated = false;
//! Will be true if the legend size should be totally reset at next paint
bool mForceResize = false;
//! Will be true if the legend should be resized automatically to fit contents
bool mSizeToContents = true;
};
#endif // QGSLAYOUTITEMLEGEND_H

View File

@ -396,17 +396,24 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
QgsExpressionContext createExpressionContext() const override;
protected:
void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override;
bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const override;
bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ) override;
/**
* Returns the conversion factor from map units to layout units.
* This is calculated using the width of the map item and the width of the
* current visible map extent.
*/
double mapUnitsToLayoutUnits() const;
/**
* Return map settings that will be used for drawing of the map.
*/
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
protected:
void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override;
bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const override;
bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ) override;
//! True if a draw is already in progress
bool isDrawing() const {return mDrawing;}
@ -422,13 +429,6 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
// In case of annotations, the bounding rectangle can be larger than the map item rectangle
QRectF boundingRect() const override;
/**
* Returns the conversion factor from map units to layout units.
* This is calculated using the width of the map item and the width of the
* current visible map extent.
*/
double mapUnitsToLayoutUnits() const;
//! Returns extent that considers rotation and shift with mOffsetX / mOffsetY
QPolygonF transformedMapPolygon() const;
@ -644,6 +644,7 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
friend class QgsLayoutItemMapGrid;
friend class QgsLayoutItemMapOverview;
friend class QgsLayoutItemLegend;
friend class TestQgsLayoutMap;
};

View File

@ -18,6 +18,7 @@
#include "qgslayoutitemshape.h"
#include "qgslayoutitemmap.h"
#include "qgslayoutitemlabel.h"
#include "qgslayoutitemlegend.h"
#include "qgslayoutitempolygon.h"
#include "qgslayoutitempolyline.h"
#include "qgslayoutitempage.h"
@ -54,6 +55,7 @@ bool QgsLayoutItemRegistry::populate()
addLayoutItemType( new QgsLayoutItemMetadata( LayoutMap, QStringLiteral( "Map" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddMap.svg" ) ), QgsLayoutItemMap::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPicture, QStringLiteral( "Picture" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddImage.svg" ) ), QgsLayoutItemPicture::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutLabel, QStringLiteral( "Label" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionLabel.svg" ) ), QgsLayoutItemLabel::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutLegend, QStringLiteral( "Legend" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLegend.svg" ) ), QgsLayoutItemLegend::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutShape, QStringLiteral( "Shape" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), []( QgsLayout * layout )
{
QgsLayoutItemShape *shape = new QgsLayoutItemShape( layout );

View File

@ -187,6 +187,7 @@ class CORE_EXPORT QgsLayoutItemRegistry : public QObject
LayoutMap, //!< Map item
LayoutPicture, //!< Picture item
LayoutLabel, //!< Label item
LayoutLegend, //!< Legend item
LayoutShape, //!< Shape item
LayoutPolygon, //!< Polygon shape item
LayoutPolyline, //!< Polyline shape item

View File

@ -109,6 +109,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core/raster
${CMAKE_SOURCE_DIR}/src/core/symbology
${CMAKE_SOURCE_DIR}/src/core/composer
${CMAKE_SOURCE_DIR}/src/core/layout
${CMAKE_SOURCE_DIR}/src/core/layertree
${CMAKE_SOURCE_DIR}/src/analysis/interpolation
${CMAKE_SOURCE_DIR}/src/plugins/diagram_overlay

View File

@ -53,6 +53,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core/symbology
${CMAKE_SOURCE_DIR}/src/core/composer
${CMAKE_SOURCE_DIR}/src/core/layertree
${CMAKE_SOURCE_DIR}/src/core/layout
${CMAKE_SOURCE_DIR}/src/gui
${CMAKE_SOURCE_DIR}/src/gui/editorwidgets
${CMAKE_SOURCE_DIR}/src/gui/editorwidgets/core

View File

@ -67,7 +67,7 @@
#include "qgslayerrestorer.h"
#include "qgsdxfexport.h"
#include "qgssymbollayerutils.h"
#include "qgslayoutitemlegend.h"
#include <QImage>
#include <QPainter>

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@
#include "qgslayertreegroup.h"
#include "qgslayertreelayer.h"
#include "qgslayertree.h"
#include "qgslayoutitemlegend.h"
#include <QObject>
#include "qgstest.h"

View File

@ -87,6 +87,7 @@ ADD_PYTHON_TEST(PyQgsLayoutGuide test_qgslayoutguides.py)
ADD_PYTHON_TEST(PyQgsLayoutItem test_qgslayoutitem.py)
ADD_PYTHON_TEST(PyQgsLayoutItemPropertiesDialog test_qgslayoutitempropertiesdialog.py)
ADD_PYTHON_TEST(PyQgsLayoutLabel test_qgslayoutlabel.py)
ADD_PYTHON_TEST(PyQgsLayoutLegend test_qgslayoutlegend.py)
ADD_PYTHON_TEST(PyQgsLayoutMap test_qgslayoutmap.py)
ADD_PYTHON_TEST(PyQgsLayoutMapGrid test_qgslayoutmapgrid.py)
ADD_PYTHON_TEST(PyQgsLayoutPicture test_qgslayoutpicture.py)

View File

@ -0,0 +1,242 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsLayoutItemLegend.
.. note:: 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.
"""
__author__ = '(C) 2017 by Nyall Dawson'
__date__ = '24/10/2017'
__copyright__ = 'Copyright 2017, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from qgis.PyQt.QtCore import QRectF
from qgis.PyQt.QtGui import QColor
from qgis.core import (QgsLayoutItemLegend,
QgsLayoutItemMap,
QgsLayout,
QgsMapSettings,
QgsVectorLayer,
QgsMarkerSymbol,
QgsSingleSymbolRenderer,
QgsRectangle,
QgsProject,
QgsLayoutObject,
QgsProperty,
QgsLayoutMeasurement)
from qgis.testing import (start_app,
unittest
)
from utilities import unitTestDataPath
from qgslayoutchecker import QgsLayoutChecker
import os
start_app()
TEST_DATA_DIR = unitTestDataPath()
class TestQgsLayoutItemLegend(unittest.TestCase):
def testInitialSizeSymbolMapUnits(self):
"""Test initial size of legend with a symbol size in map units"""
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
QgsProject.instance().addMapLayers([point_layer])
marker_symbol = QgsMarkerSymbol.createSimple({'color': '#ff0000', 'outline_style': 'no', 'size': '5', 'size_unit': 'MapUnit'})
point_layer.setRenderer(QgsSingleSymbolRenderer(marker_symbol))
s = QgsMapSettings()
s.setLayers([point_layer])
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()
map = QgsLayoutItemMap(layout)
map.attemptSetSceneRect(QRectF(20, 20, 80, 80))
map.setFrameEnabled(True)
map.setLayers([point_layer])
layout.addLayoutItem(map)
map.setExtent(point_layer.extent())
legend = QgsLayoutItemLegend(layout)
legend.attemptSetSceneRect(QRectF(120, 20, 80, 80))
legend.setFrameEnabled(True)
legend.setFrameStrokeWidth(QgsLayoutMeasurement(2))
legend.setBackgroundColor(QColor(200, 200, 200))
legend.setTitle('')
layout.addLayoutItem(legend)
legend.setMap(map)
checker = QgsLayoutChecker(
'composer_legend_mapunits', layout)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testLayout()
self.assertTrue(result, message)
QgsProject.instance().removeMapLayers([point_layer.id()])
def testResizeWithMapContent(self):
"""Test test legend resizes to match map content"""
point_path = os.path.join(TEST_DATA_DIR, 'points.shp')
point_layer = QgsVectorLayer(point_path, 'points', 'ogr')
QgsProject.instance().addMapLayers([point_layer])
s = QgsMapSettings()
s.setLayers([point_layer])
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()
map = QgsLayoutItemMap(layout)
map.attemptSetSceneRect(QRectF(20, 20, 80, 80))
map.setFrameEnabled(True)
map.setLayers([point_layer])
layout.addLayoutItem(map)
map.setExtent(point_layer.extent())
legend = QgsLayoutItemLegend(layout)
legend.attemptSetSceneRect(QRectF(120, 20, 80, 80))
legend.setFrameEnabled(True)
legend.setFrameStrokeWidth(QgsLayoutMeasurement(2))
legend.setBackgroundColor(QColor(200, 200, 200))
legend.setTitle('')
legend.setLegendFilterByMapEnabled(True)
layout.addLayoutItem(legend)
legend.setMap(map)
map.setExtent(QgsRectangle(-102.51, 41.16, -102.36, 41.30))
checker = QgsLayoutChecker(
'composer_legend_size_content', layout)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testLayout()
self.assertTrue(result, message)
QgsProject.instance().removeMapLayers([point_layer.id()])
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')
QgsProject.instance().addMapLayers([point_layer])
s = QgsMapSettings()
s.setLayers([point_layer])
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()
map = QgsLayoutItemMap(layout)
map.attemptSetSceneRect(QRectF(20, 20, 80, 80))
map.setFrameEnabled(True)
map.setLayers([point_layer])
layout.addLayoutItem(map)
map.setExtent(point_layer.extent())
legend = QgsLayoutItemLegend(layout)
legend.attemptSetSceneRect(QRectF(120, 20, 80, 80))
legend.setFrameEnabled(True)
legend.setFrameStrokeWidth(QgsLayoutMeasurement(2))
legend.setBackgroundColor(QColor(200, 200, 200))
legend.setTitle('')
legend.setLegendFilterByMapEnabled(True)
# disable auto resizing
legend.setResizeToContents(False)
layout.addLayoutItem(legend)
legend.setMap(map)
map.setExtent(QgsRectangle(-102.51, 41.16, -102.36, 41.30))
checker = QgsLayoutChecker(
'composer_legend_noresize', layout)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testLayout()
self.assertTrue(result, message)
QgsProject.instance().removeMapLayers([point_layer.id()])
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')
QgsProject.instance().addMapLayers([point_layer])
s = QgsMapSettings()
s.setLayers([point_layer])
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()
map = QgsLayoutItemMap(layout)
map.attemptSetSceneRect(QRectF(20, 20, 80, 80))
map.setFrameEnabled(True)
map.setLayers([point_layer])
layout.addLayoutItem(map)
map.setExtent(point_layer.extent())
legend = QgsLayoutItemLegend(layout)
legend.attemptSetSceneRect(QRectF(120, 20, 20, 20))
legend.setFrameEnabled(True)
legend.setFrameStrokeWidth(QgsLayoutMeasurement(2))
legend.setBackgroundColor(QColor(200, 200, 200))
legend.setTitle('')
legend.setLegendFilterByMapEnabled(True)
# disable auto resizing
legend.setResizeToContents(False)
layout.addLayoutItem(legend)
legend.setMap(map)
map.setExtent(QgsRectangle(-102.51, 41.16, -102.36, 41.30))
checker = QgsLayoutChecker(
'composer_legend_noresize_crop', layout)
checker.setControlPathPrefix("composer_legend")
result, message = checker.testLayout()
self.assertTrue(result, message)
QgsProject.instance().removeMapLayers([point_layer.id()])
def testDataDefinedTitle(self):
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()
legend = QgsLayoutItemLegend(layout)
layout.addLayoutItem(legend)
legend.setTitle('original')
self.assertEqual(legend.title(), 'original')
self.assertEqual(legend.legendSettings().title(), 'original')
legend.dataDefinedProperties().setProperty(QgsLayoutObject.LegendTitle, QgsProperty.fromExpression("'new'"))
legend.refreshDataDefinedProperty()
self.assertEqual(legend.title(), 'original')
self.assertEqual(legend.legendSettings().title(), 'new')
def testDataDefinedColumnCount(self):
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()
legend = QgsLayoutItemLegend(layout)
layout.addLayoutItem(legend)
legend.setColumnCount(2)
self.assertEqual(legend.columnCount(), 2)
self.assertEqual(legend.legendSettings().columnCount(), 2)
legend.dataDefinedProperties().setProperty(QgsLayoutObject.LegendColumnCount, QgsProperty.fromExpression("5"))
legend.refreshDataDefinedProperty()
self.assertEqual(legend.columnCount(), 2)
self.assertEqual(legend.legendSettings().columnCount(), 5)
if __name__ == '__main__':
unittest.main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB