QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgslayoutmultiframe.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutmultiframe.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 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgslayoutmultiframe.h"
17
19#include "qgslayout.h"
20#include "qgslayoutframe.h"
23#include "qgslayoutundostack.h"
24
25#include <QUuid>
26
27#include "moc_qgslayoutmultiframe.cpp"
28
31 , mUuid( QUuid::createUuid().toString() )
32{
33 mLayout->addMultiFrame( this );
34
35 connect( mLayout->pageCollection(), &QgsLayoutPageCollection::changed, this, &QgsLayoutMultiFrame::handlePageChange );
36}
37
42
44{
45 Q_UNUSED( frameIndex )
46 return QSizeF( 0, 0 );
47}
48
50{
51 Q_UNUSED( frameIndex )
52 return QSizeF( 0, 0 );
53}
54
56{
57 return yPos;
58}
59
60void QgsLayoutMultiFrame::addFrame( QgsLayoutFrame *frame, bool recalcFrameSizes )
61{
62 if ( !frame || mFrameItems.contains( frame ) )
63 return;
64
65 mFrameItems.push_back( frame );
66 frame->mMultiFrame = this;
68 connect( frame, &QgsLayoutFrame::destroyed, this, [this, frame ]
69 {
70 handleFrameRemoval( frame );
71 } );
72 if ( mLayout && !frame->scene() )
73 {
74 mLayout->addLayoutItem( frame );
75 }
76
77 if ( recalcFrameSizes )
78 {
80 }
81}
82
84{
85 if ( mode != mResizeMode )
86 {
87 mLayout->undoStack()->beginMacro( tr( "Change Resize Mode" ) );
88 mResizeMode = mode;
90 mLayout->undoStack()->endMacro();
91 emit changed();
92 }
93}
94
95QList<QgsLayoutFrame *> QgsLayoutMultiFrame::frames() const
96{
97 return mFrameItems;
98}
99
101{
102 if ( mFrameItems.empty() )
103 {
104 return;
105 }
106
107 QSizeF size = totalSize();
108 double totalHeight = size.height();
109
110 if ( totalHeight < 1 )
111 {
112 return;
113 }
114
115 if ( mBlockUndoCommands )
116 mLayout->undoStack()->blockCommands( true );
117
118 double currentY = 0;
119 double currentHeight = 0;
120 QgsLayoutFrame *currentItem = nullptr;
121
122 for ( int i = 0; i < mFrameItems.size(); ++i )
123 {
124 if ( mResizeMode != RepeatOnEveryPage && currentY >= totalHeight )
125 {
126 if ( mResizeMode == RepeatUntilFinished || mResizeMode == ExtendToNextPage ) //remove unneeded frames in extent mode
127 {
128 bool removingPages = true;
129 for ( int j = mFrameItems.size(); j > i; --j )
130 {
131 int numPagesBefore = mLayout->pageCollection()->pageCount();
132 removeFrame( j - 1, removingPages );
133 //if removing the frame didn't also remove the page, then stop removing pages
134 removingPages = removingPages && ( mLayout->pageCollection()->pageCount() < numPagesBefore );
135 }
136 return;
137 }
138 }
139
140 currentItem = mFrameItems.value( i );
141 currentHeight = currentItem->rect().height();
143 {
144 currentItem->setContentSection( QRectF( 0, 0, currentItem->rect().width(), currentHeight ) );
145 }
146 else
147 {
148 currentHeight = findNearbyPageBreak( currentY + currentHeight ) - currentY;
149 currentItem->setContentSection( QRectF( 0, currentY, currentItem->rect().width(), currentHeight ) );
150 }
151 currentItem->update();
152 currentY += currentHeight;
153 }
154
155 //at end of frames but there is still content left. Add other pages if ResizeMode ==
156 if ( mLayout->pageCollection()->pageCount() > 0 && currentItem && mResizeMode != UseExistingFrames )
157 {
158 while ( ( mResizeMode == RepeatOnEveryPage ) || currentY < totalHeight )
159 {
160 //find out on which page the lower left point of the last frame is
161 int page = mLayout->pageCollection()->predictPageNumberForPoint( QPointF( 0, currentItem->pos().y() + currentItem->rect().height() ) ) + 1;
162
164 {
165 if ( page >= mLayout->pageCollection()->pageCount() )
166 {
167 break;
168 }
169 }
170 else
171 {
172 //add new pages if required
173 for ( int p = mLayout->pageCollection()->pageCount() - 1 ; p < page; ++p )
174 {
175 mLayout->pageCollection()->extendByNewPage();
176 }
177 }
178
179 double currentPageHeight = mLayout->pageCollection()->page( page )->rect().height();
180
181 double frameHeight = 0;
182 switch ( mResizeMode )
183 {
186 {
187 frameHeight = currentItem->rect().height();
188 break;
189 }
190 case ExtendToNextPage:
191 {
192 frameHeight = ( currentY + currentPageHeight ) > totalHeight ? totalHeight - currentY : currentPageHeight;
193 break;
194 }
195
197 break;
198 }
199
200 double newFrameY = mLayout->pageCollection()->page( page )->pos().y();
202 {
203 newFrameY += currentItem->pagePos().y();
204 }
205
206 //create new frame
207 QgsLayoutFrame *newFrame = createNewFrame( currentItem,
208 QPointF( currentItem->pos().x(), newFrameY ),
209 QSizeF( currentItem->rect().width(), frameHeight ) );
210
212 {
213 newFrame->setContentSection( QRectF( 0, 0, newFrame->rect().width(), newFrame->rect().height() ) );
214 currentY += frameHeight;
215 }
216 else
217 {
218 double contentHeight = findNearbyPageBreak( currentY + newFrame->rect().height() ) - currentY;
219 newFrame->setContentSection( QRectF( 0, currentY, newFrame->rect().width(), contentHeight ) );
220 currentY += contentHeight;
221 }
222
223 currentItem = newFrame;
224 }
225 }
226
227 if ( mBlockUndoCommands )
228 mLayout->undoStack()->blockCommands( false );
229}
230
232{
233 if ( mFrameItems.empty() )
234 {
235 //no frames, nothing to do
236 return;
237 }
238
239 const QList< QgsLayoutFrame * > frames = mFrameItems;
240 for ( QgsLayoutFrame *frame : frames )
241 {
242 frame->refreshItemSize();
243 }
244}
245
250
251QgsLayoutFrame *QgsLayoutMultiFrame::createNewFrame( QgsLayoutFrame *currentFrame, QPointF pos, QSizeF size )
252{
253 if ( !currentFrame )
254 {
255 return nullptr;
256 }
257
258 QgsLayoutFrame *newFrame = new QgsLayoutFrame( mLayout, this );
259 newFrame->attemptSetSceneRect( QRectF( pos.x(), pos.y(), size.width(), size.height() ) );
260
261 //copy some settings from the parent frame
262 newFrame->setBackgroundColor( currentFrame->backgroundColor() );
263 newFrame->setBackgroundEnabled( currentFrame->hasBackground() );
264 newFrame->setBlendMode( currentFrame->blendMode() );
265 newFrame->setFrameEnabled( currentFrame->frameEnabled() );
266 newFrame->setFrameStrokeColor( currentFrame->frameStrokeColor() );
267 newFrame->setFrameJoinStyle( currentFrame->frameJoinStyle() );
268 newFrame->setFrameStrokeWidth( currentFrame->frameStrokeWidth() );
269 newFrame->setItemOpacity( currentFrame->itemOpacity() );
270 newFrame->setHideBackgroundIfEmpty( currentFrame->hideBackgroundIfEmpty() );
271
272 addFrame( newFrame, false );
273
274 return newFrame;
275}
276
278{
279 return tr( "<Multiframe>" );
280}
281
282QgsAbstractLayoutUndoCommand *QgsLayoutMultiFrame::createCommand( const QString &text, int id, QUndoCommand *parent )
283{
284 return new QgsLayoutMultiFrameUndoCommand( this, text, id, parent );
285}
286
293
295{
296 if ( !mLayout )
297 return;
298
299 mLayout->undoStack()->beginCommand( this, commandText, command );
300}
301
303{
304 if ( mLayout )
305 mLayout->undoStack()->endCommand();
306}
307
309{
310 if ( mLayout )
311 mLayout->undoStack()->cancelCommand();
312}
313
315{
316 for ( int i = 0; i < mFrameUuids.count(); ++i )
317 {
318 QgsLayoutFrame *frame = nullptr;
319 const QString uuid = mFrameUuids.at( i );
320 if ( !uuid.isEmpty() )
321 {
322 QgsLayoutItem *item = mLayout->itemByUuid( uuid, true );
323 frame = qobject_cast< QgsLayoutFrame * >( item );
324 }
325 if ( !frame )
326 {
327 const QString templateUuid = mFrameTemplateUuids.at( i );
328 if ( !templateUuid.isEmpty() )
329 {
330 QgsLayoutItem *item = mLayout->itemByTemplateUuid( templateUuid );
331 frame = qobject_cast< QgsLayoutFrame * >( item );
332 }
333 }
334
335 if ( frame )
336 {
337 addFrame( frame );
338 }
339 }
340}
341
347
348void QgsLayoutMultiFrame::handleFrameRemoval( QgsLayoutFrame *frame )
349{
350 if ( mBlockUpdates )
351 return;
352
353 if ( !frame )
354 {
355 return;
356 }
357 int index = mFrameItems.indexOf( frame );
358 if ( index == -1 )
359 {
360 return;
361 }
362
363 mFrameItems.removeAt( index );
364 if ( !mFrameItems.isEmpty() )
365 {
366 if ( resizeMode() != QgsLayoutMultiFrame::RepeatOnEveryPage && !mIsRecalculatingSize )
367 {
368 //removing a frame forces the multi frame to UseExistingFrames resize mode
369 //otherwise the frame may not actually be removed, leading to confusing ui behavior
371 emit changed();
373 }
374 }
375}
376
377void QgsLayoutMultiFrame::handlePageChange()
378{
379 if ( mLayout->pageCollection()->pageCount() < 1 )
380 {
381 return;
382 }
383
385 {
386 return;
387 }
388
389 //remove items beginning on non-existing pages
390 for ( int i = mFrameItems.size() - 1; i >= 0; --i )
391 {
393 int page = mLayout->pageCollection()->predictPageNumberForPoint( frame->pos() );
394 if ( page >= mLayout->pageCollection()->pageCount() )
395 {
396 removeFrame( i );
397 }
398 }
399
400 if ( !mFrameItems.empty() )
401 {
402 //page number of the last item
403 QgsLayoutFrame *lastFrame = mFrameItems.last();
404 int lastItemPage = mLayout->pageCollection()->predictPageNumberForPoint( lastFrame->pos() );
405
406 for ( int i = lastItemPage + 1; i < mLayout->pageCollection()->pageCount(); ++i )
407 {
408 //copy last frame to current page
409 auto newFrame = std::make_unique< QgsLayoutFrame >( mLayout, this );
410
411 newFrame->attemptSetSceneRect( QRectF( lastFrame->pos().x(),
412 mLayout->pageCollection()->page( i )->pos().y() + lastFrame->pagePos().y(),
413 lastFrame->rect().width(), lastFrame->rect().height() ) );
414 lastFrame = newFrame.get();
415 addFrame( newFrame.release(), false );
416 }
417 }
418
420 update();
421}
422
423void QgsLayoutMultiFrame::removeFrame( int i, const bool removeEmptyPages )
424{
425 if ( i >= mFrameItems.count() )
426 {
427 return;
428 }
429
430 QgsLayoutFrame *frameItem = mFrameItems.at( i );
431 if ( mLayout )
432 {
433 mIsRecalculatingSize = true;
434 int pageNumber = frameItem->page();
435 //remove item, but don't create undo command
436 mLayout->undoStack()->blockCommands( true );
437 mLayout->removeLayoutItem( frameItem );
438 //if frame was the only item on the page, remove the page
439 if ( removeEmptyPages && mLayout->pageCollection()->pageIsEmpty( pageNumber ) )
440 {
441 mLayout->pageCollection()->deletePage( pageNumber );
442 }
443 mLayout->undoStack()->blockCommands( false );
444 mIsRecalculatingSize = false;
445 }
446
447 if ( i >= mFrameItems.count() )
448 {
449 return;
450 }
451
452 mFrameItems.removeAt( i );
453}
454
456{
457 for ( QgsLayoutFrame *frame : std::as_const( mFrameItems ) )
458 {
459 frame->update();
460 }
461}
462
464{
465 mBlockUpdates = true;
466 ResizeMode bkResizeMode = mResizeMode;
468 mLayout->undoStack()->blockCommands( true );
469 for ( QgsLayoutFrame *frame : std::as_const( mFrameItems ) )
470 {
471 mLayout->removeLayoutItem( frame );
472 }
473 mLayout->undoStack()->blockCommands( false );
474 mFrameItems.clear();
475 mResizeMode = bkResizeMode;
476 mBlockUpdates = false;
477}
478
480{
481 if ( i < 0 || i >= mFrameItems.size() )
482 {
483 return nullptr;
484 }
485 return mFrameItems.at( i );
486}
487
489{
490 return mFrameItems.indexOf( frame );
491}
492
493bool QgsLayoutMultiFrame::writeXml( QDomElement &parentElement, QDomDocument &doc, const QgsReadWriteContext &context, bool includeFrames ) const
494{
495 QDomElement element = doc.createElement( QStringLiteral( "LayoutMultiFrame" ) );
496 element.setAttribute( QStringLiteral( "resizeMode" ), mResizeMode );
497 element.setAttribute( QStringLiteral( "uuid" ), mUuid );
498 element.setAttribute( QStringLiteral( "templateUuid" ), mUuid );
499 element.setAttribute( QStringLiteral( "type" ), type() );
500
502 {
503 if ( !frame )
504 continue;
505
506 QDomElement childItem = doc.createElement( QStringLiteral( "childFrame" ) );
507 childItem.setAttribute( QStringLiteral( "uuid" ), frame->uuid() );
508 childItem.setAttribute( QStringLiteral( "templateUuid" ), frame->uuid() );
509
510 if ( includeFrames )
511 {
512 frame->writeXml( childItem, doc, context );
513 }
514
515 element.appendChild( childItem );
516 }
517
518 writeObjectPropertiesToElement( element, doc, context );
519 writePropertiesToElement( element, doc, context );
520 parentElement.appendChild( element );
521 return true;
522}
523
524bool QgsLayoutMultiFrame::readXml( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context, bool includeFrames )
525{
526 if ( element.nodeName() != QLatin1String( "LayoutMultiFrame" ) )
527 {
528 return false;
529 }
530
531 mBlockUndoCommands = true;
532 mLayout->undoStack()->blockCommands( true );
533
534 readObjectPropertiesFromElement( element, doc, context );
535
536 mUuid = element.attribute( QStringLiteral( "uuid" ), QUuid::createUuid().toString() );
537 mTemplateUuid = element.attribute( QStringLiteral( "templateUuid" ), QUuid::createUuid().toString() );
538 mResizeMode = static_cast< ResizeMode >( element.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
539
540 deleteFrames();
541 mFrameUuids.clear();
542 mFrameTemplateUuids.clear();
543 QDomNodeList elementNodes = element.elementsByTagName( QStringLiteral( "childFrame" ) );
544 for ( int i = 0; i < elementNodes.count(); ++i )
545 {
546 QDomNode elementNode = elementNodes.at( i );
547 if ( !elementNode.isElement() )
548 continue;
549
550 QDomElement frameElement = elementNode.toElement();
551
552 QString uuid = frameElement.attribute( QStringLiteral( "uuid" ) );
553 mFrameUuids << uuid;
554 QString templateUuid = frameElement.attribute( QStringLiteral( "templateUuid" ) );
555 mFrameTemplateUuids << templateUuid;
556
557 if ( includeFrames )
558 {
559 QDomNodeList frameNodes = frameElement.elementsByTagName( QStringLiteral( "LayoutItem" ) );
560 if ( !frameNodes.isEmpty() )
561 {
562 QDomElement frameItemElement = frameNodes.at( 0 ).toElement();
563 auto newFrame = std::make_unique< QgsLayoutFrame >( mLayout, this );
564 newFrame->readXml( frameItemElement, doc, context );
565 addFrame( newFrame.release(), false );
566 }
567 }
568 }
569
570 bool result = readPropertiesFromElement( element, doc, context );
571
572 mBlockUndoCommands = false;
573 mLayout->undoStack()->blockCommands( false );
574 return result;
575}
576
577bool QgsLayoutMultiFrame::writePropertiesToElement( QDomElement &, QDomDocument &, const QgsReadWriteContext & ) const
578{
579 return true;
580}
581
582bool QgsLayoutMultiFrame::readPropertiesFromElement( const QDomElement &, const QDomDocument &, const QgsReadWriteContext & )
583{
584
585 return true;
586}
587
Base class for commands to undo/redo layout and layout object changes.
static QgsExpressionContextScope * multiFrameScope(const QgsLayoutMultiFrame *frame)
Creates a new scope which contains variables and functions relating to a QgsLayoutMultiFrame.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
Base class for frame items, which form a layout multiframe item.
void setContentSection(const QRectF &section)
Sets the visible part of the multiframe's content which is visible within this frame (relative to the...
bool hideBackgroundIfEmpty() const
Returns whether the background and frame stroke should be hidden if this frame is empty.
void setHideBackgroundIfEmpty(bool hideBackgroundIfEmpty)
Sets whether the background and frame stroke should be hidden if this frame is empty.
Base class for graphical items within a QgsLayout.
QColor backgroundColor(bool useDataDefined=true) const
Returns the background color for this item.
virtual void setFrameStrokeWidth(QgsLayoutMeasurement width)
Sets the frame stroke width.
void setBackgroundColor(const QColor &color)
Sets the background color for this item.
QgsLayoutMeasurement frameStrokeWidth() const
Returns the frame's stroke width.
double itemOpacity() const
Returns the item's opacity.
void setItemOpacity(double opacity)
Sets the item's opacity.
int page() const
Returns the page the item is currently on, with the first page returning 0.
void setFrameStrokeColor(const QColor &color)
Sets the frame stroke color.
void setFrameJoinStyle(Qt::PenJoinStyle style)
Sets the join style used when drawing the item's frame.
virtual void setFrameEnabled(bool drawFrame)
Sets whether this item has a frame drawn around it or not.
void sizePositionChanged()
Emitted when the item's size or position changes.
QPointF pagePos() const
Returns the item's position (in layout units) relative to the top left corner of its current page.
void setBlendMode(QPainter::CompositionMode mode)
Sets the item's composition blending mode.
bool frameEnabled() const
Returns true if the item includes a frame.
bool hasBackground() const
Returns true if the item has a background.
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item's position and size to match the passed rect in layout coordinates.
QColor frameStrokeColor() const
Returns the frame's stroke color.
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
QPainter::CompositionMode blendMode() const
Returns the item's composition blending mode.
Qt::PenJoinStyle frameJoinStyle() const
Returns the join style used for drawing the item's frame.
virtual bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const
Stores multiframe state within an XML DOM element.
void setResizeMode(ResizeMode mode)
Sets the resize mode for the multiframe, and recalculates frame sizes to match.
virtual QSizeF totalSize() const =0
Returns the total size of the multiframe's content, in layout units.
virtual void addFrame(QgsLayoutFrame *frame, bool recalcFrameSizes=true)
Adds a frame to the multiframe.
QgsLayoutMultiFrame(QgsLayout *layout)
Construct a new multiframe item, attached to the specified layout.
bool writeXml(QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context, bool includeFrames=false) const
Stores the multiframe state in a DOM element.
virtual void finalizeRestoreFromXml()
Called after all pending items have been restored from XML.
QgsAbstractLayoutUndoCommand * createCommand(const QString &text, int id, QUndoCommand *parent=nullptr) override
Creates a new layout undo command with the specified text and parent.
bool readXml(const QDomElement &itemElement, const QDomDocument &document, const QgsReadWriteContext &context, bool includeFrames=false)
Sets the item state from a DOM element.
void deleteFrames()
Removes and deletes all child frames.
QgsLayoutFrame * frame(int index) const
Returns the child frame at a specified index from the multiframe.
virtual QSizeF fixedFrameSize(int frameIndex=-1) const
Returns the fixed size for a frame, if desired.
void removeFrame(int index, bool removeEmptyPages=false)
Removes a frame by index from the multiframe.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QList< QgsLayoutFrame * > mFrameItems
void cancelCommand()
Cancels the current item command and discards it.
virtual int type() const =0
Returns unique multiframe type id.
ResizeMode resizeMode() const
Returns the resize mode for the multiframe.
virtual QSizeF minFrameSize(int frameIndex=-1) const
Returns the minimum size for a frames, if desired.
virtual double findNearbyPageBreak(double yPos)
Finds the optimal position to break a frame at.
QString uuid() const
Returns the multiframe identification string.
ResizeMode
Specifies the behavior for creating new frames to fit the multiframe's content.
@ UseExistingFrames
Don't automatically create new frames, just use existing frames.
@ RepeatOnEveryPage
Repeats the same frame on every page.
@ ExtendToNextPage
Creates new full page frames on the following page(s) until the entire multiframe content is visible.
void refresh() override
Refreshes the multiframe, causing a recalculation of any property overrides.
void endCommand()
Completes the current item command and push it onto the layout's undo stack.
QgsLayoutFrame * createNewFrame(QgsLayoutFrame *currentFrame, QPointF pos, QSizeF size)
Creates a new frame and adds it to the multi frame and layout.
void beginCommand(const QString &commandText, UndoCommand command=UndoNone)
Starts new undo command for this item.
QList< QgsLayoutFrame * > frames() const
Returns a list of all child frames for this multiframe.
virtual void recalculateFrameSizes()
Recalculates the portion of the multiframe item which is shown in each of its component frames.
void update()
Forces a redraw of all child frames.
int frameIndex(QgsLayoutFrame *frame) const
Returns the index of a frame within the multiframe.
virtual void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::DataDefinedProperty::AllProperties)
Refreshes a data defined property for the multi frame by reevaluating the property's value and redraw...
void recalculateFrameRects()
Forces a recalculation of all the associated frame's scene rectangles.
virtual bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context)
Sets multiframe state from a DOM element.
virtual QString displayName() const
Returns the multiframe display name.
UndoCommand
Multiframe item undo commands, used for collapsing undo commands.
bool readObjectPropertiesFromElement(const QDomElement &parentElement, const QDomDocument &document, const QgsReadWriteContext &context)
Sets object properties from a DOM element.
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
virtual void refresh()
Refreshes the object, causing a recalculation of any property overrides.
QPointer< QgsLayout > mLayout
QgsExpressionContext createExpressionContext() const override
Creates an expression context relating to the objects' current state.
DataDefinedProperty
Data defined properties for different item types.
QgsLayoutObject(QgsLayout *layout)
Constructor for QgsLayoutObject, with the specified parent layout.
bool writeObjectPropertiesToElement(QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context) const
Stores object properties within an XML DOM element.
void changed()
Emitted when pages are added or removed from the collection.
A container for the context for various read/write operations on objects.