Skip to content

Commit dddce25

Browse files
committedNov 24, 2017
More work on porting multiframe items
1 parent 2cf9911 commit dddce25

19 files changed

+1416
-153
lines changed
 

‎python/core/layout/qgslayout.sip

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,18 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator, QgsLayoutUndoOb
175175
%Docstring
176176
Returns the layout item with matching ``uuid`` unique identifier, or a None
177177
if a matching item could not be found.
178+
.. seealso:: multiFrameByUuid()
178179
:rtype: QgsLayoutItem
179180
%End
180181

182+
QgsLayoutMultiFrame *multiFrameByUuid( const QString &uuid ) const;
183+
%Docstring
184+
Returns the layout multiframe with matching ``uuid`` unique identifier, or a None
185+
if a matching multiframe could not be found.
186+
.. seealso:: itemByUuid()
187+
:rtype: QgsLayoutMultiFrame
188+
%End
189+
181190
QgsLayoutItem *layoutItemAt( QPointF position, const bool ignoreLocked = false ) const;
182191
%Docstring
183192
Returns the topmost layout item at a specified ``position``. Ignores paper items.

‎python/core/layout/qgslayoutframe.sip

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,108 @@
99

1010

1111

12+
class QgsLayoutFrame: QgsLayoutItem
13+
{
14+
%Docstring
15+
Base class for frame items, which form a layout multiframe item.
16+
.. versionadded:: 3.0
17+
%End
18+
19+
%TypeHeaderCode
20+
#include "qgslayoutframe.h"
21+
%End
22+
public:
23+
24+
QgsLayoutFrame( QgsLayout *layout, QgsLayoutMultiFrame *multiFrame );
25+
%Docstring
26+
Constructor for QgsLayoutFrame, with the specified parent ``layout``
27+
and belonging to a ``multiFrame``.
28+
%End
29+
30+
static QgsLayoutFrame *create( QgsLayout *layout ) /Factory/;
31+
%Docstring
32+
Creates a new QgsLayoutFrame belonging to the specified ``layout``.
33+
:rtype: QgsLayoutFrame
34+
%End
35+
36+
virtual int type() const;
37+
38+
virtual QString stringType() const;
39+
40+
41+
virtual QString displayName() const;
42+
43+
44+
void setContentSection( const QRectF &section );
45+
%Docstring
46+
Sets the visible part of the multiframe's content which is visible within
47+
this frame (relative to the total multiframe extent in layout units).
48+
.. seealso:: extent()
49+
%End
50+
51+
QgsLayoutMultiFrame *multiFrame() const;
52+
%Docstring
53+
Returns the parent multiframe for the frame.
54+
:rtype: QgsLayoutMultiFrame
55+
%End
56+
57+
58+
QRectF extent() const;
59+
%Docstring
60+
Returns the visible portion of the multi frame's content which
61+
is shown in this frame, in layout units.
62+
.. seealso:: setContentSection()
63+
:rtype: QRectF
64+
%End
65+
66+
bool hidePageIfEmpty() const;
67+
%Docstring
68+
Returns whether the page should be hidden (ie, not included in layout exports) if this frame is empty
69+
:return: true if page should be hidden if frame is empty
70+
.. seealso:: setHidePageIfEmpty()
71+
:rtype: bool
72+
%End
73+
74+
void setHidePageIfEmpty( const bool hidePageIfEmpty );
75+
%Docstring
76+
Sets whether the page should be hidden (ie, not included in layout exports) if this frame is empty
77+
\param hidePageIfEmpty set to true if page should be hidden if frame is empty
78+
.. seealso:: hidePageIfEmpty()
79+
%End
80+
81+
bool hideBackgroundIfEmpty() const;
82+
%Docstring
83+
Returns whether the background and frame stroke should be hidden if this frame is empty
84+
:return: true if background and stroke should be hidden if frame is empty
85+
.. seealso:: setHideBackgroundIfEmpty()
86+
:rtype: bool
87+
%End
88+
89+
void setHideBackgroundIfEmpty( const bool hideBackgroundIfEmpty );
90+
%Docstring
91+
Sets whether the background and frame stroke should be hidden if this frame is empty
92+
\param hideBackgroundIfEmpty set to true if background and stroke should be hidden if frame is empty
93+
.. seealso:: hideBackgroundIfEmpty()
94+
%End
95+
96+
bool isEmpty() const;
97+
%Docstring
98+
Returns whether the frame is empty.
99+
.. seealso:: hidePageIfEmpty()
100+
:rtype: bool
101+
%End
102+
103+
virtual QgsExpressionContext createExpressionContext() const;
104+
105+
106+
protected:
107+
108+
virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 );
109+
110+
void drawFrame( QgsRenderContext &context );
111+
void drawBackground( QgsRenderContext &context );
112+
113+
};
12114

13115
/************************************************************************
14116
* This file has been generated automatically from *

‎python/core/layout/qgslayoutitemregistry.sip

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,70 @@ class QgsLayoutItemAbstractMetadata
7474

7575

7676

77+
class QgsLayoutMultiFrameAbstractMetadata
78+
{
79+
%Docstring
80+
Stores metadata about one layout multiframe class.
81+
82+
A companion class, QgsLayoutMultiFrameAbstractGuiMetadata, handles the
83+
GUI behavior of QgsLayoutMultiFrames.
84+
85+
.. note::
86+
87+
In C++ you can use QgsLayoutMultiFrameMetadata convenience class.
88+
.. versionadded:: 3.0
89+
%End
90+
91+
%TypeHeaderCode
92+
#include "qgslayoutitemregistry.h"
93+
%End
94+
public:
95+
96+
QgsLayoutMultiFrameAbstractMetadata( int type, const QString &visibleName );
97+
%Docstring
98+
Constructor for QgsLayoutMultiFrameAbstractMetadata with the specified class ``type``
99+
and ``visibleName``.
100+
%End
101+
102+
virtual ~QgsLayoutMultiFrameAbstractMetadata();
103+
104+
int type() const;
105+
%Docstring
106+
Returns the unique item type code for the layout multiframe class.
107+
:rtype: int
108+
%End
109+
110+
virtual QIcon icon() const;
111+
%Docstring
112+
Returns an icon representing the layout multiframe type.
113+
:rtype: QIcon
114+
%End
115+
116+
QString visibleName() const;
117+
%Docstring
118+
Returns a translated, user visible name for the layout multiframe class.
119+
:rtype: str
120+
%End
121+
122+
virtual QgsLayoutMultiFrame *createMultiFrame( QgsLayout *layout ) = 0 /Factory/;
123+
%Docstring
124+
Creates a layout multiframe of this class for a specified ``layout``.
125+
:rtype: QgsLayoutMultiFrame
126+
%End
127+
128+
virtual void resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving );
129+
%Docstring
130+
Resolve paths in the item's ``properties`` (if there are any paths).
131+
When ``saving`` is true, paths are converted from absolute to relative,
132+
when ``saving`` is false, paths are converted from relative to absolute.
133+
This ensures that paths in project files can be relative, but in item
134+
instances the paths are always absolute.
135+
%End
136+
137+
};
138+
139+
140+
77141

78142

79143
class QgsLayoutItemRegistry : QObject
@@ -111,6 +175,9 @@ class QgsLayoutItemRegistry : QObject
111175
LayoutPolyline,
112176
LayoutFrame,
113177

178+
// known
179+
LayoutHtml,
180+
114181
// item
115182
PluginItem,
116183
};
@@ -139,21 +206,46 @@ class QgsLayoutItemRegistry : QObject
139206
%Docstring
140207
Returns the metadata for the specified item ``type``. Returns None if
141208
a corresponding type was not found in the registry.
209+
.. seealso:: multiFrameMetadata()
142210
:rtype: QgsLayoutItemAbstractMetadata
143211
%End
144212

213+
QgsLayoutMultiFrameAbstractMetadata *multiFrameMetadata( int type ) const;
214+
%Docstring
215+
Returns the metadata for the specified multiframe ``type``. Returns None if
216+
a corresponding type was not found in the registry.
217+
.. seealso:: itemMetadata()
218+
:rtype: QgsLayoutMultiFrameAbstractMetadata
219+
%End
220+
145221
bool addLayoutItemType( QgsLayoutItemAbstractMetadata *metadata /Transfer/ );
146222
%Docstring
147223
Registers a new layout item type. Takes ownership of the metadata instance.
224+
.. seealso:: addLayoutMultiFrameType()
225+
:rtype: bool
226+
%End
227+
228+
bool addLayoutMultiFrameType( QgsLayoutMultiFrameAbstractMetadata *metadata /Transfer/ );
229+
%Docstring
230+
Registers a new layout multiframe type. Takes ownership of the metadata instance.
231+
.. seealso:: addLayoutItemType()
148232
:rtype: bool
149233
%End
150234

151235
QgsLayoutItem *createItem( int type, QgsLayout *layout ) const /Factory/;
152236
%Docstring
153237
Creates a new instance of a layout item given the item ``type``, and target ``layout``.
238+
.. seealso:: createMultiFrame()
154239
:rtype: QgsLayoutItem
155240
%End
156241

242+
QgsLayoutMultiFrame *createMultiFrame( int type, QgsLayout *layout ) const /Factory/;
243+
%Docstring
244+
Creates a new instance of a layout multiframe given the multiframe ``type``, and target ``layout``.
245+
.. seealso:: createItem()
246+
:rtype: QgsLayoutMultiFrame
247+
%End
248+
157249
void resolvePaths( int type, QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving ) const;
158250
%Docstring
159251
Resolve paths in properties of a particular symbol layer.
@@ -175,6 +267,12 @@ class QgsLayoutItemRegistry : QObject
175267
``type`` and visible ``name``.
176268
%End
177269

270+
void multiFrameTypeAdded( int type, const QString &name );
271+
%Docstring
272+
Emitted whenever a new multiframe type is added to the registry, with the specified
273+
``type`` and visible ``name``.
274+
%End
275+
178276
private:
179277
QgsLayoutItemRegistry( const QgsLayoutItemRegistry &rh );
180278
};

‎python/core/layout/qgslayoutmultiframe.sip

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,331 @@
1010

1111

1212

13+
class QgsLayoutMultiFrame: QgsLayoutObject, QgsLayoutUndoObjectInterface
14+
{
15+
%Docstring
16+
Abstract base class for layout items with the ability to distribute the content to
17+
several frames (QgsLayoutFrame items).
18+
.. versionadded:: 3.0
19+
%End
20+
21+
%TypeHeaderCode
22+
#include "qgslayoutmultiframe.h"
23+
%End
24+
public:
25+
26+
enum ResizeMode
27+
{
28+
UseExistingFrames,
29+
ExtendToNextPage,
30+
RepeatOnEveryPage,
31+
RepeatUntilFinished
32+
};
33+
34+
enum UndoCommand
35+
{
36+
UndoNone,
37+
};
38+
39+
QgsLayoutMultiFrame( QgsLayout *layout /TransferThis/ );
40+
%Docstring
41+
Construct a new multiframe item, attached to the specified ``layout``.
42+
%End
43+
44+
~QgsLayoutMultiFrame();
45+
46+
QString uuid() const;
47+
%Docstring
48+
Returns the multiframe identification string. This is a unique random string set for the multiframe
49+
upon creation.
50+
.. note::
51+
52+
There is no corresponding setter for the uuid - it's created automatically.
53+
:rtype: str
54+
%End
55+
56+
virtual QSizeF totalSize() const = 0;
57+
%Docstring
58+
Returns the total size of the multiframe's content, in layout units.
59+
:rtype: QSizeF
60+
%End
61+
62+
virtual int type() const = 0;
63+
%Docstring
64+
Returns unique multiframe type id.
65+
:rtype: int
66+
%End
67+
68+
virtual QString stringType() const = 0;
69+
%Docstring
70+
Return the multiframe type as a string.
71+
72+
This string must be a unique, single word, character only representation of the item type, eg "LayoutHtml"
73+
.. seealso:: type()
74+
:rtype: str
75+
%End
76+
77+
virtual QSizeF fixedFrameSize( const int frameIndex = -1 ) const;
78+
%Docstring
79+
Returns the fixed size for a frame, if desired. If the fixed frame size changes,
80+
the sizes of all frames can be recalculated by calling recalculateFrameRects().
81+
\param frameIndex frame number
82+
:return: fixed size for frame. If the size has a width or height of 0, then
83+
the frame size is not fixed in that direction and frames can have variable width
84+
or height accordingly.
85+
.. seealso:: minFrameSize()
86+
.. seealso:: recalculateFrameRects()
87+
:rtype: QSizeF
88+
%End
89+
90+
virtual QSizeF minFrameSize( const int frameIndex = -1 ) const;
91+
%Docstring
92+
Returns the minimum size for a frames, if desired. If the minimum
93+
size changes, the sizes of all frames can be recalculated by calling
94+
recalculateFrameRects().
95+
\param frameIndex frame number
96+
:return: minimum size for frame. If the size has a width or height of 0, then
97+
the frame size has no minimum in that direction.
98+
.. seealso:: fixedFrameSize()
99+
.. seealso:: recalculateFrameRects()
100+
:rtype: QSizeF
101+
%End
102+
103+
virtual void render( QgsRenderContext &context, const QRectF &renderExtent, const int frameIndex,
104+
const QStyleOptionGraphicsItem *itemStyle = 0 ) = 0;
105+
%Docstring
106+
Renders a portion of the multiframe's content into a render ``context``.
107+
\param context destination render painter
108+
\param renderExtent visible extent of content to render into the painter.
109+
\param frameIndex frame number for content
110+
\param itemStyle item style options for graphics item rendering
111+
%End
112+
113+
virtual void addFrame( QgsLayoutFrame *frame /Transfer/, bool recalcFrameSizes = true );
114+
%Docstring
115+
Adds a ``frame`` to the multiframe.
116+
117+
If ``recalcFrameSizes`` is set to true, then a recalculation of all existing frame sizes will be forced.
118+
119+
.. seealso:: removeFrame()
120+
%End
121+
122+
virtual double findNearbyPageBreak( double yPos );
123+
%Docstring
124+
Finds the optimal position to break a frame at.
125+
\param yPos maximum vertical position for break, in layout units.
126+
:return: the optimal breakable position which occurs in the multi frame close
127+
to and before the specified yPos
128+
:rtype: float
129+
%End
130+
131+
void removeFrame( int index, bool removeEmptyPages = false );
132+
%Docstring
133+
Removes a frame by ``index`` from the multiframe. This method automatically removes the frame from the
134+
layout too.
135+
136+
If ``removeEmptyPages`` is set to true, then pages which are empty after the frame is removed will
137+
also be removed from the layout.
138+
139+
.. seealso:: addFrame()
140+
.. seealso:: deleteFrames()
141+
%End
142+
143+
void deleteFrames();
144+
%Docstring
145+
Removes and deletes all child frames.
146+
.. seealso:: removeFrame()
147+
%End
148+
149+
void setResizeMode( ResizeMode mode );
150+
%Docstring
151+
Sets the resize ``mode`` for the multiframe, and recalculates frame sizes to match.
152+
.. seealso:: resizeMode()
153+
%End
154+
155+
ResizeMode resizeMode() const;
156+
%Docstring
157+
Returns the resize mode for the multiframe.
158+
.. seealso:: setResizeMode()
159+
:rtype: ResizeMode
160+
%End
161+
162+
bool writeXml( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames = false ) const;
163+
%Docstring
164+
Stores the multiframe state in a DOM element.
165+
\param parentElement parent DOM element (e.g. 'Layout' element)
166+
\param document DOM document
167+
\param context read write context
168+
\param ignoreFrames set to false to avoid writing state information about child frames into DOM
169+
.. seealso:: readXml()
170+
:rtype: bool
171+
%End
172+
173+
bool readXml( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames = false );
174+
%Docstring
175+
Sets the item state from a DOM element.
176+
\param itemElement is the DOM node corresponding to item (e.g. 'LayoutItem' element)
177+
\param document DOM document
178+
\param context read write context
179+
\param ignoreFrames set to false to avoid read state information about child frames from DOM
180+
.. seealso:: writeXml()
181+
:rtype: bool
182+
%End
183+
184+
QList<QgsLayoutFrame *> frames() const;
185+
%Docstring
186+
Returns a list of all child frames for this multiframe.
187+
.. seealso:: frameCount()
188+
:rtype: list of QgsLayoutFrame
189+
%End
190+
191+
int frameCount() const;
192+
%Docstring
193+
Returns the number of frames associated with this multiframe.
194+
.. seealso:: frames()
195+
:rtype: int
196+
%End
197+
198+
QgsLayoutFrame *frame( int index ) const;
199+
%Docstring
200+
Returns the child frame at a specified ``index`` from the multiframe.
201+
.. seealso:: frameIndex()
202+
:rtype: QgsLayoutFrame
203+
%End
204+
205+
int frameIndex( QgsLayoutFrame *frame ) const;
206+
%Docstring
207+
Returns the index of a ``frame`` within the multiframe.
208+
:return: index for frame if found, -1 if frame not found in multiframe
209+
.. seealso:: frame()
210+
:rtype: int
211+
%End
212+
213+
QgsLayoutFrame *createNewFrame( QgsLayoutFrame *currentFrame, QPointF pos, QSizeF size );
214+
%Docstring
215+
Creates a new frame and adds it to the multi frame and layout.
216+
\param currentFrame an existing QgsLayoutFrame from which to copy the size
217+
and general frame properties (e.g., frame style, background, rendering settings).
218+
\param pos position of top-left corner of the new frame, in layout units
219+
\param size size of the new frame, in layout units
220+
:rtype: QgsLayoutFrame
221+
%End
222+
223+
virtual QString displayName() const;
224+
%Docstring
225+
Returns the multiframe display name.
226+
:rtype: str
227+
%End
228+
229+
virtual QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = 0 ) /Factory/;
230+
231+
232+
void beginCommand( const QString &commandText, UndoCommand command = UndoNone );
233+
%Docstring
234+
Starts new undo command for this item.
235+
The ``commandText`` should be a capitalized, imperative tense description (e.g. "Add Map Item").
236+
If specified, multiple consecutive commands for this item with the same ``command`` will
237+
be collapsed into a single undo command in the layout history.
238+
.. seealso:: endCommand()
239+
.. seealso:: cancelCommand()
240+
%End
241+
242+
void endCommand();
243+
%Docstring
244+
Completes the current item command and push it onto the layout's undo stack.
245+
.. seealso:: beginCommand()
246+
.. seealso:: cancelCommand()
247+
%End
248+
249+
void cancelCommand();
250+
%Docstring
251+
Cancels the current item command and discards it.
252+
.. seealso:: beginCommand()
253+
.. seealso:: endCommand()
254+
%End
255+
256+
public slots:
257+
258+
void update();
259+
%Docstring
260+
Forces a redraw of all child frames.
261+
%End
262+
263+
virtual void recalculateFrameSizes();
264+
%Docstring
265+
Recalculates the portion of the multiframe item which is shown in each of its
266+
component frames. If the resize mode is set to anything but UseExistingFrames then
267+
this may cause new frames to be added or frames to be removed, in order to fit
268+
the current size of the multiframe's content.
269+
.. seealso:: recalculateFrameRects()
270+
%End
271+
272+
void recalculateFrameRects();
273+
%Docstring
274+
Forces a recalculation of all the associated frame's scene rectangles. This
275+
method is useful for multiframes which implement a minFrameSize() or
276+
fixedFrameSize() method.
277+
.. seealso:: minFrameSize()
278+
.. seealso:: fixedFrameSize()
279+
.. seealso:: recalculateFrameSizes
280+
%End
281+
282+
signals:
283+
284+
void changed();
285+
%Docstring
286+
Emitted when the properties of a multi frame have changed, and the GUI item widget
287+
must be updated.
288+
%End
289+
290+
void contentsChanged();
291+
%Docstring
292+
Emitted when the contents of the multi frame have changed and the frames
293+
must be redrawn.
294+
%End
295+
296+
protected:
297+
298+
virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
299+
%Docstring
300+
Stores multiframe state within an XML DOM element.
301+
\param element is the DOM element to store the multiframe's properties in
302+
\param document DOM document
303+
\param context read write context
304+
.. seealso:: writeXml()
305+
.. seealso:: readPropertiesFromElement()
306+
:rtype: bool
307+
%End
308+
309+
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
310+
%Docstring
311+
Sets multiframe state from a DOM element.
312+
\param element is the DOM element for the multiframe
313+
\param document DOM document
314+
\param context read write context
315+
.. seealso:: writePropertiesToElement()
316+
.. seealso:: readXml()
317+
:rtype: bool
318+
%End
319+
320+
321+
322+
protected slots:
323+
324+
void handlePageChange();
325+
%Docstring
326+
Adapts to changed number of layout pages if resize type is RepeatOnEveryPage.
327+
%End
328+
329+
void handleFrameRemoval();
330+
%Docstring
331+
Called when a frame is removed. Updates frame list and recalculates
332+
content of remaining frames.
333+
%End
334+
335+
336+
};
337+
13338

14339
/************************************************************************
15340
* This file has been generated automatically from *

‎src/app/layout/qgslayoutapputils.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
#include "qgslayoutlabelwidget.h"
3434
#include "qgslayoutitemlegend.h"
3535
#include "qgslayoutlegendwidget.h"
36+
#include "qgslayoutframe.h"
37+
#include "qgslayoutitemhtml.h"
3638
#include "qgisapp.h"
3739
#include "qgsmapcanvas.h"
3840

@@ -198,4 +200,23 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
198200
return band.release();
199201
} );
200202
registry->addLayoutItemGuiMetadata( polylineMetadata.release() );
203+
204+
205+
// html item
206+
207+
auto htmlItemMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >( QgsLayoutItemRegistry::LayoutHtml, QObject::tr( "HTML" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddHtml.svg" ) ),
208+
[ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
209+
{
210+
return nullptr; //new QgsLayoutMapWidget( qobject_cast< QgsLayoutItemMap * >( item ) );
211+
}, createRubberBand );
212+
htmlItemMetadata->setItemCreationFunction( [ = ]( QgsLayout * layout )->QgsLayoutItem*
213+
{
214+
std::unique_ptr< QgsLayoutItemHtml > htmlMultiFrame = qgis::make_unique< QgsLayoutItemHtml >( layout );
215+
QgsLayoutItemHtml *html = htmlMultiFrame.get();
216+
layout->addMultiFrame( htmlMultiFrame.release() );
217+
std::unique_ptr< QgsLayoutFrame > frame = qgis::make_unique< QgsLayoutFrame >( layout, html );
218+
return frame.release();
219+
} );
220+
registry->addLayoutItemGuiMetadata( htmlItemMetadata.release() );
221+
201222
}

‎src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ SET(QGIS_CORE_SRCS
392392
layout/qgslayoutmeasurementconverter.cpp
393393
layout/qgslayoutmodel.cpp
394394
layout/qgslayoutmultiframe.cpp
395+
layout/qgslayoutmultiframeundocommand.cpp
395396
layout/qgslayoutobject.cpp
396397
layout/qgslayoutpagecollection.cpp
397398
layout/qgslayoutserializableobject.cpp
@@ -1012,6 +1013,7 @@ SET(QGIS_CORE_HDRS
10121013
layout/qgslayoutitemundocommand.h
10131014
layout/qgslayoutmeasurement.h
10141015
layout/qgslayoutmeasurementconverter.h
1016+
layout/qgslayoutmultiframeundocommand.h
10151017
layout/qgspagesizeregistry.h
10161018
layout/qgslayoutpoint.h
10171019
layout/qgslayoutserializableobject.h

‎src/core/layout/qgslayout.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,19 @@ QgsLayoutItem *QgsLayout::itemByUuid( const QString &uuid )
207207
return nullptr;
208208
}
209209

210+
QgsLayoutMultiFrame *QgsLayout::multiFrameByUuid( const QString &uuid ) const
211+
{
212+
for ( QgsLayoutMultiFrame *mf : mMultiFrames )
213+
{
214+
if ( mf->uuid() == uuid )
215+
{
216+
return mf;
217+
}
218+
}
219+
220+
return nullptr;
221+
}
222+
210223
QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const bool ignoreLocked ) const
211224
{
212225
return layoutItemAt( position, nullptr, ignoreLocked );
@@ -397,7 +410,8 @@ void QgsLayout::addLayoutItem( QgsLayoutItem *item )
397410
{
398411
undoText = tr( "Create Item" );
399412
}
400-
mUndoStack->stack()->push( new QgsLayoutItemAddItemCommand( item, undoText ) );
413+
if ( mBlockUndoCommandCount == 0 )
414+
mUndoStack->stack()->push( new QgsLayoutItemAddItemCommand( item, undoText ) );
401415
}
402416

403417
void QgsLayout::removeLayoutItem( QgsLayoutItem *item )

‎src/core/layout/qgslayout.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,17 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
208208
/**
209209
* Returns the layout item with matching \a uuid unique identifier, or a nullptr
210210
* if a matching item could not be found.
211+
* \see multiFrameByUuid()
211212
*/
212213
QgsLayoutItem *itemByUuid( const QString &uuid );
213214

215+
/**
216+
* Returns the layout multiframe with matching \a uuid unique identifier, or a nullptr
217+
* if a matching multiframe could not be found.
218+
* \see itemByUuid()
219+
*/
220+
QgsLayoutMultiFrame *multiFrameByUuid( const QString &uuid ) const;
221+
214222
/**
215223
* Returns the topmost layout item at a specified \a position. Ignores paper items.
216224
* If \a ignoreLocked is set to true any locked items will be ignored.

‎src/core/layout/qgslayoutframe.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,16 @@ QgsLayoutFrame::QgsLayoutFrame( QgsLayout *layout, QgsLayoutMultiFrame *multiFra
4141
}
4242
}
4343

44+
QgsLayoutFrame *QgsLayoutFrame::create( QgsLayout *layout )
45+
{
46+
return new QgsLayoutFrame( layout, nullptr );
47+
}
48+
4449
QgsLayoutMultiFrame *QgsLayoutFrame::multiFrame() const
4550
{
4651
return mMultiFrame;
4752
}
48-
#if 0// TODO
53+
#if 0// TODO - save/restore multiframe uuid!
4954
bool QgsLayoutFrame::writeXml( QDomElement &elem, QDomDocument &doc ) const
5055
{
5156
QDomElement frameElem = doc.createElement( QStringLiteral( "ComposerFrame" ) );

‎src/core/layout/qgslayoutframe.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
class QgsLayout;
2424
class QgsLayoutMultiFrame;
2525

26-
#ifndef SIP_RUN
27-
2826
/**
2927
* \ingroup core
3028
* Base class for frame items, which form a layout multiframe item.
@@ -42,6 +40,11 @@ class CORE_EXPORT QgsLayoutFrame: public QgsLayoutItem
4240
*/
4341
QgsLayoutFrame( QgsLayout *layout, QgsLayoutMultiFrame *multiFrame );
4442

43+
/**
44+
* Creates a new QgsLayoutFrame belonging to the specified \a layout.
45+
*/
46+
static QgsLayoutFrame *create( QgsLayout *layout ) SIP_FACTORY;
47+
4548
int type() const override;
4649
QString stringType() const override;
4750

@@ -134,6 +137,4 @@ class CORE_EXPORT QgsLayoutFrame: public QgsLayoutItem
134137

135138
};
136139

137-
#endif
138-
139140
#endif // QGSLAYOUTFRAME_H

‎src/core/layout/qgslayoutitemhtml.cpp

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ QgsLayoutItemHtml::~QgsLayoutItemHtml()
9090
mFetcher->deleteLater();
9191
}
9292

93+
int QgsLayoutItemHtml::type() const
94+
{
95+
return QgsLayoutItemRegistry::LayoutHtml;
96+
}
97+
98+
QString QgsLayoutItemHtml::stringType() const
99+
{
100+
return QStringLiteral( "LayoutHtml" );
101+
}
102+
103+
QgsLayoutItemHtml *QgsLayoutItemHtml::create( QgsLayout *layout )
104+
{
105+
return new QgsLayoutItemHtml( layout );
106+
}
107+
93108
void QgsLayoutItemHtml::setUrl( const QUrl &url )
94109
{
95110
if ( !mWebPage )
@@ -449,9 +464,8 @@ QString QgsLayoutItemHtml::displayName() const
449464
return tr( "<HTML frame>" );
450465
}
451466

452-
bool QgsLayoutItemHtml::writeXml( QDomElement &elem, QDomDocument &doc, bool ignoreFrames ) const
467+
bool QgsLayoutItemHtml::writePropertiesToElement( QDomElement &htmlElem, QDomDocument &, const QgsReadWriteContext & ) const
453468
{
454-
QDomElement htmlElem = doc.createElement( QStringLiteral( "ComposerHtml" ) );
455469
htmlElem.setAttribute( QStringLiteral( "contentMode" ), QString::number( static_cast< int >( mContentMode ) ) );
456470
htmlElem.setAttribute( QStringLiteral( "url" ), mUrl.toString() );
457471
htmlElem.setAttribute( QStringLiteral( "html" ), mHtml );
@@ -460,25 +474,11 @@ bool QgsLayoutItemHtml::writeXml( QDomElement &elem, QDomDocument &doc, bool ign
460474
htmlElem.setAttribute( QStringLiteral( "maxBreakDistance" ), QString::number( mMaxBreakDistance ) );
461475
htmlElem.setAttribute( QStringLiteral( "stylesheet" ), mUserStylesheet );
462476
htmlElem.setAttribute( QStringLiteral( "stylesheetEnabled" ), mEnableUserStylesheet ? "true" : "false" );
463-
464-
bool state = _writeXml( htmlElem, doc, ignoreFrames );
465-
elem.appendChild( htmlElem );
466-
return state;
477+
return true;
467478
}
468479

469-
bool QgsLayoutItemHtml::readXml( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames )
480+
bool QgsLayoutItemHtml::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext & )
470481
{
471-
if ( !ignoreFrames )
472-
{
473-
deleteFrames();
474-
}
475-
476-
//first create the frames
477-
if ( !_readXml( itemElem, doc, ignoreFrames ) )
478-
{
479-
return false;
480-
}
481-
482482
bool contentModeOK;
483483
mContentMode = static_cast< QgsLayoutItemHtml::ContentMode >( itemElem.attribute( QStringLiteral( "contentMode" ) ).toInt( &contentModeOK ) );
484484
if ( !contentModeOK )

‎src/core/layout/qgslayoutitemhtml.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ class CORE_EXPORT QgsLayoutItemHtml: public QgsLayoutMultiFrame
5555

5656
~QgsLayoutItemHtml();
5757

58+
int type() const override;
59+
QString stringType() const override;
60+
61+
/**
62+
* Returns a new QgsLayoutItemHtml for the specified parent \a layout.
63+
*/
64+
static QgsLayoutItemHtml *create( QgsLayout *layout ) SIP_FACTORY;
65+
5866
/**
5967
* Sets the source \a mode for item's HTML content.
6068
* \see contentMode()
@@ -199,8 +207,6 @@ class CORE_EXPORT QgsLayoutItemHtml: public QgsLayoutMultiFrame
199207
void render( QgsRenderContext &context, const QRectF &renderExtent, const int frameIndex,
200208
const QStyleOptionGraphicsItem *itemStyle = nullptr ) override;
201209

202-
bool writeXml( QDomElement &elem, QDomDocument &doc, bool ignoreFrames = false ) const override;
203-
bool readXml( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames = false ) override;
204210
//overridden to break frames without dividing lines of text
205211
double findNearbyPageBreak( double yPos ) override;
206212

@@ -222,6 +228,11 @@ class CORE_EXPORT QgsLayoutItemHtml: public QgsLayoutMultiFrame
222228

223229
void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );
224230

231+
protected:
232+
233+
bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
234+
bool readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context ) override;
235+
225236
private slots:
226237
void frameLoaded( bool ok = true );
227238

‎src/core/layout/qgslayoutitemregistry.cpp

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include "qgslayoutitempage.h"
2525
#include "qgslayoutitempicture.h"
2626
#include "qgslayoutitemgroup.h"
27+
#include "qgslayoutitemhtml.h"
28+
#include "qgslayoutframe.h"
2729
#include "qgsgloweffect.h"
2830
#include "qgseffectstack.h"
2931
#include <QPainter>
@@ -51,6 +53,7 @@ bool QgsLayoutItemRegistry::populate()
5153

5254
addLayoutItemType( new QgsLayoutItemMetadata( QgsLayoutItemRegistry::LayoutItem + 1002, QStringLiteral( "temp type" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLabel.svg" ) ), createTemporaryItem ) );
5355
addLayoutItemType( new QgsLayoutItemMetadata( LayoutGroup, QStringLiteral( "Group" ), QIcon(), QgsLayoutItemGroup::create ) );
56+
addLayoutItemType( new QgsLayoutItemMetadata( LayoutFrame, QStringLiteral( "Frame" ), QIcon(), QgsLayoutFrame::create ) );
5457
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPage, QStringLiteral( "Page" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileNew.svg" ) ), QgsLayoutItemPage::create ) );
5558
addLayoutItemType( new QgsLayoutItemMetadata( LayoutMap, QStringLiteral( "Map" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddMap.svg" ) ), QgsLayoutItemMap::create ) );
5659
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPicture, QStringLiteral( "Picture" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddImage.svg" ) ), QgsLayoutItemPicture::create ) );
@@ -65,6 +68,8 @@ bool QgsLayoutItemRegistry::populate()
6568
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPolygon, QStringLiteral( "Polygon" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPolygon.svg" ) ), QgsLayoutItemPolygon::create ) );
6669
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPolyline, QStringLiteral( "Polyline" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPolyline.svg" ) ), QgsLayoutItemPolyline::create ) );
6770

71+
addLayoutMultiFrameType( new QgsLayoutMultiFrameMetadata( LayoutHtml, QStringLiteral( "HTML" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddHtml.svg" ) ), QgsLayoutItemHtml::create ) );
72+
6873
return true;
6974
}
7075

@@ -73,6 +78,11 @@ QgsLayoutItemAbstractMetadata *QgsLayoutItemRegistry::itemMetadata( int type ) c
7378
return mMetadata.value( type );
7479
}
7580

81+
QgsLayoutMultiFrameAbstractMetadata *QgsLayoutItemRegistry::multiFrameMetadata( int type ) const
82+
{
83+
return mMultiFrameMetadata.value( type );
84+
}
85+
7686
bool QgsLayoutItemRegistry::addLayoutItemType( QgsLayoutItemAbstractMetadata *metadata )
7787
{
7888
if ( !metadata || mMetadata.contains( metadata->type() ) )
@@ -83,6 +93,16 @@ bool QgsLayoutItemRegistry::addLayoutItemType( QgsLayoutItemAbstractMetadata *me
8393
return true;
8494
}
8595

96+
bool QgsLayoutItemRegistry::addLayoutMultiFrameType( QgsLayoutMultiFrameAbstractMetadata *metadata )
97+
{
98+
if ( !metadata || mMultiFrameMetadata.contains( metadata->type() ) )
99+
return false;
100+
101+
mMultiFrameMetadata[metadata->type()] = metadata;
102+
emit multiFrameTypeAdded( metadata->type(), metadata->visibleName() );
103+
return true;
104+
}
105+
86106
QgsLayoutItem *QgsLayoutItemRegistry::createItem( int type, QgsLayout *layout ) const
87107
{
88108
if ( !mMetadata.contains( type ) )
@@ -91,23 +111,38 @@ QgsLayoutItem *QgsLayoutItemRegistry::createItem( int type, QgsLayout *layout )
91111
return mMetadata[type]->createItem( layout );
92112
}
93113

94-
void QgsLayoutItemRegistry::resolvePaths( int type, QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving ) const
114+
QgsLayoutMultiFrame *QgsLayoutItemRegistry::createMultiFrame( int type, QgsLayout *layout ) const
95115
{
96-
if ( !mMetadata.contains( type ) )
97-
return;
116+
if ( !mMultiFrameMetadata.contains( type ) )
117+
return nullptr;
98118

99-
mMetadata[type]->resolvePaths( properties, pathResolver, saving );
119+
return mMultiFrameMetadata[type]->createMultiFrame( layout );
120+
}
100121

122+
void QgsLayoutItemRegistry::resolvePaths( int type, QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving ) const
123+
{
124+
if ( mMetadata.contains( type ) )
125+
{
126+
mMetadata[type]->resolvePaths( properties, pathResolver, saving );
127+
}
128+
else if ( mMultiFrameMetadata.contains( type ) )
129+
{
130+
mMultiFrameMetadata[type]->resolvePaths( properties, pathResolver, saving );
131+
}
101132
}
102133

103134
QMap<int, QString> QgsLayoutItemRegistry::itemTypes() const
104135
{
105136
QMap<int, QString> types;
106-
QMap<int, QgsLayoutItemAbstractMetadata *>::ConstIterator it = mMetadata.constBegin();
107-
for ( ; it != mMetadata.constEnd(); ++it )
137+
for ( auto it = mMetadata.constBegin(); it != mMetadata.constEnd(); ++it )
138+
{
139+
types.insert( it.key(), it.value()->visibleName() );
140+
}
141+
for ( auto it = mMultiFrameMetadata.constBegin(); it != mMultiFrameMetadata.constEnd(); ++it )
108142
{
109143
types.insert( it.key(), it.value()->visibleName() );
110144
}
145+
111146
return types;
112147
}
113148

‎src/core/layout/qgslayoutitemregistry.h

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class QgsLayout;
3030
class QgsLayoutView;
3131
class QgsLayoutItem;
3232
class QgsFillSymbol;
33+
class QgsLayoutMultiFrame;
3334

3435
/**
3536
* \ingroup core
@@ -155,6 +156,129 @@ class CORE_EXPORT QgsLayoutItemMetadata : public QgsLayoutItemAbstractMetadata
155156

156157
#endif
157158

159+
/**
160+
* \ingroup core
161+
* \brief Stores metadata about one layout multiframe class.
162+
*
163+
* A companion class, QgsLayoutMultiFrameAbstractGuiMetadata, handles the
164+
* GUI behavior of QgsLayoutMultiFrames.
165+
*
166+
* \note In C++ you can use QgsLayoutMultiFrameMetadata convenience class.
167+
* \since QGIS 3.0
168+
*/
169+
class CORE_EXPORT QgsLayoutMultiFrameAbstractMetadata
170+
{
171+
public:
172+
173+
/**
174+
* Constructor for QgsLayoutMultiFrameAbstractMetadata with the specified class \a type
175+
* and \a visibleName.
176+
*/
177+
QgsLayoutMultiFrameAbstractMetadata( int type, const QString &visibleName )
178+
: mType( type )
179+
, mVisibleName( visibleName )
180+
{}
181+
182+
virtual ~QgsLayoutMultiFrameAbstractMetadata() = default;
183+
184+
/**
185+
* Returns the unique item type code for the layout multiframe class.
186+
*/
187+
int type() const { return mType; }
188+
189+
/**
190+
* Returns an icon representing the layout multiframe type.
191+
*/
192+
virtual QIcon icon() const { return QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ); }
193+
194+
/**
195+
* Returns a translated, user visible name for the layout multiframe class.
196+
*/
197+
QString visibleName() const { return mVisibleName; }
198+
199+
/**
200+
* Creates a layout multiframe of this class for a specified \a layout.
201+
*/
202+
virtual QgsLayoutMultiFrame *createMultiFrame( QgsLayout *layout ) = 0 SIP_FACTORY;
203+
204+
/**
205+
* Resolve paths in the item's \a properties (if there are any paths).
206+
* When \a saving is true, paths are converted from absolute to relative,
207+
* when \a saving is false, paths are converted from relative to absolute.
208+
* This ensures that paths in project files can be relative, but in item
209+
* instances the paths are always absolute.
210+
*/
211+
virtual void resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
212+
{
213+
Q_UNUSED( properties );
214+
Q_UNUSED( pathResolver );
215+
Q_UNUSED( saving );
216+
}
217+
218+
private:
219+
220+
int mType = -1;
221+
QString mVisibleName;
222+
};
223+
224+
//! Layout multiframe creation function
225+
typedef std::function<QgsLayoutMultiFrame *( QgsLayout * )> QgsLayoutMultiFrameCreateFunc SIP_SKIP;
226+
227+
//! Layout multiframe path resolver function
228+
typedef std::function<void( QVariantMap &, const QgsPathResolver &, bool )> QgsLayoutMultiFramePathResolverFunc SIP_SKIP;
229+
230+
#ifndef SIP_RUN
231+
232+
/**
233+
* \ingroup core
234+
* Convenience metadata class that uses static functions to create layout multiframes and their configuration widgets.
235+
* \since QGIS 3.0
236+
* \note not available in Python bindings
237+
*/
238+
class CORE_EXPORT QgsLayoutMultiFrameMetadata : public QgsLayoutMultiFrameAbstractMetadata
239+
{
240+
public:
241+
242+
/**
243+
* Constructor for QgsLayoutMultiFrameMetadata with the specified class \a type
244+
* and \a visibleName, and function pointers for the various item creation functions.
245+
*/
246+
QgsLayoutMultiFrameMetadata( int type, const QString &visibleName, const QIcon &icon,
247+
QgsLayoutMultiFrameCreateFunc pfCreate,
248+
QgsLayoutMultiFramePathResolverFunc pfPathResolver = nullptr )
249+
: QgsLayoutMultiFrameAbstractMetadata( type, visibleName )
250+
, mIcon( icon )
251+
, mCreateFunc( pfCreate )
252+
, mPathResolverFunc( pfPathResolver )
253+
{}
254+
255+
/**
256+
* Returns the classes' multiframe creation function.
257+
*/
258+
QgsLayoutMultiFrameCreateFunc createFunction() const { return mCreateFunc; }
259+
260+
/**
261+
* Returns the classes' path resolver function.
262+
*/
263+
QgsLayoutMultiFramePathResolverFunc pathResolverFunction() const { return mPathResolverFunc; }
264+
265+
QIcon icon() const override { return mIcon.isNull() ? QgsLayoutMultiFrameAbstractMetadata::icon() : mIcon; }
266+
QgsLayoutMultiFrame *createMultiFrame( QgsLayout *layout ) override { return mCreateFunc ? mCreateFunc( layout ) : nullptr; }
267+
268+
void resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving ) override
269+
{
270+
if ( mPathResolverFunc )
271+
mPathResolverFunc( properties, pathResolver, saving );
272+
}
273+
274+
protected:
275+
QIcon mIcon;
276+
QgsLayoutMultiFrameCreateFunc mCreateFunc = nullptr;
277+
QgsLayoutMultiFramePathResolverFunc mPathResolverFunc = nullptr;
278+
279+
};
280+
281+
#endif
158282

159283

160284
/**
@@ -193,6 +317,9 @@ class CORE_EXPORT QgsLayoutItemRegistry : public QObject
193317
LayoutPolyline, //!< Polyline shape item
194318
LayoutFrame, //!< Frame item, part of a QgsLayoutMultiFrame object
195319

320+
// known multi-frame types
321+
LayoutHtml, //!< Html multiframe item
322+
196323
// item types provided by plugins
197324
PluginItem, //!< Starting point for plugin item types
198325
};
@@ -223,19 +350,41 @@ class CORE_EXPORT QgsLayoutItemRegistry : public QObject
223350
/**
224351
* Returns the metadata for the specified item \a type. Returns nullptr if
225352
* a corresponding type was not found in the registry.
353+
* \see multiFrameMetadata()
226354
*/
227355
QgsLayoutItemAbstractMetadata *itemMetadata( int type ) const;
228356

357+
/**
358+
* Returns the metadata for the specified multiframe \a type. Returns nullptr if
359+
* a corresponding type was not found in the registry.
360+
* \see itemMetadata()
361+
*/
362+
QgsLayoutMultiFrameAbstractMetadata *multiFrameMetadata( int type ) const;
363+
229364
/**
230365
* Registers a new layout item type. Takes ownership of the metadata instance.
366+
* \see addLayoutMultiFrameType()
231367
*/
232368
bool addLayoutItemType( QgsLayoutItemAbstractMetadata *metadata SIP_TRANSFER );
233369

370+
/**
371+
* Registers a new layout multiframe type. Takes ownership of the metadata instance.
372+
* \see addLayoutItemType()
373+
*/
374+
bool addLayoutMultiFrameType( QgsLayoutMultiFrameAbstractMetadata *metadata SIP_TRANSFER );
375+
234376
/**
235377
* Creates a new instance of a layout item given the item \a type, and target \a layout.
378+
* \see createMultiFrame()
236379
*/
237380
QgsLayoutItem *createItem( int type, QgsLayout *layout ) const SIP_FACTORY;
238381

382+
/**
383+
* Creates a new instance of a layout multiframe given the multiframe \a type, and target \a layout.
384+
* \see createItem()
385+
*/
386+
QgsLayoutMultiFrame *createMultiFrame( int type, QgsLayout *layout ) const SIP_FACTORY;
387+
239388
/**
240389
* Resolve paths in properties of a particular symbol layer.
241390
* This normally means converting relative paths to absolute paths when loading
@@ -256,12 +405,19 @@ class CORE_EXPORT QgsLayoutItemRegistry : public QObject
256405
*/
257406
void typeAdded( int type, const QString &name );
258407

408+
/**
409+
* Emitted whenever a new multiframe type is added to the registry, with the specified
410+
* \a type and visible \a name.
411+
*/
412+
void multiFrameTypeAdded( int type, const QString &name );
413+
259414
private:
260415
#ifdef SIP_RUN
261416
QgsLayoutItemRegistry( const QgsLayoutItemRegistry &rh );
262417
#endif
263418

264419
QMap<int, QgsLayoutItemAbstractMetadata *> mMetadata;
420+
QMap<int, QgsLayoutMultiFrameAbstractMetadata *> mMultiFrameMetadata;
265421

266422
};
267423

‎src/core/layout/qgslayoutmultiframe.cpp

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
***************************************************************************/
1515

1616
#include "qgslayoutmultiframe.h"
17+
#include "qgslayoutmultiframeundocommand.h"
1718
#include "qgslayoutframe.h"
1819
#include "qgslayout.h"
1920
#include <QtCore>
2021

2122
QgsLayoutMultiFrame::QgsLayoutMultiFrame( QgsLayout *layout )
2223
: QgsLayoutObject( layout )
24+
, mUuid( QUuid::createUuid().toString() )
2325
{
2426
mLayout->addMultiFrame( this );
2527

@@ -98,6 +100,9 @@ void QgsLayoutMultiFrame::recalculateFrameSizes()
98100
return;
99101
}
100102

103+
if ( mBlockUndoCommands )
104+
mLayout->mBlockUndoCommandCount++;
105+
101106
double currentY = 0;
102107
double currentHeight = 0;
103108
QgsLayoutFrame *currentItem = nullptr;
@@ -206,6 +211,9 @@ void QgsLayoutMultiFrame::recalculateFrameSizes()
206211
currentItem = newFrame;
207212
}
208213
}
214+
215+
if ( mBlockUndoCommands )
216+
mLayout->mBlockUndoCommandCount--;
209217
}
210218

211219
void QgsLayoutMultiFrame::recalculateFrameRects()
@@ -257,6 +265,31 @@ QString QgsLayoutMultiFrame::displayName() const
257265
return tr( "<Multiframe>" );
258266
}
259267

268+
QgsAbstractLayoutUndoCommand *QgsLayoutMultiFrame::createCommand( const QString &text, int id, QUndoCommand *parent )
269+
{
270+
return new QgsLayoutMultiFrameUndoCommand( this, text, id, parent );
271+
}
272+
273+
void QgsLayoutMultiFrame::beginCommand( const QString &commandText, QgsLayoutMultiFrame::UndoCommand command )
274+
{
275+
if ( !mLayout )
276+
return;
277+
278+
mLayout->undoStack()->beginCommand( this, commandText, command );
279+
}
280+
281+
void QgsLayoutMultiFrame::endCommand()
282+
{
283+
if ( mLayout )
284+
mLayout->undoStack()->endCommand();
285+
}
286+
287+
void QgsLayoutMultiFrame::cancelCommand()
288+
{
289+
if ( mLayout )
290+
mLayout->undoStack()->cancelCommand();
291+
}
292+
260293
void QgsLayoutMultiFrame::handleFrameRemoval()
261294
{
262295
if ( mBlockUpdates )
@@ -394,10 +427,15 @@ int QgsLayoutMultiFrame::frameIndex( QgsLayoutFrame *frame ) const
394427
return mFrameItems.indexOf( frame );
395428
}
396429

397-
bool QgsLayoutMultiFrame::_writeXml( QDomElement &elem, QDomDocument &doc, bool ignoreFrames ) const
430+
bool QgsLayoutMultiFrame::writeXml( QDomElement &parentElement, QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames ) const
398431
{
432+
QDomElement element = doc.createElement( QStringLiteral( "LayoutMultiFrame" ) );
433+
element.setAttribute( QStringLiteral( "resizeMode" ), mResizeMode );
434+
element.setAttribute( QStringLiteral( "uuid" ), mUuid );
435+
element.setAttribute( QStringLiteral( "type" ), stringType() );
436+
399437
#if 0 //TODO
400-
elem.setAttribute( QStringLiteral( "resizeMode" ), mResizeMode );
438+
401439
if ( !ignoreFrames )
402440
{
403441
QList<QgsComposerFrame *>::const_iterator frameIt = mFrameItems.constBegin();
@@ -406,17 +444,34 @@ bool QgsLayoutMultiFrame::_writeXml( QDomElement &elem, QDomDocument &doc, bool
406444
( *frameIt )->writeXml( elem, doc );
407445
}
408446
}
409-
QgsComposerObject::writeXml( elem, doc );
410447
#endif
448+
449+
writeObjectPropertiesToElement( element, doc, context );
450+
writePropertiesToElement( element, doc, context );
451+
parentElement.appendChild( element );
411452
return true;
412453
}
413454

414-
bool QgsLayoutMultiFrame::_readXml( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames )
455+
bool QgsLayoutMultiFrame::readXml( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames )
415456
{
457+
if ( element.nodeName() != QStringLiteral( "LayoutMultiFrame" ) || element.attribute( QStringLiteral( "type" ) ) != stringType() )
458+
{
459+
return false;
460+
}
461+
462+
mBlockUndoCommands = true;
463+
464+
readObjectPropertiesFromElement( element, doc, context );
465+
466+
mUuid = element.attribute( QStringLiteral( "uuid" ), QUuid::createUuid().toString() );
467+
mResizeMode = static_cast< ResizeMode >( element.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
416468
#if 0 //TODO
417-
QgsComposerObject::readXml( itemElem, doc );
469+
if ( !ignoreFrames )
470+
{
471+
deleteFrames();
472+
}
473+
418474

419-
mResizeMode = static_cast< ResizeMode >( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
420475
if ( !ignoreFrames )
421476
{
422477
QDomNodeList frameList = itemElem.elementsByTagName( QStringLiteral( "ComposerFrame" ) );
@@ -431,7 +486,22 @@ bool QgsLayoutMultiFrame::_readXml( const QDomElement &itemElem, const QDomDocum
431486
//TODO - think there should be a recalculateFrameSizes() call here
432487
}
433488
#endif
489+
490+
491+
bool result = readPropertiesFromElement( element, doc, context );
492+
493+
mBlockUndoCommands = false;
494+
return result;
495+
}
496+
497+
bool QgsLayoutMultiFrame::writePropertiesToElement( QDomElement &, QDomDocument &, const QgsReadWriteContext & ) const
498+
{
434499
return true;
435500
}
436501

502+
bool QgsLayoutMultiFrame::readPropertiesFromElement( const QDomElement &, const QDomDocument &, const QgsReadWriteContext & )
503+
{
504+
505+
return true;
506+
}
437507

‎src/core/layout/qgslayoutmultiframe.h

Lines changed: 91 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "qgis_core.h"
2020
#include "qgis.h"
2121
#include "qgslayoutobject.h"
22+
#include "qgslayoutundocommand.h"
2223
#include <QObject>
2324
#include <QSizeF>
2425
#include <QPointF>
@@ -41,10 +42,7 @@ class QgsRenderContext;
4142
* \since QGIS 3.0
4243
*/
4344

44-
// sip crashes out on this file - reexamine after composer removal
45-
#ifndef SIP_RUN
46-
47-
class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
45+
class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject, public QgsLayoutUndoObjectInterface
4846
{
4947

5048
Q_OBJECT
@@ -63,18 +61,44 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
6361
until the entire multiframe content is visible */
6462
};
6563

64+
//! Multiframe item undo commands, used for collapsing undo commands
65+
enum UndoCommand
66+
{
67+
UndoNone = -1, //!< No command suppression
68+
};
69+
6670
/**
6771
* Construct a new multiframe item, attached to the specified \a layout.
6872
*/
6973
QgsLayoutMultiFrame( QgsLayout *layout SIP_TRANSFERTHIS );
7074

7175
~QgsLayoutMultiFrame();
7276

77+
/**
78+
* Returns the multiframe identification string. This is a unique random string set for the multiframe
79+
* upon creation.
80+
* \note There is no corresponding setter for the uuid - it's created automatically.
81+
*/
82+
QString uuid() const { return mUuid; }
83+
7384
/**
7485
* Returns the total size of the multiframe's content, in layout units.
7586
*/
7687
virtual QSizeF totalSize() const = 0;
7788

89+
/**
90+
* Returns unique multiframe type id.
91+
*/
92+
virtual int type() const = 0;
93+
94+
/**
95+
* Return the multiframe type as a string.
96+
*
97+
* This string must be a unique, single word, character only representation of the item type, eg "LayoutHtml"
98+
* \see type()
99+
*/
100+
virtual QString stringType() const = 0;
101+
78102
/**
79103
* Returns the fixed size for a frame, if desired. If the fixed frame size changes,
80104
* the sizes of all frames can be recalculated by calling recalculateFrameRects().
@@ -157,51 +181,30 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
157181
ResizeMode resizeMode() const { return mResizeMode; }
158182

159183
/**
160-
* Stores state information about multiframe in DOM element. Implementations of writeXml
161-
* should also call the _writeXML method to save general multiframe properties.
162-
* \param elem is DOM element
163-
* \param doc is the DOM document
184+
* Stores the multiframe state in a DOM element.
185+
* \param parentElement parent DOM element (e.g. 'Layout' element)
186+
* \param document DOM document
187+
* \param context read write context
164188
* \param ignoreFrames set to false to avoid writing state information about child frames into DOM
165-
* \see _writeXML
189+
* \see readXml()
166190
*/
167-
virtual bool writeXml( QDomElement &elem, QDomDocument &doc, bool ignoreFrames = false ) const = 0;
191+
bool writeXml( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames = false ) const;

Comment on line R191

3nids commented on Dec 6, 2017

@3nids
Member

@nyalldawson there is a warning, ignoreFrame seems unused

Code has comments. Press enter to view.
168192

169193
/**
170-
* Stores state information about base multiframe object in DOM element. Implementations of writeXml
171-
* should call this method.
172-
* \param elem is DOM element
173-
* \param doc is the DOM document
174-
* \param ignoreFrames set to false to avoid writing state information about child frames into DOM
175-
* \see writeXml
176-
*/
177-
bool _writeXml( QDomElement &elem, QDomDocument &doc, bool ignoreFrames = false ) const;
178-
179-
/**
180-
* Reads multiframe state information from a DOM element. Implementations of readXml
181-
* should also call the _readXML method to restore general multiframe properties.
182-
* \param itemElem is DOM element
183-
* \param doc is the DOM document
194+
* Sets the item state from a DOM element.
195+
* \param itemElement is the DOM node corresponding to item (e.g. 'LayoutItem' element)
196+
* \param document DOM document
197+
* \param context read write context
184198
* \param ignoreFrames set to false to avoid read state information about child frames from DOM
185-
* \see _readXML
186-
*/
187-
virtual bool readXml( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames = false ) = 0;
188-
189-
/**
190-
* Restores state information about base multiframe object from a DOM element. Implementations of readXml
191-
* should call this method.
192-
* \param itemElem is DOM element
193-
* \param doc is the DOM document
194-
* \param ignoreFrames set to false to avoid reading state information about child frames from DOM
195-
* \see readXml
199+
* \see writeXml()
196200
*/
197-
bool _readXml( const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames = false );
201+
bool readXml( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context, bool ignoreFrames = false );
198202

199203
/**
200204
* Returns a list of all child frames for this multiframe.
201205
* \see frameCount()
202-
* \note Not available in Python bindings
203206
*/
204-
QList<QgsLayoutFrame *> frames() const SIP_SKIP;
207+
QList<QgsLayoutFrame *> frames() const;
205208

206209
/**
207210
* Returns the number of frames associated with this multiframe.
@@ -236,6 +239,32 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
236239
*/
237240
virtual QString displayName() const;
238241

242+
QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = nullptr ) override SIP_FACTORY;
243+
244+
/**
245+
* Starts new undo command for this item.
246+
* The \a commandText should be a capitalized, imperative tense description (e.g. "Add Map Item").
247+
* If specified, multiple consecutive commands for this item with the same \a command will
248+
* be collapsed into a single undo command in the layout history.
249+
* \see endCommand()
250+
* \see cancelCommand()
251+
*/
252+
void beginCommand( const QString &commandText, UndoCommand command = UndoNone );
253+
254+
/**
255+
* Completes the current item command and push it onto the layout's undo stack.
256+
* \see beginCommand()
257+
* \see cancelCommand()
258+
*/
259+
void endCommand();
260+
261+
/**
262+
* Cancels the current item command and discards it.
263+
* \see beginCommand()
264+
* \see endCommand()
265+
*/
266+
void cancelCommand();
267+
239268
public slots:
240269

241270
/**
@@ -278,6 +307,26 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
278307

279308
protected:
280309

310+
/**
311+
* Stores multiframe state within an XML DOM element.
312+
* \param element is the DOM element to store the multiframe's properties in
313+
* \param document DOM document
314+
* \param context read write context
315+
* \see writeXml()
316+
* \see readPropertiesFromElement()
317+
*/
318+
virtual bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
319+
320+
/**
321+
* Sets multiframe state from a DOM element.
322+
* \param element is the DOM element for the multiframe
323+
* \param document DOM document
324+
* \param context read write context
325+
* \see writePropertiesToElement()
326+
* \see readXml()
327+
*/
328+
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
329+
281330
QList<QgsLayoutFrame *> mFrameItems;
282331

283332
ResizeMode mResizeMode = UseExistingFrames;
@@ -302,8 +351,11 @@ class CORE_EXPORT QgsLayoutMultiFrame: public QgsLayoutObject
302351
bool mIsRecalculatingSize = false;
303352

304353
bool mBlockUpdates = false;
354+
bool mBlockUndoCommands = false;
355+
356+
//! Unique id
357+
QString mUuid;
305358
};
306359

307-
#endif
308360

309361
#endif // QGSLAYOUTMULTIFRAME_H
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/***************************************************************************
2+
qgslayoutmultiframeundocommand.cpp
3+
----------------------
4+
begin : October 2017
5+
copyright : (C) 2017 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgslayoutmultiframeundocommand.h"
19+
#include "qgslayoutmultiframe.h"
20+
#include "qgsreadwritecontext.h"
21+
#include "qgslayout.h"
22+
#include "qgsproject.h"
23+
24+
///@cond PRIVATE
25+
QgsLayoutMultiFrameUndoCommand::QgsLayoutMultiFrameUndoCommand( QgsLayoutMultiFrame *frame, const QString &text, int id, QUndoCommand *parent )
26+
: QgsAbstractLayoutUndoCommand( text, id, parent )
27+
, mFrameUuid( frame->uuid() )
28+
, mLayout( frame->layout() )
29+
, mItemType( frame->type() )
30+
{
31+
32+
}
33+
34+
bool QgsLayoutMultiFrameUndoCommand::mergeWith( const QUndoCommand *command )
35+
{
36+
if ( command->id() == 0 )
37+
return false;
38+
39+
const QgsLayoutMultiFrameUndoCommand *c = dynamic_cast<const QgsLayoutMultiFrameUndoCommand *>( command );
40+
if ( !c )
41+
{
42+
return false;
43+
}
44+
if ( c->multiFrameUuid() != multiFrameUuid() )
45+
return false;
46+
47+
setAfterState( c->afterState() );
48+
return true;
49+
}
50+
51+
void QgsLayoutMultiFrameUndoCommand::saveState( QDomDocument &stateDoc ) const
52+
{
53+
stateDoc.clear();
54+
QDomElement documentElement = stateDoc.createElement( QStringLiteral( "ItemState" ) );
55+
56+
QgsLayoutMultiFrame *item = mLayout->multiFrameByUuid( mFrameUuid );
57+
Q_ASSERT_X( item, "QgsLayoutMultiFrameUndoCommand::saveState", "could not retrieve item for saving state" );
58+
59+
item->writeXml( documentElement, stateDoc, QgsReadWriteContext() );
60+
stateDoc.appendChild( documentElement );
61+
}
62+
63+
void QgsLayoutMultiFrameUndoCommand::restoreState( QDomDocument &stateDoc )
64+
{
65+
// find item by uuid...
66+
QgsLayoutMultiFrame *item = mLayout->multiFrameByUuid( mFrameUuid );
67+
if ( !item )
68+
{
69+
// uh oh - it's been deleted! we need to create a new instance
70+
item = recreateItem( mItemType, mLayout );
71+
}
72+
73+
item->readXml( stateDoc.documentElement().firstChild().toElement(), stateDoc, QgsReadWriteContext() );
74+
mLayout->project()->setDirty( true );
75+
}
76+
77+
QgsLayoutMultiFrame *QgsLayoutMultiFrameUndoCommand::recreateItem( int itemType, QgsLayout *layout )
78+
{
79+
QgsLayoutMultiFrame *item = QgsApplication::layoutItemRegistry()->createMultiFrame( itemType, layout );
80+
mLayout->addMultiFrame( item );
81+
return item;
82+
}
83+
84+
QString QgsLayoutMultiFrameUndoCommand::multiFrameUuid() const
85+
{
86+
return mFrameUuid;
87+
}
88+
89+
QgsLayout *QgsLayoutMultiFrameUndoCommand::layout() const
90+
{
91+
return mLayout;
92+
}
93+
94+
95+
//
96+
// QgsLayoutMultiFrameDeleteUndoCommand
97+
//
98+
99+
QgsLayoutMultiFrameDeleteUndoCommand::QgsLayoutMultiFrameDeleteUndoCommand( QgsLayoutMultiFrame *item, const QString &text, int id, QUndoCommand *parent )
100+
: QgsLayoutMultiFrameUndoCommand( item, text, id, parent )
101+
{
102+
saveBeforeState();
103+
}
104+
105+
bool QgsLayoutMultiFrameDeleteUndoCommand::mergeWith( const QUndoCommand * )
106+
{
107+
return false;
108+
}
109+
110+
void QgsLayoutMultiFrameDeleteUndoCommand::redo()
111+
{
112+
if ( mFirstRun )
113+
{
114+
mFirstRun = false;
115+
return;
116+
}
117+
118+
QgsLayoutMultiFrame *item = layout()->multiFrameByUuid( multiFrameUuid() );
119+
//Q_ASSERT_X( item, "QgsLayoutMultiFrameDeleteUndoCommand::redo", "could not find item to re-delete!" );
120+
121+
if ( item )
122+
{
123+
layout()->removeMultiFrame( item );
124+
delete item;
125+
}
126+
}
127+
128+
QgsLayoutMultiFrameAddItemCommand::QgsLayoutMultiFrameAddItemCommand( QgsLayoutMultiFrame *frame, const QString &text, int id, QUndoCommand *parent )
129+
: QgsLayoutMultiFrameUndoCommand( frame, text, id, parent )
130+
{
131+
saveAfterState();
132+
}
133+
134+
bool QgsLayoutMultiFrameAddItemCommand::containsChange() const
135+
{
136+
return true;
137+
}
138+
139+
bool QgsLayoutMultiFrameAddItemCommand::mergeWith( const QUndoCommand * )
140+
{
141+
return false;
142+
}
143+
144+
void QgsLayoutMultiFrameAddItemCommand::undo()
145+
{
146+
if ( mFirstRun )
147+
{
148+
mFirstRun = false;
149+
return;
150+
}
151+
152+
QgsLayoutMultiFrame *item = layout()->multiFrameByUuid( multiFrameUuid() );
153+
if ( item )
154+
{
155+
layout()->removeMultiFrame( item );
156+
delete item;
157+
}
158+
}
159+
160+
161+
///@endcond
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/***************************************************************************
2+
qgslayoutmultiframeundocommand.h
3+
----------------------
4+
begin : October 2017
5+
copyright : (C) 2017 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#ifndef QGSLAYOUTMULTIFRAMEUNDOCOMMAND_H
19+
#define QGSLAYOUTMULTIFRAMEUNDOCOMMAND_H
20+
21+
#include "qgslayoutundocommand.h"
22+
#include "qgis_core.h"
23+
24+
class QgsLayout;
25+
class QgsLayoutMultiFrame;
26+
27+
SIP_NO_FILE
28+
29+
///@cond PRIVATE
30+
31+
/**
32+
* \ingroup core
33+
* An undo command subclass for layout multiframe undo commands.
34+
*
35+
* QgsLayoutMultiFrameUndoCommand is a specific layout undo command which is
36+
* designed for use with QgsLayoutMultiFrames. It automatically handles
37+
* recreating a deleted multiframes when the undo stack rolls back past
38+
* the item deletion command.
39+
*
40+
* \since QGIS 3.0
41+
*/
42+
class CORE_EXPORT QgsLayoutMultiFrameUndoCommand: public QgsAbstractLayoutUndoCommand
43+
{
44+
public:
45+
46+
/**
47+
* Constructor for QgsLayoutMultiFrameUndoCommand.
48+
* \param item associated layout item
49+
* \param text undo command descriptive text
50+
* \param id optional undo command id, used for automatic command merging
51+
* \param parent command
52+
*/
53+
QgsLayoutMultiFrameUndoCommand( QgsLayoutMultiFrame *frame, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr );
54+
55+
bool mergeWith( const QUndoCommand *command ) override;
56+
57+
/**
58+
* Returns the layout associated with this command.
59+
*/
60+
QgsLayout *layout() const;
61+
62+
/**
63+
* Returns the associated multiframes's UUID, which uniquely identifies the frame
64+
* within the layout.
65+
*/
66+
QString multiFrameUuid() const;
67+
68+
protected:
69+
70+
void saveState( QDomDocument &stateDoc ) const override;
71+
void restoreState( QDomDocument &stateDoc ) override;
72+
73+
virtual QgsLayoutMultiFrame *recreateItem( int itemType, QgsLayout *layout ) SIP_FACTORY;
74+
75+
private:
76+
77+
QString mFrameUuid;
78+
QgsLayout *mLayout = nullptr;
79+
int mItemType = 0;
80+
81+
};
82+
83+
/**
84+
* \ingroup core
85+
* An undo command subclass for layout multiframe deletion undo commands.
86+
*
87+
* QgsLayoutMultiFrameDeleteUndoCommand is a specific layout undo command which handles
88+
* layout multiframe deletion. When applied (e.g. as a result of a 'redo' action),
89+
* the associated layout multiframe is deleted and removed from the layout.
90+
*
91+
* \since QGIS 3.0
92+
*/
93+
class CORE_EXPORT QgsLayoutMultiFrameDeleteUndoCommand: public QgsLayoutMultiFrameUndoCommand
94+
{
95+
public:
96+
97+
/**
98+
* Constructor for QgsLayoutMultiFrameDeleteUndoCommand.
99+
* \param item associated layout item
100+
* \param text undo command descriptive text
101+
* \param id optional undo command id, used for automatic command merging
102+
* \param parent command
103+
*/
104+
QgsLayoutMultiFrameDeleteUndoCommand( QgsLayoutMultiFrame *frame, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr );
105+
bool mergeWith( const QUndoCommand *command ) override;
106+
void redo() override;
107+
108+
};
109+
110+
111+
/**
112+
* \ingroup core
113+
* An undo command subclass for layout item addition undo commands.
114+
*
115+
* QgsLayoutMultiFrameAddItemCommand is a specific layout undo command which handles
116+
* layout multiframe creation. When applied (e.g. as a result of a 'redo' action),
117+
* the associated layout multiframe is recreated and added to the layout.
118+
*
119+
* \since QGIS 3.0
120+
*/
121+
class CORE_EXPORT QgsLayoutMultiFrameAddItemCommand: public QgsLayoutMultiFrameUndoCommand
122+
{
123+
public:
124+
125+
/**
126+
* Constructor for QgsLayoutMultiFrameAddItemCommand.
127+
* \param item associated layout item
128+
* \param text undo command descriptive text
129+
* \param id optional undo command id, used for automatic command merging
130+
* \param parent command
131+
*/
132+
QgsLayoutMultiFrameAddItemCommand( QgsLayoutMultiFrame *frame, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr );
133+
bool containsChange() const override;
134+
bool mergeWith( const QUndoCommand *command ) override;
135+
void undo() override;
136+
137+
};
138+
139+
///@endcond
140+
141+
#endif //QGSLAYOUTMULTIFRAMEUNDOCOMMAND_H

‎tests/src/core/testqgslayoutmultiframe.cpp

Lines changed: 128 additions & 76 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.