1 /***************************************************************************
2                          testqgslayoutitem.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 "qgslayoutitem.h"
19 #include "qgslayoutitemregistry.h"
20 #include "qgslayout.h"
21 #include "qgsmultirenderchecker.h"
22 #include "qgstest.h"
23 #include "qgsproject.h"
24 #include "qgsreadwritecontext.h"
25 #include "qgslayoutitemundocommand.h"
26 #include "qgslayoutitemmap.h"
27 #include "qgslayoutitemlabel.h"
28 #include "qgslayoutitemshape.h"
29 #include "qgslayouteffect.h"
30 #include "qgsfillsymbollayer.h"
31 #include "qgslayoutpagecollection.h"
32 #include "qgslayoutundostack.h"
33 #include "qgsvectorlayer.h"
34 #include "qgsexpressioncontextutils.h"
35 #include "qgsfillsymbol.h"
36 
37 #include <QObject>
38 #include <QPainter>
39 #include <QImage>
40 #include <QtTest/QSignalSpy>
41 
42 
43 //simple item for testing, since some methods in QgsLayoutItem are pure virtual
44 class TestItem : public QgsLayoutItem
45 {
46     Q_OBJECT
47 
48   public:
49 
TestItem(QgsLayout * layout)50     TestItem( QgsLayout *layout ) : QgsLayoutItem( layout )
51     {
52       setFrameEnabled( false );
53       setBackgroundEnabled( false );
54     }
55 
56     //implement pure virtual methods
type() const57     int type() const override { return QgsLayoutItemRegistry::LayoutItem + 101; }
58 
59     bool forceResize = false;
60 
61   protected:
draw(QgsLayoutItemRenderContext & context)62     void draw( QgsLayoutItemRenderContext &context ) override
63     {
64       QPainter *painter = context.renderContext().painter();
65       painter->save();
66       painter->setRenderHint( QPainter::Antialiasing, false );
67       painter->setPen( Qt::NoPen );
68       painter->setBrush( QColor( 255, 100, 100, 200 ) );
69       painter->drawRect( rect() );
70       painter->restore();
71     }
72 
applyItemSizeConstraint(QSizeF targetSize)73     QSizeF applyItemSizeConstraint( QSizeF targetSize ) override
74     {
75       if ( !forceResize )
76         return targetSize;
77 
78       return QSizeF( 17, 27 );
79     }
80 };
81 
82 //item with minimum size
83 class MinSizedItem : public TestItem
84 {
85     Q_OBJECT
86 
87   public:
MinSizedItem(QgsLayout * layout)88     MinSizedItem( QgsLayout *layout ) : TestItem( layout )
89     {
90       setMinimumSize( QgsLayoutSize( 5.0, 10.0, QgsUnitTypes::LayoutCentimeters ) );
91     }
92 
updateMinSize(QgsLayoutSize size)93     void updateMinSize( QgsLayoutSize size )
94     {
95       setMinimumSize( size );
96     }
97 
98 };
99 
100 //item with fixed size
101 class FixedSizedItem : public TestItem
102 {
103     Q_OBJECT
104 
105   public:
106 
FixedSizedItem(QgsLayout * layout)107     FixedSizedItem( QgsLayout *layout ) : TestItem( layout )
108     {
109       setFixedSize( QgsLayoutSize( 2.0, 4.0, QgsUnitTypes::LayoutInches ) );
110     }
111 
updateFixedSize(QgsLayoutSize size)112     void updateFixedSize( QgsLayoutSize size )
113     {
114       setFixedSize( size );
115     }
116 };
117 
118 //item with both conflicting fixed and minimum size
119 class FixedMinSizedItem : public TestItem
120 {
121     Q_OBJECT
122 
123   public:
124 
FixedMinSizedItem(QgsLayout * layout)125     FixedMinSizedItem( QgsLayout *layout ) : TestItem( layout )
126     {
127       setFixedSize( QgsLayoutSize( 2.0, 4.0, QgsUnitTypes::LayoutCentimeters ) );
128       setMinimumSize( QgsLayoutSize( 5.0, 9.0, QgsUnitTypes::LayoutCentimeters ) );
129     }
130 };
131 
132 
133 class TestQgsLayoutItem: public QObject
134 {
135     Q_OBJECT
136 
137   private slots:
138     void initTestCase();// will be called before the first testfunction is executed.
139     void cleanupTestCase();// will be called after the last testfunction was executed.
140     void init();// will be called before each testfunction is executed.
141     void cleanup();// will be called after every testfunction.
142     void creation(); //test creation of QgsLayoutItem
143     void uuid();
144     void id();
145     void registry();
146     void shouldDrawDebug();
147     void shouldDrawAntialiased();
148     void preparePainter();
149     void debugRect();
150     void draw();
151     void resize();
152     void referencePoint();
153     void itemPositionReferencePoint();
154     void adjustPointForReference();
155     void positionAtReferencePoint();
156     void fixedSize();
157     void minSize();
158     void move();
159     void positionWithUnits();
160     void sizeWithUnits();
161     void dataDefinedPosition();
162     void dataDefinedSize();
163     void combinedDataDefinedPositionAndSize();
164     void rotation();
165     void writeXml();
166     void readXml();
167     void writeReadXmlProperties();
168     void undoRedo();
169     void multiItemUndo();
170     void overlappingUndo();
171     void blendMode();
172     void opacity();
173     void excludeFromExports();
174     void setSceneRect();
175     void page();
176     void itemVariablesFunction();
177     void variables();
178     void mapCreditsFunction();
179 
180   private:
181 
182     QString mReport;
183 
184     bool renderCheck( QString testName, QImage &image, int mismatchCount );
185 
186     std::unique_ptr< QgsLayoutItem > createCopyViaXml( QgsLayout *layout, QgsLayoutItem *original );
187 
188 };
189 
initTestCase()190 void TestQgsLayoutItem::initTestCase()
191 {
192   mReport = QStringLiteral( "<h1>Layout Item Tests</h1>\n" );
193 }
194 
cleanupTestCase()195 void TestQgsLayoutItem::cleanupTestCase()
196 {
197   const QString myReportFile = QDir::tempPath() + QDir::separator() + "qgistest.html";
198   QFile myFile( myReportFile );
199   if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
200   {
201     QTextStream myQTextStream( &myFile );
202     myQTextStream << mReport;
203     myFile.close();
204   }
205 }
206 
init()207 void TestQgsLayoutItem::init()
208 {
209 
210 }
211 
cleanup()212 void TestQgsLayoutItem::cleanup()
213 {
214 
215 }
216 
creation()217 void TestQgsLayoutItem::creation()
218 {
219   QgsProject p;
220   QgsLayout *layout = new QgsLayout( &p );
221   TestItem *item = new TestItem( layout );
222   QVERIFY( item );
223   delete item;
224   delete layout;
225 }
226 
uuid()227 void TestQgsLayoutItem::uuid()
228 {
229   QgsProject p;
230   QgsLayout l( &p );
231 
232   //basic test of uuid
233   const TestItem item( &l );
234   const TestItem item2( &l );
235   QVERIFY( item.uuid() != item2.uuid() );
236 }
237 
id()238 void TestQgsLayoutItem::id()
239 {
240   QgsProject p;
241   QgsLayout l( &p );
242   TestItem item( &l );
243   item.setId( QStringLiteral( "test" ) );
244   QCOMPARE( item.id(), QStringLiteral( "test" ) );
245 }
246 
registry()247 void TestQgsLayoutItem::registry()
248 {
249   // test QgsLayoutItemRegistry
250   QgsLayoutItemRegistry registry;
251 
252   // empty registry
253   QVERIFY( !registry.itemMetadata( -1 ) );
254   QVERIFY( registry.itemTypes().isEmpty() );
255   QVERIFY( !registry.createItem( 1, nullptr ) );
256 
257   auto create = []( QgsLayout * layout )->QgsLayoutItem *
258   {
259     return new TestItem( layout );
260   };
261   auto resolve = []( QVariantMap & props, const QgsPathResolver &, bool )
262   {
263     props.clear();
264   };
265 
266   const QSignalSpy spyTypeAdded( &registry, &QgsLayoutItemRegistry::typeAdded );
267 
268   QgsLayoutItemMetadata *metadata = new QgsLayoutItemMetadata( 2, QStringLiteral( "my type" ), QStringLiteral( "my types" ), create, resolve );
269   QVERIFY( registry.addLayoutItemType( metadata ) );
270   QCOMPARE( spyTypeAdded.count(), 1 );
271   QCOMPARE( spyTypeAdded.value( 0 ).at( 0 ).toInt(), 2 );
272   QCOMPARE( spyTypeAdded.value( 0 ).at( 1 ).toString(), QStringLiteral( "my type" ) );
273   // duplicate type id
274   QVERIFY( !registry.addLayoutItemType( metadata ) );
275   QCOMPARE( spyTypeAdded.count(), 1 );
276 
277   //retrieve metadata
278   QVERIFY( !registry.itemMetadata( -1 ) );
279   QCOMPARE( registry.itemMetadata( 2 )->visibleName(), QStringLiteral( "my type" ) );
280   QCOMPARE( registry.itemMetadata( 2 )->visiblePluralName(), QStringLiteral( "my types" ) );
281   QCOMPARE( registry.itemTypes().count(), 1 );
282   QCOMPARE( registry.itemTypes().value( 2 ), QStringLiteral( "my type" ) );
283   QgsLayoutItem *item = registry.createItem( 2, nullptr );
284   QVERIFY( item );
285   QVERIFY( dynamic_cast< TestItem *>( item ) );
286   delete item;
287   QVariantMap props;
288   props.insert( QStringLiteral( "a" ), 5 );
289   registry.resolvePaths( 1, props, QgsPathResolver(), true );
290   QCOMPARE( props.size(), 1 );
291   registry.resolvePaths( 2, props, QgsPathResolver(), true );
292   QVERIFY( props.isEmpty() );
293 
294   //test populate
295   QgsLayoutItemRegistry reg2;
296   QVERIFY( reg2.itemTypes().isEmpty() );
297   QVERIFY( reg2.populate() );
298   QVERIFY( !reg2.itemTypes().isEmpty() );
299   QVERIFY( !reg2.populate() );
300 }
301 
shouldDrawDebug()302 void TestQgsLayoutItem::shouldDrawDebug()
303 {
304   QgsProject p;
305   QgsLayout l( &p );
306   TestItem *item = new TestItem( &l );
307   l.renderContext().setFlag( QgsLayoutRenderContext::FlagDebug, true );
308   QVERIFY( item->shouldDrawDebugRect() );
309   l.renderContext().setFlag( QgsLayoutRenderContext::FlagDebug, false );
310   QVERIFY( !item->shouldDrawDebugRect() );
311   delete item;
312 }
313 
shouldDrawAntialiased()314 void TestQgsLayoutItem::shouldDrawAntialiased()
315 {
316   QgsProject p;
317   QgsLayout l( &p );
318   TestItem *item = new TestItem( &l );
319   l.renderContext().setFlag( QgsLayoutRenderContext::FlagAntialiasing, false );
320   QVERIFY( !item->shouldDrawAntialiased() );
321   l.renderContext().setFlag( QgsLayoutRenderContext::FlagAntialiasing, true );
322   QVERIFY( item->shouldDrawAntialiased() );
323   delete item;
324 }
325 
preparePainter()326 void TestQgsLayoutItem::preparePainter()
327 {
328   QgsProject p;
329   QgsLayout l( &p );
330   TestItem *item = new TestItem( &l );
331   //test with no painter
332   item->preparePainter( nullptr );
333 
334   //test antialiasing correctly set for painter
335   QImage image( QSize( 100, 100 ), QImage::Format_ARGB32 );
336   QPainter painter;
337   painter.begin( &image );
338   l.renderContext().setFlag( QgsLayoutRenderContext::FlagAntialiasing, false );
339   item->preparePainter( &painter );
340   QVERIFY( !( painter.renderHints() & QPainter::Antialiasing ) );
341   l.renderContext().setFlag( QgsLayoutRenderContext::FlagAntialiasing, true );
342   item->preparePainter( &painter );
343   QVERIFY( painter.renderHints() & QPainter::Antialiasing );
344   delete item;
345 }
346 
debugRect()347 void TestQgsLayoutItem::debugRect()
348 {
349   QgsProject p;
350   QgsLayout l( &p );
351   TestItem *item = new TestItem( &l );
352   l.addItem( item );
353   item->setPos( 100, 100 );
354   item->setRect( 0, 0, 200, 200 );
355   l.setSceneRect( 0, 0, 400, 400 );
356   l.renderContext().setFlag( QgsLayoutRenderContext::FlagDebug, true );
357   QImage image( l.sceneRect().size().toSize(), QImage::Format_ARGB32 );
358   image.fill( 0 );
359   QPainter painter( &image );
360   l.render( &painter );
361   painter.end();
362 
363   const bool result = renderCheck( QStringLiteral( "layoutitem_debugrect" ), image, 0 );
364   QVERIFY( result );
365 }
366 
draw()367 void TestQgsLayoutItem::draw()
368 {
369   QgsProject p;
370   QgsLayout l( &p );
371   TestItem *item = new TestItem( &l );
372   l.addItem( item );
373   item->setPos( 100, 100 );
374   item->setRect( 0, 0, 200, 200 );
375   l.setSceneRect( 0, 0, 400, 400 );
376   l.renderContext().setFlag( QgsLayoutRenderContext::FlagAntialiasing, false ); //disable antialiasing to limit cross platform differences
377   QImage image( l.sceneRect().size().toSize(), QImage::Format_ARGB32 );
378   image.fill( 0 );
379   QPainter painter( &image );
380   l.render( &painter );
381   painter.end();
382   const bool result = renderCheck( QStringLiteral( "layoutitem_draw" ), image, 0 );
383   QVERIFY( result );
384 }
385 
renderCheck(QString testName,QImage & image,int mismatchCount)386 bool TestQgsLayoutItem::renderCheck( QString testName, QImage &image, int mismatchCount )
387 {
388   mReport += "<h2>" + testName + "</h2>\n";
389   const QString myTmpDir = QDir::tempPath() + QDir::separator();
390   const QString myFileName = myTmpDir + testName + ".png";
391   image.save( myFileName, "PNG" );
392   QgsRenderChecker myChecker;
393   myChecker.setControlPathPrefix( QStringLiteral( "layouts" ) );
394   myChecker.setControlName( "expected_" + testName );
395   myChecker.setRenderedImage( myFileName );
396   const bool myResultFlag = myChecker.compareImages( testName, mismatchCount );
397   mReport += myChecker.report();
398   return myResultFlag;
399 }
400 
positionWithUnits()401 void TestQgsLayoutItem::positionWithUnits()
402 {
403   QgsProject p;
404   QgsLayout l( &p );
405 
406   std::unique_ptr< TestItem > item( new TestItem( &l ) );
407   item->attemptMove( QgsLayoutPoint( 60.0, 15.0, QgsUnitTypes::LayoutMillimeters ) );
408   QCOMPARE( item->positionWithUnits().x(), 60.0 );
409   QCOMPARE( item->positionWithUnits().y(), 15.0 );
410   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutMillimeters );
411   item->attemptMove( QgsLayoutPoint( 50.0, 100.0, QgsUnitTypes::LayoutPixels ) );
412   QCOMPARE( item->positionWithUnits().x(), 50.0 );
413   QCOMPARE( item->positionWithUnits().y(), 100.0 );
414   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutPixels );
415 }
416 
sizeWithUnits()417 void TestQgsLayoutItem::sizeWithUnits()
418 {
419   QgsProject p;
420   QgsLayout l( &p );
421 
422   TestItem *item = new TestItem( &l );
423   item->attemptResize( QgsLayoutSize( 60.0, 15.0, QgsUnitTypes::LayoutMillimeters ) );
424   QCOMPARE( item->sizeWithUnits().width(), 60.0 );
425   QCOMPARE( item->sizeWithUnits().height(), 15.0 );
426   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutMillimeters );
427   item->attemptResize( QgsLayoutSize( 50.0, 100.0, QgsUnitTypes::LayoutPixels ) );
428   QCOMPARE( item->sizeWithUnits().width(), 50.0 );
429   QCOMPARE( item->sizeWithUnits().height(), 100.0 );
430   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutPixels );
431 
432   delete item;
433 }
434 
dataDefinedPosition()435 void TestQgsLayoutItem::dataDefinedPosition()
436 {
437   QgsProject p;
438   QgsLayout l( &p );
439 
440   //test setting data defined position
441   TestItem *item = new TestItem( &l );
442   l.setUnits( QgsUnitTypes::LayoutMillimeters );
443   item->attemptMove( QgsLayoutPoint( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
444   item->attemptResize( QgsLayoutSize( 2.0, 4.0, QgsUnitTypes::LayoutCentimeters ) );
445 
446   // position x
447   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+7" ) ) );
448   item->refreshDataDefinedProperty( QgsLayoutObject::PositionX );
449   QCOMPARE( item->positionWithUnits().x(), 11.0 );
450   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
451   QCOMPARE( item->scenePos().x(), 110.0 ); //mm
452 
453   //position y
454   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+3" ) ) );
455   item->refreshDataDefinedProperty( QgsLayoutObject::PositionY );
456   QCOMPARE( item->positionWithUnits().y(), 5.0 );
457   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
458   QCOMPARE( item->scenePos().y(), 50.0 ); //mm
459 
460   //refreshPosition should also respect data defined positioning
461   item->setPos( 0, 0 );
462   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) );
463   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) );
464   item->refreshItemPosition();
465   QCOMPARE( item->positionWithUnits().x(), 12.0 );
466   QCOMPARE( item->positionWithUnits().y(), 6.0 );
467   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
468   QCOMPARE( item->scenePos().x(), 120.0 ); //mm
469   QCOMPARE( item->scenePos().y(), 60.0 ); //mm
470 
471   //also check that data defined position overrides when attempting to move
472   item->attemptMove( QgsLayoutPoint( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
473   QCOMPARE( item->positionWithUnits().x(), 12.0 );
474   QCOMPARE( item->positionWithUnits().y(), 6.0 );
475   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
476   QCOMPARE( item->scenePos().x(), 120.0 ); //mm
477   QCOMPARE( item->scenePos().y(), 60.0 ); //mm
478   //restriction only for x position
479   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) );
480   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty() );
481   item->attemptMove( QgsLayoutPoint( 6.0, 1.5, QgsUnitTypes::LayoutCentimeters ) );
482   QCOMPARE( item->positionWithUnits().x(), 12.0 );
483   QCOMPARE( item->positionWithUnits().y(), 1.5 );
484   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
485   QCOMPARE( item->scenePos().x(), 120.0 ); //mm
486   QCOMPARE( item->scenePos().y(), 15.0 ); //mm
487   //restriction only for y position
488   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty() );
489   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) );
490   item->attemptMove( QgsLayoutPoint( 7.0, 1.5, QgsUnitTypes::LayoutCentimeters ) );
491   QCOMPARE( item->positionWithUnits().x(), 7.0 );
492   QCOMPARE( item->positionWithUnits().y(), 6.0 );
493   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
494   QCOMPARE( item->scenePos().x(), 70.0 ); //mm
495   QCOMPARE( item->scenePos().y(), 60.0 ); //mm
496 
497   //check change of units should apply to data defined position
498   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) );
499   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) );
500   //first set to same as existing position, but with different units
501   item->attemptMove( QgsLayoutPoint( 120.0, 60.0, QgsUnitTypes::LayoutMillimeters ) );
502   //data defined position should utilize new units
503   QCOMPARE( item->positionWithUnits().x(), 12.0 ); //mm
504   QCOMPARE( item->positionWithUnits().y(), 6.0 ); //mm
505   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutMillimeters );
506   QCOMPARE( item->scenePos().x(), 12.0 ); //mm
507   QCOMPARE( item->scenePos().y(), 6.0 ); //mm
508 
509   //test that data defined position applies to item's reference point
510   item->attemptMove( QgsLayoutPoint( 12.0, 6.0, QgsUnitTypes::LayoutCentimeters ) );
511   item->setReferencePoint( QgsLayoutItem::LowerRight );
512   QCOMPARE( item->positionWithUnits().x(), 12.0 ); //cm
513   QCOMPARE( item->positionWithUnits().y(), 6.0 ); //cm
514   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
515   QCOMPARE( item->scenePos().x(), 100.0 ); //mm
516   QCOMPARE( item->scenePos().y(), 20.0 ); //mm
517 
518   //also check setting data defined position AFTER setting reference point
519   item->setPos( 0, 0 );
520   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "6+10" ) ) );
521   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+6" ) ) );
522   item->refreshItemPosition();
523   QCOMPARE( item->positionWithUnits().x(), 16.0 ); //cm
524   QCOMPARE( item->positionWithUnits().y(), 8.0 ); //cm
525   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
526   QCOMPARE( item->scenePos().x(), 140.0 ); //mm
527   QCOMPARE( item->scenePos().y(), 40.0 ); //mm
528 
529   delete item;
530 }
531 
dataDefinedSize()532 void TestQgsLayoutItem::dataDefinedSize()
533 {
534   QgsProject p;
535   QgsLayout l( &p );
536 
537   //test setting data defined size
538   TestItem *item = new TestItem( &l );
539   l.setUnits( QgsUnitTypes::LayoutMillimeters );
540   item->attemptMove( QgsLayoutPoint( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
541   item->attemptResize( QgsLayoutSize( 2.0, 4.0, QgsUnitTypes::LayoutCentimeters ) );
542 
543   //width
544   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "4+7" ) ) );
545   item->refreshDataDefinedProperty( QgsLayoutObject::ItemWidth );
546   QCOMPARE( item->sizeWithUnits().width(), 11.0 );
547   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
548   QCOMPARE( item->rect().width(), 110.0 ); //mm
549 
550   //height
551   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "2+3" ) ) );
552   item->refreshDataDefinedProperty( QgsLayoutObject::ItemHeight );
553   QCOMPARE( item->sizeWithUnits().height(), 5.0 );
554   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
555   QCOMPARE( item->rect().height(), 50.0 ); //mm
556 
557   //refreshSize should also respect data defined size
558   item->setRect( 0.0, 0.0, 9.0, 8.0 );
559   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) );
560   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) );
561   item->refreshItemSize();
562   QCOMPARE( item->sizeWithUnits().width(), 12.0 );
563   QCOMPARE( item->sizeWithUnits().height(), 6.0 );
564   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
565   QCOMPARE( item->rect().width(), 120.0 ); //mm
566   QCOMPARE( item->rect().height(), 60.0 ); //mm
567 
568   //also check that data defined size overrides when attempting to resize
569   item->attemptResize( QgsLayoutSize( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
570   QCOMPARE( item->sizeWithUnits().width(), 12.0 );
571   QCOMPARE( item->sizeWithUnits().height(), 6.0 );
572   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
573   QCOMPARE( item->rect().width(), 120.0 ); //mm
574   QCOMPARE( item->rect().height(), 60.0 ); //mm
575   //restriction only for width
576   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) );
577   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty() );
578   item->attemptResize( QgsLayoutSize( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
579   QCOMPARE( item->sizeWithUnits().width(), 12.0 );
580   QCOMPARE( item->sizeWithUnits().height(), 1.5 );
581   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
582   QCOMPARE( item->rect().width(), 120.0 ); //mm
583   QCOMPARE( item->rect().height(), 15.0 ); //mm
584   //restriction only for y position
585   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty() );
586   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) );
587   item->attemptResize( QgsLayoutSize( 7.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
588   QCOMPARE( item->sizeWithUnits().width(), 7.0 );
589   QCOMPARE( item->sizeWithUnits().height(), 6.0 );
590   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
591   QCOMPARE( item->rect().width(), 70.0 ); //mm
592   QCOMPARE( item->rect().height(), 60.0 ); //mm
593 
594   // data defined page size
595   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty() );
596   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty() );
597   item->dataDefinedProperties().setProperty( QgsLayoutObject::PresetPaperSize, QgsProperty::fromValue( QStringLiteral( "A5" ) ) );
598   item->attemptResize( QgsLayoutSize( 7.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
599   QCOMPARE( item->sizeWithUnits().width(), 14.8 );
600   QCOMPARE( item->sizeWithUnits().height(), 21.0 );
601   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
602   QCOMPARE( item->rect().width(), 148.0 ); //mm
603   QCOMPARE( item->rect().height(), 210.0 ); //mm
604   // data defined height/width should override page size
605   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromValue( "13.0" ) );
606   item->attemptResize( QgsLayoutSize( 7.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
607   QCOMPARE( item->sizeWithUnits().width(), 13.0 );
608   QCOMPARE( item->sizeWithUnits().height(), 21.0 );
609   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
610   QCOMPARE( item->rect().width(), 130.0 ); //mm
611   QCOMPARE( item->rect().height(), 210.0 ); //mm
612   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromValue( "3.0" ) );
613   item->attemptResize( QgsLayoutSize( 7.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
614   QCOMPARE( item->sizeWithUnits().width(), 13.0 );
615   QCOMPARE( item->sizeWithUnits().height(), 3.0 );
616   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
617   QCOMPARE( item->rect().width(), 130.0 ); //mm
618   QCOMPARE( item->rect().height(), 30.0 ); //mm
619   // data defined orientation
620   item->dataDefinedProperties().setProperty( QgsLayoutObject::PaperOrientation, QgsProperty::fromValue( "portrait" ) );
621   item->attemptResize( QgsLayoutSize( 7.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
622   QCOMPARE( item->sizeWithUnits().width(), 3.0 );
623   QCOMPARE( item->sizeWithUnits().height(), 13.0 );
624   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
625   QCOMPARE( item->rect().width(), 30.0 ); //mm
626   QCOMPARE( item->rect().height(), 130.0 ); //mm
627 
628   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty() );
629   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty() );
630   item->dataDefinedProperties().setProperty( QgsLayoutObject::PresetPaperSize, QgsProperty() );
631   item->dataDefinedProperties().setProperty( QgsLayoutObject::PaperOrientation, QgsProperty::fromValue( "landscape" ) );
632   item->attemptResize( QgsLayoutSize( 1.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
633   QCOMPARE( item->sizeWithUnits().width(), 1.5 );
634   QCOMPARE( item->sizeWithUnits().height(), 1.0 );
635   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
636   QCOMPARE( item->rect().width(), 15.0 ); //mm
637   QCOMPARE( item->rect().height(), 10.0 ); //mm
638   item->dataDefinedProperties().setProperty( QgsLayoutObject::PaperOrientation, QgsProperty() );
639 
640   //check change of units should apply to data defined size
641   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) );
642   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) );
643   //first set to same as existing size, but with different units
644   item->attemptResize( QgsLayoutSize( 120.0, 60.0, QgsUnitTypes::LayoutMillimeters ) );
645   //data defined size should utilize new units
646   QCOMPARE( item->sizeWithUnits().width(), 12.0 ); //mm
647   QCOMPARE( item->sizeWithUnits().height(), 6.0 ); //mm
648   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutMillimeters );
649   QCOMPARE( item->rect().width(), 12.0 ); //mm
650   QCOMPARE( item->rect().height(), 6.0 ); //mm
651 
652   //test that data defined size applies to item's reference point
653   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty() );
654   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty() );
655   item->attemptResize( QgsLayoutSize( 10.0, 5.0, QgsUnitTypes::LayoutMillimeters ) );
656   item->attemptMove( QgsLayoutPoint( 20.0, 10.0, QgsUnitTypes::LayoutMillimeters ) );
657   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "5" ) ) );
658   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "6" ) ) );
659   item->setReferencePoint( QgsLayoutItem::LowerRight );
660   item->refreshItemSize();
661   QCOMPARE( item->scenePos().x(), 25.0 ); //mm
662   QCOMPARE( item->scenePos().y(), 9.0 ); //mm
663 
664   //test that data defined size applied after setting item's reference point respects reference
665   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty() );
666   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty() );
667   item->setReferencePoint( QgsLayoutItem::UpperLeft );
668   item->attemptResize( QgsLayoutSize( 10.0, 5.0, QgsUnitTypes::LayoutMillimeters ) );
669   item->attemptMove( QgsLayoutPoint( 20.0, 10.0, QgsUnitTypes::LayoutMillimeters ) );
670   item->setReferencePoint( QgsLayoutItem::LowerRight );
671   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "7" ) ) );
672   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "9" ) ) );
673   item->refreshItemSize();
674   QCOMPARE( item->scenePos().x(), 23.0 ); //mm
675   QCOMPARE( item->scenePos().y(), 6.0 ); //mm
676 
677   delete item;
678 }
679 
combinedDataDefinedPositionAndSize()680 void TestQgsLayoutItem::combinedDataDefinedPositionAndSize()
681 {
682   QgsProject p;
683   QgsLayout l( &p );
684 
685   //test setting data defined size
686   TestItem *item = new TestItem( &l );
687   l.setUnits( QgsUnitTypes::LayoutMillimeters );
688   item->attemptMove( QgsLayoutPoint( 6.0, 1.50, QgsUnitTypes::LayoutCentimeters ) );
689   item->attemptResize( QgsLayoutSize( 2.0, 4.0, QgsUnitTypes::LayoutCentimeters ) );
690 
691   //test item with all of data defined x, y, width, height set
692   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+7" ) ) );
693   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+3" ) ) );
694   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "4+9" ) ) );
695   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) );
696   item->refreshDataDefinedProperty( QgsLayoutObject::AllProperties );
697   QCOMPARE( item->positionWithUnits().x(), 11.0 );
698   QCOMPARE( item->positionWithUnits().y(), 5.0 );
699   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
700   QCOMPARE( item->sizeWithUnits().width(), 13.0 );
701   QCOMPARE( item->sizeWithUnits().height(), 6.0 );
702   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
703   QCOMPARE( item->scenePos().x(), 110.0 ); //mm
704   QCOMPARE( item->scenePos().y(), 50.0 ); //mm
705   QCOMPARE( item->rect().width(), 130.0 ); //mm
706   QCOMPARE( item->rect().height(), 60.0 ); //mm
707 
708   //also try with reference point set
709   item->setReferencePoint( QgsLayoutItem::Middle );
710   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionX, QgsProperty::fromExpression( QStringLiteral( "4+8" ) ) );
711   item->dataDefinedProperties().setProperty( QgsLayoutObject::PositionY, QgsProperty::fromExpression( QStringLiteral( "2+4" ) ) );
712   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemWidth, QgsProperty::fromExpression( QStringLiteral( "3+7" ) ) );
713   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemHeight, QgsProperty::fromExpression( QStringLiteral( "1+3" ) ) );
714   item->refreshDataDefinedProperty( QgsLayoutObject::AllProperties );
715   QCOMPARE( item->positionWithUnits().x(), 12.0 );
716   QCOMPARE( item->positionWithUnits().y(), 6.0 );
717   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
718   QCOMPARE( item->sizeWithUnits().width(), 10.0 );
719   QCOMPARE( item->sizeWithUnits().height(), 4.0 );
720   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
721   QCOMPARE( item->scenePos().x(), 70.0 ); //mm
722   QCOMPARE( item->scenePos().y(), 40.0 ); //mm
723   QCOMPARE( item->rect().width(), 100.0 ); //mm
724   QCOMPARE( item->rect().height(), 40.0 ); //mm
725 
726   delete item;
727 }
728 
729 //TODO rotation
730 
resize()731 void TestQgsLayoutItem::resize()
732 {
733   QgsProject p;
734   QgsLayout l( &p );
735 
736   //resize test item (no restrictions), same units as layout
737   l.setUnits( QgsUnitTypes::LayoutMillimeters );
738   std::unique_ptr< TestItem > item( new TestItem( &l ) );
739   const QSignalSpy spySizeChanged( item.get(), &QgsLayoutItem::sizePositionChanged );
740 
741   item->setRect( 0, 0, 55, 45 );
742   item->attemptMove( QgsLayoutPoint( 27, 29 ) );
743   QCOMPARE( spySizeChanged.count(), 1 );
744   item->attemptResize( QgsLayoutSize( 100.0, 200.0, QgsUnitTypes::LayoutMillimeters ) );
745   QCOMPARE( spySizeChanged.count(), 2 );
746   QCOMPARE( item->rect().width(), 100.0 );
747   QCOMPARE( item->rect().height(), 200.0 );
748   QCOMPARE( item->scenePos().x(), 27.0 ); //item should not move
749   QCOMPARE( item->scenePos().y(), 29.0 );
750 
751   //test conversion of units
752   l.setUnits( QgsUnitTypes::LayoutCentimeters );
753   item->setRect( 0, 0, 100, 200 );
754   item->attemptResize( QgsLayoutSize( 0.30, 0.45, QgsUnitTypes::LayoutMeters ) );
755   QCOMPARE( item->rect().width(), 30.0 );
756   QCOMPARE( item->rect().height(), 45.0 );
757   QCOMPARE( spySizeChanged.count(), 4 );
758 
759   //test pixel -> page conversion
760   l.setUnits( QgsUnitTypes::LayoutInches );
761   l.renderContext().setDpi( 100.0 );
762   item->refresh();
763   QCOMPARE( spySizeChanged.count(), 6 );
764   item->setRect( 0, 0, 1, 2 );
765   item->attemptResize( QgsLayoutSize( 140, 280, QgsUnitTypes::LayoutPixels ) );
766   QCOMPARE( item->rect().width(), 1.4 );
767   QCOMPARE( item->rect().height(), 2.8 );
768   QCOMPARE( spySizeChanged.count(), 7 );
769   //changing the dpi should resize the item
770   l.renderContext().setDpi( 200.0 );
771   item->refresh();
772   QCOMPARE( item->rect().width(), 0.7 );
773   QCOMPARE( item->rect().height(), 1.4 );
774   QCOMPARE( spySizeChanged.count(), 8 );
775 
776   //test page -> pixel conversion
777   l.setUnits( QgsUnitTypes::LayoutPixels );
778   l.renderContext().setDpi( 100.0 );
779   item->refresh();
780   item->setRect( 0, 0, 2, 2 );
781   QCOMPARE( spySizeChanged.count(), 10 );
782   item->attemptResize( QgsLayoutSize( 1, 3, QgsUnitTypes::LayoutInches ) );
783   QCOMPARE( item->rect().width(), 100.0 );
784   QCOMPARE( item->rect().height(), 300.0 );
785   QCOMPARE( spySizeChanged.count(), 11 );
786   //changing dpi results in item resize
787   l.renderContext().setDpi( 200.0 );
788   item->refresh();
789   QCOMPARE( item->rect().width(), 200.0 );
790   QCOMPARE( item->rect().height(), 600.0 );
791   QCOMPARE( spySizeChanged.count(), 13 );
792 
793   l.setUnits( QgsUnitTypes::LayoutMillimeters );
794 
795   // try override item size in item
796   item->forceResize = true;
797   item->attemptResize( QgsLayoutSize( 10.0, 15.0, QgsUnitTypes::LayoutMillimeters ) );
798   QCOMPARE( item->rect().width(), 17.0 );
799   QCOMPARE( item->rect().height(), 27.0 );
800 }
801 
referencePoint()802 void TestQgsLayoutItem::referencePoint()
803 {
804   QgsProject p;
805   QgsLayout l( &p );
806 
807   //test setting/getting reference point
808   std::unique_ptr< TestItem > item( new TestItem( &l ) );
809   item->setReferencePoint( QgsLayoutItem::LowerMiddle );
810   QCOMPARE( item->referencePoint(), QgsLayoutItem::LowerMiddle );
811 
812   //test that setting reference point results in positionWithUnits returning position at new reference
813   //point
814   item->setReferencePoint( QgsLayoutItem::UpperLeft );
815   item->attemptResize( QgsLayoutSize( 2, 4 ) );
816   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
817   QCOMPARE( item->positionWithUnits().x(), 1.0 );
818   QCOMPARE( item->positionWithUnits().y(), 2.0 );
819   item->setReferencePoint( QgsLayoutItem::UpperLeft );
820   QCOMPARE( item->positionWithUnits().x(), 1.0 );
821   QCOMPARE( item->positionWithUnits().y(), 2.0 );
822   item->setReferencePoint( QgsLayoutItem::UpperMiddle );
823   QCOMPARE( item->positionWithUnits().x(), 2.0 );
824   QCOMPARE( item->positionWithUnits().y(), 2.0 );
825   item->setReferencePoint( QgsLayoutItem::UpperRight );
826   QCOMPARE( item->positionWithUnits().x(), 3.0 );
827   QCOMPARE( item->positionWithUnits().y(), 2.0 );
828   item->setReferencePoint( QgsLayoutItem::MiddleLeft );
829   QCOMPARE( item->positionWithUnits().x(), 1.0 );
830   QCOMPARE( item->positionWithUnits().y(), 4.0 );
831   item->setReferencePoint( QgsLayoutItem::Middle );
832   QCOMPARE( item->positionWithUnits().x(), 2.0 );
833   QCOMPARE( item->positionWithUnits().y(), 4.0 );
834   item->setReferencePoint( QgsLayoutItem::MiddleRight );
835   QCOMPARE( item->positionWithUnits().x(), 3.0 );
836   QCOMPARE( item->positionWithUnits().y(), 4.0 );
837   item->setReferencePoint( QgsLayoutItem::LowerLeft );
838   QCOMPARE( item->positionWithUnits().x(), 1.0 );
839   QCOMPARE( item->positionWithUnits().y(), 6.0 );
840   item->setReferencePoint( QgsLayoutItem::LowerMiddle );
841   QCOMPARE( item->positionWithUnits().x(), 2.0 );
842   QCOMPARE( item->positionWithUnits().y(), 6.0 );
843   item->setReferencePoint( QgsLayoutItem::LowerRight );
844   QCOMPARE( item->positionWithUnits().x(), 3.0 );
845   QCOMPARE( item->positionWithUnits().y(), 6.0 );
846 
847   item.reset( new TestItem( &l ) );
848 
849   //test that setting item position is done relative to reference point
850   l.setUnits( QgsUnitTypes::LayoutMillimeters );
851   item->attemptResize( QgsLayoutSize( 2, 4 ) );
852   item->setReferencePoint( QgsLayoutItem::UpperLeft );
853   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
854   QCOMPARE( item->scenePos().x(), 1.0 );
855   QCOMPARE( item->scenePos().y(), 2.0 );
856   item->setReferencePoint( QgsLayoutItem::UpperMiddle );
857   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
858   QCOMPARE( item->scenePos().x(), 0.0 );
859   QCOMPARE( item->scenePos().y(), 2.0 );
860   item->setReferencePoint( QgsLayoutItem::UpperRight );
861   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
862   QCOMPARE( item->scenePos().x(), -1.0 );
863   QCOMPARE( item->scenePos().y(), 2.0 );
864   item->setReferencePoint( QgsLayoutItem::MiddleLeft );
865   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
866   QCOMPARE( item->scenePos().x(), 1.0 );
867   QCOMPARE( item->scenePos().y(), 0.0 );
868   item->setReferencePoint( QgsLayoutItem::Middle );
869   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
870   QCOMPARE( item->scenePos().x(), 0.0 );
871   QCOMPARE( item->scenePos().y(), 0.0 );
872   item->setReferencePoint( QgsLayoutItem::MiddleRight );
873   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
874   QCOMPARE( item->scenePos().x(), -1.0 );
875   QCOMPARE( item->scenePos().y(), 0.0 );
876   item->setReferencePoint( QgsLayoutItem::LowerLeft );
877   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
878   QCOMPARE( item->scenePos().x(), 1.0 );
879   QCOMPARE( item->scenePos().y(), -2.0 );
880   item->setReferencePoint( QgsLayoutItem::LowerMiddle );
881   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
882   QCOMPARE( item->scenePos().x(), 0.0 );
883   QCOMPARE( item->scenePos().y(), -2.0 );
884   item->setReferencePoint( QgsLayoutItem::LowerRight );
885   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
886   QCOMPARE( item->scenePos().x(), -1.0 );
887   QCOMPARE( item->scenePos().y(), -2.0 );
888 
889   item.reset( new TestItem( &l ) );
890 
891   //test that resizing is done relative to reference point
892   item->attemptResize( QgsLayoutSize( 2, 4 ) );
893   item->setReferencePoint( QgsLayoutItem::UpperLeft );
894   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
895   item->attemptResize( QgsLayoutSize( 4, 6 ) );
896   QCOMPARE( item->scenePos().x(), 1.0 );
897   QCOMPARE( item->scenePos().y(), 2.0 );
898   item->setReferencePoint( QgsLayoutItem::UpperMiddle );
899   item->attemptResize( QgsLayoutSize( 6, 4 ) );
900   QCOMPARE( item->scenePos().x(), 0.0 );
901   QCOMPARE( item->scenePos().y(), 2.0 );
902   item->setReferencePoint( QgsLayoutItem::UpperRight );
903   item->attemptResize( QgsLayoutSize( 4, 6 ) );
904   QCOMPARE( item->scenePos().x(), 2.0 );
905   QCOMPARE( item->scenePos().y(), 2.0 );
906   item->setReferencePoint( QgsLayoutItem::MiddleLeft );
907   item->attemptResize( QgsLayoutSize( 6, 4 ) );
908   QCOMPARE( item->scenePos().x(), 2.0 );
909   QCOMPARE( item->scenePos().y(), 3.0 );
910   item->setReferencePoint( QgsLayoutItem::Middle );
911   item->attemptResize( QgsLayoutSize( 4, 6 ) );
912   QCOMPARE( item->scenePos().x(), 3.0 );
913   QCOMPARE( item->scenePos().y(), 2.0 );
914   item->setReferencePoint( QgsLayoutItem::MiddleRight );
915   item->attemptResize( QgsLayoutSize( 6, 4 ) );
916   QCOMPARE( item->scenePos().x(), 1.0 );
917   QCOMPARE( item->scenePos().y(), 3.0 );
918   item->setReferencePoint( QgsLayoutItem::LowerLeft );
919   item->attemptResize( QgsLayoutSize( 4, 6 ) );
920   QCOMPARE( item->scenePos().x(), 1.0 );
921   QCOMPARE( item->scenePos().y(), 1.0 );
922   item->setReferencePoint( QgsLayoutItem::LowerMiddle );
923   item->attemptResize( QgsLayoutSize( 6, 4 ) );
924   QCOMPARE( item->scenePos().x(), 0.0 );
925   QCOMPARE( item->scenePos().y(), 3.0 );
926   item->setReferencePoint( QgsLayoutItem::LowerRight );
927   item->attemptResize( QgsLayoutSize( 4, 6 ) );
928   QCOMPARE( item->scenePos().x(), 2.0 );
929   QCOMPARE( item->scenePos().y(), 1.0 );
930 
931   item.reset( new TestItem( &l ) );
932 
933   //item with frame
934   item->attemptResize( QgsLayoutSize( 2, 4 ) );
935   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
936   item->setFrameEnabled( true );
937   item->setFrameStrokeWidth( QgsLayoutMeasurement( 1 ) );
938   item->attemptResize( QgsLayoutSize( 4, 6 ) );
939   QCOMPARE( item->sizeWithUnits().width(), 4.0 );
940   QCOMPARE( item->sizeWithUnits().height(), 6.0 );
941   item->attemptResize( QgsLayoutSize( 4, 6 ), true );
942   QCOMPARE( item->sizeWithUnits().width(), 3.0 );
943   QCOMPARE( item->sizeWithUnits().height(), 5.0 );
944 }
945 
itemPositionReferencePoint()946 void TestQgsLayoutItem::itemPositionReferencePoint()
947 {
948   QgsProject p;
949   QgsLayout l( &p );
950 
951   TestItem *item = new TestItem( &l );
952   QPointF result = item->itemPositionAtReferencePoint( QgsLayoutItem::UpperLeft, QSizeF( 2, 4 ) );
953   QCOMPARE( result.x(), 0.0 );
954   QCOMPARE( result.y(), 0.0 );
955   result = item->itemPositionAtReferencePoint( QgsLayoutItem::UpperMiddle, QSizeF( 2, 4 ) );
956   QCOMPARE( result.x(), 1.0 );
957   QCOMPARE( result.y(), 0.0 );
958   result = item->itemPositionAtReferencePoint( QgsLayoutItem::UpperRight, QSizeF( 2, 4 ) );
959   QCOMPARE( result.x(), 2.0 );
960   QCOMPARE( result.y(), 0.0 );
961   result = item->itemPositionAtReferencePoint( QgsLayoutItem::MiddleLeft, QSizeF( 2, 4 ) );
962   QCOMPARE( result.x(), 0.0 );
963   QCOMPARE( result.y(), 2.0 );
964   result = item->itemPositionAtReferencePoint( QgsLayoutItem::Middle, QSizeF( 2, 4 ) );
965   QCOMPARE( result.x(), 1.0 );
966   QCOMPARE( result.y(), 2.0 );
967   result = item->itemPositionAtReferencePoint( QgsLayoutItem::MiddleRight, QSizeF( 2, 4 ) );
968   QCOMPARE( result.x(), 2.0 );
969   QCOMPARE( result.y(), 2.0 );
970   result = item->itemPositionAtReferencePoint( QgsLayoutItem::LowerLeft, QSizeF( 2, 4 ) );
971   QCOMPARE( result.x(), 0.0 );
972   QCOMPARE( result.y(), 4.0 );
973   result = item->itemPositionAtReferencePoint( QgsLayoutItem::LowerMiddle, QSizeF( 2, 4 ) );
974   QCOMPARE( result.x(), 1.0 );
975   QCOMPARE( result.y(), 4.0 );
976   result = item->itemPositionAtReferencePoint( QgsLayoutItem::LowerRight, QSizeF( 2, 4 ) );
977   QCOMPARE( result.x(), 2.0 );
978   QCOMPARE( result.y(), 4.0 );
979 
980   delete item;
981 }
982 
adjustPointForReference()983 void TestQgsLayoutItem::adjustPointForReference()
984 {
985   QgsProject p;
986   QgsLayout l( &p );
987 
988   std::unique_ptr< TestItem > item( new TestItem( &l ) );
989   QPointF result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::UpperLeft );
990   QCOMPARE( result.x(), 5.0 );
991   QCOMPARE( result.y(), 7.0 );
992   result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::UpperMiddle );
993   QCOMPARE( result.x(), 4.0 );
994   QCOMPARE( result.y(), 7.0 );
995   result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::UpperRight );
996   QCOMPARE( result.x(), 3.0 );
997   QCOMPARE( result.y(), 7.0 );
998   result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::MiddleLeft );
999   QCOMPARE( result.x(), 5.0 );
1000   QCOMPARE( result.y(), 5.0 );
1001   result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::Middle );
1002   QCOMPARE( result.x(), 4.0 );
1003   QCOMPARE( result.y(), 5.0 );
1004   result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::MiddleRight );
1005   QCOMPARE( result.x(), 3.0 );
1006   QCOMPARE( result.y(), 5.0 );
1007   result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::LowerLeft );
1008   QCOMPARE( result.x(), 5.0 );
1009   QCOMPARE( result.y(), 3.0 );
1010   result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::LowerMiddle );
1011   QCOMPARE( result.x(), 4.0 );
1012   QCOMPARE( result.y(), 3.0 );
1013   result = item->adjustPointForReferencePosition( QPointF( 5, 7 ), QSizeF( 2, 4 ), QgsLayoutItem::LowerRight );
1014   QCOMPARE( result.x(), 3.0 );
1015   QCOMPARE( result.y(), 3.0 );
1016 }
1017 
positionAtReferencePoint()1018 void TestQgsLayoutItem::positionAtReferencePoint()
1019 {
1020   QgsProject p;
1021   QgsLayout l( &p );
1022 
1023   TestItem *item = new TestItem( &l );
1024   item->setPos( 8.0, 6.0 );
1025   item->setRect( 0.0, 0.0, 4.0, 6.0 );
1026   QPointF result = item->positionAtReferencePoint( QgsLayoutItem::UpperLeft );
1027   QCOMPARE( result.x(), 8.0 );
1028   QCOMPARE( result.y(), 6.0 );
1029   result = item->positionAtReferencePoint( QgsLayoutItem::UpperMiddle );
1030   QCOMPARE( result.x(), 10.0 );
1031   QCOMPARE( result.y(), 6.0 );
1032   result = item->positionAtReferencePoint( QgsLayoutItem::UpperRight );
1033   QCOMPARE( result.x(), 12.0 );
1034   QCOMPARE( result.y(), 6.0 );
1035   result = item->positionAtReferencePoint( QgsLayoutItem::MiddleLeft );
1036   QCOMPARE( result.x(), 8.0 );
1037   QCOMPARE( result.y(), 9.0 );
1038   result = item->positionAtReferencePoint( QgsLayoutItem::Middle );
1039   QCOMPARE( result.x(), 10.0 );
1040   QCOMPARE( result.y(), 9.0 );
1041   result = item->positionAtReferencePoint( QgsLayoutItem::MiddleRight );
1042   QCOMPARE( result.x(), 12.0 );
1043   QCOMPARE( result.y(), 9.0 );
1044   result = item->positionAtReferencePoint( QgsLayoutItem::LowerLeft );
1045   QCOMPARE( result.x(), 8.0 );
1046   QCOMPARE( result.y(), 12.0 );
1047   result = item->positionAtReferencePoint( QgsLayoutItem::LowerMiddle );
1048   QCOMPARE( result.x(), 10.0 );
1049   QCOMPARE( result.y(), 12.0 );
1050   result = item->positionAtReferencePoint( QgsLayoutItem::LowerRight );
1051   QCOMPARE( result.x(), 12.0 );
1052   QCOMPARE( result.y(), 12.0 );
1053 
1054   //test with a rotated item
1055   item->setItemRotation( 90 );
1056   result = item->positionAtReferencePoint( QgsLayoutItem::UpperLeft );
1057   QCOMPARE( result.x(), 13.0 );
1058   QCOMPARE( result.y(), 7.0 );
1059   result = item->positionAtReferencePoint( QgsLayoutItem::UpperMiddle );
1060   QCOMPARE( result.x(), 13.0 );
1061   QCOMPARE( result.y(), 9.0 );
1062   result = item->positionAtReferencePoint( QgsLayoutItem::UpperRight );
1063   QCOMPARE( result.x(), 13.0 );
1064   QCOMPARE( result.y(), 11.0 );
1065   result = item->positionAtReferencePoint( QgsLayoutItem::MiddleLeft );
1066   QCOMPARE( result.x(), 10.0 );
1067   QCOMPARE( result.y(), 7.0 );
1068   result = item->positionAtReferencePoint( QgsLayoutItem::Middle );
1069   QCOMPARE( result.x(), 10.0 );
1070   QCOMPARE( result.y(), 9.0 );
1071   result = item->positionAtReferencePoint( QgsLayoutItem::MiddleRight );
1072   QCOMPARE( result.x(), 10.0 );
1073   QCOMPARE( result.y(), 11.0 );
1074   result = item->positionAtReferencePoint( QgsLayoutItem::LowerLeft );
1075   QCOMPARE( result.x(), 7.0 );
1076   QCOMPARE( result.y(), 7.0 );
1077   result = item->positionAtReferencePoint( QgsLayoutItem::LowerMiddle );
1078   QCOMPARE( result.x(), 7.0 );
1079   QCOMPARE( result.y(), 9.0 );
1080   result = item->positionAtReferencePoint( QgsLayoutItem::LowerRight );
1081   QCOMPARE( result.x(), 7.0 );
1082   QCOMPARE( result.y(), 11.0 );
1083 
1084   delete item;
1085 }
1086 
fixedSize()1087 void TestQgsLayoutItem::fixedSize()
1088 {
1089   QgsProject p;
1090   QgsLayout l( &p );
1091 
1092   l.setUnits( QgsUnitTypes::LayoutMillimeters );
1093   std::unique_ptr< FixedSizedItem > item( new FixedSizedItem( &l ) );
1094   QCOMPARE( item->fixedSize().width(), 2.0 );
1095   QCOMPARE( item->fixedSize().height(), 4.0 );
1096   QCOMPARE( item->fixedSize().units(), QgsUnitTypes::LayoutInches );
1097 
1098   item->setRect( 0, 0, 5.0, 6.0 ); //temporarily set rect to random size
1099   item->attemptResize( QgsLayoutSize( 7.0, 8.0, QgsUnitTypes::LayoutPoints ) );
1100   //check size matches fixed item size converted to mm
1101   QGSCOMPARENEAR( item->rect().width(), 2.0 * 25.4, 4 * std::numeric_limits<double>::epsilon() );
1102   QGSCOMPARENEAR( item->rect().height(), 4.0 * 25.4, 4 * std::numeric_limits<double>::epsilon() );
1103 
1104   item->attemptResize( QgsLayoutSize( 7.0, 8.0, QgsUnitTypes::LayoutInches ) );
1105   //check size matches fixed item size converted to mm
1106   QGSCOMPARENEAR( item->rect().width(), 2.0 * 25.4, 4 * std::numeric_limits<double>::epsilon() );
1107   QGSCOMPARENEAR( item->rect().height(), 4.0 * 25.4, 4 * std::numeric_limits<double>::epsilon() );
1108 
1109   //check that setting a fixed size applies this size immediately
1110   item->updateFixedSize( QgsLayoutSize( 150, 250, QgsUnitTypes::LayoutMillimeters ) );
1111   QGSCOMPARENEAR( item->rect().width(), 150.0, 4 * std::numeric_limits<double>::epsilon() );
1112   QGSCOMPARENEAR( item->rect().height(), 250.0, 4 * std::numeric_limits<double>::epsilon() );
1113 }
1114 
minSize()1115 void TestQgsLayoutItem::minSize()
1116 {
1117   QgsProject p;
1118   QgsLayout l( &p );
1119 
1120   l.setUnits( QgsUnitTypes::LayoutMillimeters );
1121   std::unique_ptr< MinSizedItem > item( new MinSizedItem( &l ) );
1122   QCOMPARE( item->minimumSize().width(), 5.0 );
1123   QCOMPARE( item->minimumSize().height(), 10.0 );
1124   QCOMPARE( item->minimumSize().units(), QgsUnitTypes::LayoutCentimeters );
1125 
1126   item->setRect( 0, 0, 9.0, 6.0 ); //temporarily set rect to random size
1127   //try to resize to less than minimum size
1128   item->attemptResize( QgsLayoutSize( 1.0, 0.5, QgsUnitTypes::LayoutPoints ) );
1129   //check size matches min item size converted to mm
1130   QGSCOMPARENEAR( item->rect().width(), 50.0, 4 * std::numeric_limits<double>::epsilon() );
1131   QGSCOMPARENEAR( item->rect().height(), 100.0, 4 * std::numeric_limits<double>::epsilon() );
1132 
1133   //check that resize to larger than min size works
1134   item->attemptResize( QgsLayoutSize( 0.1, 0.2, QgsUnitTypes::LayoutMeters ) );
1135   QGSCOMPARENEAR( item->rect().width(), 100.0, 4 * std::numeric_limits<double>::epsilon() );
1136   QGSCOMPARENEAR( item->rect().height(), 200.0, 4 * std::numeric_limits<double>::epsilon() );
1137 
1138   //check that setting a minimum size applies this size immediately
1139   item->updateMinSize( QgsLayoutSize( 150, 250, QgsUnitTypes::LayoutMillimeters ) );
1140   QGSCOMPARENEAR( item->rect().width(), 150.0, 4 * std::numeric_limits<double>::epsilon() );
1141   QGSCOMPARENEAR( item->rect().height(), 250.0, 4 * std::numeric_limits<double>::epsilon() );
1142 
1143   //also need check that fixed size trumps min size
1144   std::unique_ptr< FixedMinSizedItem > fixedMinItem( new FixedMinSizedItem( &l ) );
1145   QCOMPARE( fixedMinItem->minimumSize().width(), 5.0 );
1146   QCOMPARE( fixedMinItem->minimumSize().height(), 9.0 );
1147   QCOMPARE( fixedMinItem->minimumSize().units(), QgsUnitTypes::LayoutCentimeters );
1148   QCOMPARE( fixedMinItem->fixedSize().width(), 2.0 );
1149   QCOMPARE( fixedMinItem->fixedSize().height(), 4.0 );
1150   QCOMPARE( fixedMinItem->fixedSize().units(), QgsUnitTypes::LayoutCentimeters );
1151   //try to resize to less than minimum size
1152   fixedMinItem->attemptResize( QgsLayoutSize( 1.0, 0.5, QgsUnitTypes::LayoutPoints ) );
1153   //check size matches fixed item size, not minimum size (converted to mm)
1154   QGSCOMPARENEAR( fixedMinItem->rect().width(), 20.0, 4 * std::numeric_limits<double>::epsilon() );
1155   QGSCOMPARENEAR( fixedMinItem->rect().height(), 40.0, 4 * std::numeric_limits<double>::epsilon() );
1156 }
1157 
move()1158 void TestQgsLayoutItem::move()
1159 {
1160   QgsProject p;
1161   QgsLayout l( &p );
1162 
1163   //move test item, same units as layout
1164   l.setUnits( QgsUnitTypes::LayoutMillimeters );
1165   std::unique_ptr< TestItem > item( new TestItem( &l ) );
1166   item->setRect( 0, 0, 55, 45 );
1167   item->setPos( 27, 29 );
1168   item->attemptMove( QgsLayoutPoint( 60.0, 15.0, QgsUnitTypes::LayoutMillimeters ) );
1169   QCOMPARE( item->rect().width(), 55.0 ); //size should not change
1170   QCOMPARE( item->rect().height(), 45.0 );
1171   QCOMPARE( item->scenePos().x(), 60.0 );
1172   QCOMPARE( item->scenePos().y(), 15.0 );
1173 
1174   //test conversion of units
1175   l.setUnits( QgsUnitTypes::LayoutCentimeters );
1176   item->setPos( 100, 200 );
1177   item->attemptMove( QgsLayoutPoint( 0.30, 0.45, QgsUnitTypes::LayoutMeters ) );
1178   QCOMPARE( item->scenePos().x(), 30.0 );
1179   QCOMPARE( item->scenePos().y(), 45.0 );
1180 
1181   //test pixel -> page conversion
1182   l.setUnits( QgsUnitTypes::LayoutInches );
1183   l.renderContext().setDpi( 100.0 );
1184   item->refresh();
1185   item->setPos( 1, 2 );
1186   item->attemptMove( QgsLayoutPoint( 140, 280, QgsUnitTypes::LayoutPixels ) );
1187   QCOMPARE( item->scenePos().x(), 1.4 );
1188   QCOMPARE( item->scenePos().y(), 2.8 );
1189   //changing the dpi should move the item
1190   l.renderContext().setDpi( 200.0 );
1191   item->refresh();
1192   QCOMPARE( item->scenePos().x(), 0.7 );
1193   QCOMPARE( item->scenePos().y(), 1.4 );
1194 
1195   //test page -> pixel conversion
1196   l.setUnits( QgsUnitTypes::LayoutPixels );
1197   l.renderContext().setDpi( 100.0 );
1198   item->refresh();
1199   item->setPos( 2, 2 );
1200   item->attemptMove( QgsLayoutPoint( 1, 3, QgsUnitTypes::LayoutInches ) );
1201   QCOMPARE( item->scenePos().x(), 100.0 );
1202   QCOMPARE( item->scenePos().y(), 300.0 );
1203   //changing dpi results in item move
1204   l.renderContext().setDpi( 200.0 );
1205   item->refresh();
1206   QCOMPARE( item->scenePos().x(), 200.0 );
1207   QCOMPARE( item->scenePos().y(), 600.0 );
1208 
1209   l.setUnits( QgsUnitTypes::LayoutMillimeters );
1210 
1211   //reference points
1212   item->attemptMove( QgsLayoutPoint( 5, 9 ) );
1213   item->attemptResize( QgsLayoutSize( 4, 6 ) );
1214   item->setReferencePoint( QgsLayoutItem::LowerRight );
1215   QCOMPARE( item->positionWithUnits().x(), 9.0 );
1216   QCOMPARE( item->positionWithUnits().y(), 15.0 );
1217 
1218   item->attemptMove( QgsLayoutPoint( 11, 13 ) );
1219   QCOMPARE( item->positionWithUnits().x(), 11.0 );
1220   QCOMPARE( item->positionWithUnits().y(), 13.0 );
1221   QCOMPARE( item->scenePos().x(), 7.0 );
1222   QCOMPARE( item->scenePos().y(), 7.0 );
1223 
1224   item->attemptMove( QgsLayoutPoint( 10, 12 ), false );
1225   QCOMPARE( item->positionWithUnits().x(), 14.0 );
1226   QCOMPARE( item->positionWithUnits().y(), 18.0 );
1227   QCOMPARE( item->scenePos().x(), 10.0 );
1228   QCOMPARE( item->scenePos().y(), 12.0 );
1229 
1230   //moveBy
1231   item.reset( new TestItem( &l ) );
1232   item->attemptMove( QgsLayoutPoint( 5, 9, QgsUnitTypes::LayoutCentimeters ) );
1233   item->attemptResize( QgsLayoutSize( 4, 6 ) );
1234   QCOMPARE( item->positionWithUnits().x(), 5.0 );
1235   QCOMPARE( item->positionWithUnits().y(), 9.0 );
1236   QCOMPARE( item->scenePos().x(), 50.0 );
1237   QCOMPARE( item->scenePos().y(), 90.0 );
1238   item->attemptMoveBy( 5, -6 );
1239   QCOMPARE( item->positionWithUnits().x(), 5.5 );
1240   QCOMPARE( item->positionWithUnits().y(), 8.4 );
1241   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
1242   QCOMPARE( item->scenePos().x(), 55.0 );
1243   QCOMPARE( item->scenePos().y(), 84.0 );
1244 
1245   //item with frame
1246   item.reset( new TestItem( &l ) );
1247   item->attemptResize( QgsLayoutSize( 2, 4 ) );
1248   item->attemptMove( QgsLayoutPoint( 1, 2 ) );
1249   item->setFrameEnabled( true );
1250   item->setFrameStrokeWidth( QgsLayoutMeasurement( 1 ) );
1251   item->attemptMove( QgsLayoutPoint( 1, 3 ) );
1252   QCOMPARE( item->positionWithUnits().x(), 1.0 );
1253   QCOMPARE( item->positionWithUnits().y(), 3.0 );
1254   item->attemptMove( QgsLayoutPoint( 4, 6 ), false, true );
1255   QCOMPARE( item->positionWithUnits().x(), 4.5 );
1256   QCOMPARE( item->positionWithUnits().y(), 6.5 );
1257 }
1258 
setSceneRect()1259 void TestQgsLayoutItem::setSceneRect()
1260 {
1261   QgsProject p;
1262   QgsLayout l( &p );
1263 
1264   //resize test item (no restrictions), same units as layout
1265   l.setUnits( QgsUnitTypes::LayoutMillimeters );
1266   std::unique_ptr< TestItem > item( new TestItem( &l ) );
1267   const QSignalSpy spySizeChanged( item.get(), &QgsLayoutItem::sizePositionChanged );
1268 
1269   item->attemptSetSceneRect( QRectF( 27.0, 29.0, 100, 200 ) );
1270   QCOMPARE( spySizeChanged.count(), 1 );
1271   QCOMPARE( item->rect().width(), 100.0 );
1272   QCOMPARE( item->rect().height(), 200.0 );
1273   QCOMPARE( item->scenePos().x(), 27.0 );
1274   QCOMPARE( item->scenePos().y(), 29.0 );
1275   QCOMPARE( item->positionWithUnits().x(), 27.0 );
1276   QCOMPARE( item->positionWithUnits().y(), 29.0 );
1277   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutMillimeters );
1278   QCOMPARE( item->sizeWithUnits().width(), 100.0 );
1279   QCOMPARE( item->sizeWithUnits().height(), 200.0 );
1280   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutMillimeters );
1281 
1282   //test conversion of units
1283   item->attemptMove( QgsLayoutPoint( 1, 2, QgsUnitTypes::LayoutCentimeters ) );
1284   item->attemptResize( QgsLayoutSize( 3, 4, QgsUnitTypes::LayoutCentimeters ) );
1285   QCOMPARE( spySizeChanged.count(), 3 );
1286   item->attemptSetSceneRect( QRectF( 27.0, 29.0, 100, 200 ) );
1287   QCOMPARE( item->rect().width(), 100.0 );
1288   QCOMPARE( item->rect().height(), 200.0 );
1289   QCOMPARE( item->scenePos().x(), 27.0 );
1290   QCOMPARE( item->scenePos().y(), 29.0 );
1291   QCOMPARE( spySizeChanged.count(), 4 );
1292 
1293   QCOMPARE( item->positionWithUnits().x(), 2.70 );
1294   QCOMPARE( item->positionWithUnits().y(), 2.90 );
1295   QCOMPARE( item->positionWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
1296   QCOMPARE( item->sizeWithUnits().width(), 10.0 );
1297   QCOMPARE( item->sizeWithUnits().height(), 20.0 );
1298   QCOMPARE( item->sizeWithUnits().units(), QgsUnitTypes::LayoutCentimeters );
1299 }
1300 
page()1301 void TestQgsLayoutItem::page()
1302 {
1303   QgsProject proj;
1304   QgsLayout l( &proj );
1305 
1306   TestItem *item = new TestItem( nullptr );
1307   item->attemptMove( QgsLayoutPoint( 5, 5 ) );
1308   // no layout
1309   QCOMPARE( item->page(), -1 );
1310   QCOMPARE( item->pagePos(), QPointF( 5, 5 ) );
1311   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 5 ) );
1312 
1313   delete item;
1314   item = new TestItem( &l );
1315   item->attemptMove( QgsLayoutPoint( 5, 5 ) );
1316   l.addLayoutItem( item );
1317   QCOMPARE( item->page(), -1 );
1318   QCOMPARE( item->pagePos(), QPointF( 5, 5 ) );
1319   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 5 ) );
1320 
1321   // add pages
1322   std::unique_ptr< QgsLayoutItemPage > page( new QgsLayoutItemPage( &l ) );
1323   page->setPageSize( QgsLayoutSize( 500, 100, QgsUnitTypes::LayoutMillimeters ) );
1324   l.pageCollection()->addPage( page.release() );
1325   QCOMPARE( item->page(), 0 );
1326   QCOMPARE( item->pagePos(), QPointF( 5, 5 ) );
1327   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 5 ) );
1328   item->attemptMove( QgsLayoutPoint( 5, 5 ) );
1329   QCOMPARE( item->page(), 0 );
1330   QCOMPARE( item->pagePos(), QPointF( 5, 5 ) );
1331   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 5 ) );
1332   item->attemptMove( QgsLayoutPoint( 5, 120 ) );
1333   QCOMPARE( item->page(), 0 );
1334   QCOMPARE( item->pagePos(), QPointF( 5, 120 ) );
1335   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 120 ) );
1336 
1337   // second page
1338   page.reset( new QgsLayoutItemPage( &l ) );
1339   page->setPageSize( QgsLayoutSize( 500, 200, QgsUnitTypes::LayoutMillimeters ) );
1340   l.pageCollection()->addPage( page.release() );
1341   QCOMPARE( item->page(), 1 );
1342   item->attemptMove( QgsLayoutPoint( 5, 190 ) );
1343   QCOMPARE( item->pagePos(), QPointF( 5, 80 ) );
1344   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 80 ) );
1345   QCOMPARE( item->page(), 1 );
1346   item->attemptMove( QgsLayoutPoint( 5, 350 ) );
1347   QCOMPARE( item->page(), 1 );
1348   QCOMPARE( item->pagePos(), QPointF( 5, 240 ) );
1349   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 240 ) );
1350 
1351   page.reset( new QgsLayoutItemPage( &l ) );
1352   page->setPageSize( QgsLayoutSize( 500, 200, QgsUnitTypes::LayoutMillimeters ) );
1353   l.pageCollection()->addPage( page.release() );
1354   QCOMPARE( item->page(), 2 );
1355   QCOMPARE( item->pagePos(), QPointF( 5, 30 ) );
1356   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 30 ) );
1357 
1358   // x position should not matter
1359   item->attemptMove( QgsLayoutPoint( -50, 350 ) );
1360   QCOMPARE( item->page(), 2 );
1361   QCOMPARE( item->pagePos(), QPointF( -50, 30 ) );
1362   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( -50, 30 ) );
1363   item->attemptMove( QgsLayoutPoint( 55555, 350 ) );
1364   QCOMPARE( item->page(), 2 );
1365   QCOMPARE( item->pagePos(), QPointF( 55555, 30 ) );
1366   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 55555, 30 ) );
1367 
1368   // with units
1369   item->attemptMove( QgsLayoutPoint( 5, 35, QgsUnitTypes::LayoutCentimeters ) );
1370   QCOMPARE( item->page(), 2 );
1371   QCOMPARE( item->pagePos(), QPointF( 50, 30 ) );
1372   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 3, QgsUnitTypes::LayoutCentimeters ) );
1373 
1374   // move with page
1375   item->attemptMove( QgsLayoutPoint( 5, 6 ), true, false, 0 );
1376   QCOMPARE( item->page(), 0 );
1377   QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 5, 6, QgsUnitTypes::LayoutMillimeters ) );
1378   item->attemptMove( QgsLayoutPoint( 5, 6 ), true, false, -1 );
1379   QCOMPARE( item->page(), 0 );
1380   QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 5, 6, QgsUnitTypes::LayoutMillimeters ) );
1381   item->attemptMove( QgsLayoutPoint( 5, 6 ), true, false, 10000 );
1382   QCOMPARE( item->page(), 0 );
1383   QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 5, 6, QgsUnitTypes::LayoutMillimeters ) );
1384   item->attemptMove( QgsLayoutPoint( 5, 6 ), true, false, 1 );
1385   QCOMPARE( item->page(), 1 );
1386   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 6, QgsUnitTypes::LayoutMillimeters ) );
1387   QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 5, 116, QgsUnitTypes::LayoutMillimeters ) );
1388   item->attemptMove( QgsLayoutPoint( 5, 6 ), true, false, 2 );
1389   QCOMPARE( item->page(), 2 );
1390   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 6, QgsUnitTypes::LayoutMillimeters ) );
1391   QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 5, 326, QgsUnitTypes::LayoutMillimeters ) );
1392   item->attemptMove( QgsLayoutPoint( 5, 6, QgsUnitTypes::LayoutCentimeters ), true, false, 2 );
1393   QCOMPARE( item->page(), 2 );
1394   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 6, QgsUnitTypes::LayoutCentimeters ) );
1395   QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 5, 38, QgsUnitTypes::LayoutCentimeters ) );
1396 
1397   // non-top-left reference
1398   item->setReferencePoint( QgsLayoutItem::Middle );
1399   item->attemptMove( QgsLayoutPoint( 5, 6 ), true, false, 0 );
1400   QCOMPARE( item->pagePos(), QPointF( 5, 6 ) );
1401   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 6 ) );
1402   item->attemptMove( QgsLayoutPoint( 5, 6 ), true, false, 1 );
1403   QCOMPARE( item->page(), 1 );
1404   QCOMPARE( item->pagePos(), QPointF( 5, 6 ) );
1405   QCOMPARE( item->pagePositionWithUnits(), QgsLayoutPoint( 5, 6, QgsUnitTypes::LayoutMillimeters ) );
1406 }
1407 
itemVariablesFunction()1408 void TestQgsLayoutItem::itemVariablesFunction()
1409 {
1410   const QgsRectangle extent( 2000, 2800, 2500, 2900 );
1411   QgsLayout l( QgsProject::instance() );
1412 
1413   QgsExpression e( QStringLiteral( "map_get( item_variables( 'Map_id' ), 'map_scale' )" ) );
1414   // no map
1415   QgsExpressionContext c = l.createExpressionContext();
1416   QVariant r = e.evaluate( &c );
1417   QVERIFY( !r.isValid() );
1418 
1419   QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
1420   map->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
1421   map->attemptSetSceneRect( QRectF( 30, 60, 200, 100 ) );
1422   map->setExtent( extent );
1423   l.addLayoutItem( map );
1424   map->setId( QStringLiteral( "Map_id" ) );
1425 
1426   c = l.createExpressionContext();
1427   e.prepare( &c );
1428   r = e.evaluate( &c );
1429   QGSCOMPARENEAR( r.toDouble(), 184764103, 100 );
1430 
1431   QgsExpression e2( QStringLiteral( "map_get( item_variables( 'Map_id' ), 'map_crs' )" ) );
1432   r = e2.evaluate( &c );
1433   QCOMPARE( r.toString(), QString( "EPSG:4326" ) );
1434 
1435   QgsExpression e3( QStringLiteral( "map_get( item_variables( 'Map_id' ), 'map_crs_definition' )" ) );
1436   r = e3.evaluate( &c );
1437   QCOMPARE( r.toString(), QString( "+proj=longlat +datum=WGS84 +no_defs" ) );
1438 
1439   QgsExpression e4( QStringLiteral( "map_get( item_variables( 'Map_id' ), 'map_units' )" ) );
1440   r = e4.evaluate( &c );
1441   QCOMPARE( r.toString(), QString( "degrees" ) );
1442 
1443   std::unique_ptr< QgsVectorLayer > layer = std::make_unique< QgsVectorLayer >( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "A" ), QStringLiteral( "memory" ) );
1444   std::unique_ptr< QgsVectorLayer > layer2 = std::make_unique< QgsVectorLayer >( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "B" ), QStringLiteral( "memory" ) );
1445   map->setLayers( QList<QgsMapLayer *>() << layer.get() << layer2.get() );
1446   QgsExpression e5( QStringLiteral( "map_get( item_variables( 'Map_id' ), 'map_layer_ids' )" ) );
1447   r = e5.evaluate( &c );
1448   QCOMPARE( r.toStringList().join( ',' ), QStringLiteral( "%1,%2" ).arg( layer->id(), layer2->id() ) );
1449   e5 = QgsExpression( QStringLiteral( "array_foreach(map_get( item_variables( 'Map_id' ), 'map_layers' ), layer_property(@element, 'name'))" ) );
1450   r = e5.evaluate( &c );
1451   QCOMPARE( r.toStringList().join( ',' ), QStringLiteral( "A,B" ) );
1452 }
1453 
variables()1454 void TestQgsLayoutItem::variables()
1455 {
1456   QgsLayout l( QgsProject::instance() );
1457 
1458   QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
1459   std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::layoutItemScope( map ) );
1460   const int before = scope->variableCount();
1461 
1462   QgsExpressionContextUtils::setLayoutItemVariable( map, QStringLiteral( "var" ), 5 );
1463   scope.reset( QgsExpressionContextUtils::layoutItemScope( map ) );
1464   QCOMPARE( scope->variableCount(), before + 1 );
1465   QCOMPARE( scope->variable( QStringLiteral( "var" ) ).toInt(), 5 );
1466 
1467   QVariantMap vars;
1468   vars.insert( QStringLiteral( "var2" ), 7 );
1469   QgsExpressionContextUtils::setLayoutItemVariables( map, vars );
1470   scope.reset( QgsExpressionContextUtils::layoutItemScope( map ) );
1471   QCOMPARE( scope->variableCount(), before + 1 );
1472   QVERIFY( !scope->hasVariable( QStringLiteral( "var" ) ) );
1473   QCOMPARE( scope->variable( QStringLiteral( "var2" ) ).toInt(), 7 );
1474 }
1475 
mapCreditsFunction()1476 void TestQgsLayoutItem::mapCreditsFunction()
1477 {
1478   const QgsRectangle extent( 2000, 2800, 2500, 2900 );
1479   QgsLayout l( QgsProject::instance() );
1480 
1481   QgsExpression e( QStringLiteral( "array_to_string( map_credits( 'Map_id' ) )" ) );
1482   // no map
1483   QgsExpressionContext c = l.createExpressionContext();
1484   QVariant r = e.evaluate( &c );
1485   QVERIFY( !r.isValid() );
1486 
1487   QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
1488   map->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
1489   map->attemptSetSceneRect( QRectF( 30, 60, 200, 100 ) );
1490   map->setExtent( extent );
1491   l.addLayoutItem( map );
1492   map->setId( QStringLiteral( "Map_id" ) );
1493 
1494   c = l.createExpressionContext();
1495   e.prepare( &c );
1496   r = e.evaluate( &c );
1497   // no layers
1498   QCOMPARE( r.toString(), QString() );
1499 
1500   // with layers
1501   std::unique_ptr< QgsVectorLayer > layer = std::make_unique< QgsVectorLayer >( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "A" ), QStringLiteral( "memory" ) );
1502   QgsLayerMetadata metadata;
1503   metadata.setRights( QStringList() << QStringLiteral( "CC BY SA" ) );
1504   layer->setMetadata( metadata );
1505   std::unique_ptr< QgsVectorLayer > layer2 = std::make_unique< QgsVectorLayer >( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "B" ), QStringLiteral( "memory" ) );
1506   metadata.setRights( QStringList() << QStringLiteral( "CC NC" ) );
1507   layer2->setMetadata( metadata );
1508   std::unique_ptr< QgsVectorLayer > layer3 = std::make_unique< QgsVectorLayer >( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "C" ), QStringLiteral( "memory" ) );
1509   metadata.setRights( QStringList() << QStringLiteral( "CC BY SA" ) );
1510   layer3->setMetadata( metadata );
1511   const std::unique_ptr< QgsVectorLayer > layer4 = std::make_unique< QgsVectorLayer >( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "C" ), QStringLiteral( "memory" ) );
1512 
1513   map->setLayers( QList<QgsMapLayer *>() << layer.get() << layer2.get() << layer3.get()  << layer4.get() );
1514   e.prepare( &c );
1515   QCOMPARE( e.evaluate( &c ).toString(), QStringLiteral( "CC BY SA,CC NC" ) );
1516   map->setLayers( QList<QgsMapLayer *>() << layer.get() << layer3.get()  << layer4.get() );
1517   e.prepare( &c );
1518   QCOMPARE( e.evaluate( &c ).toString(), QStringLiteral( "CC BY SA" ) );
1519 
1520   QgsExpression e2( QStringLiteral( "array_to_string( map_credits( 'Map_id', include_layer_names:=true ) )" ) );
1521   e2.prepare( &c );
1522   QCOMPARE( e2.evaluate( &c ).toString(), QStringLiteral( "A: CC BY SA,C: CC BY SA" ) );
1523   map->setLayers( QList<QgsMapLayer *>() << layer.get() << layer2.get() << layer3.get()  << layer4.get() );
1524   QgsExpression e3( QStringLiteral( "array_to_string( map_credits( 'Map_id', include_layer_names:=true, layer_name_separator:='|' ) )" ) );
1525   e3.prepare( &c );
1526   QCOMPARE( e3.evaluate( &c ).toString(), QStringLiteral( "A|CC BY SA,B|CC NC,C|CC BY SA" ) );
1527 
1528   // second map
1529   QgsLayoutItemMap *map2 = new QgsLayoutItemMap( &l );
1530   map2->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
1531   map2->attemptSetSceneRect( QRectF( 30, 60, 200, 100 ) );
1532   map2->setExtent( extent );
1533   l.addLayoutItem( map2 );
1534   map2->setId( QStringLiteral( "Map_2" ) );
1535   map2->setLayers( QList<QgsMapLayer *>() << layer.get()  << layer4.get() );
1536   QgsExpression e4( QStringLiteral( "array_to_string( map_credits( 'Map_2', include_layer_names:=true ) )" ) );
1537   e4.prepare( &c );
1538   QCOMPARE( e4.evaluate( &c ).toString(), QStringLiteral( "A: CC BY SA" ) );
1539 }
1540 
rotation()1541 void TestQgsLayoutItem::rotation()
1542 {
1543   QgsProject proj;
1544   QgsLayout l( &proj );
1545 
1546   TestItem *item = new TestItem( &l );
1547 
1548   const QSignalSpy spyRotationChanged( item, &QgsLayoutItem::rotationChanged );
1549 
1550   l.setUnits( QgsUnitTypes::LayoutMillimeters );
1551   item->setPos( 6.0, 10.0 );
1552   item->setRect( 0.0, 0.0, 10.0, 8.0 );
1553   item->setPen( Qt::NoPen );
1554   QRectF bounds = item->sceneBoundingRect();
1555   QCOMPARE( bounds.left(), 6.0 );
1556   QCOMPARE( bounds.right(), 16.0 );
1557   QCOMPARE( bounds.top(), 10.0 );
1558   QCOMPARE( bounds.bottom(), 18.0 );
1559 
1560   item->setItemRotation( 90.0 );
1561   QCOMPARE( item->itemRotation(), 90.0 );
1562   QCOMPARE( item->rotation(), 90.0 );
1563   bounds = item->sceneBoundingRect();
1564   QCOMPARE( bounds.left(), 7.0 );
1565   QCOMPARE( bounds.right(), 15.0 );
1566   QCOMPARE( bounds.top(), 9.0 );
1567   QCOMPARE( bounds.bottom(), 19.0 );
1568   QCOMPARE( spyRotationChanged.count(), 1 );
1569   QCOMPARE( spyRotationChanged.at( 0 ).at( 0 ).toDouble(), 90.0 );
1570 
1571 
1572   //check that negative angles are preserved as negative
1573   item->setItemRotation( -90.0 );
1574   QCOMPARE( item->itemRotation(), -90.0 );
1575   QCOMPARE( item->rotation(), -90.0 );
1576   bounds = item->sceneBoundingRect();
1577   QCOMPARE( bounds.width(), 8.0 );
1578   QCOMPARE( bounds.height(), 10.0 );
1579   QCOMPARE( spyRotationChanged.count(), 2 );
1580   QCOMPARE( spyRotationChanged.at( 1 ).at( 0 ).toDouble(), -90.0 );
1581 
1582   //check that rotating changes stored item position for reference point
1583   item->setItemRotation( 0.0 );
1584   QCOMPARE( spyRotationChanged.count(), 3 );
1585   QCOMPARE( spyRotationChanged.at( 2 ).at( 0 ).toDouble(), 0.0 );
1586 
1587   item->attemptMove( QgsLayoutPoint( 5.0, 8.0 ) );
1588   item->attemptResize( QgsLayoutSize( 10.0, 6.0 ) );
1589   item->setItemRotation( 90.0 );
1590   QCOMPARE( item->positionWithUnits().x(), 13.0 );
1591   QCOMPARE( item->positionWithUnits().y(), 6.0 );
1592   QCOMPARE( spyRotationChanged.count(), 4 );
1593   QCOMPARE( spyRotationChanged.at( 3 ).at( 0 ).toDouble(), 90.0 );
1594 
1595   //setting item position (for reference point) respects rotation
1596   item->attemptMove( QgsLayoutPoint( 10.0, 8.0 ) );
1597   QCOMPARE( item->scenePos().x(), 10.0 );
1598   QCOMPARE( item->scenePos().y(), 8.0 );
1599   const QRectF p = item->sceneBoundingRect();
1600   qDebug() << p.left();
1601   QCOMPARE( item->sceneBoundingRect().left(), 4.0 );
1602   QCOMPARE( item->sceneBoundingRect().right(), 10.0 );
1603   QCOMPARE( item->sceneBoundingRect().top(), 8.0 );
1604   QCOMPARE( item->sceneBoundingRect().bottom(), 18.0 );
1605 
1606   // set rotation, using top left
1607   std::unique_ptr< TestItem > item2( new TestItem( &l ) );
1608   item2->attemptMove( QgsLayoutPoint( 5.0, 8.0 ) );
1609   item2->attemptResize( QgsLayoutSize( 10.0, 6.0 ) );
1610   item2->setItemRotation( 90, false );
1611   QCOMPARE( item2->positionWithUnits().x(), 5.0 );
1612   QCOMPARE( item2->positionWithUnits().y(), 8.0 );
1613   QCOMPARE( item2->pos().x(), 5.0 );
1614   QCOMPARE( item2->pos().y(), 8.0 );
1615   item2->setItemRotation( 180, true );
1616   QCOMPARE( item2->positionWithUnits().x(), 7.0 );
1617   QCOMPARE( item2->positionWithUnits().y(), 16.0 );
1618   QCOMPARE( item2->pos().x(), 7.0 );
1619   QCOMPARE( item2->pos().y(), 16.0 );
1620 
1621   // test that refresh rotation doesn't move item (#18037)
1622   item2 = std::make_unique< TestItem >( &l );
1623   item2->setReferencePoint( QgsLayoutItem::Middle );
1624   item2->attemptMove( QgsLayoutPoint( 5.0, 8.0 ) );
1625   item2->attemptResize( QgsLayoutSize( 10.0, 6.0 ) );
1626   item2->setItemRotation( 45 );
1627   QCOMPARE( item2->positionWithUnits().x(), 5.0 );
1628   QCOMPARE( item2->positionWithUnits().y(), 8.0 );
1629   QGSCOMPARENEAR( item2->pos().x(), 3.58, 0.01 );
1630   QGSCOMPARENEAR( item2->pos().y(), 2.343146, 0.01 );
1631   QCOMPARE( item2->rotation(), 45.0 );
1632   item2->refresh();
1633   QCOMPARE( item2->positionWithUnits().x(), 5.0 );
1634   QCOMPARE( item2->positionWithUnits().y(), 8.0 );
1635   QGSCOMPARENEAR( item2->pos().x(), 3.58, 0.01 );
1636   QGSCOMPARENEAR( item2->pos().y(), 2.343146, 0.01 );
1637   QCOMPARE( item2->rotation(), 45.0 );
1638 
1639 
1640   //TODO also changing size?
1641 
1642 
1643   //data defined rotation
1644   item->setItemRotation( 0.0 );
1645   QCOMPARE( spyRotationChanged.count(), 5 );
1646   QCOMPARE( spyRotationChanged.at( 4 ).at( 0 ).toDouble(), 0.0 );
1647 
1648   item->attemptMove( QgsLayoutPoint( 5.0, 8.0 ) );
1649   item->attemptResize( QgsLayoutSize( 10.0, 6.0 ) );
1650   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemRotation, QgsProperty::fromExpression( QStringLiteral( "90" ) ) );
1651   item->refreshDataDefinedProperty( QgsLayoutObject::ItemRotation );
1652   QCOMPARE( item->itemRotation(), 0.0 ); // should be unchanged
1653   QCOMPARE( item->rotation(), 90.0 );
1654   QCOMPARE( spyRotationChanged.count(), 6 );
1655   QCOMPARE( spyRotationChanged.at( 5 ).at( 0 ).toDouble(), 90.0 );
1656 
1657   // rotation should have applied around item center
1658   QCOMPARE( item->positionWithUnits().x(), 13.0 );
1659   QCOMPARE( item->positionWithUnits().y(), 6.0 );
1660   QCOMPARE( item->pos().x(), 13.0 );
1661   QCOMPARE( item->pos().y(), 6.0 );
1662 
1663   //also check when refreshing all properties
1664   item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemRotation, QgsProperty::fromExpression( QStringLiteral( "180" ) ) );
1665   item->refreshDataDefinedProperty( QgsLayoutObject::AllProperties );
1666   QCOMPARE( item->itemRotation(), 0.0 ); // should be unchanged
1667   QCOMPARE( item->rotation(), 180.0 );
1668   QCOMPARE( spyRotationChanged.count(), 7 );
1669   QCOMPARE( spyRotationChanged.at( 6 ).at( 0 ).toDouble(), 180.0 );
1670   QCOMPARE( item->positionWithUnits().x(), 15.0 );
1671   QCOMPARE( item->positionWithUnits().y(), 14.0 );
1672   QCOMPARE( item->pos().x(), 15.0 );
1673   QCOMPARE( item->pos().y(), 14.0 );
1674 
1675   delete item;
1676 
1677   //render check
1678   item = new TestItem( &l );
1679   item->setItemRotation( 0.0 );
1680   item->setPos( 100, 150 );
1681   item->setRect( 0, 0, 200, 100 );
1682   l.addItem( item );
1683   item->setItemRotation( 45 );
1684   l.setSceneRect( 0, 0, 400, 400 );
1685   l.renderContext().setFlag( QgsLayoutRenderContext::FlagDebug, true );
1686   QImage image( l.sceneRect().size().toSize(), QImage::Format_ARGB32 );
1687   image.fill( 0 );
1688   QPainter painter( &image );
1689   l.render( &painter );
1690   painter.end();
1691 
1692   const bool result = renderCheck( QStringLiteral( "layoutitem_rotation" ), image, 0 );
1693   delete item;
1694   QVERIFY( result );
1695 }
1696 
1697 //TODO rotation tests:
1698 //rotate item around layout point
1699 
1700 
writeXml()1701 void TestQgsLayoutItem::writeXml()
1702 {
1703   QDomImplementation DomImplementation;
1704   const QDomDocumentType documentType =
1705     DomImplementation.createDocumentType(
1706       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
1707   QDomDocument doc( documentType );
1708   QDomElement rootNode = doc.createElement( QStringLiteral( "qgis" ) );
1709 
1710   QgsProject proj;
1711   QgsLayout l( &proj );
1712   TestItem *item = new TestItem( &l );
1713   QVERIFY( item->writeXml( rootNode, doc, QgsReadWriteContext() ) );
1714 
1715   //make sure type was written
1716   const QDomElement element = rootNode.firstChildElement();
1717 
1718   QCOMPARE( element.nodeName(), QString( "LayoutItem" ) );
1719   QCOMPARE( element.attribute( "type", "" ).toInt(), item->type() );
1720 
1721   //check that element has an object node
1722   const QDomNodeList objectNodeList = element.elementsByTagName( QStringLiteral( "LayoutObject" ) );
1723   QCOMPARE( objectNodeList.count(), 1 );
1724 
1725   delete item;
1726 }
1727 
readXml()1728 void TestQgsLayoutItem::readXml()
1729 {
1730   QDomImplementation DomImplementation;
1731   const QDomDocumentType documentType =
1732     DomImplementation.createDocumentType(
1733       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
1734   QDomDocument doc( documentType );
1735 
1736   QgsProject proj;
1737   QgsLayout l( &proj );
1738   TestItem *item = new TestItem( &l );
1739 
1740   //try reading bad elements
1741   const QDomElement badElement = doc.createElement( QStringLiteral( "bad" ) );
1742   const QDomElement noNode;
1743   QVERIFY( !item->readXml( badElement, doc, QgsReadWriteContext() ) );
1744   QVERIFY( !item->readXml( noNode, doc, QgsReadWriteContext() ) );
1745 
1746   //try good element
1747   QDomElement goodElement = doc.createElement( QStringLiteral( "LayoutItem" ) );
1748   goodElement.setAttribute( QStringLiteral( "type" ), QStringLiteral( "TestItemType" ) );
1749   QVERIFY( item->readXml( goodElement, doc, QgsReadWriteContext() ) );
1750   delete item;
1751 }
1752 
writeReadXmlProperties()1753 void TestQgsLayoutItem::writeReadXmlProperties()
1754 {
1755   QgsProject proj;
1756   QgsLayout l( &proj );
1757   TestItem *original = new TestItem( &l );
1758 
1759   original->dataDefinedProperties().setProperty( QgsLayoutObject::TestProperty, QgsProperty::fromExpression( QStringLiteral( "10 + 40" ) ) );
1760 
1761   original->setReferencePoint( QgsLayoutItem::MiddleRight );
1762   original->attemptResize( QgsLayoutSize( 6, 8, QgsUnitTypes::LayoutCentimeters ) );
1763   original->attemptMove( QgsLayoutPoint( 0.05, 0.09, QgsUnitTypes::LayoutMeters ) );
1764   original->setItemRotation( 45.0 );
1765   original->setId( QStringLiteral( "test" ) );
1766   original->setLocked( true );
1767   original->setZValue( 55 );
1768   original->setVisible( false );
1769   original->setFrameEnabled( true );
1770   original->setFrameStrokeColor( QColor( 100, 150, 200 ) );
1771   original->setFrameStrokeWidth( QgsLayoutMeasurement( 5, QgsUnitTypes::LayoutCentimeters ) );
1772   original->setFrameJoinStyle( Qt::MiterJoin );
1773   original->setBackgroundEnabled( false );
1774   original->setBackgroundColor( QColor( 200, 150, 100 ) );
1775   original->setBlendMode( QPainter::CompositionMode_Darken );
1776   original->setExcludeFromExports( true );
1777   original->setItemOpacity( 0.75 );
1778 
1779   std::unique_ptr< QgsLayoutItem > copy = createCopyViaXml( &l, original );
1780 
1781   QCOMPARE( copy->uuid(), original->uuid() );
1782   QCOMPARE( copy->id(), original->id() );
1783   const QgsProperty dd = copy->dataDefinedProperties().property( QgsLayoutObject::TestProperty );
1784   QVERIFY( dd );
1785   QVERIFY( dd.isActive() );
1786   QCOMPARE( dd.propertyType(), QgsProperty::ExpressionBasedProperty );
1787   QCOMPARE( copy->referencePoint(), original->referencePoint() );
1788   QCOMPARE( copy->sizeWithUnits(), original->sizeWithUnits() );
1789   QGSCOMPARENEAR( copy->positionWithUnits().x(), original->positionWithUnits().x(), 0.001 );
1790   QGSCOMPARENEAR( copy->positionWithUnits().y(), original->positionWithUnits().y(), 0.001 );
1791   QCOMPARE( copy->positionWithUnits().units(), original->positionWithUnits().units() );
1792   QCOMPARE( copy->itemRotation(), original->itemRotation() );
1793   QGSCOMPARENEAR( copy->pos().x(), original->pos().x(), 0.001 );
1794   QGSCOMPARENEAR( copy->pos().y(), original->pos().y(), 0.001 );
1795   QVERIFY( copy->isLocked() );
1796   QCOMPARE( copy->zValue(), 55.0 );
1797   QVERIFY( !copy->isVisible() );
1798   QVERIFY( copy->frameEnabled() );
1799   QCOMPARE( copy->frameStrokeColor(), QColor( 100, 150, 200 ) );
1800   QCOMPARE( copy->frameStrokeWidth(), QgsLayoutMeasurement( 5, QgsUnitTypes::LayoutCentimeters ) );
1801   QCOMPARE( copy->frameJoinStyle(), Qt::MiterJoin );
1802   QVERIFY( !copy->hasBackground() );
1803   QCOMPARE( copy->backgroundColor(), QColor( 200, 150, 100 ) );
1804   QCOMPARE( copy->blendMode(), QPainter::CompositionMode_Darken );
1805   QVERIFY( copy->excludeFromExports( ) );
1806   QCOMPARE( copy->itemOpacity(), 0.75 );
1807   delete original;
1808 }
1809 
undoRedo()1810 void TestQgsLayoutItem::undoRedo()
1811 {
1812   QgsProject proj;
1813   QgsLayout l( &proj );
1814 
1815   QgsLayoutItemShape *item = new QgsLayoutItemShape( &l );
1816   const QString uuid = item->uuid();
1817   QPointer< QgsLayoutItemShape > pItem( item ); // for testing deletion
1818   item->setFrameStrokeColor( QColor( 255, 100, 200 ) );
1819   l.addLayoutItem( item );
1820 
1821   QVERIFY( pItem );
1822   QVERIFY( l.items().contains( item ) );
1823   QCOMPARE( l.itemByUuid( uuid ), item );
1824 
1825   // undo should delete item
1826   l.undoStack()->stack()->undo();
1827   QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
1828   QVERIFY( !pItem );
1829   QVERIFY( !l.items().contains( item ) );
1830   QVERIFY( !l.itemByUuid( uuid ) );
1831 
1832   // redo should restore
1833   l.undoStack()->stack()->redo();
1834   QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
1835   item = dynamic_cast< QgsLayoutItemShape * >( l.itemByUuid( uuid ) );
1836   QVERIFY( item );
1837   QVERIFY( l.items().contains( item ) );
1838   pItem = item;
1839   QCOMPARE( item->frameStrokeColor().name(), QColor( 255, 100, 200 ).name() );
1840 
1841   //... and repeat!
1842 
1843   // undo should delete item
1844   l.undoStack()->stack()->undo();
1845   QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
1846   QVERIFY( !pItem );
1847   QVERIFY( !l.items().contains( item ) );
1848   QVERIFY( !l.itemByUuid( uuid ) );
1849 
1850   // redo should restore
1851   l.undoStack()->stack()->redo();
1852   QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
1853   item = dynamic_cast< QgsLayoutItemShape * >( l.itemByUuid( uuid ) );
1854   QVERIFY( item );
1855   QVERIFY( l.items().contains( item ) );
1856   pItem = item;
1857   QCOMPARE( item->frameStrokeColor().name(), QColor( 255, 100, 200 ).name() );
1858 
1859   // delete item
1860   l.removeLayoutItem( item );
1861 
1862   QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
1863   QVERIFY( !pItem );
1864   QVERIFY( !l.items().contains( item ) );
1865   QVERIFY( !l.itemByUuid( uuid ) );
1866 
1867   // undo should restore
1868   l.undoStack()->stack()->undo();
1869   QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
1870   item = dynamic_cast< QgsLayoutItemShape * >( l.itemByUuid( uuid ) );
1871   QVERIFY( item );
1872   QVERIFY( l.items().contains( item ) );
1873   pItem = item;
1874   QCOMPARE( item->frameStrokeColor().name(), QColor( 255, 100, 200 ).name() );
1875 
1876   // another undo should delete item
1877   l.undoStack()->stack()->undo();
1878   QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
1879   QVERIFY( !pItem );
1880   QVERIFY( !l.items().contains( item ) );
1881   QVERIFY( !l.itemByUuid( uuid ) );
1882 
1883   // redo should restore
1884   l.undoStack()->stack()->redo();
1885   QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
1886   item = dynamic_cast< QgsLayoutItemShape * >( l.itemByUuid( uuid ) );
1887   QVERIFY( item );
1888   QVERIFY( l.items().contains( item ) );
1889   pItem = item;
1890   QCOMPARE( item->frameStrokeColor().name(), QColor( 255, 100, 200 ).name() );
1891 
1892   // another redo should delete item
1893   l.undoStack()->stack()->redo();
1894   QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
1895   QVERIFY( !pItem );
1896   QVERIFY( !l.items().contains( item ) );
1897   QVERIFY( !l.itemByUuid( uuid ) );
1898 
1899 }
1900 
multiItemUndo()1901 void TestQgsLayoutItem::multiItemUndo()
1902 {
1903   QgsProject proj;
1904   QgsLayout l( &proj );
1905 
1906   QgsLayoutItemShape *item = new QgsLayoutItemShape( &l );
1907   l.addLayoutItem( item );
1908   item->attemptMove( QgsLayoutPoint( 10, 10 ) );
1909   QgsLayoutItemShape *item2 = new QgsLayoutItemShape( &l );
1910   l.addLayoutItem( item2 );
1911   item2->attemptMove( QgsLayoutPoint( 20, 20 ) );
1912 
1913   l.undoStack()->beginCommand( item, tr( "Item moved" ), QgsLayoutItem::UndoIncrementalMove );
1914   item->attemptMove( QgsLayoutPoint( 1, 1 ) );
1915   l.undoStack()->endCommand();
1916 
1917   l.undoStack()->beginCommand( item2, tr( "Item moved" ), QgsLayoutItem::UndoIncrementalMove );
1918   item2->attemptMove( QgsLayoutPoint( 21, 21 ) );
1919   l.undoStack()->endCommand();
1920 
1921   // undo should only remove item2 move
1922   l.undoStack()->stack()->undo();
1923   QCOMPARE( item2->positionWithUnits(), QgsLayoutPoint( 20, 20 ) );
1924   QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 1, 1 ) );
1925   l.undoStack()->stack()->undo();
1926   QCOMPARE( item2->positionWithUnits(), QgsLayoutPoint( 20, 20 ) );
1927   QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 10, 10 ) );
1928 }
1929 
overlappingUndo()1930 void TestQgsLayoutItem::overlappingUndo()
1931 {
1932   QgsProject proj;
1933   QgsLayout l( &proj );
1934 
1935   QgsLayoutItemShape *item = new QgsLayoutItemShape( &l );
1936   l.addLayoutItem( item );
1937   item->attemptMove( QgsLayoutPoint( 10, 10 ) );
1938   QgsLayoutItemShape *item2 = new QgsLayoutItemShape( &l );
1939   l.addLayoutItem( item2 );
1940   item2->attemptMove( QgsLayoutPoint( 20, 20 ) );
1941 
1942   //commands overlap
1943   l.undoStack()->beginCommand( item, tr( "Item moved" ), QgsLayoutItem::UndoIncrementalMove );
1944   item->attemptMove( QgsLayoutPoint( 1, 1 ) );
1945   l.undoStack()->beginCommand( item2, tr( "Item moved" ), QgsLayoutItem::UndoIncrementalMove );
1946   item2->attemptMove( QgsLayoutPoint( 21, 21 ) );
1947   l.undoStack()->endCommand();
1948   l.undoStack()->endCommand();
1949 
1950   // undo should remove item move
1951   l.undoStack()->stack()->undo();
1952   QCOMPARE( item2->positionWithUnits(), QgsLayoutPoint( 21, 21 ) );
1953   QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 10, 10 ) );
1954   l.undoStack()->stack()->undo();
1955   QCOMPARE( item2->positionWithUnits(), QgsLayoutPoint( 20, 20 ) );
1956   QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 10, 10 ) );
1957 
1958 }
1959 
blendMode()1960 void TestQgsLayoutItem::blendMode()
1961 {
1962   QgsProject proj;
1963   QgsLayout l( &proj );
1964 
1965   QgsLayoutItemShape *item = new QgsLayoutItemShape( &l );
1966   l.addLayoutItem( item );
1967 
1968   item->setBlendMode( QPainter::CompositionMode_Darken );
1969   QCOMPARE( item->blendMode(), QPainter::CompositionMode_Darken );
1970   QVERIFY( item->mEffect->isEnabled() );
1971 
1972   l.renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, false );
1973   QVERIFY( !item->mEffect->isEnabled() );
1974   l.renderContext().setFlag( QgsLayoutRenderContext::FlagUseAdvancedEffects, true );
1975   QVERIFY( item->mEffect->isEnabled() );
1976 
1977   item->dataDefinedProperties().setProperty( QgsLayoutObject::BlendMode, QgsProperty::fromExpression( "'lighten'" ) );
1978   item->refreshDataDefinedProperty();
1979   QCOMPARE( item->blendMode(), QPainter::CompositionMode_Darken ); // should not change
1980   QCOMPARE( item->mEffect->compositionMode(), QPainter::CompositionMode_Lighten );
1981 
1982   QgsLayout l2( QgsProject::instance() );
1983   l2.initializeDefaults();
1984   QgsLayoutItemShape *mComposerRect1 = new QgsLayoutItemShape( &l2 );
1985   mComposerRect1->attemptSetSceneRect( QRectF( 20, 20, 150, 100 ) );
1986   mComposerRect1->setShapeType( QgsLayoutItemShape::Rectangle );
1987   QgsSimpleFillSymbolLayer *simpleFill = new QgsSimpleFillSymbolLayer();
1988   QgsFillSymbol *fillSymbol = new QgsFillSymbol();
1989   fillSymbol->changeSymbolLayer( 0, simpleFill );
1990   simpleFill->setColor( QColor( 255, 150, 0 ) );
1991   simpleFill->setStrokeColor( Qt::black );
1992   mComposerRect1->setSymbol( fillSymbol );
1993   delete fillSymbol;
1994 
1995   l2.addLayoutItem( mComposerRect1 );
1996   QgsLayoutItemShape *mComposerRect2 = new QgsLayoutItemShape( &l2 );
1997   mComposerRect2->attemptSetSceneRect( QRectF( 50, 50, 150, 100 ) );
1998   mComposerRect2->setShapeType( QgsLayoutItemShape::Rectangle );
1999   l2.addLayoutItem( mComposerRect2 );
2000   QgsSimpleFillSymbolLayer *simpleFill2 = new QgsSimpleFillSymbolLayer();
2001   QgsFillSymbol *fillSymbol2 = new QgsFillSymbol();
2002   fillSymbol2->changeSymbolLayer( 0, simpleFill2 );
2003   simpleFill2->setColor( QColor( 0, 100, 150 ) );
2004   simpleFill2->setStrokeColor( Qt::black );
2005   mComposerRect2->setSymbol( fillSymbol2 );
2006   delete fillSymbol2;
2007 
2008   mComposerRect2->setBlendMode( QPainter::CompositionMode_Multiply );
2009 
2010   QgsLayoutChecker checker( QStringLiteral( "composereffects_blend" ), &l2 );
2011   checker.setControlPathPrefix( QStringLiteral( "composer_effects" ) );
2012   QVERIFY( checker.testLayout( mReport ) );
2013 }
2014 
opacity()2015 void TestQgsLayoutItem::opacity()
2016 {
2017   QgsProject proj;
2018   QgsLayout l( &proj );
2019   l.initializeDefaults();
2020 
2021   QgsSimpleFillSymbolLayer *simpleFill = new QgsSimpleFillSymbolLayer();
2022   QgsFillSymbol *fillSymbol = new QgsFillSymbol();
2023   fillSymbol->changeSymbolLayer( 0, simpleFill );
2024   simpleFill->setColor( QColor( 255, 150, 0 ) );
2025   simpleFill->setStrokeColor( Qt::black );
2026 
2027   QgsLayoutItemShape *item = new QgsLayoutItemShape( &l );
2028   item->setShapeType( QgsLayoutItemShape::Rectangle );
2029   item->attemptSetSceneRect( QRectF( 50, 50, 150, 100 ) );
2030   item->setSymbol( fillSymbol->clone() );
2031 
2032   l.addLayoutItem( item );
2033 
2034   item->setItemOpacity( 0.75 );
2035   QCOMPARE( item->itemOpacity(), 0.75 );
2036 
2037   // we handle opacity ourselves, so QGraphicsItem opacity should never be set
2038   QCOMPARE( item->opacity(), 1.0 );
2039 
2040   QgsLayoutChecker checker( QStringLiteral( "composereffects_transparency75" ), &l );
2041   checker.setControlPathPrefix( QStringLiteral( "composer_effects" ) );
2042   QVERIFY( checker.testLayout( mReport ) );
2043 
2044   item->dataDefinedProperties().setProperty( QgsLayoutObject::Opacity, QgsProperty::fromExpression( "35" ) );
2045   item->refreshDataDefinedProperty();
2046   QCOMPARE( item->itemOpacity(), 0.75 ); // should not change
2047   QCOMPARE( item->opacity(), 1.0 );
2048 
2049   checker = QgsLayoutChecker( QStringLiteral( "composereffects_transparency35" ), &l );
2050   checker.setControlPathPrefix( QStringLiteral( "composer_effects" ) );
2051   QVERIFY( checker.testLayout( mReport ) );
2052 
2053   // with background and frame
2054   l.removeLayoutItem( item );
2055 
2056   QgsLayoutItemLabel *labelItem = new QgsLayoutItemLabel( &l );
2057   l.addLayoutItem( labelItem );
2058   labelItem->attemptSetSceneRect( QRectF( 50, 50, 150, 100 ) );
2059   labelItem->setBackgroundEnabled( true );
2060   labelItem->setBackgroundColor( QColor( 40, 140, 240 ) );
2061   labelItem->setFrameEnabled( true );
2062   labelItem->setFrameStrokeColor( QColor( 40, 30, 20 ) );
2063   labelItem->setItemOpacity( 0.5 );
2064   checker = QgsLayoutChecker( QStringLiteral( "composereffects_transparency_bgframe" ), &l );
2065   checker.setControlPathPrefix( QStringLiteral( "composer_effects" ) );
2066   QVERIFY( checker.testLayout( mReport ) );
2067 
2068   QgsLayout l2( QgsProject::instance() );
2069   l2.initializeDefaults();
2070   QgsLayoutItemShape *mComposerRect1 = new QgsLayoutItemShape( &l2 );
2071   mComposerRect1->attemptSetSceneRect( QRectF( 20, 20, 150, 100 ) );
2072   mComposerRect1->setShapeType( QgsLayoutItemShape::Rectangle );
2073   mComposerRect1->setSymbol( fillSymbol->clone() );
2074   delete fillSymbol;
2075 
2076   l2.addLayoutItem( mComposerRect1 );
2077   QgsLayoutItemShape *mComposerRect2 = new QgsLayoutItemShape( &l2 );
2078   mComposerRect2->attemptSetSceneRect( QRectF( 50, 50, 150, 100 ) );
2079   mComposerRect2->setShapeType( QgsLayoutItemShape::Rectangle );
2080   l2.addLayoutItem( mComposerRect2 );
2081   QgsSimpleFillSymbolLayer *simpleFill2 = new QgsSimpleFillSymbolLayer();
2082   QgsFillSymbol *fillSymbol2 = new QgsFillSymbol();
2083   fillSymbol2->changeSymbolLayer( 0, simpleFill2 );
2084   simpleFill2->setColor( QColor( 0, 100, 150 ) );
2085   simpleFill2->setStrokeColor( Qt::black );
2086   mComposerRect2->setSymbol( fillSymbol2 );
2087   delete fillSymbol2;
2088 
2089   mComposerRect2->setItemOpacity( 0.5 );
2090 
2091   checker = QgsLayoutChecker( QStringLiteral( "composereffects_transparency" ), &l2 );
2092   checker.setControlPathPrefix( QStringLiteral( "composer_effects" ) );
2093   QVERIFY( checker.testLayout( mReport ) );
2094 }
2095 
excludeFromExports()2096 void TestQgsLayoutItem::excludeFromExports()
2097 {
2098   QgsProject proj;
2099   QgsLayout l( &proj );
2100 
2101   std::unique_ptr< QgsLayoutItemPage > page( new QgsLayoutItemPage( &l ) );
2102   page->setPageSize( QgsLayoutSize( 297, 210, QgsUnitTypes::LayoutMillimeters ) );
2103   l.pageCollection()->addPage( page.release() );
2104 
2105   QgsSimpleFillSymbolLayer *simpleFill = new QgsSimpleFillSymbolLayer();
2106   std::unique_ptr< QgsFillSymbol > fillSymbol( new QgsFillSymbol() );
2107   fillSymbol->changeSymbolLayer( 0, simpleFill );
2108   simpleFill->setColor( Qt::transparent );
2109   simpleFill->setStrokeColor( Qt::transparent );
2110   l.pageCollection()->setPageStyleSymbol( fillSymbol.get() );
2111 
2112   QgsLayoutItemShape *item = new QgsLayoutItemShape( &l );
2113   l.addLayoutItem( item );
2114 
2115   item->setExcludeFromExports( true );
2116   QVERIFY( item->excludeFromExports() );
2117   item->setExcludeFromExports( false );
2118   QVERIFY( !item->excludeFromExports() );
2119 
2120   item->dataDefinedProperties().setProperty( QgsLayoutObject::ExcludeFromExports, QgsProperty::fromExpression( "1" ) );
2121   item->refreshDataDefinedProperty();
2122   QVERIFY( !item->excludeFromExports() ); // should not change
2123   QVERIFY( item->mEvaluatedExcludeFromExports );
2124 
2125   item->attemptMove( QgsLayoutPoint( 100, 100 ) );
2126   item->attemptResize( QgsLayoutSize( 200, 200 ) );
2127   l.updateBounds();
2128 
2129   QgsLayoutChecker checker( QStringLiteral( "layoutitem_excluded" ), &l );
2130   checker.setControlPathPrefix( QStringLiteral( "layouts" ) );
2131   checker.setSize( QSize( 400, 400 ) );
2132   QVERIFY( checker.testLayout( mReport ) );
2133 }
2134 
createCopyViaXml(QgsLayout * layout,QgsLayoutItem * original)2135 std::unique_ptr<QgsLayoutItem> TestQgsLayoutItem::createCopyViaXml( QgsLayout *layout, QgsLayoutItem *original )
2136 {
2137   //save original item to xml
2138   QDomImplementation DomImplementation;
2139   const QDomDocumentType documentType =
2140     DomImplementation.createDocumentType(
2141       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
2142   QDomDocument doc( documentType );
2143   QDomElement rootNode = doc.createElement( QStringLiteral( "qgis" ) );
2144 
2145   original->writeXml( rootNode, doc, QgsReadWriteContext() );
2146 
2147   //create new item and restore settings from xml
2148   std::unique_ptr< TestItem > copy = std::make_unique< TestItem >( layout );
2149   copy->readXml( rootNode.firstChildElement(), doc, QgsReadWriteContext() );
2150 
2151   return std::move( copy );
2152 }
2153 
2154 QGSTEST_MAIN( TestQgsLayoutItem )
2155 #include "testqgslayoutitem.moc"
2156