Skip to content

Commit b74a0ef

Browse files
committed
Work on layout and layout item serialization and restoration
1 parent ca75e8c commit b74a0ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+396
-196
lines changed

python/core/layout/qgslayout.sip

+17
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,23 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator, QgsLayoutUndoOb
424424
:rtype: bool
425425
%End
426426

427+
QVector< QgsLayoutItem * > addItemsFromXml( const QDomElement &parentElement, const QDomDocument &document,
428+
const QgsReadWriteContext &context,
429+
QPointF *position = 0, bool pasteInPlace = false );
430+
%Docstring
431+
Add items from an XML representation to the layout. Used for project file reading and pasting items from clipboard.
432+
433+
The ``position`` argument is optional, and if it is not specified the items will be restored to their
434+
original position from the XML serialization. If specified, the items will be positioned such that the top-left
435+
bounds of all added items is located at this ``position``.
436+
437+
The ``pasteInPlace`` argument determines whether the serialized position should be respected, but remapped to the
438+
origin of the page corresponding to the page at ``position``.
439+
440+
A list of the newly added items is returned.
441+
:rtype: list of QgsLayoutItem
442+
%End
443+
427444
QgsLayoutUndoStack *undoStack();
428445
%Docstring
429446
Returns a pointer to the layout's undo stack, which manages undo/redo states for the layout

python/core/layout/qgslayoutframe.sip

-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ class QgsLayoutFrame: QgsLayoutItem
3535

3636
virtual int type() const;
3737

38-
virtual QString stringType() const;
39-
4038
virtual QString uuid() const;
4139

4240

python/core/layout/qgslayoutitemattributetable.sip

-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ class QgsLayoutItemAttributeTable: QgsLayoutTable
3838

3939
virtual int type() const;
4040

41-
virtual QString stringType() const;
42-
4341
virtual QString displayName() const;
4442

4543

python/core/layout/qgslayoutitemgroup.sip

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ class QgsLayoutItemGroup: QgsLayoutItem
2828

2929
virtual int type() const;
3030

31-
virtual QString stringType() const;
32-
3331
virtual QString displayName() const;
3432

3533

@@ -70,6 +68,9 @@ class QgsLayoutItemGroup: QgsLayoutItem
7068
virtual void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget );
7169

7270

71+
virtual void finalizeRestoreFromXml();
72+
73+
7374
protected:
7475
virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 );
7576

python/core/layout/qgslayoutitemhtml.sip

-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ class QgsLayoutItemHtml: QgsLayoutMultiFrame
3838

3939
virtual int type() const;
4040

41-
virtual QString stringType() const;
42-
4341

4442
static QgsLayoutItemHtml *create( QgsLayout *layout ) /Factory/;
4543
%Docstring

python/core/layout/qgslayoutitemlabel.sip

-3
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,8 @@ class QgsLayoutItemLabel: QgsLayoutItem
4040
:rtype: QgsLayoutItemLabel
4141
%End
4242

43-
4443
virtual int type() const;
4544

46-
virtual QString stringType() const;
47-
4845
virtual QString displayName() const;
4946

5047
void adjustSizeToText();

python/core/layout/qgslayoutitemlegend.sip

+3-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ class QgsLayoutItemLegend : QgsLayoutItem
6565

6666
virtual int type() const;
6767

68-
virtual QString stringType() const;
69-
7068
virtual QString displayName() const;
7169

7270

@@ -451,6 +449,9 @@ class QgsLayoutItemLegend : QgsLayoutItem
451449
virtual void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget );
452450

453451

452+
virtual void finalizeRestoreFromXml();
453+
454+
454455

455456
public slots:
456457

python/core/layout/qgslayoutitemmap.sip

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ class QgsLayoutItemMap : QgsLayoutItem
3838

3939
virtual int type() const;
4040

41-
virtual QString stringType() const;
42-
4341

4442
void assignFreeId();
4543
%Docstring
@@ -423,6 +421,9 @@ Returns true if the map contains layers with blend modes or flattened layers for
423421
:rtype: QgsMapSettings
424422
%End
425423

424+
virtual void finalizeRestoreFromXml();
425+
426+
426427
protected:
427428

428429
virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 );

python/core/layout/qgslayoutitempage.sip

+1-2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@ class QgsLayoutItemPage : QgsLayoutItem
4646
:rtype: QgsLayoutItemPage
4747
%End
4848

49-
5049
virtual int type() const;
51-
virtual QString stringType() const;
50+
5251

5352
void setPageSize( const QgsLayoutSize &size );
5453
%Docstring

python/core/layout/qgslayoutitempicture.sip

+3-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ class QgsLayoutItemPicture: QgsLayoutItem
5050

5151
virtual int type() const;
5252

53-
virtual QString stringType() const;
54-
5553

5654
static QgsLayoutItemPicture *create( QgsLayout *layout ) /Factory/;
5755
%Docstring
@@ -234,6 +232,9 @@ class QgsLayoutItemPicture: QgsLayoutItem
234232
:rtype: Format
235233
%End
236234

235+
virtual void finalizeRestoreFromXml();
236+
237+
237238
public slots:
238239

239240
void setPictureRotation( double rotation );

python/core/layout/qgslayoutitempolygon.sip

-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ class QgsLayoutItemPolygon: QgsLayoutNodesItem
4343

4444
virtual int type() const;
4545

46-
virtual QString stringType() const;
47-
4846
virtual QString displayName() const;
4947

5048

python/core/layout/qgslayoutitempolyline.sip

-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ class QgsLayoutItemPolyline: QgsLayoutNodesItem
4949

5050
virtual int type() const;
5151

52-
virtual QString stringType() const;
53-
5452
virtual QString displayName() const;
5553

5654

python/core/layout/qgslayoutitemscalebar.sip

+1-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ class QgsLayoutItemScaleBar: QgsLayoutItem
2727

2828
virtual int type() const;
2929

30-
virtual QString stringType() const;
31-
3230

3331
static QgsLayoutItemScaleBar *create( QgsLayout *layout ) /Factory/;
3432
%Docstring
@@ -436,6 +434,7 @@ class QgsLayoutItemScaleBar: QgsLayoutItem
436434

437435
virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );
438436

437+
virtual void finalizeRestoreFromXml();
439438

440439
protected:
441440

python/core/layout/qgslayoutitemshape.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class QgsLayoutItemShape : QgsLayoutItem
3434
%End
3535

3636
virtual int type() const;
37-
virtual QString stringType() const;
37+
3838

3939
virtual QString displayName() const;
4040

python/core/layout/qgslayoutitemtexttable.sip

-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ class QgsLayoutItemTextTable : QgsLayoutTable
3030

3131
virtual int type() const;
3232

33-
virtual QString stringType() const;
34-
3533
virtual QString displayName() const;
3634

3735

src/core/layout/qgslayout.cpp

+167
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,26 @@ QDomElement QgsLayout::writeXml( QDomDocument &document, const QgsReadWriteConte
584584
save( &mGridSettings );
585585
save( mPageCollection.get() );
586586

587+
//save items except paper items and frame items (they are saved with the corresponding multiframe)
588+
const QList<QGraphicsItem *> itemList = items();
589+
for ( const QGraphicsItem *graphicsItem : itemList )
590+
{
591+
if ( const QgsLayoutItem *item = dynamic_cast< const QgsLayoutItem *>( graphicsItem ) )
592+
{
593+
if ( item->type() == QgsLayoutItemRegistry::LayoutPage
594+
|| item->type() == QgsLayoutItemRegistry::LayoutFrame )
595+
continue;
596+
597+
item->writeXml( element, document, context );
598+
}
599+
}
600+
601+
//save multiframes
602+
for ( QgsLayoutMultiFrame *mf : mMultiFrames )
603+
{
604+
mf->writeXml( element, document, context );
605+
}
606+
587607
writeXmlLayoutSettings( element, document, context );
588608
return element;
589609
}
@@ -623,6 +643,26 @@ void QgsLayout::deleteAndRemoveMultiFrames()
623643
mMultiFrames.clear();
624644
}
625645

646+
QPointF QgsLayout::minPointFromXml( const QDomElement &elem ) const
647+
{
648+
double minX = std::numeric_limits<double>::max();
649+
double minY = std::numeric_limits<double>::max();
650+
const QDomNodeList itemList = elem.elementsByTagName( QStringLiteral( "LayoutItem" ) );
651+
bool found = false;
652+
for ( int i = 0; i < itemList.size(); ++i )
653+
{
654+
const QDomElement currentItemElem = itemList.at( i ).toElement();
655+
656+
QgsLayoutPoint pos = QgsLayoutPoint::decodePoint( currentItemElem.attribute( QStringLiteral( "position" ) ) );
657+
QPointF layoutPoint = convertToLayoutUnits( pos );
658+
659+
minX = std::min( minX, layoutPoint.x() );
660+
minY = std::min( minY, layoutPoint.y() );
661+
found = true;
662+
}
663+
return found ? QPointF( minX, minY ) : QPointF( 0, 0 );
664+
}
665+
626666
void QgsLayout::updateZValues( const bool addUndoCommands )
627667
{
628668
int counter = mItemsModel->zOrderListSize();
@@ -671,10 +711,137 @@ bool QgsLayout::readXml( const QDomElement &layoutElement, const QDomDocument &d
671711
restore( mPageCollection.get() );
672712
restore( &mSnapper );
673713
restore( &mGridSettings );
714+
addItemsFromXml( layoutElement, document, context );
674715

675716
return true;
676717
}
677718

719+
QVector< QgsLayoutItem * > QgsLayout::addItemsFromXml( const QDomElement &parentElement, const QDomDocument &document, const QgsReadWriteContext &context, QPointF *position, bool pasteInPlace )
720+
{
721+
std::unique_ptr< QPointF > pasteInPlacePt;
722+
QVector< QgsLayoutItem * > newItems;
723+
QVector< QgsLayoutMultiFrame * > newMultiFrames;
724+
725+
//if we are adding items to a layout which already contains items, we need to make sure
726+
//these items are placed at the top of the layout and that zValues are not duplicated
727+
//so, calculate an offset which needs to be added to the zValue of created items
728+
int zOrderOffset = mItemsModel->zOrderListSize();
729+
730+
QPointF pasteShiftPos;
731+
if ( position )
732+
{
733+
//If we are placing items relative to a certain point, then calculate how much we need
734+
//to shift the items by so that they are placed at this point
735+
//First, calculate the minimum position from the xml
736+
QPointF minItemPos = minPointFromXml( parentElement );
737+
//next, calculate how much each item needs to be shifted from its original position
738+
//so that it's placed at the correct relative position
739+
pasteShiftPos = *position - minItemPos;
740+
741+
#if 0 // TODO - move to gui
742+
//since we are pasting items, clear the existing selection
743+
setAllDeselected();
744+
#endif
745+
if ( pasteInPlace )
746+
{
747+
int pageNumber = mPageCollection->pageNumberForPoint( *position );
748+
pasteInPlacePt = qgis::make_unique< QPointF >( 0, mPageCollection->page( pageNumber )->pos().y() );
749+
}
750+
}
751+
752+
const QDomNodeList layoutItemList = parentElement.elementsByTagName( QStringLiteral( "LayoutItem" ) );
753+
for ( int i = 0; i < layoutItemList.size(); ++i )
754+
{
755+
const QDomElement currentItemElem = layoutItemList.at( i ).toElement();
756+
const int itemType = currentItemElem.attribute( QStringLiteral( "type" ) ).toInt();
757+
std::unique_ptr< QgsLayoutItem > item( QgsApplication::layoutItemRegistry()->createItem( itemType, this ) );
758+
if ( !item )
759+
{
760+
// e.g. plugin based item which is no longer available
761+
continue;
762+
}
763+
764+
item->readXml( currentItemElem, document, context );
765+
if ( position )
766+
{
767+
#if 0 //TODO
768+
if ( pasteInPlacePt )
769+
{
770+
item->setItemPosition( newLabel->pos().x(), std::fmod( newLabel->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
771+
item->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
772+
}
773+
else
774+
{
775+
item->move( pasteShiftPos.x(), pasteShiftPos.y() );
776+
}
777+
#endif
778+
}
779+
780+
#if 0 //TODO - move to gui
781+
newLabel->setSelected( true );
782+
#endif
783+
784+
QgsLayoutItem *layoutItem = item.get();
785+
addLayoutItem( item.release() );
786+
layoutItem->setZValue( layoutItem->zValue() + zOrderOffset );
787+
newItems << layoutItem;
788+
}
789+
790+
// multiframes
791+
792+
//TODO - fix this. pasting multiframe frame items has no effect
793+
const QDomNodeList multiFrameList = parentElement.elementsByTagName( QStringLiteral( "LayoutMultiFrame" ) );
794+
for ( int i = 0; i < multiFrameList.size(); ++i )
795+
{
796+
const QDomElement multiFrameElem = multiFrameList.at( i ).toElement();
797+
const int itemType = multiFrameElem.attribute( QStringLiteral( "type" ) ).toInt();
798+
std::unique_ptr< QgsLayoutMultiFrame > mf( QgsApplication::layoutItemRegistry()->createMultiFrame( itemType, this ) );
799+
if ( !mf )
800+
{
801+
// e.g. plugin based item which is no longer available
802+
continue;
803+
}
804+
mf->readXml( multiFrameElem, document, context );
805+
806+
#if 0 //TODO?
807+
mf->setCreateUndoCommands( true );
808+
#endif
809+
810+
QgsLayoutMultiFrame *m = mf.get();
811+
this->addMultiFrame( mf.release() );
812+
813+
//offset z values for frames
814+
//TODO - fix this after fixing multiframe item paste
815+
/*for ( int frameIdx = 0; frameIdx < mf->frameCount(); ++frameIdx )
816+
{
817+
QgsLayoutItemFrame * frame = mf->frame( frameIdx );
818+
frame->setZValue( frame->zValue() + zOrderOffset );
819+
}*/
820+
newMultiFrames << m;
821+
}
822+
823+
824+
// we now allow items to "post-process", e.g. if they need to setup connections
825+
// to other items in the layout, which may not have existed at the time the
826+
// item's state was restored. E.g. a scalebar may have been restored before the map
827+
// it is linked to
828+
for ( QgsLayoutItem *item : qgis::as_const( newItems ) )
829+
{
830+
item->finalizeRestoreFromXml();
831+
}
832+
for ( QgsLayoutMultiFrame *mf : qgis::as_const( newMultiFrames ) )
833+
{
834+
mf->finalizeRestoreFromXml();
835+
}
836+
837+
//Since this function adds items in an order which isn't the z-order, and each item is added to end of
838+
//z order list in turn, it will now be inconsistent with the actual order of items in the scene.
839+
//Make sure z order list matches the actual order of items in the scene.
840+
mItemsModel->rebuildZList();
841+
842+
return newItems;
843+
}
844+
678845
void QgsLayout::updateBounds()
679846
{
680847
setSceneRect( layoutBounds( false, 0.05 ) );

0 commit comments

Comments
 (0)