Skip to content
Permalink
Browse files

[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.
  • Loading branch information
nyalldawson committed Oct 19, 2018
1 parent 0a66257 commit f00e43d3c2a767f25408a597f06a3261056d9b44
@@ -62,7 +62,6 @@ scaled by 50% in order to have a constant visible size.
QgsLayoutItemRenderContext( const QgsLayoutItemRenderContext &rh );
};


class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInterface
{
%Docstring
@@ -86,6 +85,12 @@ Base class for graphical items within a :py:class:`QgsLayout`.
#include "qgslayoutitempage.h"
%End
%ConvertToSubClassCode

// FREAKKKKIIN IMPORTANT!!!!!!!!!!!
// IF YOU PUT SOMETHING HERE, PUT IT IN QgsLayoutObject CASTING *****ALSO******
// (it's not enough for it to be in only one of the places, as sip inconsistently
// decides which casting code to perform here)

// the conversions have to be static, because they're using multiple inheritance
// (seen in PyQt4 .sip files for some QGraphicsItem classes)
switch ( sipCpp->type() )
@@ -135,8 +140,11 @@ Base class for graphical items within a :py:class:`QgsLayout`.
sipType = sipType_QgsLayoutFrame;
*sipCppRet = static_cast<QgsLayoutFrame *>( sipCpp );
break;

// did you read that comment above? NO? Go read it now. You're about to break stuff.

default:
sipType = 0;
sipType = NULL;
}
%End
public:
@@ -19,6 +19,85 @@ A base class for objects which belong to a layout.

%TypeHeaderCode
#include "qgslayoutobject.h"
#include <qgslayoutitem.h>
#include "qgslayoutitemgroup.h"
#include "qgslayoutitemmap.h"
#include "qgslayoutitempicture.h"
#include "qgslayoutitemlabel.h"
#include "qgslayoutitemlegend.h"
#include "qgslayoutitempolygon.h"
#include "qgslayoutitempolyline.h"
#include "qgslayoutitemscalebar.h"
#include "qgslayoutframe.h"
#include "qgslayoutitemshape.h"
#include "qgslayoutitempage.h"
%End
%ConvertToSubClassCode
if ( QgsLayoutItem *item = qobject_cast< QgsLayoutItem * >( sipCpp ) )
{
// the conversions have to be static, because they're using multiple inheritance
// (seen in PyQt4 .sip files for some QGraphicsItem classes)
switch ( item->type() )
{
// FREAKKKKIIN IMPORTANT!
// IF YOU PUT SOMETHING HERE, PUT IT IN QgsLayoutItem CASTING **ALSO**
// (it's not enough for it to be in only one of the places, as sip inconsistently
// decides which casting code to perform here)

// really, these *should* use the constants from QgsLayoutItemRegistry, but sip doesn't like that!
case QGraphicsItem::UserType + 101:
sipType = sipType_QgsLayoutItemGroup;
*sipCppRet = static_cast<QgsLayoutItemGroup *>( sipCpp );
break;
case QGraphicsItem::UserType + 102:
sipType = sipType_QgsLayoutItemPage;
*sipCppRet = static_cast<QgsLayoutItemPage *>( sipCpp );
break;
case QGraphicsItem::UserType + 103:
sipType = sipType_QgsLayoutItemMap;
*sipCppRet = static_cast<QgsLayoutItemMap *>( sipCpp );
break;
case QGraphicsItem::UserType + 104:
sipType = sipType_QgsLayoutItemPicture;
*sipCppRet = static_cast<QgsLayoutItemPicture *>( sipCpp );
break;
case QGraphicsItem::UserType + 105:
sipType = sipType_QgsLayoutItemLabel;
*sipCppRet = static_cast<QgsLayoutItemLabel *>( sipCpp );
break;
case QGraphicsItem::UserType + 106:
sipType = sipType_QgsLayoutItemLegend;
*sipCppRet = static_cast<QgsLayoutItemLegend *>( sipCpp );
break;
case QGraphicsItem::UserType + 107:
sipType = sipType_QgsLayoutItemShape;
*sipCppRet = static_cast<QgsLayoutItemShape *>( sipCpp );
break;
case QGraphicsItem::UserType + 108:
sipType = sipType_QgsLayoutItemPolygon;
*sipCppRet = static_cast<QgsLayoutItemPolygon *>( sipCpp );
break;
case QGraphicsItem::UserType + 109:
sipType = sipType_QgsLayoutItemPolyline;
*sipCppRet = static_cast<QgsLayoutItemPolyline *>( sipCpp );
break;
case QGraphicsItem::UserType + 110:
sipType = sipType_QgsLayoutItemScaleBar;
*sipCppRet = static_cast<QgsLayoutItemScaleBar *>( sipCpp );
break;
case QGraphicsItem::UserType + 111:
sipType = sipType_QgsLayoutFrame;
*sipCppRet = static_cast<QgsLayoutFrame *>( sipCpp );
break;

// did you read that comment above? NO? Go read it now. You're about to break stuff.

default:
sipType = sipType_QgsLayoutItem;
}
}
else
sipType = NULL;
%End
public:

@@ -102,7 +102,6 @@ class CORE_EXPORT QgsLayoutItemRenderContext
double mViewScaleFactor = 1.0;
};


/**
* \ingroup core
* \class QgsLayoutItem
@@ -125,9 +124,14 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
#include "qgslayoutitempage.h"
#endif


#ifdef SIP_RUN
SIP_CONVERT_TO_SUBCLASS_CODE

// FREAKKKKIIN IMPORTANT!!!!!!!!!!!
// IF YOU PUT SOMETHING HERE, PUT IT IN QgsLayoutObject CASTING *****ALSO******
// (it's not enough for it to be in only one of the places, as sip inconsistently
// decides which casting code to perform here)

// the conversions have to be static, because they're using multiple inheritance
// (seen in PyQt4 .sip files for some QGraphicsItem classes)
switch ( sipCpp->type() )
@@ -177,8 +181,11 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
sipType = sipType_QgsLayoutFrame;
*sipCppRet = static_cast<QgsLayoutFrame *>( sipCpp );
break;

// did you read that comment above? NO? Go read it now. You're about to break stuff.

default:
sipType = 0;
sipType = NULL;
}
SIP_END
#endif
@@ -38,6 +38,91 @@ class QgsReadWriteContext;
*/
class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGenerator
{
#ifdef SIP_RUN
#include <qgslayoutitem.h>
#include "qgslayoutitemgroup.h"
#include "qgslayoutitemmap.h"
#include "qgslayoutitempicture.h"
#include "qgslayoutitemlabel.h"
#include "qgslayoutitemlegend.h"
#include "qgslayoutitempolygon.h"
#include "qgslayoutitempolyline.h"
#include "qgslayoutitemscalebar.h"
#include "qgslayoutframe.h"
#include "qgslayoutitemshape.h"
#include "qgslayoutitempage.h"
#endif

#ifdef SIP_RUN
SIP_CONVERT_TO_SUBCLASS_CODE
if ( QgsLayoutItem *item = qobject_cast< QgsLayoutItem * >( sipCpp ) )
{
// the conversions have to be static, because they're using multiple inheritance
// (seen in PyQt4 .sip files for some QGraphicsItem classes)
switch ( item->type() )
{
// FREAKKKKIIN IMPORTANT!
// IF YOU PUT SOMETHING HERE, PUT IT IN QgsLayoutItem CASTING **ALSO**
// (it's not enough for it to be in only one of the places, as sip inconsistently
// decides which casting code to perform here)

// really, these *should* use the constants from QgsLayoutItemRegistry, but sip doesn't like that!
case QGraphicsItem::UserType + 101:
sipType = sipType_QgsLayoutItemGroup;
*sipCppRet = static_cast<QgsLayoutItemGroup *>( sipCpp );
break;
case QGraphicsItem::UserType + 102:
sipType = sipType_QgsLayoutItemPage;
*sipCppRet = static_cast<QgsLayoutItemPage *>( sipCpp );
break;
case QGraphicsItem::UserType + 103:
sipType = sipType_QgsLayoutItemMap;
*sipCppRet = static_cast<QgsLayoutItemMap *>( sipCpp );
break;
case QGraphicsItem::UserType + 104:
sipType = sipType_QgsLayoutItemPicture;
*sipCppRet = static_cast<QgsLayoutItemPicture *>( sipCpp );
break;
case QGraphicsItem::UserType + 105:
sipType = sipType_QgsLayoutItemLabel;
*sipCppRet = static_cast<QgsLayoutItemLabel *>( sipCpp );
break;
case QGraphicsItem::UserType + 106:
sipType = sipType_QgsLayoutItemLegend;
*sipCppRet = static_cast<QgsLayoutItemLegend *>( sipCpp );
break;
case QGraphicsItem::UserType + 107:
sipType = sipType_QgsLayoutItemShape;
*sipCppRet = static_cast<QgsLayoutItemShape *>( sipCpp );
break;
case QGraphicsItem::UserType + 108:
sipType = sipType_QgsLayoutItemPolygon;
*sipCppRet = static_cast<QgsLayoutItemPolygon *>( sipCpp );
break;
case QGraphicsItem::UserType + 109:
sipType = sipType_QgsLayoutItemPolyline;
*sipCppRet = static_cast<QgsLayoutItemPolyline *>( sipCpp );
break;
case QGraphicsItem::UserType + 110:
sipType = sipType_QgsLayoutItemScaleBar;
*sipCppRet = static_cast<QgsLayoutItemScaleBar *>( sipCpp );
break;
case QGraphicsItem::UserType + 111:
sipType = sipType_QgsLayoutFrame;
*sipCppRet = static_cast<QgsLayoutFrame *>( sipCpp );
break;

// did you read that comment above? NO? Go read it now. You're about to break stuff.

default:
sipType = sipType_QgsLayoutItem;
}
}
else
sipType = NULL;
SIP_END
#endif

Q_OBJECT
public:

@@ -13,6 +13,7 @@
__revision__ = '$Format:%H$'
import qgis # NOQA

import os
from qgis.testing import start_app, unittest
from qgis.core import (QgsProject,
QgsLayout,
@@ -24,12 +25,17 @@
QgsUnitTypes,
QgsLayoutPoint,
QgsLayoutSize,
QgsLayoutItemLabel,
QgsLayoutItem,
QgsApplication)
from qgis.PyQt.QtCore import QRectF
from qgis.PyQt.QtGui import QColor, QPainter
from qgis.PyQt.QtTest import QSignalSpy
from utilities import unitTestDataPath


TEST_DATA_DIR = unitTestDataPath()

start_app()


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

def testCasting(self):
"""
Test that sip correctly casts stuff
"""
p = QgsProject()
p.read(os.path.join(TEST_DATA_DIR, 'layouts', 'layout_casting.qgs'))

layout = p.layoutManager().layouts()[0]

# check a method which often fails casting
map = layout.itemById('map')
self.assertIsInstance(map, QgsLayoutItemMap)
label = layout.itemById('label')
self.assertIsInstance(label, QgsLayoutItemLabel)

# another method -- sometimes this fails casting for different(?) reasons
# make sure we start from a new project so sip hasn't remembered item instances
p2 = QgsProject()
p2.read(os.path.join(TEST_DATA_DIR, 'layouts', 'layout_casting.qgs'))
layout = p2.layoutManager().layouts()[0]

items = layout.items()
map2 = [i for i in items if isinstance(i, QgsLayoutItem) and i.id() == 'map'][0]
self.assertIsInstance(map2, QgsLayoutItemMap)
label2 = [i for i in items if isinstance(i, QgsLayoutItem) and i.id() == 'label'][0]
self.assertIsInstance(label2, QgsLayoutItemLabel)


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

0 comments on commit f00e43d

Please sign in to comment.
You can’t perform that action at this time.