More work on porting multiframe items

This commit is contained in:
Nyall Dawson 2017-10-31 17:20:02 +10:00
parent 2cf99116d7
commit dddce25e13
19 changed files with 1420 additions and 157 deletions

View File

@ -175,9 +175,18 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator, QgsLayoutUndoOb
%Docstring
Returns the layout item with matching ``uuid`` unique identifier, or a None
if a matching item could not be found.
.. seealso:: multiFrameByUuid()
:rtype: QgsLayoutItem
%End
QgsLayoutMultiFrame *multiFrameByUuid( const QString &uuid ) const;
%Docstring
Returns the layout multiframe with matching ``uuid`` unique identifier, or a None
if a matching multiframe could not be found.
.. seealso:: itemByUuid()
:rtype: QgsLayoutMultiFrame
%End
QgsLayoutItem *layoutItemAt( QPointF position, const bool ignoreLocked = false ) const;
%Docstring
Returns the topmost layout item at a specified ``position``. Ignores paper items.

View File

@ -9,6 +9,108 @@
class QgsLayoutFrame: QgsLayoutItem
{
%Docstring
Base class for frame items, which form a layout multiframe item.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslayoutframe.h"
%End
public:
QgsLayoutFrame( QgsLayout *layout, QgsLayoutMultiFrame *multiFrame );
%Docstring
Constructor for QgsLayoutFrame, with the specified parent ``layout``
and belonging to a ``multiFrame``.
%End
static QgsLayoutFrame *create( QgsLayout *layout ) /Factory/;
%Docstring
Creates a new QgsLayoutFrame belonging to the specified ``layout``.
:rtype: QgsLayoutFrame
%End
virtual int type() const;
virtual QString stringType() const;
virtual QString displayName() const;
void setContentSection( const QRectF &section );
%Docstring
Sets the visible part of the multiframe's content which is visible within
this frame (relative to the total multiframe extent in layout units).
.. seealso:: extent()
%End
QgsLayoutMultiFrame *multiFrame() const;
%Docstring
Returns the parent multiframe for the frame.
:rtype: QgsLayoutMultiFrame
%End
QRectF extent() const;
%Docstring
Returns the visible portion of the multi frame's content which
is shown in this frame, in layout units.
.. seealso:: setContentSection()
:rtype: QRectF
%End
bool hidePageIfEmpty() const;
%Docstring
Returns whether the page should be hidden (ie, not included in layout exports) if this frame is empty
:return: true if page should be hidden if frame is empty
.. seealso:: setHidePageIfEmpty()
:rtype: bool
%End
void setHidePageIfEmpty( const bool hidePageIfEmpty );
%Docstring
Sets whether the page should be hidden (ie, not included in layout exports) if this frame is empty
\param hidePageIfEmpty set to true if page should be hidden if frame is empty
.. seealso:: hidePageIfEmpty()
%End
bool hideBackgroundIfEmpty() const;
%Docstring
Returns whether the background and frame stroke should be hidden if this frame is empty
:return: true if background and stroke should be hidden if frame is empty
.. seealso:: setHideBackgroundIfEmpty()
:rtype: bool
%End
void setHideBackgroundIfEmpty( const bool hideBackgroundIfEmpty );
%Docstring
Sets whether the background and frame stroke should be hidden if this frame is empty
\param hideBackgroundIfEmpty set to true if background and stroke should be hidden if frame is empty
.. seealso:: hideBackgroundIfEmpty()
%End
bool isEmpty() const;
%Docstring
Returns whether the frame is empty.
.. seealso:: hidePageIfEmpty()
:rtype: bool
%End
virtual QgsExpressionContext createExpressionContext() const;
protected:
virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 );
void drawFrame( QgsRenderContext &context );
void drawBackground( QgsRenderContext &context );
};
/************************************************************************
* This file has been generated automatically from *

View File

@ -74,6 +74,70 @@ class QgsLayoutItemAbstractMetadata
class QgsLayoutMultiFrameAbstractMetadata
{
%Docstring
Stores metadata about one layout multiframe class.
A companion class, QgsLayoutMultiFrameAbstractGuiMetadata, handles the
GUI behavior of QgsLayoutMultiFrames.
.. note::
In C++ you can use QgsLayoutMultiFrameMetadata convenience class.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslayoutitemregistry.h"
%End
public:
QgsLayoutMultiFrameAbstractMetadata( int type, const QString &visibleName );
%Docstring
Constructor for QgsLayoutMultiFrameAbstractMetadata with the specified class ``type``
and ``visibleName``.
%End
virtual ~QgsLayoutMultiFrameAbstractMetadata();
int type() const;
%Docstring
Returns the unique item type code for the layout multiframe class.
:rtype: int
%End
virtual QIcon icon() const;
%Docstring
Returns an icon representing the layout multiframe type.
:rtype: QIcon
%End
QString visibleName() const;
%Docstring
Returns a translated, user visible name for the layout multiframe class.
:rtype: str
%End
virtual QgsLayoutMultiFrame *createMultiFrame( QgsLayout *layout ) = 0 /Factory/;
%Docstring
Creates a layout multiframe of this class for a specified ``layout``.
:rtype: QgsLayoutMultiFrame
%End
virtual void resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving );
%Docstring
Resolve paths in the item's ``properties`` (if there are any paths).
When ``saving`` is true, paths are converted from absolute to relative,
when ``saving`` is false, paths are converted from relative to absolute.
This ensures that paths in project files can be relative, but in item
instances the paths are always absolute.
%End
};
class QgsLayoutItemRegistry : QObject
@ -111,6 +175,9 @@ class QgsLayoutItemRegistry : QObject
LayoutPolyline,
LayoutFrame,
// known
LayoutHtml,
// item
PluginItem,
};
@ -139,21 +206,46 @@ class QgsLayoutItemRegistry : QObject
%Docstring
Returns the metadata for the specified item ``type``. Returns None if
a corresponding type was not found in the registry.
.. seealso:: multiFrameMetadata()
:rtype: QgsLayoutItemAbstractMetadata
%End
QgsLayoutMultiFrameAbstractMetadata *multiFrameMetadata( int type ) const;
%Docstring
Returns the metadata for the specified multiframe ``type``. Returns None if
a corresponding type was not found in the registry.
.. seealso:: itemMetadata()
:rtype: QgsLayoutMultiFrameAbstractMetadata
%End
bool addLayoutItemType( QgsLayoutItemAbstractMetadata *metadata /Transfer/ );
%Docstring
Registers a new layout item type. Takes ownership of the metadata instance.
.. seealso:: addLayoutMultiFrameType()
:rtype: bool
%End
bool addLayoutMultiFrameType( QgsLayoutMultiFrameAbstractMetadata *metadata /Transfer/ );
%Docstring
Registers a new layout multiframe type. Takes ownership of the metadata instance.
.. seealso:: addLayoutItemType()
:rtype: bool
%End
QgsLayoutItem *createItem( int type, QgsLayout *layout ) const /Factory/;
%Docstring
Creates a new instance of a layout item given the item ``type``, and target ``layout``.
.. seealso:: createMultiFrame()
:rtype: QgsLayoutItem
%End
QgsLayoutMultiFrame *createMultiFrame( int type, QgsLayout *layout ) const /Factory/;
%Docstring
Creates a new instance of a layout multiframe given the multiframe ``type``, and target ``layout``.
.. seealso:: createItem()
:rtype: QgsLayoutMultiFrame
%End
void resolvePaths( int type, QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving ) const;
%Docstring
Resolve paths in properties of a particular symbol layer.
@ -175,6 +267,12 @@ class QgsLayoutItemRegistry : QObject
``type`` and visible ``name``.
%End
void multiFrameTypeAdded( int type, const QString &name );
%Docstring
Emitted whenever a new multiframe type is added to the registry, with the specified
``type`` and visible ``name``.
%End
private:
QgsLayoutItemRegistry( const QgsLayoutItemRegistry &rh );
};

View File

@ -10,6 +10,331 @@
class QgsLayoutMultiFrame: QgsLayoutObject, QgsLayoutUndoObjectInterface
{
%Docstring
Abstract base class for layout items with the ability to distribute the content to
several frames (QgsLayoutFrame items).
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslayoutmultiframe.h"
%End
public:
enum ResizeMode
{
UseExistingFrames,
ExtendToNextPage,
RepeatOnEveryPage,
RepeatUntilFinished
};
enum UndoCommand
{
UndoNone,
};
QgsLayoutMultiFrame( QgsLayout *layout /TransferThis/ );
%Docstring
Construct a new multiframe item, attached to the specified ``layout``.
%End
~QgsLayoutMultiFrame();
QString uuid() const;
%Docstring
Returns the multiframe identification string. This is a unique random string set for the multiframe
upon creation.
.. note::
There is no corresponding setter for the uuid - it's created automatically.
:rtype: str
%End
virtual QSizeF totalSize() const = 0;
%Docstring
Returns the total size of the multiframe's content, in layout units.
:rtype: QSizeF
%End
virtual int type() const = 0;
%Docstring
Returns unique multiframe type id.
:rtype: int
%End
virtual QString stringType() const = 0;
%Docstring
Return the multiframe type as a string.
This string must be a unique, single word, character only representation of the item type, eg "LayoutHtml"
.. seealso:: type()
:rtype: str
%End
virtual QSizeF fixedFrameSize( const int frameIndex = -1 ) const;
%Docstring
Returns the fixed size for a frame, if desired. If the fixed frame size changes,
the sizes of all frames can be recalculated by calling recalculateFrameRects().
\param frameIndex frame number
:return: fixed size for frame. If the size has a width or height of 0, then
the frame size is not fixed in that direction and frames can have variable width
or height accordingly.
.. seealso:: minFrameSize()
.. seealso:: recalculateFrameRects()
:rtype: QSizeF
%End
virtual QSizeF minFrameSize( const int frameIndex = -1 ) const;
%Docstring
Returns the minimum size for a frames, if desired. If the minimum
size changes, the sizes of all frames can be recalculated by calling
recalculateFrameRects().
\param frameIndex frame number
:return: minimum size for frame. If the size has a width or height of 0, then
the frame size has no minimum in that direction.
.. seealso:: fixedFrameSize()
.. seealso:: recalculateFrameRects()
:rtype: QSizeF
%End
virtual void render( QgsRenderContext &context, const QRectF &renderExtent, const int frameIndex,
const QStyleOptionGraphicsItem *itemStyle = 0 ) = 0;
%Docstring
Renders a portion of the multiframe's content into a render ``context``.
\param context destination render painter
\param renderExtent visible extent of content to render into the painter.
\param frameIndex frame number for content
\param itemStyle item style options for graphics item rendering
%End
virtual void addFrame( QgsLayoutFrame *frame /Transfer/, bool recalcFrameSizes = true );
%Docstring
Adds a ``frame`` to the multiframe.
If ``recalcFrameSizes`` is set to true, then a recalculation of all existing frame sizes will be forced.
.. seealso:: removeFrame()
%End
virtual double findNearbyPageBreak( double yPos );
%Docstring
Finds the optimal position to break a frame at.
\param yPos maximum vertical position for break, in layout units.
:return: the optimal breakable position which occurs in the multi frame close
to and before the specified yPos
:rtype: float
%End
void removeFrame( int index, bool removeEmptyPages = false );
%Docstring
Removes a frame by ``index`` from the multiframe. This method automatically removes the frame from the
layout too.
If ``removeEmptyPages`` is set to true, then pages which are empty after the frame is removed will
also be removed from the layout.
.. seealso:: addFrame()
.. seealso:: deleteFrames()
%End
void deleteFrames();
%Docstring
Removes and deletes all child frames.
.. seealso:: removeFrame()
%End
void setResizeMode( ResizeMode mode );
%Docstring
Sets the resize ``mode`` for the multiframe, and recalculates frame sizes to match.
.. seealso:: resizeMode()
%End
ResizeMode resizeMode() const;
%Docstring
Returns the resize mode for the multiframe.
.. seealso:: setResizeMode()
:rtype: ResizeMode
%End
bool writeXml( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames = false ) const;
%Docstring
Stores the multiframe state in a DOM element.
\param parentElement parent DOM element (e.g. 'Layout' element)
\param document DOM document
\param context read write context
\param ignoreFrames set to false to avoid writing state information about child frames into DOM
.. seealso:: readXml()
:rtype: bool
%End
bool readXml( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames = false );
%Docstring
Sets the item state from a DOM element.
\param itemElement is the DOM node corresponding to item (e.g. 'LayoutItem' element)
\param document DOM document
\param context read write context
\param ignoreFrames set to false to avoid read state information about child frames from DOM
.. seealso:: writeXml()
:rtype: bool
%End
QList<QgsLayoutFrame *> frames() const;
%Docstring
Returns a list of all child frames for this multiframe.
.. seealso:: frameCount()
:rtype: list of QgsLayoutFrame
%End
int frameCount() const;
%Docstring
Returns the number of frames associated with this multiframe.
.. seealso:: frames()
:rtype: int
%End
QgsLayoutFrame *frame( int index ) const;
%Docstring
Returns the child frame at a specified ``index`` from the multiframe.
.. seealso:: frameIndex()
:rtype: QgsLayoutFrame
%End
int frameIndex( QgsLayoutFrame *frame ) const;
%Docstring
Returns the index of a ``frame`` within the multiframe.
:return: index for frame if found, -1 if frame not found in multiframe
.. seealso:: frame()
:rtype: int
%End
QgsLayoutFrame *createNewFrame( QgsLayoutFrame *currentFrame, QPointF pos, QSizeF size );
%Docstring
Creates a new frame and adds it to the multi frame and layout.
\param currentFrame an existing QgsLayoutFrame from which to copy the size
and general frame properties (e.g., frame style, background, rendering settings).
\param pos position of top-left corner of the new frame, in layout units
\param size size of the new frame, in layout units
:rtype: QgsLayoutFrame
%End
virtual QString displayName() const;
%Docstring
Returns the multiframe display name.
:rtype: str
%End
virtual QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = 0 ) /Factory/;
void beginCommand( const QString &commandText, UndoCommand command = UndoNone );
%Docstring
Starts new undo command for this item.
The ``commandText`` should be a capitalized, imperative tense description (e.g. "Add Map Item").
If specified, multiple consecutive commands for this item with the same ``command`` will
be collapsed into a single undo command in the layout history.
.. seealso:: endCommand()
.. seealso:: cancelCommand()
%End
void endCommand();
%Docstring
Completes the current item command and push it onto the layout's undo stack.
.. seealso:: beginCommand()
.. seealso:: cancelCommand()
%End
void cancelCommand();
%Docstring
Cancels the current item command and discards it.
.. seealso:: beginCommand()
.. seealso:: endCommand()
%End
public slots:
void update();
%Docstring
Forces a redraw of all child frames.
%End
virtual void recalculateFrameSizes();
%Docstring
Recalculates the portion of the multiframe item which is shown in each of its
component frames. If the resize mode is set to anything but UseExistingFrames then
this may cause new frames to be added or frames to be removed, in order to fit
the current size of the multiframe's content.
.. seealso:: recalculateFrameRects()
%End
void recalculateFrameRects();
%Docstring
Forces a recalculation of all the associated frame's scene rectangles. This
method is useful for multiframes which implement a minFrameSize() or
fixedFrameSize() method.
.. seealso:: minFrameSize()
.. seealso:: fixedFrameSize()
.. seealso:: recalculateFrameSizes
%End
signals:
void changed();
%Docstring
Emitted when the properties of a multi frame have changed, and the GUI item widget
must be updated.
%End
void contentsChanged();
%Docstring
Emitted when the contents of the multi frame have changed and the frames
must be redrawn.
%End
protected:
virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
%Docstring
Stores multiframe state within an XML DOM element.
\param element is the DOM element to store the multiframe's properties in
\param document DOM document
\param context read write context
.. seealso:: writeXml()
.. seealso:: readPropertiesFromElement()
:rtype: bool
%End
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
%Docstring
Sets multiframe state from a DOM element.
\param element is the DOM element for the multiframe
\param document DOM document
\param context read write context
.. seealso:: writePropertiesToElement()
.. seealso:: readXml()
:rtype: bool
%End
protected slots:
void handlePageChange();
%Docstring
Adapts to changed number of layout pages if resize type is RepeatOnEveryPage.
%End
void handleFrameRemoval();
%Docstring
Called when a frame is removed. Updates frame list and recalculates
content of remaining frames.
%End
};
/************************************************************************
* This file has been generated automatically from *

View File

@ -33,6 +33,8 @@
#include "qgslayoutlabelwidget.h"
#include "qgslayoutitemlegend.h"
#include "qgslayoutlegendwidget.h"
#include "qgslayoutframe.h"
#include "qgslayoutitemhtml.h"
#include "qgisapp.h"
#include "qgsmapcanvas.h"
@ -198,4 +200,23 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
return band.release();
} );
registry->addLayoutItemGuiMetadata( polylineMetadata.release() );
// html item
auto htmlItemMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >( QgsLayoutItemRegistry::LayoutHtml, QObject::tr( "HTML" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddHtml.svg" ) ),
[ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
{
return nullptr; //new QgsLayoutMapWidget( qobject_cast< QgsLayoutItemMap * >( item ) );
}, createRubberBand );
htmlItemMetadata->setItemCreationFunction( [ = ]( QgsLayout * layout )->QgsLayoutItem*
{
std::unique_ptr< QgsLayoutItemHtml > htmlMultiFrame = qgis::make_unique< QgsLayoutItemHtml >( layout );
QgsLayoutItemHtml *html = htmlMultiFrame.get();
layout->addMultiFrame( htmlMultiFrame.release() );
std::unique_ptr< QgsLayoutFrame > frame = qgis::make_unique< QgsLayoutFrame >( layout, html );
return frame.release();
} );
registry->addLayoutItemGuiMetadata( htmlItemMetadata.release() );
}

View File

@ -392,6 +392,7 @@ SET(QGIS_CORE_SRCS
layout/qgslayoutmeasurementconverter.cpp
layout/qgslayoutmodel.cpp
layout/qgslayoutmultiframe.cpp
layout/qgslayoutmultiframeundocommand.cpp
layout/qgslayoutobject.cpp
layout/qgslayoutpagecollection.cpp
layout/qgslayoutserializableobject.cpp
@ -1012,6 +1013,7 @@ SET(QGIS_CORE_HDRS
layout/qgslayoutitemundocommand.h
layout/qgslayoutmeasurement.h
layout/qgslayoutmeasurementconverter.h
layout/qgslayoutmultiframeundocommand.h
layout/qgspagesizeregistry.h
layout/qgslayoutpoint.h
layout/qgslayoutserializableobject.h

View File

@ -207,6 +207,19 @@ QgsLayoutItem *QgsLayout::itemByUuid( const QString &uuid )
return nullptr;
}
QgsLayoutMultiFrame *QgsLayout::multiFrameByUuid( const QString &uuid ) const
{
for ( QgsLayoutMultiFrame *mf : mMultiFrames )
{
if ( mf->uuid() == uuid )
{
return mf;
}
}
return nullptr;
}
QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const bool ignoreLocked ) const
{
return layoutItemAt( position, nullptr, ignoreLocked );
@ -397,7 +410,8 @@ void QgsLayout::addLayoutItem( QgsLayoutItem *item )
{
undoText = tr( "Create Item" );
}
mUndoStack->stack()->push( new QgsLayoutItemAddItemCommand( item, undoText ) );
if ( mBlockUndoCommandCount == 0 )
mUndoStack->stack()->push( new QgsLayoutItemAddItemCommand( item, undoText ) );
}
void QgsLayout::removeLayoutItem( QgsLayoutItem *item )

View File

@ -208,9 +208,17 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
/**
* Returns the layout item with matching \a uuid unique identifier, or a nullptr
* if a matching item could not be found.
* \see multiFrameByUuid()
*/
QgsLayoutItem *itemByUuid( const QString &uuid );
/**
* Returns the layout multiframe with matching \a uuid unique identifier, or a nullptr
* if a matching multiframe could not be found.
* \see itemByUuid()
*/
QgsLayoutMultiFrame *multiFrameByUuid( const QString &uuid ) const;
/**
* Returns the topmost layout item at a specified \a position. Ignores paper items.
* If \a ignoreLocked is set to true any locked items will be ignored.

View File

@ -41,11 +41,16 @@ QgsLayoutFrame::QgsLayoutFrame( QgsLayout *layout, QgsLayoutMultiFrame *multiFra
}
}
QgsLayoutFrame *QgsLayoutFrame::create( QgsLayout *layout )
{
return new QgsLayoutFrame( layout, nullptr );
}
QgsLayoutMultiFrame *QgsLayoutFrame::multiFrame() const
{
return mMultiFrame;
}
#if 0// TODO
#if 0// TODO - save/restore multiframe uuid!
bool QgsLayoutFrame::writeXml( QDomElement &elem, QDomDocument &doc ) const
{
QDomElement frameElem = doc.createElement( QStringLiteral( "ComposerFrame" ) );

View File

@ -23,8 +23,6 @@
class QgsLayout;
class QgsLayoutMultiFrame;
#ifndef SIP_RUN
/**
* \ingroup core
* Base class for frame items, which form a layout multiframe item.
@ -42,6 +40,11 @@ class CORE_EXPORT QgsLayoutFrame: public QgsLayoutItem
*/
QgsLayoutFrame( QgsLayout *layout, QgsLayoutMultiFrame *multiFrame );
/**
* Creates a new QgsLayoutFrame belonging to the specified \a layout.
*/
static QgsLayoutFrame *create( QgsLayout *layout ) SIP_FACTORY;
int type() const override;
QString stringType() const override;
@ -134,6 +137,4 @@ class CORE_EXPORT QgsLayoutFrame: public QgsLayoutItem
};
#endif
#endif // QGSLAYOUTFRAME_H

View File

@ -90,6 +90,21 @@ QgsLayoutItemHtml::~QgsLayoutItemHtml()
mFetcher->deleteLater();
}
int QgsLayoutItemHtml::type() const
{
return QgsLayoutItemRegistry::LayoutHtml;
}
QString QgsLayoutItemHtml::stringType() const
{
return QStringLiteral( "LayoutHtml" );
}
QgsLayoutItemHtml *QgsLayoutItemHtml::create( QgsLayout *layout )
{
return new QgsLayoutItemHtml( layout );
}
void QgsLayoutItemHtml::setUrl( const QUrl &url )
{
if ( !mWebPage )
@ -449,9 +464,8 @@ QString QgsLayoutItemHtml::displayName() const
return tr( "<HTML frame>" );
}
bool QgsLayoutItemHtml::writeXml( QDomElement &elem, QDomDocument &doc, bool ignoreFrames ) const
bool QgsLayoutItemHtml::writePropertiesToElement( QDomElement &htmlElem, QDomDocument &, const QgsReadWriteContext & ) const
{
QDomElement htmlElem = doc.createElement( QStringLiteral( "ComposerHtml" ) );
htmlElem.setAttribute( QStringLiteral( "contentMode" ), QString::number( static_cast< int >( mContentMode ) ) );
htmlElem.setAttribute( QStringLiteral( "url" ), mUrl.toString() );
htmlElem.setAttribute( QStringLiteral( "html" ), mHtml );
@ -460,25 +474,11 @@ bool QgsLayoutItemHtml::writeXml( QDomElement &elem, QDomDocument &doc, bool ign
htmlElem.setAttribute( QStringLiteral( "maxBreakDistance" ), QString::number( mMaxBreakDistance ) );
htmlElem.setAttribute( QStringLiteral( "stylesheet" ), mUserStylesheet );
htmlElem.setAttribute( QStringLiteral( "stylesheetEnabled" ), mEnableUserStylesheet ? "true" : "false" );
bool state = _writeXml( htmlElem, doc, ignoreFrames );
elem.appendChild( htmlElem );
return state;
return true;
}
bool QgsLayoutItemHtml::readXml( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames )
bool QgsLayoutItemHtml::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext & )
{
if ( !ignoreFrames )
{
deleteFrames();
}
//first create the frames
if ( !_readXml( itemElem, doc, ignoreFrames ) )
{
return false;
}
bool contentModeOK;
mContentMode = static_cast< QgsLayoutItemHtml::ContentMode >( itemElem.attribute( QStringLiteral( "contentMode" ) ).toInt( &contentModeOK ) );
if ( !contentModeOK )

View File

@ -55,6 +55,14 @@ class CORE_EXPORT QgsLayoutItemHtml: public QgsLayoutMultiFrame
~QgsLayoutItemHtml();
int type() const override;
QString stringType() const override;
/**
* Returns a new QgsLayoutItemHtml for the specified parent \a layout.
*/
static QgsLayoutItemHtml *create( QgsLayout *layout ) SIP_FACTORY;
/**
* Sets the source \a mode for item's HTML content.
* \see contentMode()
@ -199,8 +207,6 @@ class CORE_EXPORT QgsLayoutItemHtml: public QgsLayoutMultiFrame
void render( QgsRenderContext &context, const QRectF &renderExtent, const int frameIndex,
const QStyleOptionGraphicsItem *itemStyle = nullptr ) override;
bool writeXml( QDomElement &elem, QDomDocument &doc, bool ignoreFrames = false ) const override;
bool readXml( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames = false ) override;
//overridden to break frames without dividing lines of text
double findNearbyPageBreak( double yPos ) override;
@ -222,6 +228,11 @@ class CORE_EXPORT QgsLayoutItemHtml: public QgsLayoutMultiFrame
void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );
protected:
bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
bool readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context ) override;
private slots:
void frameLoaded( bool ok = true );

View File

@ -24,6 +24,8 @@
#include "qgslayoutitempage.h"
#include "qgslayoutitempicture.h"
#include "qgslayoutitemgroup.h"
#include "qgslayoutitemhtml.h"
#include "qgslayoutframe.h"
#include "qgsgloweffect.h"
#include "qgseffectstack.h"
#include <QPainter>
@ -51,6 +53,7 @@ bool QgsLayoutItemRegistry::populate()
addLayoutItemType( new QgsLayoutItemMetadata( QgsLayoutItemRegistry::LayoutItem + 1002, QStringLiteral( "temp type" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLabel.svg" ) ), createTemporaryItem ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutGroup, QStringLiteral( "Group" ), QIcon(), QgsLayoutItemGroup::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutFrame, QStringLiteral( "Frame" ), QIcon(), QgsLayoutFrame::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPage, QStringLiteral( "Page" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileNew.svg" ) ), QgsLayoutItemPage::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutMap, QStringLiteral( "Map" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddMap.svg" ) ), QgsLayoutItemMap::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPicture, QStringLiteral( "Picture" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddImage.svg" ) ), QgsLayoutItemPicture::create ) );
@ -65,6 +68,8 @@ bool QgsLayoutItemRegistry::populate()
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPolygon, QStringLiteral( "Polygon" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPolygon.svg" ) ), QgsLayoutItemPolygon::create ) );
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPolyline, QStringLiteral( "Polyline" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPolyline.svg" ) ), QgsLayoutItemPolyline::create ) );
addLayoutMultiFrameType( new QgsLayoutMultiFrameMetadata( LayoutHtml, QStringLiteral( "HTML" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddHtml.svg" ) ), QgsLayoutItemHtml::create ) );
return true;
}
@ -73,6 +78,11 @@ QgsLayoutItemAbstractMetadata *QgsLayoutItemRegistry::itemMetadata( int type ) c
return mMetadata.value( type );
}
QgsLayoutMultiFrameAbstractMetadata *QgsLayoutItemRegistry::multiFrameMetadata( int type ) const
{
return mMultiFrameMetadata.value( type );
}
bool QgsLayoutItemRegistry::addLayoutItemType( QgsLayoutItemAbstractMetadata *metadata )
{
if ( !metadata || mMetadata.contains( metadata->type() ) )
@ -83,6 +93,16 @@ bool QgsLayoutItemRegistry::addLayoutItemType( QgsLayoutItemAbstractMetadata *me
return true;
}
bool QgsLayoutItemRegistry::addLayoutMultiFrameType( QgsLayoutMultiFrameAbstractMetadata *metadata )
{
if ( !metadata || mMultiFrameMetadata.contains( metadata->type() ) )
return false;
mMultiFrameMetadata[metadata->type()] = metadata;
emit multiFrameTypeAdded( metadata->type(), metadata->visibleName() );
return true;
}
QgsLayoutItem *QgsLayoutItemRegistry::createItem( int type, QgsLayout *layout ) const
{
if ( !mMetadata.contains( type ) )
@ -91,23 +111,38 @@ QgsLayoutItem *QgsLayoutItemRegistry::createItem( int type, QgsLayout *layout )
return mMetadata[type]->createItem( layout );
}
QgsLayoutMultiFrame *QgsLayoutItemRegistry::createMultiFrame( int type, QgsLayout *layout ) const
{
if ( !mMultiFrameMetadata.contains( type ) )
return nullptr;
return mMultiFrameMetadata[type]->createMultiFrame( layout );
}
void QgsLayoutItemRegistry::resolvePaths( int type, QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving ) const
{
if ( !mMetadata.contains( type ) )
return;
mMetadata[type]->resolvePaths( properties, pathResolver, saving );
if ( mMetadata.contains( type ) )
{
mMetadata[type]->resolvePaths( properties, pathResolver, saving );
}
else if ( mMultiFrameMetadata.contains( type ) )
{
mMultiFrameMetadata[type]->resolvePaths( properties, pathResolver, saving );
}
}
QMap<int, QString> QgsLayoutItemRegistry::itemTypes() const
{
QMap<int, QString> types;
QMap<int, QgsLayoutItemAbstractMetadata *>::ConstIterator it = mMetadata.constBegin();
for ( ; it != mMetadata.constEnd(); ++it )
for ( auto it = mMetadata.constBegin(); it != mMetadata.constEnd(); ++it )
{
types.insert( it.key(), it.value()->visibleName() );
}
for ( auto it = mMultiFrameMetadata.constBegin(); it != mMultiFrameMetadata.constEnd(); ++it )
{
types.insert( it.key(), it.value()->visibleName() );
}
return types;
}

View File

@ -30,6 +30,7 @@ class QgsLayout;
class QgsLayoutView;
class QgsLayoutItem;
class QgsFillSymbol;
class QgsLayoutMultiFrame;
/**
* \ingroup core
@ -155,6 +156,129 @@ class CORE_EXPORT QgsLayoutItemMetadata : public QgsLayoutItemAbstractMetadata
#endif
/**
* \ingroup core
* \brief Stores metadata about one layout multiframe class.
*
* A companion class, QgsLayoutMultiFrameAbstractGuiMetadata, handles the
* GUI behavior of QgsLayoutMultiFrames.
*
* \note In C++ you can use QgsLayoutMultiFrameMetadata convenience class.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLayoutMultiFrameAbstractMetadata
{
public:
/**
* Constructor for QgsLayoutMultiFrameAbstractMetadata with the specified class \a type
* and \a visibleName.
*/
QgsLayoutMultiFrameAbstractMetadata( int type, const QString &visibleName )
: mType( type )
, mVisibleName( visibleName )
{}
virtual ~QgsLayoutMultiFrameAbstractMetadata() = default;
/**
* Returns the unique item type code for the layout multiframe class.
*/
int type() const { return mType; }
/**
* Returns an icon representing the layout multiframe type.
*/
virtual QIcon icon() const { return QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ); }
/**
* Returns a translated, user visible name for the layout multiframe class.
*/
QString visibleName() const { return mVisibleName; }
/**
* Creates a layout multiframe of this class for a specified \a layout.
*/
virtual QgsLayoutMultiFrame *createMultiFrame( QgsLayout *layout ) = 0 SIP_FACTORY;
/**
* Resolve paths in the item's \a properties (if there are any paths).
* When \a saving is true, paths are converted from absolute to relative,
* when \a saving is false, paths are converted from relative to absolute.
* This ensures that paths in project files can be relative, but in item
* instances the paths are always absolute.
*/
virtual void resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
{
Q_UNUSED( properties );
Q_UNUSED( pathResolver );
Q_UNUSED( saving );
}
private:
int mType = -1;
QString mVisibleName;
};
//! Layout multiframe creation function
typedef std::function<QgsLayoutMultiFrame *( QgsLayout * )> QgsLayoutMultiFrameCreateFunc SIP_SKIP;
//! Layout multiframe path resolver function
typedef std::function<void( QVariantMap &, const QgsPathResolver &, bool )> QgsLayoutMultiFramePathResolverFunc SIP_SKIP;
#ifndef SIP_RUN
/**
* \ingroup core
* Convenience metadata class that uses static functions to create layout multiframes and their configuration widgets.
* \since QGIS 3.0
* \note not available in Python bindings
*/
class CORE_EXPORT QgsLayoutMultiFrameMetadata : public QgsLayoutMultiFrameAbstractMetadata
{
public:
/**
* Constructor for QgsLayoutMultiFrameMetadata with the specified class \a type
* and \a visibleName, and function pointers for the various item creation functions.
*/
QgsLayoutMultiFrameMetadata( int type, const QString &visibleName, const QIcon &icon,
QgsLayoutMultiFrameCreateFunc pfCreate,
QgsLayoutMultiFramePathResolverFunc pfPathResolver = nullptr )
: QgsLayoutMultiFrameAbstractMetadata( type, visibleName )
, mIcon( icon )
, mCreateFunc( pfCreate )
, mPathResolverFunc( pfPathResolver )
{}
/**
* Returns the classes' multiframe creation function.
*/
QgsLayoutMultiFrameCreateFunc createFunction() const { return mCreateFunc; }
/**
* Returns the classes' path resolver function.
*/
QgsLayoutMultiFramePathResolverFunc pathResolverFunction() const { return mPathResolverFunc; }
QIcon icon() const override { return mIcon.isNull() ? QgsLayoutMultiFrameAbstractMetadata::icon() : mIcon; }
QgsLayoutMultiFrame *createMultiFrame( QgsLayout *layout ) override { return mCreateFunc ? mCreateFunc( layout ) : nullptr; }
void resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving ) override
{
if ( mPathResolverFunc )
mPathResolverFunc( properties, pathResolver, saving );
}
protected:
QIcon mIcon;
QgsLayoutMultiFrameCreateFunc mCreateFunc = nullptr;
QgsLayoutMultiFramePathResolverFunc mPathResolverFunc = nullptr;
};
#endif
/**
@ -193,6 +317,9 @@ class CORE_EXPORT QgsLayoutItemRegistry : public QObject
LayoutPolyline, //!< Polyline shape item
LayoutFrame, //!< Frame item, part of a QgsLayoutMultiFrame object
// known multi-frame types
LayoutHtml, //!< Html multiframe item
// item types provided by plugins
PluginItem, //!< Starting point for plugin item types
};
@ -223,19 +350,41 @@ class CORE_EXPORT QgsLayoutItemRegistry : public QObject
/**
* Returns the metadata for the specified item \a type. Returns nullptr if
* a corresponding type was not found in the registry.
* \see multiFrameMetadata()
*/
QgsLayoutItemAbstractMetadata *itemMetadata( int type ) const;
/**
* Returns the metadata for the specified multiframe \a type. Returns nullptr if
* a corresponding type was not found in the registry.
* \see itemMetadata()
*/
QgsLayoutMultiFrameAbstractMetadata *multiFrameMetadata( int type ) const;
/**
* Registers a new layout item type. Takes ownership of the metadata instance.
* \see addLayoutMultiFrameType()
*/
bool addLayoutItemType( QgsLayoutItemAbstractMetadata *metadata SIP_TRANSFER );
/**
* Registers a new layout multiframe type. Takes ownership of the metadata instance.
* \see addLayoutItemType()
*/
bool addLayoutMultiFrameType( QgsLayoutMultiFrameAbstractMetadata *metadata SIP_TRANSFER );
/**
* Creates a new instance of a layout item given the item \a type, and target \a layout.
* \see createMultiFrame()
*/
QgsLayoutItem *createItem( int type, QgsLayout *layout ) const SIP_FACTORY;
/**
* Creates a new instance of a layout multiframe given the multiframe \a type, and target \a layout.
* \see createItem()
*/
QgsLayoutMultiFrame *createMultiFrame( int type, QgsLayout *layout ) const SIP_FACTORY;
/**
* Resolve paths in properties of a particular symbol layer.
* This normally means converting relative paths to absolute paths when loading
@ -256,12 +405,19 @@ class CORE_EXPORT QgsLayoutItemRegistry : public QObject
*/
void typeAdded( int type, const QString &name );
/**
* Emitted whenever a new multiframe type is added to the registry, with the specified
* \a type and visible \a name.
*/
void multiFrameTypeAdded( int type, const QString &name );
private:
#ifdef SIP_RUN
QgsLayoutItemRegistry( const QgsLayoutItemRegistry &rh );
#endif
QMap<int, QgsLayoutItemAbstractMetadata *> mMetadata;
QMap<int, QgsLayoutMultiFrameAbstractMetadata *> mMultiFrameMetadata;
};

View File

@ -14,12 +14,14 @@
***************************************************************************/
#include "qgslayoutmultiframe.h"
#include "qgslayoutmultiframeundocommand.h"
#include "qgslayoutframe.h"
#include "qgslayout.h"
#include <QtCore>
QgsLayoutMultiFrame::QgsLayoutMultiFrame( QgsLayout *layout )
: QgsLayoutObject( layout )
, mUuid( QUuid::createUuid().toString() )
{
mLayout->addMultiFrame( this );
@ -98,6 +100,9 @@ void QgsLayoutMultiFrame::recalculateFrameSizes()
return;
}
if ( mBlockUndoCommands )
mLayout->mBlockUndoCommandCount++;
double currentY = 0;
double currentHeight = 0;
QgsLayoutFrame *currentItem = nullptr;
@ -206,6 +211,9 @@ void QgsLayoutMultiFrame::recalculateFrameSizes()
currentItem = newFrame;
}
}
if ( mBlockUndoCommands )
mLayout->mBlockUndoCommandCount--;
}
void QgsLayoutMultiFrame::recalculateFrameRects()
@ -257,6 +265,31 @@ QString QgsLayoutMultiFrame::displayName() const
return tr( "<Multiframe>" );
}
QgsAbstractLayoutUndoCommand *QgsLayoutMultiFrame::createCommand( const QString &text, int id, QUndoCommand *parent )
{
return new QgsLayoutMultiFrameUndoCommand( this, text, id, parent );
}
void QgsLayoutMultiFrame::beginCommand( const QString &commandText, QgsLayoutMultiFrame::UndoCommand command )
{
if ( !mLayout )
return;
mLayout->undoStack()->beginCommand( this, commandText, command );
}
void QgsLayoutMultiFrame::endCommand()
{
if ( mLayout )
mLayout->undoStack()->endCommand();
}
void QgsLayoutMultiFrame::cancelCommand()
{
if ( mLayout )
mLayout->undoStack()->cancelCommand();
}
void QgsLayoutMultiFrame::handleFrameRemoval()
{
if ( mBlockUpdates )
@ -394,10 +427,15 @@ int QgsLayoutMultiFrame::frameIndex( QgsLayoutFrame *frame ) const
return mFrameItems.indexOf( frame );
}
bool QgsLayoutMultiFrame::_writeXml( QDomElement &elem, QDomDocument &doc, bool ignoreFrames ) const
bool QgsLayoutMultiFrame::writeXml( QDomElement &parentElement, QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames ) const
{
QDomElement element = doc.createElement( QStringLiteral( "LayoutMultiFrame" ) );
element.setAttribute( QStringLiteral( "resizeMode" ), mResizeMode );
element.setAttribute( QStringLiteral( "uuid" ), mUuid );
element.setAttribute( QStringLiteral( "type" ), stringType() );
#if 0 //TODO
elem.setAttribute( QStringLiteral( "resizeMode" ), mResizeMode );
if ( !ignoreFrames )
{
QList<QgsComposerFrame *>::const_iterator frameIt = mFrameItems.constBegin();
@ -406,17 +444,34 @@ bool QgsLayoutMultiFrame::_writeXml( QDomElement &elem, QDomDocument &doc, bool
( *frameIt )->writeXml( elem, doc );
}
}
QgsComposerObject::writeXml( elem, doc );
#endif
writeObjectPropertiesToElement( element, doc, context );
writePropertiesToElement( element, doc, context );
parentElement.appendChild( element );
return true;
}
bool QgsLayoutMultiFrame::_readXml( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames )
bool QgsLayoutMultiFrame::readXml( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames )
{
#if 0 //TODO
QgsComposerObject::readXml( itemElem, doc );
if ( element.nodeName() != QStringLiteral( "LayoutMultiFrame" ) || element.attribute( QStringLiteral( "type" ) ) != stringType() )
{
return false;
}
mBlockUndoCommands = true;
readObjectPropertiesFromElement( element, doc, context );
mUuid = element.attribute( QStringLiteral( "uuid" ), QUuid::createUuid().toString() );
mResizeMode = static_cast< ResizeMode >( element.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
#if 0 //TODO
if ( !ignoreFrames )
{
deleteFrames();
}
mResizeMode = static_cast< ResizeMode >( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
if ( !ignoreFrames )
{
QDomNodeList frameList = itemElem.elementsByTagName( QStringLiteral( "ComposerFrame" ) );
@ -431,7 +486,22 @@ bool QgsLayoutMultiFrame::_readXml( const QDomElement &itemElem, const QDomDocum
//TODO - think there should be a recalculateFrameSizes() call here
}
#endif
bool result = readPropertiesFromElement( element, doc, context );
mBlockUndoCommands = false;
return result;
}
bool QgsLayoutMultiFrame::writePropertiesToElement( QDomElement &, QDomDocument &, const QgsReadWriteContext & ) const
{
return true;
}
bool QgsLayoutMultiFrame::readPropertiesFromElement( const QDomElement &, const QDomDocument &, const QgsReadWriteContext & )
{
return true;
}

View File

@ -19,6 +19,7 @@
#include "qgis_core.h"
#include "qgis.h"
#include "qgslayoutobject.h"
#include "qgslayoutundocommand.h"
#include <QObject>
#include <QSizeF>
#include <QPointF>
@ -41,10 +42,7 @@ class QgsRenderContext;
* \since QGIS 3.0
*/
// sip crashes out on this file - reexamine after composer removal
#ifndef SIP_RUN
class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject, public QgsLayoutUndoObjectInterface
{
Q_OBJECT
@ -63,6 +61,12 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
until the entire multiframe content is visible */
};
//! Multiframe item undo commands, used for collapsing undo commands
enum UndoCommand
{
UndoNone = -1, //!< No command suppression
};
/**
* Construct a new multiframe item, attached to the specified \a layout.
*/
@ -70,11 +74,31 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
~QgsLayoutMultiFrame();
/**
* Returns the multiframe identification string. This is a unique random string set for the multiframe
* upon creation.
* \note There is no corresponding setter for the uuid - it's created automatically.
*/
QString uuid() const { return mUuid; }
/**
* Returns the total size of the multiframe's content, in layout units.
*/
virtual QSizeF totalSize() const = 0;
/**
* Returns unique multiframe type id.
*/
virtual int type() const = 0;
/**
* Return the multiframe type as a string.
*
* This string must be a unique, single word, character only representation of the item type, eg "LayoutHtml"
* \see type()
*/
virtual QString stringType() const = 0;
/**
* Returns the fixed size for a frame, if desired. If the fixed frame size changes,
* the sizes of all frames can be recalculated by calling recalculateFrameRects().
@ -157,51 +181,30 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
ResizeMode resizeMode() const { return mResizeMode; }
/**
* Stores state information about multiframe in DOM element. Implementations of writeXml
* should also call the _writeXML method to save general multiframe properties.
* \param elem is DOM element
* \param doc is the DOM document
* Stores the multiframe state in a DOM element.
* \param parentElement parent DOM element (e.g. 'Layout' element)
* \param document DOM document
* \param context read write context
* \param ignoreFrames set to false to avoid writing state information about child frames into DOM
* \see _writeXML
* \see readXml()
*/
virtual bool writeXml( QDomElement &elem, QDomDocument &doc, bool ignoreFrames = false ) const = 0;
bool writeXml( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames = false ) const;
/**
* Stores state information about base multiframe object in DOM element. Implementations of writeXml
* should call this method.
* \param elem is DOM element
* \param doc is the DOM document
* \param ignoreFrames set to false to avoid writing state information about child frames into DOM
* \see writeXml
*/
bool _writeXml( QDomElement &elem, QDomDocument &doc, bool ignoreFrames = false ) const;
/**
* Reads multiframe state information from a DOM element. Implementations of readXml
* should also call the _readXML method to restore general multiframe properties.
* \param itemElem is DOM element
* \param doc is the DOM document
* Sets the item state from a DOM element.
* \param itemElement is the DOM node corresponding to item (e.g. 'LayoutItem' element)
* \param document DOM document
* \param context read write context
* \param ignoreFrames set to false to avoid read state information about child frames from DOM
* \see _readXML
* \see writeXml()
*/
virtual bool readXml( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames = false ) = 0;
/**
* Restores state information about base multiframe object from a DOM element. Implementations of readXml
* should call this method.
* \param itemElem is DOM element
* \param doc is the DOM document
* \param ignoreFrames set to false to avoid reading state information about child frames from DOM
* \see readXml
*/
bool _readXml( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames = false );
bool readXml( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames = false );
/**
* Returns a list of all child frames for this multiframe.
* \see frameCount()
* \note Not available in Python bindings
*/
QList<QgsLayoutFrame *> frames() const SIP_SKIP;
QList<QgsLayoutFrame *> frames() const;
/**
* Returns the number of frames associated with this multiframe.
@ -236,6 +239,32 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
*/
virtual QString displayName() const;
QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = nullptr ) override SIP_FACTORY;
/**
* Starts new undo command for this item.
* The \a commandText should be a capitalized, imperative tense description (e.g. "Add Map Item").
* If specified, multiple consecutive commands for this item with the same \a command will
* be collapsed into a single undo command in the layout history.
* \see endCommand()
* \see cancelCommand()
*/
void beginCommand( const QString &commandText, UndoCommand command = UndoNone );
/**
* Completes the current item command and push it onto the layout's undo stack.
* \see beginCommand()
* \see cancelCommand()
*/
void endCommand();
/**
* Cancels the current item command and discards it.
* \see beginCommand()
* \see endCommand()
*/
void cancelCommand();
public slots:
/**
@ -278,6 +307,26 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
protected:
/**
* Stores multiframe state within an XML DOM element.
* \param element is the DOM element to store the multiframe's properties in
* \param document DOM document
* \param context read write context
* \see writeXml()
* \see readPropertiesFromElement()
*/
virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
/**
* Sets multiframe state from a DOM element.
* \param element is the DOM element for the multiframe
* \param document DOM document
* \param context read write context
* \see writePropertiesToElement()
* \see readXml()
*/
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
QList<QgsLayoutFrame *> mFrameItems;
ResizeMode mResizeMode = UseExistingFrames;
@ -302,8 +351,11 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
bool mIsRecalculatingSize = false;
bool mBlockUpdates = false;
bool mBlockUndoCommands = false;
//! Unique id
QString mUuid;
};
#endif
#endif // QGSLAYOUTMULTIFRAME_H

View File

@ -0,0 +1,161 @@
/***************************************************************************
qgslayoutmultiframeundocommand.cpp
----------------------
begin : October 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgslayoutmultiframeundocommand.h"
#include "qgslayoutmultiframe.h"
#include "qgsreadwritecontext.h"
#include "qgslayout.h"
#include "qgsproject.h"
///@cond PRIVATE
QgsLayoutMultiFrameUndoCommand::QgsLayoutMultiFrameUndoCommand( QgsLayoutMultiFrame *frame, const QString &text, int id, QUndoCommand *parent )
: QgsAbstractLayoutUndoCommand( text, id, parent )
, mFrameUuid( frame->uuid() )
, mLayout( frame->layout() )
, mItemType( frame->type() )
{
}
bool QgsLayoutMultiFrameUndoCommand::mergeWith( const QUndoCommand *command )
{
if ( command->id() == 0 )
return false;
const QgsLayoutMultiFrameUndoCommand *c = dynamic_cast<const QgsLayoutMultiFrameUndoCommand *>( command );
if ( !c )
{
return false;
}
if ( c->multiFrameUuid() != multiFrameUuid() )
return false;
setAfterState( c->afterState() );
return true;
}
void QgsLayoutMultiFrameUndoCommand::saveState( QDomDocument &stateDoc ) const
{
stateDoc.clear();
QDomElement documentElement = stateDoc.createElement( QStringLiteral( "ItemState" ) );
QgsLayoutMultiFrame *item = mLayout->multiFrameByUuid( mFrameUuid );
Q_ASSERT_X( item, "QgsLayoutMultiFrameUndoCommand::saveState", "could not retrieve item for saving state" );
item->writeXml( documentElement, stateDoc, QgsReadWriteContext() );
stateDoc.appendChild( documentElement );
}
void QgsLayoutMultiFrameUndoCommand::restoreState( QDomDocument &stateDoc )
{
// find item by uuid...
QgsLayoutMultiFrame *item = mLayout->multiFrameByUuid( mFrameUuid );
if ( !item )
{
// uh oh - it's been deleted! we need to create a new instance
item = recreateItem( mItemType, mLayout );
}
item->readXml( stateDoc.documentElement().firstChild().toElement(), stateDoc, QgsReadWriteContext() );
mLayout->project()->setDirty( true );
}
QgsLayoutMultiFrame *QgsLayoutMultiFrameUndoCommand::recreateItem( int itemType, QgsLayout *layout )
{
QgsLayoutMultiFrame *item = QgsApplication::layoutItemRegistry()->createMultiFrame( itemType, layout );
mLayout->addMultiFrame( item );
return item;
}
QString QgsLayoutMultiFrameUndoCommand::multiFrameUuid() const
{
return mFrameUuid;
}
QgsLayout *QgsLayoutMultiFrameUndoCommand::layout() const
{
return mLayout;
}
//
// QgsLayoutMultiFrameDeleteUndoCommand
//
QgsLayoutMultiFrameDeleteUndoCommand::QgsLayoutMultiFrameDeleteUndoCommand( QgsLayoutMultiFrame *item, const QString &text, int id, QUndoCommand *parent )
: QgsLayoutMultiFrameUndoCommand( item, text, id, parent )
{
saveBeforeState();
}
bool QgsLayoutMultiFrameDeleteUndoCommand::mergeWith( const QUndoCommand * )
{
return false;
}
void QgsLayoutMultiFrameDeleteUndoCommand::redo()
{
if ( mFirstRun )
{
mFirstRun = false;
return;
}
QgsLayoutMultiFrame *item = layout()->multiFrameByUuid( multiFrameUuid() );
//Q_ASSERT_X( item, "QgsLayoutMultiFrameDeleteUndoCommand::redo", "could not find item to re-delete!" );
if ( item )
{
layout()->removeMultiFrame( item );
delete item;
}
}
QgsLayoutMultiFrameAddItemCommand::QgsLayoutMultiFrameAddItemCommand( QgsLayoutMultiFrame *frame, const QString &text, int id, QUndoCommand *parent )
: QgsLayoutMultiFrameUndoCommand( frame, text, id, parent )
{
saveAfterState();
}
bool QgsLayoutMultiFrameAddItemCommand::containsChange() const
{
return true;
}
bool QgsLayoutMultiFrameAddItemCommand::mergeWith( const QUndoCommand * )
{
return false;
}
void QgsLayoutMultiFrameAddItemCommand::undo()
{
if ( mFirstRun )
{
mFirstRun = false;
return;
}
QgsLayoutMultiFrame *item = layout()->multiFrameByUuid( multiFrameUuid() );
if ( item )
{
layout()->removeMultiFrame( item );
delete item;
}
}
///@endcond

View File

@ -0,0 +1,141 @@
/***************************************************************************
qgslayoutmultiframeundocommand.h
----------------------
begin : October 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSLAYOUTMULTIFRAMEUNDOCOMMAND_H
#define QGSLAYOUTMULTIFRAMEUNDOCOMMAND_H
#include "qgslayoutundocommand.h"
#include "qgis_core.h"
class QgsLayout;
class QgsLayoutMultiFrame;
SIP_NO_FILE
///@cond PRIVATE
/**
* \ingroup core
* An undo command subclass for layout multiframe undo commands.
*
* QgsLayoutMultiFrameUndoCommand is a specific layout undo command which is
* designed for use with QgsLayoutMultiFrames. It automatically handles
* recreating a deleted multiframes when the undo stack rolls back past
* the item deletion command.
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLayoutMultiFrameUndoCommand: public QgsAbstractLayoutUndoCommand
{
public:
/**
* Constructor for QgsLayoutMultiFrameUndoCommand.
* \param item associated layout item
* \param text undo command descriptive text
* \param id optional undo command id, used for automatic command merging
* \param parent command
*/
QgsLayoutMultiFrameUndoCommand( QgsLayoutMultiFrame *frame, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr );
bool mergeWith( const QUndoCommand *command ) override;
/**
* Returns the layout associated with this command.
*/
QgsLayout *layout() const;
/**
* Returns the associated multiframes's UUID, which uniquely identifies the frame
* within the layout.
*/
QString multiFrameUuid() const;
protected:
void saveState( QDomDocument &stateDoc ) const override;
void restoreState( QDomDocument &stateDoc ) override;
virtual QgsLayoutMultiFrame *recreateItem( int itemType, QgsLayout *layout ) SIP_FACTORY;
private:
QString mFrameUuid;
QgsLayout *mLayout = nullptr;
int mItemType = 0;
};
/**
* \ingroup core
* An undo command subclass for layout multiframe deletion undo commands.
*
* QgsLayoutMultiFrameDeleteUndoCommand is a specific layout undo command which handles
* layout multiframe deletion. When applied (e.g. as a result of a 'redo' action),
* the associated layout multiframe is deleted and removed from the layout.
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLayoutMultiFrameDeleteUndoCommand: public QgsLayoutMultiFrameUndoCommand
{
public:
/**
* Constructor for QgsLayoutMultiFrameDeleteUndoCommand.
* \param item associated layout item
* \param text undo command descriptive text
* \param id optional undo command id, used for automatic command merging
* \param parent command
*/
QgsLayoutMultiFrameDeleteUndoCommand( QgsLayoutMultiFrame *frame, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr );
bool mergeWith( const QUndoCommand *command ) override;
void redo() override;
};
/**
* \ingroup core
* An undo command subclass for layout item addition undo commands.
*
* QgsLayoutMultiFrameAddItemCommand is a specific layout undo command which handles
* layout multiframe creation. When applied (e.g. as a result of a 'redo' action),
* the associated layout multiframe is recreated and added to the layout.
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsLayoutMultiFrameAddItemCommand: public QgsLayoutMultiFrameUndoCommand
{
public:
/**
* Constructor for QgsLayoutMultiFrameAddItemCommand.
* \param item associated layout item
* \param text undo command descriptive text
* \param id optional undo command id, used for automatic command merging
* \param parent command
*/
QgsLayoutMultiFrameAddItemCommand( QgsLayoutMultiFrame *frame, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr );
bool containsChange() const override;
bool mergeWith( const QUndoCommand *command ) override;
void undo() override;
};
///@endcond
#endif //QGSLAYOUTMULTIFRAMEUNDOCOMMAND_H

View File

@ -22,6 +22,7 @@
#include "qgsmultirenderchecker.h"
#include "qgsapplication.h"
#include "qgsproject.h"
#include "qgslayoutitemhtml.h"
#include <QObject>
#include "qgstest.h"
@ -42,6 +43,7 @@ class TestQgsLayoutMultiFrame : public QObject
void addRemovePage(); //test if page is added and removed for RepeatUntilFinished mode
void undoRedo(); //test that combinations of frame/multiframe undo/redo don't crash
void undoRedoRemovedFrame(); //test that undo doesn't crash with removed frames
void registry();
private:
QgsLayout *mLayout = nullptr;
@ -60,6 +62,17 @@ class TestMultiFrame : public QgsLayoutMultiFrame
}
int type() const override
{
return QgsLayoutItemRegistry::PluginItem + 1;
}
QString stringType() const override
{
return QStringLiteral( "TestMultiFrame" );
}
void render( QgsRenderContext &, const QRectF &, const int,
const QStyleOptionGraphicsItem * ) override
{
@ -71,16 +84,6 @@ class TestMultiFrame : public QgsLayoutMultiFrame
return QSizeF();
}
bool writeXml( QDomElement &, QDomDocument &, bool ) const override
{
return true;
}
bool readXml( const QDomElement &, const QDomDocument &, bool ) override
{
return true;
}
};
void TestQgsLayoutMultiFrame::initTestCase()
@ -131,6 +134,8 @@ void TestQgsLayoutMultiFrame::layoutMethods()
QPointer< TestMultiFrame > pMF( mF );
QCOMPARE( l->multiFrames().count(), 1 );
QVERIFY( l->multiFrames().contains( mF ) );
QVERIFY( !l->multiFrameByUuid( QString() ) );
QCOMPARE( l->multiFrameByUuid( mF->uuid() ), mF );
// try readding
l->addMultiFrame( mF );
@ -146,9 +151,11 @@ void TestQgsLayoutMultiFrame::layoutMethods()
QCOMPARE( l->multiFrames().count(), 2 );
QVERIFY( l->multiFrames().contains( mF ) );
QVERIFY( l->multiFrames().contains( mF2 ) );
QCOMPARE( l->multiFrameByUuid( mF2->uuid() ), mF2 );
l->removeMultiFrame( mF2 );
QCOMPARE( l->multiFrames().count(), 1 );
QVERIFY( l->multiFrames().contains( mF ) );
QVERIFY( !l->multiFrameByUuid( mF2->uuid() ) );
// should not be deleted
QVERIFY( pMF2 );
@ -245,13 +252,14 @@ void TestQgsLayoutMultiFrame::displayName()
void TestQgsLayoutMultiFrame::frameIsEmpty()
{
#if 0 //TODO
QgsComposerHtml *htmlItem = new QgsComposerHtml( mLayout, false );
QgsComposerFrame *frame1 = new QgsComposerFrame( mLayout, htmlItem, 0, 0, 100, 200 );
QgsComposerFrame *frame2 = new QgsComposerFrame( mLayout, htmlItem, 0, 0, 100, 200 );
QgsLayoutItemHtml *htmlItem = new QgsLayoutItemHtml( mLayout );
QgsLayoutFrame *frame1 = new QgsLayoutFrame( mLayout, htmlItem );
frame1->attemptSetSceneRect( QRectF( 0, 0, 100, 200 ) );
QgsLayoutFrame *frame2 = new QgsLayoutFrame( mLayout, htmlItem );
frame2->attemptSetSceneRect( QRectF( 0, 0, 100, 200 ) );
htmlItem->addFrame( frame1 );
htmlItem->addFrame( frame2 );
htmlItem->setContentMode( QgsComposerHtml::ManualHtml );
htmlItem->setContentMode( QgsLayoutItemHtml::ManualHtml );
//short content, so frame 2 should be empty
htmlItem->setHtml( QStringLiteral( "<p><i>Test manual <b>html</b></i></p>" ) );
htmlItem->loadHtml();
@ -275,17 +283,16 @@ void TestQgsLayoutMultiFrame::frameIsEmpty()
mLayout->removeMultiFrame( htmlItem );
delete htmlItem;
#endif
}
void TestQgsLayoutMultiFrame::addRemovePage()
{
#if 0 //TODO
QgsComposerHtml *htmlItem = new QgsComposerHtml( mLayout, false );
QgsComposerFrame *frame1 = new QgsComposerFrame( mLayout, htmlItem, 0, 0, 100, 200 );
QgsLayoutItemHtml *htmlItem = new QgsLayoutItemHtml( mLayout );
QgsLayoutFrame *frame1 = new QgsLayoutFrame( mLayout, htmlItem );
frame1->attemptSetSceneRect( QRectF( 0, 0, 100, 200 ) );
htmlItem->addFrame( frame1 );
htmlItem->setContentMode( QgsComposerHtml::ManualHtml );
htmlItem->setResizeMode( QgsComposerMultiFrame::RepeatUntilFinished );
htmlItem->setContentMode( QgsLayoutItemHtml::ManualHtml );
htmlItem->setResizeMode( QgsLayoutMultiFrame::RepeatUntilFinished );
//short content, so should fit in one frame
htmlItem->setHtml( QStringLiteral( "<p><i>Test manual <b>html</b></i></p>" ) );
@ -293,55 +300,56 @@ void TestQgsLayoutMultiFrame::addRemovePage()
//should be one page
QCOMPARE( htmlItem->frameCount(), 1 );
QCOMPARE( mLayout->numPages(), 1 );
QCOMPARE( mLayout->pageCollection()->pageCount(), 1 );
//long content, so we require 3 frames
htmlItem->setHtml( QStringLiteral( "<p style=\"height: 2000px\"><i>Test manual <b>html</b></i></p>" ) );
htmlItem->loadHtml();
QCOMPARE( htmlItem->frameCount(), 3 );
QCOMPARE( mLayout->numPages(), 3 );
QCOMPARE( mLayout->pageCollection()->pageCount(), 3 );
//..and back again..
htmlItem->setHtml( QStringLiteral( "<p><i>Test manual <b>html</b></i></p>" ) );
htmlItem->loadHtml();
QCOMPARE( htmlItem->frameCount(), 1 );
QCOMPARE( mLayout->numPages(), 1 );
QCOMPARE( mLayout->pageCollection()->pageCount(), 1 );
//get a bit more complicated - add another item to page 3
QgsComposerLabel *label1 = new QgsComposerLabel( mLayout );
mLayout->addComposerLabel( label1 );
label1->setItemPosition( 10, 10, 50, 50, QgsComposerItem::UpperLeft, false, 3 );
QgsLayoutItemLabel *label1 = new QgsLayoutItemLabel( mLayout );
mLayout->addLayoutItem( label1 );
label1->attemptResize( QgsLayoutSize( 50, 50 ), false );
//long content, so we require 4 pages
htmlItem->setHtml( QStringLiteral( "<p style=\"height: 3000px\"><i>Test manual <b>html</b></i></p>" ) );
htmlItem->loadHtml();
QCOMPARE( htmlItem->frameCount(), 4 );
QCOMPARE( mLayout->numPages(), 4 );
QCOMPARE( mLayout->pageCollection()->pageCount(), 4 );
label1->attemptMove( QgsLayoutPoint( 10, 10 ), true, false, 2 );
//..and back again. Since there's an item on page 3, only page 4 should be removed
htmlItem->setHtml( QStringLiteral( "<p><i>Test manual <b>html</b></i></p>" ) );
htmlItem->loadHtml();
QCOMPARE( htmlItem->frameCount(), 1 );
QCOMPARE( mLayout->numPages(), 3 );
QCOMPARE( mLayout->pageCollection()->pageCount(), 3 );
mLayout->removeMultiFrame( htmlItem );
delete htmlItem;
#endif
}
void TestQgsLayoutMultiFrame::undoRedo()
{
#if 0 //TODO
QgsComposerHtml *htmlItem = new QgsComposerHtml( mLayout, false );
QgsComposerFrame *frame1 = new QgsComposerFrame( mLayout, htmlItem, 0, 0, 100, 200 );
QgsLayoutItemHtml *htmlItem = new QgsLayoutItemHtml( mLayout );
QgsLayoutFrame *frame1 = new QgsLayoutFrame( mLayout, htmlItem );
frame1->attemptSetSceneRect( QRectF( 0, 0, 100, 200 ) );
htmlItem->addFrame( frame1 );
htmlItem->setContentMode( QgsComposerHtml::ManualHtml );
htmlItem->setResizeMode( QgsComposerMultiFrame::RepeatUntilFinished );
htmlItem->setContentMode( QgsLayoutItemHtml::ManualHtml );
htmlItem->setResizeMode( QgsLayoutMultiFrame::RepeatUntilFinished );
//short content, so should fit in one frame
htmlItem->setHtml( QStringLiteral( "<p>Test content</p>" ) );
@ -350,76 +358,75 @@ void TestQgsLayoutMultiFrame::undoRedo()
//do some combinations of undo/redo commands for both the frame and multiframe
//to try to trigger a crash
frame1->beginCommand( QStringLiteral( "move" ) );
frame1->setSceneRect( QRectF( 10, 10, 20, 20 ) );
frame1->attemptSetSceneRect( QRectF( 10, 10, 20, 20 ) );
frame1->endCommand();
frame1->beginCommand( QStringLiteral( "stroke" ), QgsComposerMergeCommand::ItemStrokeWidth );
frame1->setFrameStrokeWidth( 4.0 );
frame1->beginCommand( QStringLiteral( "stroke" ), QgsLayoutItem::UndoStrokeWidth );
frame1->setFrameStrokeWidth( QgsLayoutMeasurement( 4.0 ) );
frame1->endCommand();
frame1->beginCommand( QStringLiteral( "stroke" ), QgsComposerMergeCommand::ItemStrokeWidth );
frame1->setFrameStrokeWidth( 7.0 );
frame1->beginCommand( QStringLiteral( "stroke" ), QgsLayoutItem::UndoStrokeWidth );
frame1->setFrameStrokeWidth( QgsLayoutMeasurement( 7.0 ) );
frame1->endCommand();
//multiframe commands
mLayout->beginMultiFrameCommand( htmlItem, QStringLiteral( "maxbreak" ) );
htmlItem->beginCommand( QStringLiteral( "maxbreak" ) );
htmlItem->setMaxBreakDistance( 100 );
mLayout->endMultiFrameCommand();
htmlItem->endCommand();
//another frame command
frame1->beginCommand( QStringLiteral( "bgcolor" ), QgsComposerMergeCommand::ItemOpacity );
frame1->beginCommand( QStringLiteral( "bgcolor" ), QgsLayoutItem::UndoOpacity );
frame1->setBackgroundColor( QColor( 255, 255, 0 ) );
frame1->endCommand();
frame1->beginCommand( QStringLiteral( "bgcolor" ), QgsComposerMergeCommand::ItemOpacity );
frame1->beginCommand( QStringLiteral( "bgcolor" ), QgsLayoutItem::UndoOpacity );
frame1->setBackgroundColor( QColor( 255, 0, 0 ) );
frame1->endCommand();
//undo changes
//frame bg
mLayout->undoStack()->undo();
mLayout->undoStack()->stack()->undo();
//multiframe max break
mLayout->undoStack()->undo();
mLayout->undoStack()->stack()->undo();
//frame stroke width
mLayout->undoStack()->undo();
mLayout->undoStack()->stack()->undo();
//frame move
mLayout->undoStack()->undo();
mLayout->undoStack()->stack()->undo();
//check result
QCOMPARE( htmlItem->maxBreakDistance(), 10.0 );
QCOMPARE( htmlItem->frame( 0 )->frameStrokeWidth(), 0.3 );
QCOMPARE( htmlItem->frame( 0 )->frameStrokeWidth().length(), 0.3 );
QCOMPARE( htmlItem->frame( 0 )->pos(), QPointF( 0, 0 ) );
QCOMPARE( htmlItem->frame( 0 )->backgroundColor(), QColor( 255, 255, 255 ) );
//now redo
//frame move
mLayout->undoStack()->redo();
mLayout->undoStack()->stack()->redo();
//frame stroke width
mLayout->undoStack()->redo();
mLayout->undoStack()->stack()->redo();
//multiframe max break
mLayout->undoStack()->redo();
mLayout->undoStack()->stack()->redo();
//frame bg color
mLayout->undoStack()->redo();
mLayout->undoStack()->stack()->redo();
//check result
QCOMPARE( htmlItem->maxBreakDistance(), 100.0 );
QCOMPARE( htmlItem->frame( 0 )->frameStrokeWidth(), 7.0 );
QCOMPARE( htmlItem->frame( 0 )->frameStrokeWidth().length(), 7.0 );
QCOMPARE( htmlItem->frame( 0 )->pos(), QPointF( 10, 10 ) );
QCOMPARE( htmlItem->frame( 0 )->backgroundColor(), QColor( 255, 0, 0 ) );
mLayout->removeMultiFrame( htmlItem );
delete htmlItem;
#endif
}
void TestQgsLayoutMultiFrame::undoRedoRemovedFrame()
{
#if 0 //TODO
QgsComposerHtml *htmlItem = new QgsComposerHtml( mLayout, false );
QgsComposerFrame *frame1 = new QgsComposerFrame( mLayout, htmlItem, 0, 0, 100, 200 );
QgsLayoutItemHtml *htmlItem = new QgsLayoutItemHtml( mLayout );
QgsLayoutFrame *frame1 = new QgsLayoutFrame( mLayout, htmlItem );
frame1->attemptSetSceneRect( QRectF( 0, 0, 100, 200 ) );
htmlItem->addFrame( frame1 );
htmlItem->setContentMode( QgsComposerHtml::ManualHtml );
htmlItem->setResizeMode( QgsComposerMultiFrame::RepeatUntilFinished );
htmlItem->setContentMode( QgsLayoutItemHtml::ManualHtml );
htmlItem->setResizeMode( QgsLayoutMultiFrame::RepeatUntilFinished );
//long content, so should require multiple frames
htmlItem->setHtml( QStringLiteral( "<p style=\"height: 2000px\">Test content</p>" ) );
@ -428,18 +435,18 @@ void TestQgsLayoutMultiFrame::undoRedoRemovedFrame()
QVERIFY( htmlItem->frameCount() > 1 );
//do a command on the first frame
htmlItem->frame( 0 )->beginCommand( QStringLiteral( "stroke" ), QgsComposerMergeCommand::ItemStrokeWidth );
htmlItem->frame( 0 )->setFrameStrokeWidth( 4.0 );
htmlItem->frame( 0 )->beginCommand( QStringLiteral( "stroke" ), QgsLayoutItem::UndoStrokeWidth );
htmlItem->frame( 0 )->setFrameStrokeWidth( QgsLayoutMeasurement( 4.0 ) );
htmlItem->frame( 0 )->endCommand();
//do a command on the second frame
htmlItem->frame( 1 )->beginCommand( QStringLiteral( "stroke" ), QgsComposerMergeCommand::ItemStrokeWidth );
htmlItem->frame( 1 )->setFrameStrokeWidth( 8.0 );
htmlItem->frame( 1 )->beginCommand( QStringLiteral( "stroke" ), QgsLayoutItem::UndoStrokeWidth );
htmlItem->frame( 1 )->setFrameStrokeWidth( QgsLayoutMeasurement( 8.0 ) );
htmlItem->frame( 1 )->endCommand();
//do a multiframe command which removes extra frames
mLayout->beginMultiFrameCommand( htmlItem, QStringLiteral( "source" ) );
htmlItem->beginCommand( QStringLiteral( "source" ) );
htmlItem->setHtml( QStringLiteral( "<p style=\"height: 20px\">Test content</p>" ) );
mLayout->endMultiFrameCommand();
htmlItem->endCommand();
//wipes the second frame
htmlItem->loadHtml();
@ -449,36 +456,81 @@ void TestQgsLayoutMultiFrame::undoRedoRemovedFrame()
//undo changes
//multiframe command
mLayout->undoStack()->undo();
mLayout->undoStack()->stack()->undo();
//frame 2 command
mLayout->undoStack()->undo();
mLayout->undoStack()->stack()->undo();
//frame 1 command
mLayout->undoStack()->undo();
mLayout->undoStack()->stack()->undo();
//check result
QVERIFY( htmlItem->frameCount() > 1 );
QCOMPARE( htmlItem->frame( 0 )->frameStrokeWidth(), 0.3 );
QCOMPARE( htmlItem->frame( 1 )->frameStrokeWidth(), 0.3 );
QCOMPARE( htmlItem->frame( 0 )->frameStrokeWidth().length(), 0.3 );
QCOMPARE( htmlItem->frame( 1 )->frameStrokeWidth().length(), 0.3 );
//now redo
//frame 1 command
mLayout->undoStack()->redo();
mLayout->undoStack()->stack()->redo();
//frame 2 command
mLayout->undoStack()->redo();
mLayout->undoStack()->stack()->redo();
//check result
QVERIFY( htmlItem->frameCount() > 1 );
QCOMPARE( htmlItem->frame( 0 )->frameStrokeWidth(), 4.0 );
QCOMPARE( htmlItem->frame( 1 )->frameStrokeWidth(), 8.0 );
QCOMPARE( htmlItem->frame( 0 )->frameStrokeWidth().length(), 4.0 );
QCOMPARE( htmlItem->frame( 1 )->frameStrokeWidth().length(), 8.0 );
//multiframe command
mLayout->undoStack()->redo();
mLayout->undoStack()->stack()->redo();
QCOMPARE( htmlItem->frameCount(), 1 );
mLayout->removeMultiFrame( htmlItem );
delete htmlItem;
#endif
}
void TestQgsLayoutMultiFrame::registry()
{
// test QgsLayoutItemRegistry
QgsLayoutItemRegistry registry;
// empty registry
QVERIFY( !registry.multiFrameMetadata( -1 ) );
QVERIFY( registry.itemTypes().isEmpty() );
QVERIFY( !registry.createMultiFrame( 1, nullptr ) );
auto create = []( QgsLayout * layout )->QgsLayoutMultiFrame*
{
return new TestMultiFrame( layout );
};
auto resolve = []( QVariantMap & props, const QgsPathResolver &, bool )
{
props.clear();
};
QSignalSpy spyTypeAdded( &registry, &QgsLayoutItemRegistry::multiFrameTypeAdded );
QgsLayoutMultiFrameMetadata *metadata = new QgsLayoutMultiFrameMetadata( QgsLayoutItemRegistry::PluginItem + 1, QStringLiteral( "TestMultiFrame" ), QIcon(), create, resolve );
QVERIFY( registry.addLayoutMultiFrameType( metadata ) );
QCOMPARE( spyTypeAdded.count(), 1 );
QCOMPARE( spyTypeAdded.value( 0 ).at( 0 ).toInt(), QgsLayoutItemRegistry::PluginItem + 1 );
QCOMPARE( spyTypeAdded.value( 0 ).at( 1 ).toString(), QStringLiteral( "TestMultiFrame" ) );
// duplicate type id
QVERIFY( !registry.addLayoutMultiFrameType( metadata ) );
QCOMPARE( spyTypeAdded.count(), 1 );
//retrieve metadata
QVERIFY( !registry.multiFrameMetadata( -1 ) );
QCOMPARE( registry.multiFrameMetadata( QgsLayoutItemRegistry::PluginItem + 1 )->visibleName(), QStringLiteral( "TestMultiFrame" ) );
QCOMPARE( registry.itemTypes().count(), 1 );
QCOMPARE( registry.itemTypes().value( QgsLayoutItemRegistry::PluginItem + 1 ), QStringLiteral( "TestMultiFrame" ) );
QgsLayout l( QgsProject::instance() );
QgsLayoutMultiFrame *item = registry.createMultiFrame( QgsLayoutItemRegistry::PluginItem + 1, &l );
QVERIFY( item );
QVERIFY( dynamic_cast< TestMultiFrame *>( item ) );
QVariantMap props;
props.insert( QStringLiteral( "a" ), 5 );
registry.resolvePaths( 1, props, QgsPathResolver(), true );
QCOMPARE( props.size(), 1 );
registry.resolvePaths( QgsLayoutItemRegistry::PluginItem + 1, props, QgsPathResolver(), true );
QVERIFY( props.isEmpty() );
}
QGSTEST_MAIN( TestQgsLayoutMultiFrame )