Skip to content

Commit f00e43d

Browse files
committed
[layouts] Hack around inconsistent subclassing of layout items by sip
Sometimes, calling some layout methods, results in sip being inable to downcast the items to their correct type, resulting only in a QgsLayoutItem object. This works around the problem, albeit in an incredibly hacky way.
1 parent 0a66257 commit f00e43d

File tree

6 files changed

+412
-5
lines changed

6 files changed

+412
-5
lines changed

python/core/auto_generated/layout/qgslayoutitem.sip.in

+10-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ scaled by 50% in order to have a constant visible size.
6262
QgsLayoutItemRenderContext( const QgsLayoutItemRenderContext &rh );
6363
};
6464

65-
6665
class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInterface
6766
{
6867
%Docstring
@@ -86,6 +85,12 @@ Base class for graphical items within a :py:class:`QgsLayout`.
8685
#include "qgslayoutitempage.h"
8786
%End
8887
%ConvertToSubClassCode
88+
89+
// FREAKKKKIIN IMPORTANT!!!!!!!!!!!
90+
// IF YOU PUT SOMETHING HERE, PUT IT IN QgsLayoutObject CASTING *****ALSO******
91+
// (it's not enough for it to be in only one of the places, as sip inconsistently
92+
// decides which casting code to perform here)
93+
8994
// the conversions have to be static, because they're using multiple inheritance
9095
// (seen in PyQt4 .sip files for some QGraphicsItem classes)
9196
switch ( sipCpp->type() )
@@ -135,8 +140,11 @@ Base class for graphical items within a :py:class:`QgsLayout`.
135140
sipType = sipType_QgsLayoutFrame;
136141
*sipCppRet = static_cast<QgsLayoutFrame *>( sipCpp );
137142
break;
143+
144+
// did you read that comment above? NO? Go read it now. You're about to break stuff.
145+
138146
default:
139-
sipType = 0;
147+
sipType = NULL;
140148
}
141149
%End
142150
public:

python/core/auto_generated/layout/qgslayoutobject.sip.in

+79
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,85 @@ A base class for objects which belong to a layout.
1919

2020
%TypeHeaderCode
2121
#include "qgslayoutobject.h"
22+
#include <qgslayoutitem.h>
23+
#include "qgslayoutitemgroup.h"
24+
#include "qgslayoutitemmap.h"
25+
#include "qgslayoutitempicture.h"
26+
#include "qgslayoutitemlabel.h"
27+
#include "qgslayoutitemlegend.h"
28+
#include "qgslayoutitempolygon.h"
29+
#include "qgslayoutitempolyline.h"
30+
#include "qgslayoutitemscalebar.h"
31+
#include "qgslayoutframe.h"
32+
#include "qgslayoutitemshape.h"
33+
#include "qgslayoutitempage.h"
34+
%End
35+
%ConvertToSubClassCode
36+
if ( QgsLayoutItem *item = qobject_cast< QgsLayoutItem * >( sipCpp ) )
37+
{
38+
// the conversions have to be static, because they're using multiple inheritance
39+
// (seen in PyQt4 .sip files for some QGraphicsItem classes)
40+
switch ( item->type() )
41+
{
42+
// FREAKKKKIIN IMPORTANT!
43+
// IF YOU PUT SOMETHING HERE, PUT IT IN QgsLayoutItem CASTING **ALSO**
44+
// (it's not enough for it to be in only one of the places, as sip inconsistently
45+
// decides which casting code to perform here)
46+
47+
// really, these *should* use the constants from QgsLayoutItemRegistry, but sip doesn't like that!
48+
case QGraphicsItem::UserType + 101:
49+
sipType = sipType_QgsLayoutItemGroup;
50+
*sipCppRet = static_cast<QgsLayoutItemGroup *>( sipCpp );
51+
break;
52+
case QGraphicsItem::UserType + 102:
53+
sipType = sipType_QgsLayoutItemPage;
54+
*sipCppRet = static_cast<QgsLayoutItemPage *>( sipCpp );
55+
break;
56+
case QGraphicsItem::UserType + 103:
57+
sipType = sipType_QgsLayoutItemMap;
58+
*sipCppRet = static_cast<QgsLayoutItemMap *>( sipCpp );
59+
break;
60+
case QGraphicsItem::UserType + 104:
61+
sipType = sipType_QgsLayoutItemPicture;
62+
*sipCppRet = static_cast<QgsLayoutItemPicture *>( sipCpp );
63+
break;
64+
case QGraphicsItem::UserType + 105:
65+
sipType = sipType_QgsLayoutItemLabel;
66+
*sipCppRet = static_cast<QgsLayoutItemLabel *>( sipCpp );
67+
break;
68+
case QGraphicsItem::UserType + 106:
69+
sipType = sipType_QgsLayoutItemLegend;
70+
*sipCppRet = static_cast<QgsLayoutItemLegend *>( sipCpp );
71+
break;
72+
case QGraphicsItem::UserType + 107:
73+
sipType = sipType_QgsLayoutItemShape;
74+
*sipCppRet = static_cast<QgsLayoutItemShape *>( sipCpp );
75+
break;
76+
case QGraphicsItem::UserType + 108:
77+
sipType = sipType_QgsLayoutItemPolygon;
78+
*sipCppRet = static_cast<QgsLayoutItemPolygon *>( sipCpp );
79+
break;
80+
case QGraphicsItem::UserType + 109:
81+
sipType = sipType_QgsLayoutItemPolyline;
82+
*sipCppRet = static_cast<QgsLayoutItemPolyline *>( sipCpp );
83+
break;
84+
case QGraphicsItem::UserType + 110:
85+
sipType = sipType_QgsLayoutItemScaleBar;
86+
*sipCppRet = static_cast<QgsLayoutItemScaleBar *>( sipCpp );
87+
break;
88+
case QGraphicsItem::UserType + 111:
89+
sipType = sipType_QgsLayoutFrame;
90+
*sipCppRet = static_cast<QgsLayoutFrame *>( sipCpp );
91+
break;
92+
93+
// did you read that comment above? NO? Go read it now. You're about to break stuff.
94+
95+
default:
96+
sipType = sipType_QgsLayoutItem;
97+
}
98+
}
99+
else
100+
sipType = NULL;
22101
%End
23102
public:
24103

src/core/layout/qgslayoutitem.h

+10-3
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ class CORE_EXPORT QgsLayoutItemRenderContext
102102
double mViewScaleFactor = 1.0;
103103
};
104104

105-
106105
/**
107106
* \ingroup core
108107
* \class QgsLayoutItem
@@ -125,9 +124,14 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
125124
#include "qgslayoutitempage.h"
126125
#endif
127126

128-
129127
#ifdef SIP_RUN
130128
SIP_CONVERT_TO_SUBCLASS_CODE
129+
130+
// FREAKKKKIIN IMPORTANT!!!!!!!!!!!
131+
// IF YOU PUT SOMETHING HERE, PUT IT IN QgsLayoutObject CASTING *****ALSO******
132+
// (it's not enough for it to be in only one of the places, as sip inconsistently
133+
// decides which casting code to perform here)
134+
131135
// the conversions have to be static, because they're using multiple inheritance
132136
// (seen in PyQt4 .sip files for some QGraphicsItem classes)
133137
switch ( sipCpp->type() )
@@ -177,8 +181,11 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
177181
sipType = sipType_QgsLayoutFrame;
178182
*sipCppRet = static_cast<QgsLayoutFrame *>( sipCpp );
179183
break;
184+
185+
// did you read that comment above? NO? Go read it now. You're about to break stuff.
186+
180187
default:
181-
sipType = 0;
188+
sipType = NULL;
182189
}
183190
SIP_END
184191
#endif

src/core/layout/qgslayoutobject.h

+85
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,91 @@ class QgsReadWriteContext;
3838
*/
3939
class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGenerator
4040
{
41+
#ifdef SIP_RUN
42+
#include <qgslayoutitem.h>
43+
#include "qgslayoutitemgroup.h"
44+
#include "qgslayoutitemmap.h"
45+
#include "qgslayoutitempicture.h"
46+
#include "qgslayoutitemlabel.h"
47+
#include "qgslayoutitemlegend.h"
48+
#include "qgslayoutitempolygon.h"
49+
#include "qgslayoutitempolyline.h"
50+
#include "qgslayoutitemscalebar.h"
51+
#include "qgslayoutframe.h"
52+
#include "qgslayoutitemshape.h"
53+
#include "qgslayoutitempage.h"
54+
#endif
55+
56+
#ifdef SIP_RUN
57+
SIP_CONVERT_TO_SUBCLASS_CODE
58+
if ( QgsLayoutItem *item = qobject_cast< QgsLayoutItem * >( sipCpp ) )
59+
{
60+
// the conversions have to be static, because they're using multiple inheritance
61+
// (seen in PyQt4 .sip files for some QGraphicsItem classes)
62+
switch ( item->type() )
63+
{
64+
// FREAKKKKIIN IMPORTANT!
65+
// IF YOU PUT SOMETHING HERE, PUT IT IN QgsLayoutItem CASTING **ALSO**
66+
// (it's not enough for it to be in only one of the places, as sip inconsistently
67+
// decides which casting code to perform here)
68+
69+
// really, these *should* use the constants from QgsLayoutItemRegistry, but sip doesn't like that!
70+
case QGraphicsItem::UserType + 101:
71+
sipType = sipType_QgsLayoutItemGroup;
72+
*sipCppRet = static_cast<QgsLayoutItemGroup *>( sipCpp );
73+
break;
74+
case QGraphicsItem::UserType + 102:
75+
sipType = sipType_QgsLayoutItemPage;
76+
*sipCppRet = static_cast<QgsLayoutItemPage *>( sipCpp );
77+
break;
78+
case QGraphicsItem::UserType + 103:
79+
sipType = sipType_QgsLayoutItemMap;
80+
*sipCppRet = static_cast<QgsLayoutItemMap *>( sipCpp );
81+
break;
82+
case QGraphicsItem::UserType + 104:
83+
sipType = sipType_QgsLayoutItemPicture;
84+
*sipCppRet = static_cast<QgsLayoutItemPicture *>( sipCpp );
85+
break;
86+
case QGraphicsItem::UserType + 105:
87+
sipType = sipType_QgsLayoutItemLabel;
88+
*sipCppRet = static_cast<QgsLayoutItemLabel *>( sipCpp );
89+
break;
90+
case QGraphicsItem::UserType + 106:
91+
sipType = sipType_QgsLayoutItemLegend;
92+
*sipCppRet = static_cast<QgsLayoutItemLegend *>( sipCpp );
93+
break;
94+
case QGraphicsItem::UserType + 107:
95+
sipType = sipType_QgsLayoutItemShape;
96+
*sipCppRet = static_cast<QgsLayoutItemShape *>( sipCpp );
97+
break;
98+
case QGraphicsItem::UserType + 108:
99+
sipType = sipType_QgsLayoutItemPolygon;
100+
*sipCppRet = static_cast<QgsLayoutItemPolygon *>( sipCpp );
101+
break;
102+
case QGraphicsItem::UserType + 109:
103+
sipType = sipType_QgsLayoutItemPolyline;
104+
*sipCppRet = static_cast<QgsLayoutItemPolyline *>( sipCpp );
105+
break;
106+
case QGraphicsItem::UserType + 110:
107+
sipType = sipType_QgsLayoutItemScaleBar;
108+
*sipCppRet = static_cast<QgsLayoutItemScaleBar *>( sipCpp );
109+
break;
110+
case QGraphicsItem::UserType + 111:
111+
sipType = sipType_QgsLayoutFrame;
112+
*sipCppRet = static_cast<QgsLayoutFrame *>( sipCpp );
113+
break;
114+
115+
// did you read that comment above? NO? Go read it now. You're about to break stuff.
116+
117+
default:
118+
sipType = sipType_QgsLayoutItem;
119+
}
120+
}
121+
else
122+
sipType = NULL;
123+
SIP_END
124+
#endif
125+
41126
Q_OBJECT
42127
public:
43128

tests/src/python/test_qgslayoutitem.py

+33
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
__revision__ = '$Format:%H$'
1414
import qgis # NOQA
1515

16+
import os
1617
from qgis.testing import start_app, unittest
1718
from qgis.core import (QgsProject,
1819
QgsLayout,
@@ -24,12 +25,17 @@
2425
QgsUnitTypes,
2526
QgsLayoutPoint,
2627
QgsLayoutSize,
28+
QgsLayoutItemLabel,
29+
QgsLayoutItem,
2730
QgsApplication)
2831
from qgis.PyQt.QtCore import QRectF
2932
from qgis.PyQt.QtGui import QColor, QPainter
3033
from qgis.PyQt.QtTest import QSignalSpy
34+
from utilities import unitTestDataPath
3135

3236

37+
TEST_DATA_DIR = unitTestDataPath()
38+
3339
start_app()
3440

3541

@@ -166,6 +172,33 @@ def testDisplayName(self):
166172
self.assertEqual(item.displayName(), 'a')
167173
self.assertEqual(item.id(), 'a')
168174

175+
def testCasting(self):
176+
"""
177+
Test that sip correctly casts stuff
178+
"""
179+
p = QgsProject()
180+
p.read(os.path.join(TEST_DATA_DIR, 'layouts', 'layout_casting.qgs'))
181+
182+
layout = p.layoutManager().layouts()[0]
183+
184+
# check a method which often fails casting
185+
map = layout.itemById('map')
186+
self.assertIsInstance(map, QgsLayoutItemMap)
187+
label = layout.itemById('label')
188+
self.assertIsInstance(label, QgsLayoutItemLabel)
189+
190+
# another method -- sometimes this fails casting for different(?) reasons
191+
# make sure we start from a new project so sip hasn't remembered item instances
192+
p2 = QgsProject()
193+
p2.read(os.path.join(TEST_DATA_DIR, 'layouts', 'layout_casting.qgs'))
194+
layout = p2.layoutManager().layouts()[0]
195+
196+
items = layout.items()
197+
map2 = [i for i in items if isinstance(i, QgsLayoutItem) and i.id() == 'map'][0]
198+
self.assertIsInstance(map2, QgsLayoutItemMap)
199+
label2 = [i for i in items if isinstance(i, QgsLayoutItem) and i.id() == 'label'][0]
200+
self.assertIsInstance(label2, QgsLayoutItemLabel)
201+
169202

170203
if __name__ == '__main__':
171204
unittest.main()

0 commit comments

Comments
 (0)