Skip to content

Commit bfa9c1d

Browse files
committed
Port methods for retrieving item at a point to layouts
1 parent d7bd44d commit bfa9c1d

File tree

4 files changed

+102
-1
lines changed

4 files changed

+102
-1
lines changed

python/core/layout/qgslayout.sip

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,20 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator, QgsLayoutUndoOb
9696
:rtype: QgsLayoutItem
9797
%End
9898

99+
QgsLayoutItem *layoutItemAt( QPointF position, const bool ignoreLocked = false ) const;
100+
%Docstring
101+
Returns the topmost layout item at a specified ``position``. Ignores paper items.
102+
If ``ignoreLocked`` is set to true any locked items will be ignored.
103+
:rtype: QgsLayoutItem
104+
%End
105+
106+
QgsLayoutItem *layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, const bool ignoreLocked = false ) const;
107+
%Docstring
108+
Returns the topmost composer item at a specified ``position`` which is below a specified ``item``. Ignores paper items.
109+
If ``ignoreLocked`` is set to true any locked items will be ignored.
110+
:rtype: QgsLayoutItem
111+
%End
112+
99113
void setUnits( QgsUnitTypes::LayoutUnit units );
100114
%Docstring
101115
Sets the native measurement ``units`` for the layout. These also form the default unit

src/core/layout/qgslayout.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,42 @@ QgsLayoutItem *QgsLayout::itemByUuid( const QString &uuid )
105105
return nullptr;
106106
}
107107

108+
QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const bool ignoreLocked ) const
109+
{
110+
return layoutItemAt( position, nullptr, ignoreLocked );
111+
}
112+
113+
QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, const bool ignoreLocked ) const
114+
{
115+
//get a list of items which intersect the specified position, in descending z order
116+
const QList<QGraphicsItem *> itemList = items( position, Qt::IntersectsItemShape, Qt::DescendingOrder );
117+
118+
bool foundBelowItem = false;
119+
for ( QGraphicsItem *graphicsItem : itemList )
120+
{
121+
QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( graphicsItem );
122+
QgsLayoutItemPage *paperItem = dynamic_cast<QgsLayoutItemPage *>( layoutItem );
123+
if ( layoutItem && !paperItem )
124+
{
125+
// If we are not checking for a an item below a specified item, or if we've
126+
// already found that item, then we've found our target
127+
if ( ( ! belowItem || foundBelowItem ) && ( !ignoreLocked || !layoutItem->isLocked() ) )
128+
{
129+
return layoutItem;
130+
}
131+
else
132+
{
133+
if ( layoutItem == belowItem )
134+
{
135+
//Target item is next in list
136+
foundBelowItem = true;
137+
}
138+
}
139+
}
140+
}
141+
return nullptr;
142+
}
143+
108144
double QgsLayout::convertToLayoutUnits( const QgsLayoutMeasurement &measurement ) const
109145
{
110146
return mContext.measurementConverter().convert( measurement, mUnits ).length();

src/core/layout/qgslayout.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,18 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
132132
*/
133133
QgsLayoutItem *itemByUuid( const QString &uuid );
134134

135+
/**
136+
* Returns the topmost layout item at a specified \a position. Ignores paper items.
137+
* If \a ignoreLocked is set to true any locked items will be ignored.
138+
*/
139+
QgsLayoutItem *layoutItemAt( QPointF position, const bool ignoreLocked = false ) const;
140+
141+
/**
142+
* Returns the topmost composer item at a specified \a position which is below a specified \a item. Ignores paper items.
143+
* If \a ignoreLocked is set to true any locked items will be ignored.
144+
*/
145+
QgsLayoutItem *layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, const bool ignoreLocked = false ) const;
146+
135147
/**
136148
* Sets the native measurement \a units for the layout. These also form the default unit
137149
* for measurements for the layout.

tests/src/python/test_qgslayout.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
QgsLayoutMeasurement,
2727
QgsFillSymbol,
2828
QgsReadWriteContext,
29-
QgsLayoutItemMap)
29+
QgsLayoutItemMap,
30+
QgsLayoutSize,
31+
QgsLayoutPoint)
3032
from qgis.PyQt.QtCore import Qt, QCoreApplication, QEvent, QPointF, QRectF
3133
from qgis.PyQt.QtTest import QSignalSpy
3234
from qgis.PyQt.QtXml import QDomDocument
@@ -140,6 +142,43 @@ def testSelections(self):
140142
self.assertEqual(len(select_changed_spy), 5)
141143
self.assertEqual(select_changed_spy[-1][0], None)
142144

145+
def testLayoutItemAt(self):
146+
p = QgsProject()
147+
l = QgsLayout(p)
148+
149+
# add some items
150+
item1 = QgsLayoutItemMap(l)
151+
item1.attemptMove(QgsLayoutPoint(4, 8, QgsUnitTypes.LayoutMillimeters))
152+
item1.attemptResize(QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
153+
l.addItem(item1)
154+
155+
item2 = QgsLayoutItemMap(l)
156+
item2.attemptMove(QgsLayoutPoint(6, 10, QgsUnitTypes.LayoutMillimeters))
157+
item2.attemptResize(QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
158+
l.addItem(item2)
159+
160+
item3 = QgsLayoutItemMap(l)
161+
item3.attemptMove(QgsLayoutPoint(8, 12, QgsUnitTypes.LayoutMillimeters))
162+
item3.attemptResize(QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
163+
item3.setLocked(True)
164+
l.addItem(item3)
165+
166+
self.assertIsNone(l.layoutItemAt(QPointF(0, 0)))
167+
self.assertIsNone(l.layoutItemAt(QPointF(100, 100)))
168+
169+
self.assertEqual(l.layoutItemAt(QPointF(5, 9)), item1)
170+
self.assertEqual(l.layoutItemAt(QPointF(25, 23)), item3)
171+
self.assertIsNone(l.layoutItemAt(QPointF(25, 23), True))
172+
self.assertEqual(l.layoutItemAt(QPointF(7, 11)), item2)
173+
self.assertEqual(l.layoutItemAt(QPointF(9, 13)), item3)
174+
self.assertEqual(l.layoutItemAt(QPointF(9, 13), True), item2)
175+
176+
self.assertEqual(l.layoutItemAt(QPointF(9, 13), item3), item2)
177+
self.assertEqual(l.layoutItemAt(QPointF(9, 13), item2), item1)
178+
self.assertIsNone(l.layoutItemAt(QPointF(9, 13), item1))
179+
item2.setLocked(True)
180+
self.assertEqual(l.layoutItemAt(QPointF(9, 13), item3, True), item1)
181+
143182

144183
if __name__ == '__main__':
145184
unittest.main()

0 commit comments

Comments
 (0)