1 /***************************************************************************
2     testqgslayoutview.cpp
3      --------------------
4     Date                 : July 2017
5     Copyright            : (C) 2017 Nyall Dawson
6     Email                : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgstest.h"
17 #include "qgslayout.h"
18 #include "qgslayoutview.h"
19 #include "qgslayoutviewtool.h"
20 #include "qgslayoutviewmouseevent.h"
21 #include "qgslayoutitem.h"
22 #include "qgslayoutviewrubberband.h"
23 #include "qgslayoutitemregistry.h"
24 #include "qgslayoutitemguiregistry.h"
25 #include "qgslayoutitemwidget.h"
26 #include "qgslayoutitemshape.h"
27 #include "qgsproject.h"
28 #include "qgsgui.h"
29 #include "qgslayoututils.h"
30 #include <QtTest/QSignalSpy>
31 #include <QSvgGenerator>
32 #include <QPrinter>
33 
34 class TestQgsLayoutView: public QObject
35 {
36     Q_OBJECT
37   private slots:
38     void initTestCase(); // will be called before the first testfunction is executed.
39     void cleanupTestCase(); // will be called after the last testfunction was executed.
40     void init(); // will be called before each testfunction is executed.
41     void cleanup(); // will be called after every testfunction.
42     void basic();
43     void tool();
44     void events();
45     void guiRegistry();
46     void rubberBand();
47 
48   private:
49 
50 };
51 
initTestCase()52 void TestQgsLayoutView::initTestCase()
53 {
54 
55 }
56 
cleanupTestCase()57 void TestQgsLayoutView::cleanupTestCase()
58 {
59 }
60 
init()61 void TestQgsLayoutView::init()
62 {
63 }
64 
cleanup()65 void TestQgsLayoutView::cleanup()
66 {
67 }
68 
basic()69 void TestQgsLayoutView::basic()
70 {
71   QgsProject p;
72   QgsLayout *layout = new QgsLayout( &p );
73   QgsLayoutView *view = new QgsLayoutView();
74 
75   QSignalSpy spyLayoutChanged( view, &QgsLayoutView::layoutSet );
76   view->setCurrentLayout( layout );
77   QCOMPARE( view->currentLayout(), layout );
78   QCOMPARE( spyLayoutChanged.count(), 1 );
79 
80   delete view;
81   delete layout;
82 }
83 
tool()84 void TestQgsLayoutView::tool()
85 {
86   QgsLayoutView *view = new QgsLayoutView();
87   QgsLayoutViewTool *tool = new QgsLayoutViewTool( view, QStringLiteral( "name" ) );
88   QgsLayoutViewTool *tool2 = new QgsLayoutViewTool( view, QStringLiteral( "name2" ) );
89 
90   QVERIFY( tool->isClickAndDrag( QPoint( 0, 10 ), QPoint( 5, 10 ) ) );
91   QVERIFY( tool->isClickAndDrag( QPoint( 0, 10 ), QPoint( 5, 15 ) ) );
92   QVERIFY( tool->isClickAndDrag( QPoint( 5, 10 ), QPoint( 5, 15 ) ) );
93   QVERIFY( !tool->isClickAndDrag( QPoint( 0, 10 ), QPoint( 1, 11 ) ) );
94   QVERIFY( !tool->isClickAndDrag( QPoint( 1, 10 ), QPoint( 1, 11 ) ) );
95   QVERIFY( !tool->isClickAndDrag( QPoint( 0, 10 ), QPoint( 1, 10 ) ) );
96   QVERIFY( !tool->isClickAndDrag( QPoint( 0, 10 ), QPoint( 0, 10 ) ) );
97 
98   QSignalSpy spySetTool( view, &QgsLayoutView::toolSet );
99   QSignalSpy spyToolActivated( tool, &QgsLayoutViewTool::activated );
100   QSignalSpy spyToolActivated2( tool2, &QgsLayoutViewTool::activated );
101   QSignalSpy spyToolDeactivated( tool, &QgsLayoutViewTool::deactivated );
102   QSignalSpy spyToolDeactivated2( tool2, &QgsLayoutViewTool::deactivated );
103   view->setTool( tool );
104   QCOMPARE( view->tool(), tool );
105   QCOMPARE( spySetTool.count(), 1 );
106   QCOMPARE( spyToolActivated.count(), 1 );
107   QCOMPARE( spyToolDeactivated.count(), 0 );
108 
109   view->setTool( tool2 );
110   QCOMPARE( view->tool(), tool2 );
111   QCOMPARE( spySetTool.count(), 2 );
112   QCOMPARE( spyToolActivated.count(), 1 );
113   QCOMPARE( spyToolDeactivated.count(), 1 );
114   QCOMPARE( spyToolActivated2.count(), 1 );
115   QCOMPARE( spyToolDeactivated2.count(), 0 );
116 
117   delete tool2;
118   QVERIFY( !view->tool() );
119   QCOMPARE( spySetTool.count(), 3 );
120   QCOMPARE( spyToolActivated.count(), 1 );
121   QCOMPARE( spyToolDeactivated.count(), 1 );
122   QCOMPARE( spyToolActivated2.count(), 1 );
123   QCOMPARE( spyToolDeactivated2.count(), 1 );
124 
125   delete view;
126 }
127 
128 class LoggingTool : public QgsLayoutViewTool // clazy:exclude=missing-qobject-macro
129 {
130   public:
131 
LoggingTool(QgsLayoutView * view)132     LoggingTool( QgsLayoutView *view )
133       : QgsLayoutViewTool( view, QStringLiteral( "logging" ) )
134     {}
135 
136     bool receivedMoveEvent = false;
layoutMoveEvent(QgsLayoutViewMouseEvent * event)137     void layoutMoveEvent( QgsLayoutViewMouseEvent *event ) override
138     {
139       receivedMoveEvent = true;
140       QCOMPARE( event->layoutPoint().x(), 8.0 );
141       QCOMPARE( event->layoutPoint().y(), 6.0 );
142     }
143 
144     bool receivedDoubleClickEvent = false;
layoutDoubleClickEvent(QgsLayoutViewMouseEvent * event)145     void layoutDoubleClickEvent( QgsLayoutViewMouseEvent *event ) override
146     {
147       receivedDoubleClickEvent = true;
148       QCOMPARE( event->layoutPoint().x(), 8.0 );
149       QCOMPARE( event->layoutPoint().y(), 6.0 );
150     }
151 
152     bool receivedPressEvent = false;
layoutPressEvent(QgsLayoutViewMouseEvent * event)153     void layoutPressEvent( QgsLayoutViewMouseEvent *event ) override
154     {
155       receivedPressEvent  = true;
156       QCOMPARE( event->layoutPoint().x(), 8.0 );
157       QCOMPARE( event->layoutPoint().y(), 6.0 );
158     }
159 
160     bool receivedReleaseEvent = false;
layoutReleaseEvent(QgsLayoutViewMouseEvent * event)161     void layoutReleaseEvent( QgsLayoutViewMouseEvent *event ) override
162     {
163       receivedReleaseEvent  = true;
164       QCOMPARE( event->layoutPoint().x(), 8.0 );
165       QCOMPARE( event->layoutPoint().y(), 6.0 );
166     }
167 
168     bool receivedWheelEvent = false;
wheelEvent(QWheelEvent *)169     void wheelEvent( QWheelEvent * ) override
170     {
171       receivedWheelEvent = true;
172     }
173 
174     bool receivedKeyPressEvent = false;
keyPressEvent(QKeyEvent *)175     void keyPressEvent( QKeyEvent * ) override
176     {
177       receivedKeyPressEvent  = true;
178     }
179 
180     bool receivedKeyReleaseEvent = false;
keyReleaseEvent(QKeyEvent *)181     void keyReleaseEvent( QKeyEvent * ) override
182     {
183       receivedKeyReleaseEvent = true;
184     }
185 };
186 
events()187 void TestQgsLayoutView::events()
188 {
189   QgsProject p;
190   QgsLayoutView *view = new QgsLayoutView();
191   QgsLayout *layout = new QgsLayout( &p );
192   view->setCurrentLayout( layout );
193   layout->setSceneRect( 0, 0, 1000, 1000 );
194   view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
195   view->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
196   view->setFrameStyle( 0 );
197   view->resize( 100, 100 );
198   view->setFixedSize( 100, 100 );
199   QCOMPARE( view->width(), 100 );
200   QCOMPARE( view->height(), 100 );
201 
202   QTransform transform;
203   transform.scale( 10, 10 );
204   view->setTransform( transform );
205 
206   LoggingTool *tool = new LoggingTool( view );
207   view->setTool( tool );
208 
209   QPointF point( 80, 60 );
210   QMouseEvent press( QEvent::MouseButtonPress, point,
211                      Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
212   QMouseEvent move( QEvent::MouseMove, point,
213                     Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
214   QMouseEvent releases( QEvent::MouseButtonRelease, point,
215                         Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
216   QMouseEvent dblClick( QEvent::MouseButtonDblClick, point,
217                         Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
218   QWheelEvent wheelEvent( point, 10,
219                           Qt::LeftButton, Qt::NoModifier );
220   QKeyEvent keyPress( QEvent::KeyPress, 10, Qt::NoModifier );
221   QKeyEvent keyRelease( QEvent::KeyRelease, 10, Qt::NoModifier );
222 
223   view->mouseMoveEvent( &move );
224   QVERIFY( tool->receivedMoveEvent );
225   view->mousePressEvent( &press );
226   QVERIFY( tool->receivedPressEvent );
227   view->mouseReleaseEvent( &releases );
228   QVERIFY( tool->receivedReleaseEvent );
229   view->mouseDoubleClickEvent( &dblClick );
230   QVERIFY( tool->receivedDoubleClickEvent );
231   view->wheelEvent( &wheelEvent );
232   QVERIFY( tool->receivedWheelEvent );
233   view->keyPressEvent( &keyPress );
234   QVERIFY( tool->receivedKeyPressEvent );
235   view->keyReleaseEvent( &keyRelease );
236   QVERIFY( tool->receivedKeyReleaseEvent );
237 }
238 
239 //simple item for testing, since some methods in QgsLayoutItem are pure virtual
240 class TestItem : public QgsLayoutItem // clazy:exclude=missing-qobject-macro
241 {
242   public:
243 
TestItem(QgsLayout * layout)244     TestItem( QgsLayout *layout ) : QgsLayoutItem( layout ) {}
245 
246     int mFlag = 0;
247 
248     //implement pure virtual methods
type() const249     int type() const override { return QgsLayoutItemRegistry::LayoutItem + 101; }
draw(QgsLayoutItemRenderContext &)250     void draw( QgsLayoutItemRenderContext & ) override
251     {    }
252 };
253 
254 QgsLayout *mLayout = nullptr;
255 QString mReport;
256 
257 bool renderCheck( QString testName, QImage &image, int mismatchCount );
258 
guiRegistry()259 void TestQgsLayoutView::guiRegistry()
260 {
261   // test QgsLayoutItemGuiRegistry
262   QgsLayoutItemGuiRegistry registry;
263 
264   // empty registry
265   QVERIFY( !registry.itemMetadata( -1 ) );
266   QVERIFY( registry.itemMetadataIds().isEmpty() );
267   QVERIFY( !registry.createItemWidget( nullptr ) );
268   QVERIFY( !registry.createItemWidget( nullptr ) );
269   std::unique_ptr< TestItem > testItem = qgis::make_unique< TestItem >( nullptr );
270   QVERIFY( !registry.createItemWidget( testItem.get() ) ); // not in registry
271 
272   QSignalSpy spyTypeAdded( &registry, &QgsLayoutItemGuiRegistry::typeAdded );
273 
274   // add a dummy item to registry
275   auto createWidget = []( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
276   {
277     return new QgsLayoutItemBaseWidget( nullptr, item );
278   };
279 
280   auto createRubberBand = []( QgsLayoutView * view )->QgsLayoutViewRubberBand *
281   {
282     return new QgsLayoutViewRectangularRubberBand( view );
283   };
284 
285   QgsLayoutItemGuiMetadata *metadata = new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutItem + 101, QStringLiteral( "mytype" ), QIcon(), createWidget, createRubberBand );
286   QVERIFY( registry.addLayoutItemGuiMetadata( metadata ) );
287   QCOMPARE( spyTypeAdded.count(), 1 );
288   int uuid = registry.itemMetadataIds().value( 0 );
289   QCOMPARE( spyTypeAdded.value( 0 ).at( 0 ).toInt(), uuid );
290 
291   // duplicate type id is allowed
292   metadata = new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutItem + 101, QStringLiteral( "mytype" ), QIcon(), createWidget, createRubberBand );
293   QVERIFY( registry.addLayoutItemGuiMetadata( metadata ) );
294   QCOMPARE( spyTypeAdded.count(), 2 );
295   //retrieve metadata
296   QVERIFY( !registry.itemMetadata( -1 ) );
297   QCOMPARE( registry.itemMetadataIds().count(), 2 );
298 
299   QVERIFY( registry.itemMetadata( uuid ) );
300   QCOMPARE( registry.itemMetadata( uuid )->visibleName(), QStringLiteral( "mytype" ) );
301 
302   QWidget *widget = registry.createItemWidget( testItem.get() );
303   QVERIFY( widget );
304   delete widget;
305 
306   QgsLayoutView *view = new QgsLayoutView();
307   //should use metadata's method
308   QgsLayoutViewRubberBand *band = registry.createItemRubberBand( uuid, view );
309   QVERIFY( band );
310   QVERIFY( dynamic_cast< QgsLayoutViewRectangularRubberBand * >( band ) );
311   QCOMPARE( band->view(), view );
312   delete band;
313 
314   // groups
315   QVERIFY( registry.addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "g1" ) ) ) );
316   QCOMPARE( registry.itemGroup( QStringLiteral( "g1" ) ).id, QStringLiteral( "g1" ) );
317   // can't add duplicate group
318   QVERIFY( !registry.addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "g1" ) ) ) );
319 
320   //creating item
321   QgsLayoutItem *item = registry.createItem( uuid, nullptr );
322   QVERIFY( !item );
323   QgsApplication::layoutItemRegistry()->addLayoutItemType( new QgsLayoutItemMetadata( QgsLayoutItemRegistry::LayoutItem + 101, QStringLiteral( "my type" ), QStringLiteral( "my types" ), []( QgsLayout * layout )->QgsLayoutItem*
324   {
325     return new TestItem( layout );
326   } ) );
327 
328   item = registry.createItem( uuid, nullptr );
329   QVERIFY( item );
330   QCOMPARE( item->type(), QgsLayoutItemRegistry::LayoutItem + 101 );
331   QCOMPARE( static_cast< TestItem * >( item )->mFlag, 0 );
332   delete item;
333 
334   // override create func
335   metadata = new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutItem + 101, QStringLiteral( "mytype" ), QIcon(), createWidget, createRubberBand );
336   metadata->setItemCreationFunction( []( QgsLayout * layout )->QgsLayoutItem*
337   {
338     TestItem *item = new TestItem( layout );
339     item->mFlag = 2;
340     return item;
341   } );
342   QVERIFY( registry.addLayoutItemGuiMetadata( metadata ) );
343   uuid = spyTypeAdded.at( spyTypeAdded.count() - 1 ).at( 0 ).toInt();
344   item = registry.createItem( uuid, nullptr );
345   QVERIFY( item );
346   QCOMPARE( item->type(), QgsLayoutItemRegistry::LayoutItem + 101 );
347   QCOMPARE( static_cast< TestItem * >( item )->mFlag, 2 );
348   delete item;
349 
350 }
351 
rubberBand()352 void TestQgsLayoutView::rubberBand()
353 {
354   QgsLayoutViewRectangularRubberBand band;
355   band.setBrush( QBrush( QColor( 255, 0, 0 ) ) );
356   QCOMPARE( band.brush().color(), QColor( 255, 0, 0 ) );
357   band.setPen( QPen( QColor( 0, 255, 0 ) ) );
358   QCOMPARE( band.pen().color(), QColor( 0, 255, 0 ) );
359 }
360 
361 QGSTEST_MAIN( TestQgsLayoutView )
362 #include "testqgslayoutview.moc"
363