QGIS API Documentation 3.99.0-Master (d270888f95f)
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 <QString>
26#include <QUuid>
27
28#include "moc_qgslayoutmultiframe.cpp"
29
30using namespace Qt::StringLiterals;
31
34 , mUuid( QUuid::createUuid().toString() )
35{
36 mLayout->addMultiFrame( this );
37
38 connect( mLayout->pageCollection(), &QgsLayoutPageCollection::changed, this, &QgsLayoutMultiFrame::handlePageChange );
39}
40
45
47{
48 Q_UNUSED( frameIndex )
49 return QSizeF( 0, 0 );
50}
51
53{
54 Q_UNUSED( frameIndex )
55 return QSizeF( 0, 0 );
56}
57
59{
60 return yPos;
61}
62
63void QgsLayoutMultiFrame::addFrame( QgsLayoutFrame *frame, bool recalcFrameSizes )
64{
65 if ( !frame || mFrameItems.contains( frame ) )
66 return;
67
68 mFrameItems.push_back( frame );
69 frame->mMultiFrame = this;
71 connect( frame, &QgsLayoutFrame::destroyed, this, [this, frame ]
72 {
73 handleFrameRemoval( frame );
74 } );
75 if ( mLayout && !frame->scene() )
76 {
77 mLayout->addLayoutItem( frame );
78 }
79
80 if ( recalcFrameSizes )
81 {
83 }
84}
85
87{
88 if ( mode != mResizeMode )
89 {
90 mLayout->undoStack()->beginMacro( tr( "Change Resize Mode" ) );
91 mResizeMode = mode;
93 mLayout->undoStack()->endMacro();
94 emit changed();
95 }
96}
97
98QList<QgsLayoutFrame *> QgsLayoutMultiFrame::frames() const
99{
100 return mFrameItems;
101}
102
104{
105 if ( mFrameItems.empty() )
106 {
107 return;
108 }
109
110 QSizeF size = totalSize();
111 double totalHeight = size.height();
112
113 if ( totalHeight < 1 )
114 {
115 return;
116 }
117
118 if ( mBlockUndoCommands )
119 mLayout->undoStack()->blockCommands( true );
120
121 double currentY = 0;
122 double currentHeight = 0;
123 QgsLayoutFrame *currentItem = nullptr;
124
125 for ( int i = 0; i < mFrameItems.size(); ++i )
126 {
127 if ( mResizeMode != RepeatOnEveryPage && currentY >= totalHeight )
128 {
129 if ( mResizeMode == RepeatUntilFinished || mResizeMode == ExtendToNextPage ) //remove unneeded frames in extent mode
130 {
131 bool removingPages = true;
132 for ( int j = mFrameItems.size(); j > i; --j )
133 {
134 int numPagesBefore = mLayout->pageCollection()->pageCount();
135 removeFrame( j - 1, removingPages );
136 //if removing the frame didn't also remove the page, then stop removing pages
137 removingPages = removingPages && ( mLayout->pageCollection()->pageCount() < numPagesBefore );
138 }
139 return;
140 }
141 }
142
143 currentItem = mFrameItems.value( i );
144 currentHeight = currentItem->rect().height();
146 {
147 currentItem->setContentSection( QRectF( 0, 0, currentItem->rect().width(), currentHeight ) );
148 }
149 else
150 {
151 currentHeight = findNearbyPageBreak( currentY + currentHeight ) - currentY;
152 currentItem->setContentSection( QRectF( 0, currentY, currentItem->rect().width(), currentHeight ) );
153 }
154 currentItem->update();
155 currentY += currentHeight;
156 }
157
158 //at end of frames but there is still content left. Add other pages if ResizeMode ==
159 if ( mLayout->pageCollection()->pageCount() > 0 && currentItem && mResizeMode != UseExistingFrames )
160 {
161 while ( ( mResizeMode == RepeatOnEveryPage ) || currentY < totalHeight )
162 {
163 //find out on which page the lower left point of the last frame is
164 int page = mLayout->pageCollection()->predictPageNumberForPoint( QPointF( 0, currentItem->pos().y() + currentItem->rect().height() ) ) + 1;
165
167 {
168 if ( page >= mLayout->pageCollection()->pageCount() )
169 {
170 break;
171 }
172 }
173 else
174 {
175 //add new pages if required
176 for ( int p = mLayout->pageCollection()->pageCount() - 1 ; p < page; ++p )
177 {
178 mLayout->pageCollection()->extendByNewPage();
179 }
180 }
181
182 double currentPageHeight = mLayout->pageCollection()->page( page )->rect().height();
183
184 double frameHeight = 0;
185 switch ( mResizeMode )
186 {
189 {
190 frameHeight = currentItem->rect().height();
191 break;
192 }
193 case ExtendToNextPage:
194 {
195 frameHeight = ( currentY + currentPageHeight ) > totalHeight ? totalHeight - currentY : currentPageHeight;
196 break;
197 }
198
200 break;
201 }
202
203 double newFrameY = mLayout->pageCollection()->page( page )->pos().y();
205 {
206 newFrameY += currentItem->pagePos().y();
207 }
208
209 //create new frame
210 QgsLayoutFrame *newFrame = createNewFrame( currentItem,
211 QPointF( currentItem->pos().x(), newFrameY ),
212 QSizeF( currentItem->rect().width(), frameHeight ) );
213
215 {
216 newFrame->setContentSection( QRectF( 0, 0, newFrame->rect().width(), newFrame->rect().height() ) );
217 currentY += frameHeight;
218 }
219 else
220 {
221 double contentHeight = findNearbyPageBreak( currentY + newFrame->rect().height() ) - currentY;
222 newFrame->setContentSection( QRectF( 0, currentY, newFrame->rect().width(), contentHeight ) );
223 currentY += contentHeight;
224 }
225
226 currentItem = newFrame;
227 }
228 }
229
230 if ( mBlockUndoCommands )
231 mLayout->undoStack()->blockCommands( false );
232}
233
235{
236 if ( mFrameItems.empty() )
237 {
238 //no frames, nothing to do
239 return;
240 }
241
242 const QList< QgsLayoutFrame * > frames = mFrameItems;
243 for ( QgsLayoutFrame *frame : frames )
244 {
245 frame->refreshItemSize();
246 }
247}
248
253
254QgsLayoutFrame *QgsLayoutMultiFrame::createNewFrame( QgsLayoutFrame *currentFrame, QPointF pos, QSizeF size )
255{
256 if ( !currentFrame )
257 {
258 return nullptr;
259 }
260
261 QgsLayoutFrame *newFrame = new QgsLayoutFrame( mLayout, this );
262 newFrame->attemptSetSceneRect( QRectF( pos.x(), pos.y(), size.width(), size.height() ) );
263
264 //copy some settings from the parent frame
265 newFrame->setBackgroundColor( currentFrame->backgroundColor() );
266 newFrame->setBackgroundEnabled( currentFrame->hasBackground() );
267 newFrame->setBlendMode( currentFrame->blendMode() );
268 newFrame->setFrameEnabled( currentFrame->frameEnabled() );
269 newFrame->setFrameStrokeColor( currentFrame->frameStrokeColor() );
270 newFrame->setFrameJoinStyle( currentFrame->frameJoinStyle() );
271 newFrame->setFrameStrokeWidth( currentFrame->frameStrokeWidth() );
272 newFrame->setItemOpacity( currentFrame->itemOpacity() );
273 newFrame->setHideBackgroundIfEmpty( currentFrame->hideBackgroundIfEmpty() );
274
275 addFrame( newFrame, false );
276
277 return newFrame;
278}
279
281{
282 return tr( "<Multiframe>" );
283}
284
285QgsAbstractLayoutUndoCommand *QgsLayoutMultiFrame::createCommand( const QString &text, int id, QUndoCommand *parent )
286{
287 return new QgsLayoutMultiFrameUndoCommand( this, text, id, parent );
288}
289
296
298{
299 if ( !mLayout )
300 return;
301
302 mLayout->undoStack()->beginCommand( this, commandText, command );
303}
304
306{
307 if ( mLayout )
308 mLayout->undoStack()->endCommand();
309}
310
312{
313 if ( mLayout )
314 mLayout->undoStack()->cancelCommand();
315}
316
318{
319 for ( int i = 0; i < mFrameUuids.count(); ++i )
320 {
321 QgsLayoutFrame *frame = nullptr;
322 const QString uuid = mFrameUuids.at( i );
323 if ( !uuid.isEmpty() )
324 {
325 QgsLayoutItem *item = mLayout->itemByUuid( uuid, true );
326 frame = qobject_cast< QgsLayoutFrame * >( item );
327 }
328 if ( !frame )
329 {
330 const QString templateUuid = mFrameTemplateUuids.at( i );
331 if ( !templateUuid.isEmpty() )
332 {
333 QgsLayoutItem *item = mLayout->itemByTemplateUuid( templateUuid );
334 frame = qobject_cast< QgsLayoutFrame * >( item );
335 }
336 }
337
338 if ( frame )
339 {
340 addFrame( frame );
341 }
342 }
343}
344
350
351void QgsLayoutMultiFrame::handleFrameRemoval( QgsLayoutFrame *frame )
352{
353 if ( mBlockUpdates )
354 return;
355
356 if ( !frame )
357 {
358 return;
359 }
360 int index = mFrameItems.indexOf( frame );
361 if ( index == -1 )
362 {
363 return;
364 }
365
366 mFrameItems.removeAt( index );
367 if ( !mFrameItems.isEmpty() )
368 {
369 if ( resizeMode() != QgsLayoutMultiFrame::RepeatOnEveryPage && !mIsRecalculatingSize )
370 {
371 //removing a frame forces the multi frame to UseExistingFrames resize mode
372 //otherwise the frame may not actually be removed, leading to confusing ui behavior
374 emit changed();
376 }
377 }
378}
379
380void QgsLayoutMultiFrame::handlePageChange()
381{
382 if ( mLayout->pageCollection()->pageCount() < 1 )
383 {
384 return;
385 }
386
388 {
389 return;
390 }
391
392 //remove items beginning on non-existing pages
393 for ( int i = mFrameItems.size() - 1; i >= 0; --i )
394 {
396 int page = mLayout->pageCollection()->predictPageNumberForPoint( frame->pos() );
397 if ( page >= mLayout->pageCollection()->pageCount() )
398 {
399 removeFrame( i );
400 }
401 }
402
403 if ( !mFrameItems.empty() )
404 {
405 //page number of the last item
406 QgsLayoutFrame *lastFrame = mFrameItems.last();
407 int lastItemPage = mLayout->pageCollection()->predictPageNumberForPoint( lastFrame->pos() );
408
409 for ( int i = lastItemPage + 1; i < mLayout->pageCollection()->pageCount(); ++i )
410 {
411 //copy last frame to current page
412 auto newFrame = std::make_unique< QgsLayoutFrame >( mLayout, this );
413
414 newFrame->attemptSetSceneRect( QRectF( lastFrame->pos().x(),
415 mLayout->pageCollection()->page( i )->pos().y() + lastFrame->pagePos().y(),
416 lastFrame->rect().width(), lastFrame->rect().height() ) );
417 lastFrame = newFrame.get();
418 addFrame( newFrame.release(), false );
419 }
420 }
421
423 update();
424}
425
426void QgsLayoutMultiFrame::removeFrame( int i, const bool removeEmptyPages )
427{
428 if ( i >= mFrameItems.count() )
429 {
430 return;
431 }
432
433 QgsLayoutFrame *frameItem = mFrameItems.at( i );
434 if ( mLayout )
435 {
436 mIsRecalculatingSize = true;
437 int pageNumber = frameItem->page();
438 //remove item, but don't create undo command
439 mLayout->undoStack()->blockCommands( true );
440 mLayout->removeLayoutItem( frameItem );
441 //if frame was the only item on the page, remove the page
442 if ( removeEmptyPages && mLayout->pageCollection()->pageIsEmpty( pageNumber ) )
443 {
444 mLayout->pageCollection()->deletePage( pageNumber );
445 }
446 mLayout->undoStack()->blockCommands( false );
447 mIsRecalculatingSize = false;
448 }
449
450 if ( i >= mFrameItems.count() )
451 {
452 return;
453 }
454
455 mFrameItems.removeAt( i );
456}
457
459{
460 for ( QgsLayoutFrame *frame : std::as_const( mFrameItems ) )
461 {
462 frame->update();
463 }
464}
465
467{
468 mBlockUpdates = true;
469 ResizeMode bkResizeMode = mResizeMode;
471 mLayout->undoStack()->blockCommands( true );
472 for ( QgsLayoutFrame *frame : std::as_const( mFrameItems ) )
473 {
474 mLayout->removeLayoutItem( frame );
475 }
476 mLayout->undoStack()->blockCommands( false );
477 mFrameItems.clear();
478 mResizeMode = bkResizeMode;
479 mBlockUpdates = false;
480}
481
483{
484 if ( i < 0 || i >= mFrameItems.size() )
485 {
486 return nullptr;
487 }
488 return mFrameItems.at( i );
489}
490
492{
493 return mFrameItems.indexOf( frame );
494}
495
496bool QgsLayoutMultiFrame::writeXml( QDomElement &parentElement, QDomDocument &doc, const QgsReadWriteContext &context, bool includeFrames ) const
497{
498 QDomElement element = doc.createElement( u"LayoutMultiFrame"_s );
499 element.setAttribute( u"resizeMode"_s, mResizeMode );
500 element.setAttribute( u"uuid"_s, mUuid );
501 element.setAttribute( u"templateUuid"_s, mUuid );
502 element.setAttribute( u"type"_s, type() );
503
505 {
506 if ( !frame )
507 continue;
508
509 QDomElement childItem = doc.createElement( u"childFrame"_s );
510 childItem.setAttribute( u"uuid"_s, frame->uuid() );
511 childItem.setAttribute( u"templateUuid"_s, frame->uuid() );
512
513 if ( includeFrames )
514 {
515 frame->writeXml( childItem, doc, context );
516 }
517
518 element.appendChild( childItem );
519 }
520
521 writeObjectPropertiesToElement( element, doc, context );
522 writePropertiesToElement( element, doc, context );
523 parentElement.appendChild( element );
524 return true;
525}
526
527bool QgsLayoutMultiFrame::readXml( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context, bool includeFrames )
528{
529 if ( element.nodeName() != "LayoutMultiFrame"_L1 )
530 {
531 return false;
532 }
533
534 mBlockUndoCommands = true;
535 mLayout->undoStack()->blockCommands( true );
536
537 readObjectPropertiesFromElement( element, doc, context );
538
539 mUuid = element.attribute( u"uuid"_s, QUuid::createUuid().toString() );
540 mTemplateUuid = element.attribute( u"templateUuid"_s, QUuid::createUuid().toString() );
541 mResizeMode = static_cast< ResizeMode >( element.attribute( u"resizeMode"_s, u"0"_s ).toInt() );
542
543 deleteFrames();
544 mFrameUuids.clear();
545 mFrameTemplateUuids.clear();
546 QDomNodeList elementNodes = element.elementsByTagName( u"childFrame"_s );
547 for ( int i = 0; i < elementNodes.count(); ++i )
548 {
549 QDomNode elementNode = elementNodes.at( i );
550 if ( !elementNode.isElement() )
551 continue;
552
553 QDomElement frameElement = elementNode.toElement();
554
555 QString uuid = frameElement.attribute( u"uuid"_s );
556 mFrameUuids << uuid;
557 QString templateUuid = frameElement.attribute( u"templateUuid"_s );
558 mFrameTemplateUuids << templateUuid;
559
560 if ( includeFrames )
561 {
562 QDomNodeList frameNodes = frameElement.elementsByTagName( u"LayoutItem"_s );
563 if ( !frameNodes.isEmpty() )
564 {
565 QDomElement frameItemElement = frameNodes.at( 0 ).toElement();
566 auto newFrame = std::make_unique< QgsLayoutFrame >( mLayout, this );
567 newFrame->readXml( frameItemElement, doc, context );
568 addFrame( newFrame.release(), false );
569 }
570 }
571 }
572
573 bool result = readPropertiesFromElement( element, doc, context );
574
575 mBlockUndoCommands = false;
576 mLayout->undoStack()->blockCommands( false );
577 return result;
578}
579
580bool QgsLayoutMultiFrame::writePropertiesToElement( QDomElement &, QDomDocument &, const QgsReadWriteContext & ) const
581{
582 return true;
583}
584
585bool QgsLayoutMultiFrame::readPropertiesFromElement( const QDomElement &, const QDomDocument &, const QgsReadWriteContext & )
586{
587
588 return true;
589}
590
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.