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   const 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   const QSignalSpy spySetTool( view, &QgsLayoutView::toolSet );
99   const QSignalSpy spyToolActivated( tool, &QgsLayoutViewTool::activated );
100   const QSignalSpy spyToolActivated2( tool2, &QgsLayoutViewTool::activated );
101   const QSignalSpy spyToolDeactivated( tool, &QgsLayoutViewTool::deactivated );
102   const 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   const 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   QCOMPARE( registry.metadataIdForItemType( 0 ), -1 );
268   QVERIFY( !registry.createItemWidget( nullptr ) );
269   QVERIFY( !registry.createItemWidget( nullptr ) );
270   const std::unique_ptr< TestItem > testItem = std::make_unique< TestItem >( nullptr );
271   QVERIFY( !registry.createItemWidget( testItem.get() ) ); // not in registry
272 
273   const QSignalSpy spyTypeAdded( &registry, &QgsLayoutItemGuiRegistry::typeAdded );
274 
275   // add a dummy item to registry
276   auto createWidget = []( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
277   {
278     return new QgsLayoutItemBaseWidget( nullptr, item );
279   };
280 
281   auto createRubberBand = []( QgsLayoutView * view )->QgsLayoutViewRubberBand *
282   {
283     return new QgsLayoutViewRectangularRubberBand( view );
284   };
285 
286   QgsLayoutItemGuiMetadata *metadata = new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutItem + 101, QStringLiteral( "mytype" ), QIcon(), createWidget, createRubberBand );
287   QVERIFY( registry.addLayoutItemGuiMetadata( metadata ) );
288   QCOMPARE( spyTypeAdded.count(), 1 );
289   int uuid = registry.itemMetadataIds().value( 0 );
290   QCOMPARE( spyTypeAdded.value( 0 ).at( 0 ).toInt(), uuid );
291   QCOMPARE( registry.metadataIdForItemType( QgsLayoutItemRegistry::LayoutItem + 101 ), uuid );
292 
293   // duplicate type id is allowed
294   metadata = new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutItem + 101, QStringLiteral( "mytype" ), QIcon(), createWidget, createRubberBand );
295   QVERIFY( registry.addLayoutItemGuiMetadata( metadata ) );
296   QCOMPARE( spyTypeAdded.count(), 2 );
297   //retrieve metadata
298   QVERIFY( !registry.itemMetadata( -1 ) );
299   QCOMPARE( registry.itemMetadataIds().count(), 2 );
300   QCOMPARE( registry.metadataIdForItemType( QgsLayoutItemRegistry::LayoutItem + 101 ), uuid );
301 
302   QVERIFY( registry.itemMetadata( uuid ) );
303   QCOMPARE( registry.itemMetadata( uuid )->visibleName(), QStringLiteral( "mytype" ) );
304 
305   QWidget *widget = registry.createItemWidget( testItem.get() );
306   QVERIFY( widget );
307   delete widget;
308 
309   QgsLayoutView *view = new QgsLayoutView();
310   //should use metadata's method
311   QgsLayoutViewRubberBand *band = registry.createItemRubberBand( uuid, view );
312   QVERIFY( band );
313   QVERIFY( dynamic_cast< QgsLayoutViewRectangularRubberBand * >( band ) );
314   QCOMPARE( band->view(), view );
315   delete band;
316 
317   // groups
318   QVERIFY( registry.addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "g1" ) ) ) );
319   QCOMPARE( registry.itemGroup( QStringLiteral( "g1" ) ).id, QStringLiteral( "g1" ) );
320   // can't add duplicate group
321   QVERIFY( !registry.addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "g1" ) ) ) );
322 
323   //creating item
324   QgsLayoutItem *item = registry.createItem( uuid, nullptr );
325   QVERIFY( !item );
326   QgsApplication::layoutItemRegistry()->addLayoutItemType( new QgsLayoutItemMetadata( QgsLayoutItemRegistry::LayoutItem + 101, QStringLiteral( "my type" ), QStringLiteral( "my types" ), []( QgsLayout * layout )->QgsLayoutItem*
327   {
328     return new TestItem( layout );
329   } ) );
330 
331   item = registry.createItem( uuid, nullptr );
332   QVERIFY( item );
333   QCOMPARE( item->type(), QgsLayoutItemRegistry::LayoutItem + 101 );
334   QCOMPARE( static_cast< TestItem * >( item )->mFlag, 0 );
335   delete item;
336 
337   // override create func
338   metadata = new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutItem + 101, QStringLiteral( "mytype" ), QIcon(), createWidget, createRubberBand );
339   metadata->setItemCreationFunction( []( QgsLayout * layout )->QgsLayoutItem*
340   {
341     TestItem *item = new TestItem( layout );
342     item->mFlag = 2;
343     return item;
344   } );
345   QVERIFY( registry.addLayoutItemGuiMetadata( metadata ) );
346   uuid = spyTypeAdded.at( spyTypeAdded.count() - 1 ).at( 0 ).toInt();
347   item = registry.createItem( uuid, nullptr );
348   QVERIFY( item );
349   QCOMPARE( item->type(), QgsLayoutItemRegistry::LayoutItem + 101 );
350   QCOMPARE( static_cast< TestItem * >( item )->mFlag, 2 );
351   delete item;
352 
353 }
354 
rubberBand()355 void TestQgsLayoutView::rubberBand()
356 {
357   QgsLayoutViewRectangularRubberBand band;
358   band.setBrush( QBrush( QColor( 255, 0, 0 ) ) );
359   QCOMPARE( band.brush().color(), QColor( 255, 0, 0 ) );
360   band.setPen( QPen( QColor( 0, 255, 0 ) ) );
361   QCOMPARE( band.pen().color(), QColor( 0, 255, 0 ) );
362 }
363 
364 QGSTEST_MAIN( TestQgsLayoutView )
365 #include "testqgslayoutview.moc"
366