1 /***************************************************************************
2                             qgslayoutobject.cpp
3                              -------------------
4     begin                : June 2017
5     copyright            : (C) 2017 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include <QPainter>
19 
20 #include "qgslayout.h"
21 #include "qgslayoutrendercontext.h"
22 #include "qgslayoutreportcontext.h"
23 #include "qgslayoutobject.h"
24 #include "qgsfeedback.h"
25 #include "qgsexpressioncontextutils.h"
26 
27 
28 QgsPropertiesDefinition QgsLayoutObject::sPropertyDefinitions;
29 
initPropertyDefinitions()30 void QgsLayoutObject::initPropertyDefinitions()
31 {
32   if ( !sPropertyDefinitions.isEmpty() )
33     return;
34 
35   sPropertyDefinitions = QgsPropertiesDefinition
36   {
37     { QgsLayoutObject::TestProperty, QgsPropertyDefinition( "dataDefinedProperty", QgsPropertyDefinition::DataTypeString, "invalid property", QString() ) },
38     {
39       QgsLayoutObject::PresetPaperSize, QgsPropertyDefinition( "dataDefinedPaperSize", QgsPropertyDefinition::DataTypeString, QObject::tr( "Paper size" ), QObject::tr( "string " ) + QLatin1String( "[<b>A5</b>|<b>A4</b>|<b>A3</b>|<b>A2</b>|<b>A1</b>|<b>A0</b>"
40           "|<b>B5</b>|<b>B4</b>|<b>B3</b>|<b>B2</b>|<b>B1</b>|<b>B0</b>"
41           "|<b>Legal</b>|<b>Ansi A</b>|<b>Ansi B</b>|<b>Ansi C</b>|<b>Ansi D</b>|<b>Ansi E</b>"
42           "|<b>Arch A</b>|<b>Arch B</b>|<b>Arch C</b>|<b>Arch D</b>|<b>Arch E</b>|<b>Arch E1</b>]"
43                                                                                                                                                                                                    ) )
44     },
45     { QgsLayoutObject::PaperWidth, QgsPropertyDefinition( "dataDefinedPaperWidth", QObject::tr( "Page width" ), QgsPropertyDefinition::DoublePositive ) },
46     { QgsLayoutObject::PaperHeight, QgsPropertyDefinition( "dataDefinedPaperHeight", QObject::tr( "Page height" ), QgsPropertyDefinition::DoublePositive ) },
47     { QgsLayoutObject::NumPages, QgsPropertyDefinition( "dataDefinedNumPages", QObject::tr( "Number of pages" ), QgsPropertyDefinition::IntegerPositive ) },
48     { QgsLayoutObject::PaperOrientation, QgsPropertyDefinition( "dataDefinedPaperOrientation", QgsPropertyDefinition::DataTypeString, QObject::tr( "Symbol size" ), QObject::tr( "string " ) + QLatin1String( "[<b>portrait</b>|<b>landscape</b>]" ) ) },
49     { QgsLayoutObject::PageNumber, QgsPropertyDefinition( "dataDefinedPageNumber", QObject::tr( "Page number" ), QgsPropertyDefinition::IntegerPositive ) },
50     { QgsLayoutObject::PositionX, QgsPropertyDefinition( "dataDefinedPositionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double ) },
51     { QgsLayoutObject::PositionY, QgsPropertyDefinition( "dataDefinedPositionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double ) },
52     { QgsLayoutObject::ItemWidth, QgsPropertyDefinition( "dataDefinedWidth", QObject::tr( "Width" ), QgsPropertyDefinition::DoublePositive ) },
53     { QgsLayoutObject::ItemHeight, QgsPropertyDefinition( "dataDefinedHeight", QObject::tr( "Height" ), QgsPropertyDefinition::DoublePositive ) },
54     { QgsLayoutObject::ItemRotation, QgsPropertyDefinition( "dataDefinedRotation", QObject::tr( "Rotation angle" ), QgsPropertyDefinition::Rotation ) },
55     { QgsLayoutObject::Transparency, QgsPropertyDefinition( "dataDefinedTransparency", QObject::tr( "Transparency" ), QgsPropertyDefinition::Opacity ) },
56     { QgsLayoutObject::Opacity, QgsPropertyDefinition( "dataDefinedOpacity", QObject::tr( "Opacity" ), QgsPropertyDefinition::Opacity ) },
57     { QgsLayoutObject::BlendMode, QgsPropertyDefinition( "dataDefinedBlendMode", QObject::tr( "Blend mode" ), QgsPropertyDefinition::BlendMode ) },
58     { QgsLayoutObject::ExcludeFromExports, QgsPropertyDefinition( "dataDefinedExcludeExports", QObject::tr( "Exclude item from exports" ), QgsPropertyDefinition::Boolean ) },
59     { QgsLayoutObject::FrameColor, QgsPropertyDefinition( "dataDefinedFrameColor", QObject::tr( "Frame color" ), QgsPropertyDefinition::ColorWithAlpha ) },
60     { QgsLayoutObject::BackgroundColor, QgsPropertyDefinition( "dataDefinedBackgroundColor", QObject::tr( "Background color" ), QgsPropertyDefinition::ColorWithAlpha ) },
61     { QgsLayoutObject::MapRotation, QgsPropertyDefinition( "dataDefinedMapRotation", QObject::tr( "Map rotation" ), QgsPropertyDefinition::Rotation ) },
62     { QgsLayoutObject::MapScale, QgsPropertyDefinition( "dataDefinedMapScale", QObject::tr( "Map scale" ), QgsPropertyDefinition::DoublePositive ) },
63     { QgsLayoutObject::MapXMin, QgsPropertyDefinition( "dataDefinedMapXMin", QObject::tr( "Extent minimum X" ), QgsPropertyDefinition::Double ) },
64     { QgsLayoutObject::MapYMin, QgsPropertyDefinition( "dataDefinedMapYMin", QObject::tr( "Extent minimum Y" ), QgsPropertyDefinition::Double ) },
65     { QgsLayoutObject::MapXMax, QgsPropertyDefinition( "dataDefinedMapXMax", QObject::tr( "Extent maximum X" ), QgsPropertyDefinition::Double ) },
66     { QgsLayoutObject::MapYMax, QgsPropertyDefinition( "dataDefinedMapYMax", QObject::tr( "Extent maximum Y" ), QgsPropertyDefinition::Double ) },
67     { QgsLayoutObject::MapLabelMargin, QgsPropertyDefinition( "dataDefinedMapLabelMargin", QObject::tr( "Label margin" ), QgsPropertyDefinition::DoublePositive ) },
68     { QgsLayoutObject::MapAtlasMargin, QgsPropertyDefinition( "dataDefinedMapAtlasMargin", QObject::tr( "Atlas margin" ), QgsPropertyDefinition::DoublePositive ) },
69     { QgsLayoutObject::MapLayers, QgsPropertyDefinition( "dataDefinedMapLayers", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map Layers" ), tr( "list of map layer names separated by | characters" ) ) },
70     { QgsLayoutObject::MapStylePreset, QgsPropertyDefinition( "dataDefinedMapStylePreset", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map theme" ), tr( "name of an existing map theme (case-sensitive)" ) ) },
71     { QgsLayoutObject::MapGridEnabled, QgsPropertyDefinition( "dataDefinedMapGridEnabled", QObject::tr( "Grid enabled" ), QgsPropertyDefinition::Boolean ) },
72     { QgsLayoutObject::MapGridIntervalX, QgsPropertyDefinition( "dataDefinedMapGridIntervalX", QObject::tr( "Grid interval X" ), QgsPropertyDefinition::DoublePositive ) },
73     { QgsLayoutObject::MapGridIntervalY, QgsPropertyDefinition( "dataDefinedMapGridIntervalY", QObject::tr( "Grid interval Y" ), QgsPropertyDefinition::DoublePositive ) },
74     { QgsLayoutObject::MapGridOffsetX, QgsPropertyDefinition( "dataDefinedMapGridOffsetX", QObject::tr( "Grid offset X" ), QgsPropertyDefinition::Double ) },
75     { QgsLayoutObject::MapGridOffsetY, QgsPropertyDefinition( "dataDefinedMapGridOffsetY", QObject::tr( "Grid offset Y" ), QgsPropertyDefinition::Double ) },
76     { QgsLayoutObject::MapGridFrameSize, QgsPropertyDefinition( "dataDefinedMapGridFrameSize", QObject::tr( "Grid frame size" ), QgsPropertyDefinition::DoublePositive ) },
77     { QgsLayoutObject::MapGridFrameLineThickness, QgsPropertyDefinition( "dataDefinedMapGridFrameLineThickness", QObject::tr( "Grid frame line thickness" ), QgsPropertyDefinition::DoublePositive ) },
78     { QgsLayoutObject::MapGridCrossSize, QgsPropertyDefinition( "dataDefinedMapGridCrossSize", QObject::tr( "Grid cross size" ), QgsPropertyDefinition::DoublePositive ) },
79     { QgsLayoutObject::MapGridFrameMargin, QgsPropertyDefinition( "dataDefinedMapGridFrameMargin", QObject::tr( "Grid frame margin" ), QgsPropertyDefinition::DoublePositive ) },
80     { QgsLayoutObject::MapGridLabelDistance, QgsPropertyDefinition( "dataDefinedMapGridLabelDistance", QObject::tr( "Grid label distance" ), QgsPropertyDefinition::DoublePositive ) },
81     { QgsLayoutObject::MapGridAnnotationDisplayLeft, QgsPropertyDefinition( "dataDefinedMapGridAnnotationDisplayLeft", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map grid annotation display left" ), QObject::tr( "string " ) + QLatin1String( "[<b>all</b>|<b>x_only</b>|<b>y_only</b>|<b>disabled</b>]" ) ) },
82     { QgsLayoutObject::MapGridAnnotationDisplayRight, QgsPropertyDefinition( "dataDefinedMapGridAnnotationDisplayRight", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map grid annotation display right" ), QObject::tr( "string " ) + QLatin1String( "[<b>all</b>|<b>x_only</b>|<b>y_only</b>|<b>disabled</b>]" ) ) },
83     { QgsLayoutObject::MapGridAnnotationDisplayTop, QgsPropertyDefinition( "dataDefinedMapGridAnnotationDisplayTop", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map grid annotation display top" ), QObject::tr( "string " ) + QLatin1String( "[<b>all</b>|<b>x_only</b>|<b>y_only</b>|<b>disabled</b>]" ) ) },
84     { QgsLayoutObject::MapGridAnnotationDisplayBottom, QgsPropertyDefinition( "dataDefinedMapGridAnnotationDisplayBottom", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map grid annotation display bottom" ), QObject::tr( "string " ) + QLatin1String( "[<b>all</b>|<b>x_only</b>|<b>y_only</b>|<b>disabled</b>]" ) ) },
85     { QgsLayoutObject::MapGridFrameDivisionsLeft, QgsPropertyDefinition( "dataDefinedMapGridFrameDivisionsLeft", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map grid frame divisions display left" ), QObject::tr( "string " ) + QLatin1String( "[<b>all</b>|<b>x_only</b>|<b>y_only</b>|<b>disabled</b>]" ) ) },
86     { QgsLayoutObject::MapGridFrameDivisionsRight, QgsPropertyDefinition( "dataDefinedMapGridFrameDivisionsRight", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map grid frame divisions display right" ), QObject::tr( "string " ) + QLatin1String( "[<b>all</b>|<b>x_only</b>|<b>y_only</b>|<b>disabled</b>]" ) ) },
87     { QgsLayoutObject::MapGridFrameDivisionsTop, QgsPropertyDefinition( "dataDefinedMapGridFrameDivisionsTop", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map grid frame divisions display top" ), QObject::tr( "string " ) + QLatin1String( "[<b>all</b>|<b>x_only</b>|<b>y_only</b>|<b>disabled</b>]" ) ) },
88     { QgsLayoutObject::MapGridFrameDivisionsBottom, QgsPropertyDefinition( "dataDefinedMapGridFrameDivisionsBottom", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map grid frame divisions display bottom" ), QObject::tr( "string " ) + QLatin1String( "[<b>all</b>|<b>x_only</b>|<b>y_only</b>|<b>disabled</b>]" ) ) },
89     { QgsLayoutObject::PictureSource, QgsPropertyDefinition( "dataDefinedSource", QObject::tr( "Picture source (URL)" ), QgsPropertyDefinition::String ) },
90     { QgsLayoutObject::SourceUrl, QgsPropertyDefinition( "dataDefinedSourceUrl", QObject::tr( "Source URL" ), QgsPropertyDefinition::String ) },
91     { QgsLayoutObject::PictureSvgBackgroundColor, QgsPropertyDefinition( "dataDefinedSvgBackgroundColor", QObject::tr( "SVG background color" ), QgsPropertyDefinition::ColorWithAlpha ) },
92     { QgsLayoutObject::PictureSvgStrokeColor, QgsPropertyDefinition( "dataDefinedSvgStrokeColor", QObject::tr( "SVG stroke color" ), QgsPropertyDefinition::ColorWithAlpha ) },
93     { QgsLayoutObject::PictureSvgStrokeWidth, QgsPropertyDefinition( "dataDefinedSvgStrokeWidth", QObject::tr( "SVG stroke width" ), QgsPropertyDefinition::StrokeWidth ) },
94     { QgsLayoutObject::LegendTitle, QgsPropertyDefinition( "dataDefinedLegendTitle", QObject::tr( "Legend title" ), QgsPropertyDefinition::String ) },
95     { QgsLayoutObject::LegendColumnCount, QgsPropertyDefinition( "dataDefinedLegendColumns", QObject::tr( "Number of columns" ), QgsPropertyDefinition::IntegerPositiveGreaterZero ) },
96     { QgsLayoutObject::ScalebarFillColor, QgsPropertyDefinition( "dataDefinedScalebarFill", QObject::tr( "Fill color" ), QgsPropertyDefinition::ColorWithAlpha ) },
97     { QgsLayoutObject::ScalebarFillColor2, QgsPropertyDefinition( "dataDefinedScalebarFill2", QObject::tr( "Secondary fill color" ), QgsPropertyDefinition::ColorWithAlpha ) },
98     { QgsLayoutObject::ScalebarLineColor, QgsPropertyDefinition( "dataDefinedScalebarLineColor", QObject::tr( "Line color" ), QgsPropertyDefinition::ColorWithAlpha ) },
99     { QgsLayoutObject::ScalebarLineWidth, QgsPropertyDefinition( "dataDefinedScalebarLineWidth", QObject::tr( "Line width" ), QgsPropertyDefinition::StrokeWidth ) },
100     { QgsLayoutObject::AttributeTableSourceLayer, QgsPropertyDefinition( "dataDefinedAttributeTableSourceLayer", QObject::tr( "Table source layer" ), QgsPropertyDefinition::String ) },
101     { QgsLayoutObject::MapCrs, QgsPropertyDefinition( "dataDefinedCrs", QgsPropertyDefinition::DataTypeString, QObject::tr( "Map CRS" ), QObject::tr( "string representing a CRS, either an authority/id pair (e.g. \"EPSG:4326\"), a proj string prefixes by \"PROJ:\" (e.g. \"PROJ: +proj=...\") or a WKT string prefixed by \"WKT:\" (e.g. \"WKT:GEOGCS[\"WGS 84\"...)" ) ) },
102     { QgsLayoutObject::StartDateTime, QgsPropertyDefinition( "dataDefinedStartDateTime", QObject::tr( "Temporal range start date / time" ), QgsPropertyDefinition::DateTime ) },
103     { QgsLayoutObject::EndDateTime, QgsPropertyDefinition( "dataDefinedEndDateTime", QObject::tr( "Temporal range end date / time" ), QgsPropertyDefinition::DateTime ) },
104   };
105 }
106 
propertyDefinitions()107 const QgsPropertiesDefinition &QgsLayoutObject::propertyDefinitions()
108 {
109   QgsLayoutObject::initPropertyDefinitions();
110   return sPropertyDefinitions;
111 }
112 
propertyAssociatesWithParentMultiframe(QgsLayoutObject::DataDefinedProperty property)113 bool QgsLayoutObject::propertyAssociatesWithParentMultiframe( QgsLayoutObject::DataDefinedProperty property )
114 {
115   switch ( property )
116   {
117     case QgsLayoutObject::SourceUrl:
118     case QgsLayoutObject::AttributeTableSourceLayer:
119       return true;
120 
121     case QgsLayoutObject::NoProperty:
122     case QgsLayoutObject::AllProperties:
123     case QgsLayoutObject::TestProperty:
124     case QgsLayoutObject::PresetPaperSize:
125     case QgsLayoutObject::PaperWidth:
126     case QgsLayoutObject::PaperHeight:
127     case QgsLayoutObject::NumPages:
128     case QgsLayoutObject::PaperOrientation:
129     case QgsLayoutObject::PageNumber:
130     case QgsLayoutObject::PositionX:
131     case QgsLayoutObject::PositionY:
132     case QgsLayoutObject::ItemWidth:
133     case QgsLayoutObject::ItemHeight:
134     case QgsLayoutObject::ItemRotation:
135     case QgsLayoutObject::Transparency:
136     case QgsLayoutObject::Opacity:
137     case QgsLayoutObject::BlendMode:
138     case QgsLayoutObject::ExcludeFromExports:
139     case QgsLayoutObject::FrameColor:
140     case QgsLayoutObject::BackgroundColor:
141     case QgsLayoutObject::MapRotation:
142     case QgsLayoutObject::MapScale:
143     case QgsLayoutObject::MapXMin:
144     case QgsLayoutObject::MapYMin:
145     case QgsLayoutObject::MapXMax:
146     case QgsLayoutObject::MapYMax:
147     case QgsLayoutObject::MapAtlasMargin:
148     case QgsLayoutObject::MapLayers:
149     case QgsLayoutObject::MapStylePreset:
150     case QgsLayoutObject::MapLabelMargin:
151     case QgsLayoutObject::MapGridEnabled:
152     case QgsLayoutObject::MapGridIntervalX:
153     case QgsLayoutObject::MapGridIntervalY:
154     case QgsLayoutObject::MapGridOffsetX:
155     case QgsLayoutObject::MapGridOffsetY:
156     case QgsLayoutObject::MapGridFrameSize:
157     case QgsLayoutObject::MapGridFrameMargin:
158     case QgsLayoutObject::MapGridLabelDistance:
159     case QgsLayoutObject::MapGridCrossSize:
160     case QgsLayoutObject::MapGridFrameLineThickness:
161     case QgsLayoutObject::MapGridAnnotationDisplayLeft:
162     case QgsLayoutObject::MapGridAnnotationDisplayRight:
163     case QgsLayoutObject::MapGridAnnotationDisplayTop:
164     case QgsLayoutObject::MapGridAnnotationDisplayBottom:
165     case QgsLayoutObject::MapGridFrameDivisionsLeft:
166     case QgsLayoutObject::MapGridFrameDivisionsRight:
167     case QgsLayoutObject::MapGridFrameDivisionsTop:
168     case QgsLayoutObject::MapGridFrameDivisionsBottom:
169     case QgsLayoutObject::PictureSource:
170     case QgsLayoutObject::PictureSvgBackgroundColor:
171     case QgsLayoutObject::PictureSvgStrokeColor:
172     case QgsLayoutObject::PictureSvgStrokeWidth:
173     case QgsLayoutObject::LegendTitle:
174     case QgsLayoutObject::LegendColumnCount:
175     case QgsLayoutObject::ScalebarFillColor:
176     case QgsLayoutObject::ScalebarFillColor2:
177     case QgsLayoutObject::ScalebarLineColor:
178     case QgsLayoutObject::ScalebarLineWidth:
179     case QgsLayoutObject::MapCrs:
180     case QgsLayoutObject::StartDateTime:
181     case QgsLayoutObject::EndDateTime:
182       return false;
183   }
184   return false;
185 }
186 
QgsLayoutObject(QgsLayout * layout)187 QgsLayoutObject::QgsLayoutObject( QgsLayout *layout )
188   : QObject( nullptr )
189   , mLayout( layout )
190 {
191   initPropertyDefinitions();
192 
193   if ( mLayout )
194   {
195     connect( mLayout, &QgsLayout::refreshed, this, &QgsLayoutObject::refresh );
196     connect( &mLayout->reportContext(), &QgsLayoutReportContext::changed, this, &QgsLayoutObject::refresh );
197   }
198 }
199 
layout() const200 const QgsLayout *QgsLayoutObject::layout() const
201 {
202   return mLayout.data();
203 }
204 
layout()205 QgsLayout *QgsLayoutObject::layout()
206 {
207   return mLayout.data();
208 }
209 
setCustomProperty(const QString & key,const QVariant & value)210 void QgsLayoutObject::setCustomProperty( const QString &key, const QVariant &value )
211 {
212   mCustomProperties.setValue( key, value );
213 }
214 
customProperty(const QString & key,const QVariant & defaultValue) const215 QVariant QgsLayoutObject::customProperty( const QString &key, const QVariant &defaultValue ) const
216 {
217   return mCustomProperties.value( key, defaultValue );
218 }
219 
removeCustomProperty(const QString & key)220 void QgsLayoutObject::removeCustomProperty( const QString &key )
221 {
222   mCustomProperties.remove( key );
223 }
224 
customProperties() const225 QStringList QgsLayoutObject::customProperties() const
226 {
227   return mCustomProperties.keys();
228 }
229 
createExpressionContext() const230 QgsExpressionContext QgsLayoutObject::createExpressionContext() const
231 {
232   if ( mLayout )
233   {
234     return mLayout->createExpressionContext();
235   }
236   else
237   {
238     return QgsExpressionContext() << QgsExpressionContextUtils::globalScope();
239   }
240 }
241 
writeObjectPropertiesToElement(QDomElement & parentElement,QDomDocument & document,const QgsReadWriteContext &) const242 bool QgsLayoutObject::writeObjectPropertiesToElement( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const
243 {
244   if ( parentElement.isNull() )
245   {
246     return false;
247   }
248 
249   //create object element
250   QDomElement objectElement = document.createElement( QStringLiteral( "LayoutObject" ) );
251 
252   QDomElement ddPropsElement = document.createElement( QStringLiteral( "dataDefinedProperties" ) );
253   mDataDefinedProperties.writeXml( ddPropsElement, sPropertyDefinitions );
254   objectElement.appendChild( ddPropsElement );
255 
256   //custom properties
257   mCustomProperties.writeXml( objectElement, document );
258 
259   parentElement.appendChild( objectElement );
260   return true;
261 }
262 
readObjectPropertiesFromElement(const QDomElement & parentElement,const QDomDocument & document,const QgsReadWriteContext &)263 bool QgsLayoutObject::readObjectPropertiesFromElement( const QDomElement &parentElement, const QDomDocument &document, const QgsReadWriteContext & )
264 {
265   Q_UNUSED( document )
266   if ( parentElement.isNull() )
267   {
268     return false;
269   }
270 
271   const QDomNodeList objectNodeList = parentElement.elementsByTagName( QStringLiteral( "LayoutObject" ) );
272   if ( objectNodeList.size() < 1 )
273   {
274     return false;
275   }
276   const QDomElement objectElement = objectNodeList.at( 0 ).toElement();
277 
278   const QDomNode propsNode = objectElement.namedItem( QStringLiteral( "dataDefinedProperties" ) );
279   if ( !propsNode.isNull() )
280   {
281     mDataDefinedProperties.readXml( propsNode.toElement(), sPropertyDefinitions );
282   }
283 
284   //custom properties
285   mCustomProperties.readXml( objectElement );
286 
287   return true;
288 }
289