Skip to content

Commit

Permalink
[layouts] Hack around inconsistent subclassing of layout items by sip
Browse files Browse the repository at this point in the history
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 f00e43d
Show file tree
Hide file tree
Showing 6 changed files with 412 additions and 5 deletions.
12 changes: 10 additions & 2 deletions python/core/auto_generated/layout/qgslayoutitem.sip.in
Expand Up @@ -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
Expand All @@ -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() )
Expand Down Expand Up @@ -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:
Expand Down
79 changes: 79 additions & 0 deletions python/core/auto_generated/layout/qgslayoutobject.sip.in
Expand Up @@ -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:

Expand Down
13 changes: 10 additions & 3 deletions src/core/layout/qgslayoutitem.h
Expand Up @@ -102,7 +102,6 @@ class CORE_EXPORT QgsLayoutItemRenderContext
double mViewScaleFactor = 1.0;
};


/**
* \ingroup core
* \class QgsLayoutItem
Expand All @@ -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() )
Expand Down Expand Up @@ -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
Expand Down
85 changes: 85 additions & 0 deletions src/core/layout/qgslayoutobject.h
Expand Up @@ -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:

Expand Down
33 changes: 33 additions & 0 deletions tests/src/python/test_qgslayoutitem.py
Expand Up @@ -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,
Expand All @@ -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()


Expand Down Expand Up @@ -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.