1# -*- coding: utf-8 -*-
2"""QGIS Unit tests for QgsLayoutView.
3
4.. note:: This program is free software; you can redistribute it and/or modify
5it under the terms of the GNU General Public License as published by
6the Free Software Foundation; either version 2 of the License, or
7(at your option) any later version.
8"""
9__author__ = 'Nyall Dawson'
10__date__ = '05/07/2017'
11__copyright__ = 'Copyright 2017, The QGIS Project'
12
13import qgis  # NOQA
14from qgis.PyQt import sip
15from qgis.core import (QgsProject,
16                       QgsLayout,
17                       QgsUnitTypes,
18                       QgsLayoutItemPicture,
19                       QgsLayoutItemLabel,
20                       QgsLayoutItemHtml,
21                       QgsLayoutItemRegistry,
22                       QgsLayoutFrame,
23                       QgsLayoutPoint,
24                       QgsLayoutSize,
25                       QgsLayoutAligner)
26from qgis.gui import QgsLayoutView
27from qgis.PyQt.QtCore import QRectF, QMimeData, QByteArray
28from qgis.PyQt.QtGui import QTransform
29from qgis.PyQt.QtWidgets import QApplication
30from qgis.PyQt.QtTest import QSignalSpy
31
32from qgis.testing import start_app, unittest
33
34start_app()
35
36
37class TestQgsLayoutView(unittest.TestCase):
38
39    def testScaleSafe(self):
40        """ test scaleSafe method """
41
42        view = QgsLayoutView()
43        view.fitInView(QRectF(0, 0, 10, 10))
44        scale = view.transform().m11()
45        view.scaleSafe(2)
46        self.assertAlmostEqual(view.transform().m11(), 2)
47        view.scaleSafe(4)
48        self.assertAlmostEqual(view.transform().m11(), 8)
49
50        # try to zoom in heaps
51        view.scaleSafe(99999999)
52        # assume we have hit the limit
53        scale = view.transform().m11()
54        view.scaleSafe(2)
55        self.assertAlmostEqual(view.transform().m11(), scale)
56
57        view.setTransform(QTransform.fromScale(1, 1))
58        self.assertAlmostEqual(view.transform().m11(), 1)
59        # test zooming out
60        view.scaleSafe(0.5)
61        self.assertAlmostEqual(view.transform().m11(), 0.5)
62        view.scaleSafe(0.1)
63        self.assertAlmostEqual(view.transform().m11(), 0.05)
64
65        # try zooming out heaps
66        view.scaleSafe(0.000000001)
67        # assume we have hit the limit
68        scale = view.transform().m11()
69        view.scaleSafe(0.5)
70        self.assertAlmostEqual(view.transform().m11(), scale)
71
72    def testLayoutScalePixels(self):
73        p = QgsProject()
74        l = QgsLayout(p)
75        l.setUnits(QgsUnitTypes.LayoutPixels)
76        view = QgsLayoutView()
77        view.setCurrentLayout(l)
78        view.setZoomLevel(1)
79        # should be no transform, since 100% with pixel units should be pixel-pixel
80        self.assertEqual(view.transform().m11(), 1)
81        view.setZoomLevel(0.5)
82        self.assertEqual(view.transform().m11(), 0.5)
83
84    def testSelectAll(self):
85        p = QgsProject()
86        l = QgsLayout(p)
87
88        # add some items
89        item1 = QgsLayoutItemPicture(l)
90        l.addItem(item1)
91        item2 = QgsLayoutItemPicture(l)
92        l.addItem(item2)
93        item3 = QgsLayoutItemPicture(l)
94        item3.setLocked(True)
95        l.addItem(item3)
96
97        view = QgsLayoutView()
98        # no layout, no crash
99        view.selectAll()
100
101        view.setCurrentLayout(l)
102
103        focused_item_spy = QSignalSpy(view.itemFocused)
104
105        view.selectAll()
106        self.assertTrue(item1.isSelected())
107        self.assertTrue(item2.isSelected())
108        self.assertFalse(item3.isSelected())  # locked
109
110        self.assertEqual(len(focused_item_spy), 1)
111
112        item3.setSelected(True)  # locked item selection should be cleared
113        view.selectAll()
114        self.assertTrue(item1.isSelected())
115        self.assertTrue(item2.isSelected())
116        self.assertFalse(item3.isSelected())  # locked
117
118    def testDeselectAll(self):
119        p = QgsProject()
120        l = QgsLayout(p)
121
122        # add some items
123        item1 = QgsLayoutItemPicture(l)
124        l.addItem(item1)
125        item2 = QgsLayoutItemPicture(l)
126        l.addItem(item2)
127        item3 = QgsLayoutItemPicture(l)
128        item3.setLocked(True)
129        l.addItem(item3)
130
131        view = QgsLayoutView()
132        # no layout, no crash
133        view.deselectAll()
134
135        view.setCurrentLayout(l)
136
137        focused_item_spy = QSignalSpy(view.itemFocused)
138
139        view.deselectAll()
140        self.assertFalse(item1.isSelected())
141        self.assertFalse(item2.isSelected())
142        self.assertFalse(item3.isSelected())
143
144        self.assertEqual(len(focused_item_spy), 1)
145
146        item1.setSelected(True)
147        item2.setSelected(True)
148        item3.setSelected(True)
149        view.deselectAll()
150        self.assertFalse(item1.isSelected())
151        self.assertFalse(item2.isSelected())
152        self.assertFalse(item3.isSelected())
153
154    def testInvertSelection(self):
155        p = QgsProject()
156        l = QgsLayout(p)
157
158        # add some items
159        item1 = QgsLayoutItemPicture(l)
160        l.addItem(item1)
161        item2 = QgsLayoutItemPicture(l)
162        l.addItem(item2)
163        item3 = QgsLayoutItemPicture(l)
164        item3.setLocked(True)
165        l.addItem(item3)
166
167        view = QgsLayoutView()
168        # no layout, no crash
169        view.invertSelection()
170
171        view.setCurrentLayout(l)
172
173        focused_item_spy = QSignalSpy(view.itemFocused)
174
175        view.invertSelection()
176        self.assertTrue(item1.isSelected())
177        self.assertTrue(item2.isSelected())
178        self.assertFalse(item3.isSelected())  # locked
179
180        self.assertEqual(len(focused_item_spy), 1)
181
182        item3.setSelected(True)  # locked item selection should be cleared
183        view.invertSelection()
184        self.assertFalse(item1.isSelected())
185        self.assertFalse(item2.isSelected())
186        self.assertFalse(item3.isSelected())  # locked
187
188    def testSelectNextByZOrder(self):
189        p = QgsProject()
190        l = QgsLayout(p)
191
192        # add some items
193        item1 = QgsLayoutItemPicture(l)
194        l.addItem(item1)
195        item2 = QgsLayoutItemPicture(l)
196        l.addItem(item2)
197        item3 = QgsLayoutItemPicture(l)
198        item3.setLocked(True)
199        l.addItem(item3)
200
201        view = QgsLayoutView()
202        # no layout, no crash
203        view.selectNextItemAbove()
204        view.selectNextItemBelow()
205
206        view.setCurrentLayout(l)
207
208        focused_item_spy = QSignalSpy(view.itemFocused)
209
210        # no selection
211        view.selectNextItemAbove()
212        view.selectNextItemBelow()
213        self.assertEqual(len(focused_item_spy), 0)
214
215        l.setSelectedItem(item1)
216        self.assertEqual(len(focused_item_spy), 1)
217        # already bottom most
218        view.selectNextItemBelow()
219        self.assertTrue(item1.isSelected())
220        self.assertFalse(item2.isSelected())
221        self.assertFalse(item3.isSelected())
222        self.assertEqual(len(focused_item_spy), 1)
223
224        view.selectNextItemAbove()
225        self.assertFalse(item1.isSelected())
226        self.assertTrue(item2.isSelected())
227        self.assertFalse(item3.isSelected())
228        self.assertEqual(len(focused_item_spy), 2)
229
230        view.selectNextItemAbove()
231        self.assertFalse(item1.isSelected())
232        self.assertFalse(item2.isSelected())
233        self.assertTrue(item3.isSelected())
234        self.assertEqual(len(focused_item_spy), 3)
235
236        view.selectNextItemAbove()  # already top most
237        self.assertFalse(item1.isSelected())
238        self.assertFalse(item2.isSelected())
239        self.assertTrue(item3.isSelected())
240        self.assertEqual(len(focused_item_spy), 3)
241
242        view.selectNextItemBelow()
243        self.assertFalse(item1.isSelected())
244        self.assertTrue(item2.isSelected())
245        self.assertFalse(item3.isSelected())
246        self.assertEqual(len(focused_item_spy), 4)
247
248        view.selectNextItemBelow()
249        self.assertTrue(item1.isSelected())
250        self.assertFalse(item2.isSelected())
251        self.assertFalse(item3.isSelected())
252        self.assertEqual(len(focused_item_spy), 5)
253
254        view.selectNextItemBelow()  # back to bottom most
255        self.assertTrue(item1.isSelected())
256        self.assertFalse(item2.isSelected())
257        self.assertFalse(item3.isSelected())
258        self.assertEqual(len(focused_item_spy), 5)
259
260    def testLockActions(self):
261        p = QgsProject()
262        l = QgsLayout(p)
263
264        view = QgsLayoutView()
265        view.setCurrentLayout(l)
266
267        # add some items
268        item1 = QgsLayoutItemPicture(l)
269        l.addItem(item1)
270        item2 = QgsLayoutItemPicture(l)
271        l.addItem(item2)
272        item3 = QgsLayoutItemPicture(l)
273        l.addItem(item3)
274
275        item1.setLocked(True)
276        item3.setLocked(True)
277        self.assertTrue(item1.isLocked())
278        self.assertFalse(item2.isLocked())
279        self.assertTrue(item3.isLocked())
280
281        view.unlockAllItems()
282        self.assertFalse(item1.isLocked())
283        self.assertFalse(item2.isLocked())
284        self.assertFalse(item3.isLocked())
285        self.assertTrue(item1.isSelected())
286        self.assertFalse(item2.isSelected())
287        self.assertTrue(item3.isSelected())
288
289        view.lockSelectedItems()
290        self.assertTrue(item1.isLocked())
291        self.assertFalse(item2.isLocked())
292        self.assertTrue(item3.isLocked())
293        self.assertFalse(item1.isSelected())
294        self.assertFalse(item2.isSelected())
295        self.assertFalse(item3.isSelected())
296
297    def testStacking(self):
298        p = QgsProject()
299        l = QgsLayout(p)
300
301        # add some items
302        item1 = QgsLayoutItemPicture(l)
303        l.addLayoutItem(item1)
304        item2 = QgsLayoutItemPicture(l)
305        l.addLayoutItem(item2)
306        item3 = QgsLayoutItemPicture(l)
307        l.addLayoutItem(item3)
308
309        view = QgsLayoutView()
310        view.setCurrentLayout(l)
311
312        self.assertEqual(item1.zValue(), 1)
313        self.assertEqual(item2.zValue(), 2)
314        self.assertEqual(item3.zValue(), 3)
315
316        # no effect interactions
317        view.raiseSelectedItems()
318        view.lowerSelectedItems()
319        view.moveSelectedItemsToTop()
320        view.moveSelectedItemsToBottom()
321
322        self.assertEqual(item1.zValue(), 1)
323        self.assertEqual(item2.zValue(), 2)
324        self.assertEqual(item3.zValue(), 3)
325
326        # raising
327        item3.setSelected(True)
328        view.raiseSelectedItems()
329        self.assertEqual(item1.zValue(), 1)
330        self.assertEqual(item2.zValue(), 2)
331        self.assertEqual(item3.zValue(), 3)
332
333        item3.setSelected(False)
334        item2.setSelected(True)
335        view.raiseSelectedItems()
336        self.assertEqual(item1.zValue(), 1)
337        self.assertEqual(item2.zValue(), 3)
338        self.assertEqual(item3.zValue(), 2)
339
340        view.raiseSelectedItems()
341        self.assertEqual(item1.zValue(), 1)
342        self.assertEqual(item2.zValue(), 3)
343        self.assertEqual(item3.zValue(), 2)
344
345        item2.setSelected(False)
346        item1.setSelected(True)
347        view.raiseSelectedItems()
348        self.assertEqual(item1.zValue(), 2)
349        self.assertEqual(item2.zValue(), 3)
350        self.assertEqual(item3.zValue(), 1)
351
352        # lower
353        item1.setSelected(False)
354        item3.setSelected(True)
355        view.lowerSelectedItems()
356        self.assertEqual(item1.zValue(), 2)
357        self.assertEqual(item2.zValue(), 3)
358        self.assertEqual(item3.zValue(), 1)
359
360        item3.setSelected(False)
361        item2.setSelected(True)
362        view.lowerSelectedItems()
363        self.assertEqual(item1.zValue(), 3)
364        self.assertEqual(item2.zValue(), 2)
365        self.assertEqual(item3.zValue(), 1)
366
367        view.lowerSelectedItems()
368        self.assertEqual(item1.zValue(), 3)
369        self.assertEqual(item2.zValue(), 1)
370        self.assertEqual(item3.zValue(), 2)
371
372        # raise to top
373        item2.setSelected(False)
374        item1.setSelected(True)
375        view.moveSelectedItemsToTop()
376        self.assertEqual(item1.zValue(), 3)
377        self.assertEqual(item2.zValue(), 1)
378        self.assertEqual(item3.zValue(), 2)
379
380        item1.setSelected(False)
381        item3.setSelected(True)
382        view.moveSelectedItemsToTop()
383        self.assertEqual(item1.zValue(), 2)
384        self.assertEqual(item2.zValue(), 1)
385        self.assertEqual(item3.zValue(), 3)
386
387        item3.setSelected(False)
388        item2.setSelected(True)
389        view.moveSelectedItemsToTop()
390        self.assertEqual(item1.zValue(), 1)
391        self.assertEqual(item2.zValue(), 3)
392        self.assertEqual(item3.zValue(), 2)
393
394        # move to bottom
395        item2.setSelected(False)
396        item1.setSelected(True)
397        view.moveSelectedItemsToBottom()
398        self.assertEqual(item1.zValue(), 1)
399        self.assertEqual(item2.zValue(), 3)
400        self.assertEqual(item3.zValue(), 2)
401
402        item1.setSelected(False)
403        item3.setSelected(True)
404        view.moveSelectedItemsToBottom()
405        self.assertEqual(item1.zValue(), 2)
406        self.assertEqual(item2.zValue(), 3)
407        self.assertEqual(item3.zValue(), 1)
408
409        item3.setSelected(False)
410        item2.setSelected(True)
411        view.moveSelectedItemsToBottom()
412        self.assertEqual(item1.zValue(), 3)
413        self.assertEqual(item2.zValue(), 1)
414        self.assertEqual(item3.zValue(), 2)
415
416    def testAlign(self):
417        p = QgsProject()
418        l = QgsLayout(p)
419
420        # add some items
421        item1 = QgsLayoutItemPicture(l)
422        item1.attemptMove(QgsLayoutPoint(4, 8, QgsUnitTypes.LayoutMillimeters))
423        item1.attemptResize(QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
424        l.addItem(item1)
425        item2 = QgsLayoutItemPicture(l)
426        item2.attemptMove(QgsLayoutPoint(6, 10, QgsUnitTypes.LayoutMillimeters))
427        item2.attemptResize(QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
428        l.addItem(item2)
429        item3 = QgsLayoutItemPicture(l)
430        item3.attemptMove(QgsLayoutPoint(0.8, 1.2, QgsUnitTypes.LayoutCentimeters))
431        item3.attemptResize(QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
432        l.addItem(item3)
433
434        view = QgsLayoutView()
435        view.setCurrentLayout(l)
436
437        view.alignSelectedItems(QgsLayoutAligner.AlignLeft)
438
439        item1.setSelected(True)
440        item2.setSelected(True)
441        item3.setSelected(True)
442
443        view.alignSelectedItems(QgsLayoutAligner.AlignLeft)
444        self.assertEqual(item1.positionWithUnits(), QgsLayoutPoint(4, 8, QgsUnitTypes.LayoutMillimeters))
445        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
446        self.assertEqual(item2.positionWithUnits(), QgsLayoutPoint(4, 10, QgsUnitTypes.LayoutMillimeters))
447        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
448        self.assertEqual(item3.positionWithUnits(), QgsLayoutPoint(0.4, 1.2, QgsUnitTypes.LayoutCentimeters))
449        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
450
451        view.alignSelectedItems(QgsLayoutAligner.AlignHCenter)
452        self.assertEqual(item1.positionWithUnits(), QgsLayoutPoint(4, 8, QgsUnitTypes.LayoutMillimeters))
453        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
454        self.assertEqual(item2.positionWithUnits(), QgsLayoutPoint(8, 10, QgsUnitTypes.LayoutMillimeters))
455        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
456        self.assertEqual(item3.positionWithUnits(), QgsLayoutPoint(0.4, 1.2, QgsUnitTypes.LayoutCentimeters))
457        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
458
459        view.alignSelectedItems(QgsLayoutAligner.AlignRight)
460        self.assertEqual(item1.positionWithUnits(), QgsLayoutPoint(4, 8, QgsUnitTypes.LayoutMillimeters))
461        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
462        self.assertEqual(item2.positionWithUnits(), QgsLayoutPoint(12, 10, QgsUnitTypes.LayoutMillimeters))
463        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
464        self.assertEqual(item3.positionWithUnits(), QgsLayoutPoint(0.4, 1.2, QgsUnitTypes.LayoutCentimeters))
465        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
466
467        view.alignSelectedItems(QgsLayoutAligner.AlignTop)
468        self.assertEqual(item1.positionWithUnits(), QgsLayoutPoint(4, 8, QgsUnitTypes.LayoutMillimeters))
469        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
470        self.assertEqual(item2.positionWithUnits(), QgsLayoutPoint(12, 8, QgsUnitTypes.LayoutMillimeters))
471        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
472        self.assertEqual(item3.positionWithUnits(), QgsLayoutPoint(0.4, 0.8, QgsUnitTypes.LayoutCentimeters))
473        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
474
475        view.alignSelectedItems(QgsLayoutAligner.AlignVCenter)
476        self.assertEqual(item1.positionWithUnits(), QgsLayoutPoint(4, 10, QgsUnitTypes.LayoutMillimeters))
477        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
478        self.assertEqual(item2.positionWithUnits(), QgsLayoutPoint(12, 11.5, QgsUnitTypes.LayoutMillimeters))
479        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
480        self.assertEqual(item3.positionWithUnits(), QgsLayoutPoint(0.4, 0.8, QgsUnitTypes.LayoutCentimeters))
481        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
482
483        view.alignSelectedItems(QgsLayoutAligner.AlignBottom)
484        self.assertEqual(item1.positionWithUnits(), QgsLayoutPoint(4, 12, QgsUnitTypes.LayoutMillimeters))
485        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
486        self.assertEqual(item2.positionWithUnits(), QgsLayoutPoint(12, 15, QgsUnitTypes.LayoutMillimeters))
487        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
488        self.assertEqual(item3.positionWithUnits(), QgsLayoutPoint(0.4, 0.8, QgsUnitTypes.LayoutCentimeters))
489        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
490
491    def testDistribute(self):
492        p = QgsProject()
493        l = QgsLayout(p)
494
495        # add some items
496        item1 = QgsLayoutItemPicture(l)
497        item1.attemptMove(QgsLayoutPoint(4, 8, QgsUnitTypes.LayoutMillimeters))
498        item1.attemptResize(QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
499        l.addItem(item1)
500        item2 = QgsLayoutItemPicture(l)
501        item2.attemptMove(QgsLayoutPoint(7, 10, QgsUnitTypes.LayoutMillimeters))
502        item2.attemptResize(QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
503        l.addItem(item2)
504        item3 = QgsLayoutItemPicture(l)
505        item3.attemptMove(QgsLayoutPoint(0.8, 1.2, QgsUnitTypes.LayoutCentimeters))
506        item3.attemptResize(QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
507        l.addItem(item3)
508
509        view = QgsLayoutView()
510        view.setCurrentLayout(l)
511
512        view.distributeSelectedItems(QgsLayoutAligner.DistributeLeft)
513
514        item1.setSelected(True)
515        item2.setSelected(True)
516        item3.setSelected(True)
517
518        view.distributeSelectedItems(QgsLayoutAligner.DistributeLeft)
519        self.assertEqual(item1.positionWithUnits(), QgsLayoutPoint(4, 8, QgsUnitTypes.LayoutMillimeters))
520        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
521        self.assertAlmostEqual(item2.positionWithUnits().x(), 6.0, 3)
522        self.assertEqual(item2.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
523        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
524        self.assertAlmostEqual(item3.positionWithUnits().x(), 0.8, 3)
525        self.assertEqual(item3.positionWithUnits().units(), QgsUnitTypes.LayoutCentimeters)
526        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
527
528        view.distributeSelectedItems(QgsLayoutAligner.DistributeHCenter)
529        self.assertAlmostEqual(item1.positionWithUnits().x(), 5.0, 3)
530        self.assertEqual(item1.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
531        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
532        self.assertAlmostEqual(item2.positionWithUnits().x(), 6.0, 3)
533        self.assertEqual(item2.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
534        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
535        self.assertAlmostEqual(item3.positionWithUnits().x(), 0.8, 3)
536        self.assertEqual(item3.positionWithUnits().units(), QgsUnitTypes.LayoutCentimeters)
537        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
538
539        view.distributeSelectedItems(QgsLayoutAligner.DistributeRight)
540        self.assertAlmostEqual(item1.positionWithUnits().x(), 3.0, 3)
541        self.assertEqual(item1.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
542        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
543        self.assertAlmostEqual(item2.positionWithUnits().x(), 6.0, 3)
544        self.assertEqual(item2.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
545        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
546        self.assertAlmostEqual(item3.positionWithUnits().x(), 0.8, 3)
547        self.assertEqual(item3.positionWithUnits().units(), QgsUnitTypes.LayoutCentimeters)
548        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
549
550        view.distributeSelectedItems(QgsLayoutAligner.DistributeTop)
551        self.assertAlmostEqual(item1.positionWithUnits().y(), 8.0, 3)
552        self.assertEqual(item1.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
553        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
554        self.assertAlmostEqual(item2.positionWithUnits().y(), 10.0, 3)
555        self.assertEqual(item2.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
556        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
557        self.assertAlmostEqual(item3.positionWithUnits().y(), 1.2, 3)
558        self.assertEqual(item3.positionWithUnits().units(), QgsUnitTypes.LayoutCentimeters)
559        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
560
561        view.distributeSelectedItems(QgsLayoutAligner.DistributeVCenter)
562        self.assertAlmostEqual(item1.positionWithUnits().y(), 8.0, 3)
563        self.assertEqual(item1.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
564        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
565        self.assertAlmostEqual(item2.positionWithUnits().y(), 12.5, 3)
566        self.assertEqual(item2.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
567        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
568        self.assertAlmostEqual(item3.positionWithUnits().y(), 1.2, 3)
569        self.assertEqual(item3.positionWithUnits().units(), QgsUnitTypes.LayoutCentimeters)
570        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
571
572        view.distributeSelectedItems(QgsLayoutAligner.DistributeBottom)
573        self.assertAlmostEqual(item1.positionWithUnits().y(), 8.0, 3)
574        self.assertEqual(item1.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
575        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
576        self.assertAlmostEqual(item2.positionWithUnits().y(), 15.0, 3)
577        self.assertEqual(item2.positionWithUnits().units(), QgsUnitTypes.LayoutMillimeters)
578        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
579        self.assertAlmostEqual(item3.positionWithUnits().y(), 1.2, 3)
580        self.assertEqual(item3.positionWithUnits().units(), QgsUnitTypes.LayoutCentimeters)
581        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
582
583    def testResize(self):
584        p = QgsProject()
585        l = QgsLayout(p)
586
587        # add some items
588        item1 = QgsLayoutItemPicture(l)
589        item1.attemptMove(QgsLayoutPoint(4, 8, QgsUnitTypes.LayoutMillimeters))
590        item1.attemptResize(QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
591        l.addItem(item1)
592        item2 = QgsLayoutItemPicture(l)
593        item2.attemptMove(QgsLayoutPoint(7, 10, QgsUnitTypes.LayoutMillimeters))
594        item2.attemptResize(QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
595        l.addItem(item2)
596        item3 = QgsLayoutItemPicture(l)
597        item3.attemptMove(QgsLayoutPoint(0.8, 1.2, QgsUnitTypes.LayoutCentimeters))
598        item3.attemptResize(QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
599        l.addItem(item3)
600
601        view = QgsLayoutView()
602        view.setCurrentLayout(l)
603
604        view.resizeSelectedItems(QgsLayoutAligner.ResizeNarrowest)
605
606        item1.setSelected(True)
607        item2.setSelected(True)
608        item3.setSelected(True)
609
610        view.resizeSelectedItems(QgsLayoutAligner.ResizeNarrowest)
611        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(10, 12, QgsUnitTypes.LayoutMillimeters))
612        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
613        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.0, 1.6, QgsUnitTypes.LayoutCentimeters))
614        l.undoStack().stack().undo()
615
616        view.resizeSelectedItems(QgsLayoutAligner.ResizeWidest)
617        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
618        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(18, 9, QgsUnitTypes.LayoutMillimeters))
619        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
620        l.undoStack().stack().undo()
621
622        view.resizeSelectedItems(QgsLayoutAligner.ResizeShortest)
623        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 9, QgsUnitTypes.LayoutMillimeters))
624        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
625        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 0.9, QgsUnitTypes.LayoutCentimeters))
626        l.undoStack().stack().undo()
627
628        view.resizeSelectedItems(QgsLayoutAligner.ResizeTallest)
629        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 16, QgsUnitTypes.LayoutMillimeters))
630        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 16, QgsUnitTypes.LayoutMillimeters))
631        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
632        l.undoStack().stack().undo()
633
634        item2.attemptResize(QgsLayoutSize(10, 19, QgsUnitTypes.LayoutMillimeters))
635        view.resizeSelectedItems(QgsLayoutAligner.ResizeToSquare)
636        self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 18, QgsUnitTypes.LayoutMillimeters))
637        self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(19, 19, QgsUnitTypes.LayoutMillimeters))
638        self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.8, QgsUnitTypes.LayoutCentimeters))
639
640    def testDeleteItems(self):
641        p = QgsProject()
642        l = QgsLayout(p)
643
644        # add some items
645        item1 = QgsLayoutItemLabel(l)
646        item1.setText('label 1')
647        l.addLayoutItem(item1)
648        item2 = QgsLayoutItemLabel(l)
649        item2.setText('label 2')
650        l.addLayoutItem(item2)
651        item3 = QgsLayoutItemLabel(l)
652        item3.setText('label 2')
653        l.addLayoutItem(item3)
654
655        view = QgsLayoutView()
656        view.setCurrentLayout(l)
657        count_before = len(l.items())
658        view.deleteSelectedItems()
659        self.assertEqual(len(l.items()), count_before)
660
661        item2.setSelected(True)
662        view.deleteSelectedItems()
663        self.assertEqual(len(l.items()), count_before - 1)
664        self.assertIn(item1, l.items())
665        self.assertIn(item3, l.items())
666        view.deleteItems([item3])
667        self.assertEqual(len(l.items()), count_before - 2)
668        self.assertIn(item1, l.items())
669
670    def testCopyPaste(self):
671        p = QgsProject()
672        l = QgsLayout(p)
673
674        # clear clipboard
675        mime_data = QMimeData()
676        mime_data.setData("text/xml", QByteArray())
677        clipboard = QApplication.clipboard()
678        clipboard.setMimeData(mime_data)
679
680        # add an item
681        item1 = QgsLayoutItemLabel(l)
682        item1.setText('label 1')
683        l.addLayoutItem(item1)
684        item1.setSelected(True)
685        item2 = QgsLayoutItemLabel(l)
686        item2.setText('label 2')
687        l.addLayoutItem(item2)
688        item2.setSelected(True)
689
690        # multiframes
691        multiframe1 = QgsLayoutItemHtml(l)
692        multiframe1.setHtml('mf1')
693        l.addMultiFrame(multiframe1)
694        frame1 = QgsLayoutFrame(l, multiframe1)
695        frame1.setId('frame1a')
696        multiframe1.addFrame(frame1)
697        frame1b = QgsLayoutFrame(l, multiframe1)
698        frame1b.setId('frame1b')
699        multiframe1.addFrame(frame1b)  # not selected
700        frame1c = QgsLayoutFrame(l, multiframe1)
701        frame1c.setId('frame1b')
702        multiframe1.addFrame(frame1c)  # not selected
703
704        multiframe2 = QgsLayoutItemHtml(l)
705        multiframe2.setHtml('mf2')
706        l.addMultiFrame(multiframe2)
707        frame2 = QgsLayoutFrame(l, multiframe2)
708        frame2.setId('frame2')
709        multiframe2.addFrame(frame2)
710
711        frame1.setSelected(True)
712        frame2.setSelected(True)
713
714        view = QgsLayoutView()
715        view.setCurrentLayout(l)
716        self.assertFalse(view.hasItemsInClipboard())
717
718        view.copySelectedItems(QgsLayoutView.ClipboardCopy)
719        self.assertTrue(view.hasItemsInClipboard())
720
721        pasted = view.pasteItems(QgsLayoutView.PasteModeCursor)
722        self.assertEqual(len(pasted), 4)
723
724        new_multiframes = [m for m in l.multiFrames() if m not in [multiframe1, multiframe2]]
725        self.assertEqual(len(new_multiframes), 2)
726
727        self.assertIn(pasted[0], l.items())
728        self.assertIn(pasted[1], l.items())
729        labels = [p for p in pasted if p.type() == QgsLayoutItemRegistry.LayoutLabel]
730        self.assertIn(sip.cast(labels[0], QgsLayoutItemLabel).text(), ('label 1', 'label 2'))
731        self.assertIn(sip.cast(labels[1], QgsLayoutItemLabel).text(), ('label 1', 'label 2'))
732        frames = [p for p in pasted if p.type() == QgsLayoutItemRegistry.LayoutFrame]
733        pasted_frame1 = sip.cast(frames[0], QgsLayoutFrame)
734        pasted_frame2 = sip.cast(frames[1], QgsLayoutFrame)
735        self.assertIn(pasted_frame1.multiFrame(), new_multiframes)
736        self.assertIn(new_multiframes[0].frames()[0].uuid(), (pasted_frame1.uuid(), pasted_frame2.uuid()))
737        self.assertIn(pasted_frame2.multiFrame(), new_multiframes)
738        self.assertIn(new_multiframes[1].frames()[0].uuid(), (pasted_frame1.uuid(), pasted_frame2.uuid()))
739
740        self.assertEqual(frame1.multiFrame(), multiframe1)
741        self.assertCountEqual(multiframe1.frames(), [frame1, frame1b, frame1c])
742        self.assertEqual(frame1b.multiFrame(), multiframe1)
743        self.assertEqual(frame1c.multiFrame(), multiframe1)
744        self.assertEqual(frame2.multiFrame(), multiframe2)
745        self.assertCountEqual(multiframe2.frames(), [frame2])
746
747        # copy specific item
748        view.copyItems([item2], QgsLayoutView.ClipboardCopy)
749        l2 = QgsLayout(p)
750        view2 = QgsLayoutView()
751        view2.setCurrentLayout(l2)
752        pasted = view2.pasteItems(QgsLayoutView.PasteModeCursor)
753        self.assertEqual(len(pasted), 1)
754        self.assertIn(pasted[0], l2.items())
755        self.assertEqual(sip.cast(pasted[0], QgsLayoutItemLabel).text(), 'label 2')
756
757    def testCutPaste(self):
758        p = QgsProject()
759        l = QgsLayout(p)
760
761        # clear clipboard
762        mime_data = QMimeData()
763        mime_data.setData("text/xml", QByteArray())
764        clipboard = QApplication.clipboard()
765        clipboard.setMimeData(mime_data)
766
767        # add an item
768        item1 = QgsLayoutItemLabel(l)
769        item1.setText('label 1')
770        l.addLayoutItem(item1)
771        item1.setSelected(True)
772        item2 = QgsLayoutItemLabel(l)
773        item2.setText('label 2')
774        l.addLayoutItem(item2)
775        item2.setSelected(True)
776
777        view = QgsLayoutView()
778        view.setCurrentLayout(l)
779        self.assertFalse(view.hasItemsInClipboard())
780
781        len_before = len(l.items())
782        view.copySelectedItems(QgsLayoutView.ClipboardCut)
783        self.assertTrue(view.hasItemsInClipboard())
784        self.assertEqual(len(l.items()), len_before - 2)
785
786        pasted = view.pasteItems(QgsLayoutView.PasteModeCursor)
787        self.assertEqual(len(pasted), 2)
788        self.assertEqual(len(l.items()), len_before)
789        self.assertIn(pasted[0], l.items())
790        self.assertIn(pasted[1], l.items())
791        self.assertIn(sip.cast(pasted[0], QgsLayoutItemLabel).text(), ('label 1', 'label 2'))
792        self.assertIn(sip.cast(pasted[1], QgsLayoutItemLabel).text(), ('label 1', 'label 2'))
793
794
795if __name__ == '__main__':
796    unittest.main()
797