QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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] { handleFrameRemoval( frame ); } );
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, QPointF( currentItem->pos().x(), newFrameY ), QSizeF( currentItem->rect().width(), frameHeight ) );
208
210 {
211 newFrame->setContentSection( QRectF( 0, 0, newFrame->rect().width(), newFrame->rect().height() ) );
212 currentY += frameHeight;
213 }
214 else
215 {
216 double contentHeight = findNearbyPageBreak( currentY + newFrame->rect().height() ) - currentY;
217 newFrame->setContentSection( QRectF( 0, currentY, newFrame->rect().width(), contentHeight ) );
218 currentY += contentHeight;
219 }
220
221 currentItem = newFrame;
222 }
223 }
224
225 if ( mBlockUndoCommands )
226 mLayout->undoStack()->blockCommands( false );
227}
228
230{
231 if ( mFrameItems.empty() )
232 {
233 //no frames, nothing to do
234 return;
235 }
236
237 const QList< QgsLayoutFrame * > frames = mFrameItems;
238 for ( QgsLayoutFrame *frame : frames )
239 {
240 frame->refreshItemSize();
241 }
242}
243
246
247QgsLayoutFrame *QgsLayoutMultiFrame::createNewFrame( QgsLayoutFrame *currentFrame, QPointF pos, QSizeF size )
248{
249 if ( !currentFrame )
250 {
251 return nullptr;
252 }
253
254 QgsLayoutFrame *newFrame = new QgsLayoutFrame( mLayout, this );
255 newFrame->attemptSetSceneRect( QRectF( pos.x(), pos.y(), size.width(), size.height() ) );
256
257 //copy some settings from the parent frame
258 newFrame->setBackgroundColor( currentFrame->backgroundColor() );
259 newFrame->setBackgroundEnabled( currentFrame->hasBackground() );
260 newFrame->setBlendMode( currentFrame->blendMode() );
261 newFrame->setFrameEnabled( currentFrame->frameEnabled() );
262 newFrame->setFrameStrokeColor( currentFrame->frameStrokeColor() );
263 newFrame->setFrameJoinStyle( currentFrame->frameJoinStyle() );
264 newFrame->setFrameStrokeWidth( currentFrame->frameStrokeWidth() );
265 newFrame->setItemOpacity( currentFrame->itemOpacity() );
266 newFrame->setHideBackgroundIfEmpty( currentFrame->hideBackgroundIfEmpty() );
267
268 addFrame( newFrame, false );
269
270 return newFrame;
271}
272
274{
275 return tr( "<Multiframe>" );
276}
277
278QgsAbstractLayoutUndoCommand *QgsLayoutMultiFrame::createCommand( const QString &text, int id, QUndoCommand *parent )
279{
280 return new QgsLayoutMultiFrameUndoCommand( this, text, id, parent );
281}
282
289
291{
292 if ( !mLayout )
293 return;
294
295 mLayout->undoStack()->beginCommand( this, commandText, command );
296}
297
299{
300 if ( mLayout )
301 mLayout->undoStack()->endCommand();
302}
303
305{
306 if ( mLayout )
307 mLayout->undoStack()->cancelCommand();
308}
309
311{
312 for ( int i = 0; i < mFrameUuids.count(); ++i )
313 {
314 QgsLayoutFrame *frame = nullptr;
315 const QString uuid = mFrameUuids.at( i );
316 if ( !uuid.isEmpty() )
317 {
318 QgsLayoutItem *item = mLayout->itemByUuid( uuid, true );
319 frame = qobject_cast< QgsLayoutFrame * >( item );
320 }
321 if ( !frame )
322 {
323 const QString templateUuid = mFrameTemplateUuids.at( i );
324 if ( !templateUuid.isEmpty() )
325 {
326 QgsLayoutItem *item = mLayout->itemByTemplateUuid( templateUuid );
327 frame = qobject_cast< QgsLayoutFrame * >( item );
328 }
329 }
330
331 if ( frame )
332 {
333 addFrame( frame );
334 }
335 }
336}
337
343
344void QgsLayoutMultiFrame::handleFrameRemoval( QgsLayoutFrame *frame )
345{
346 if ( mBlockUpdates )
347 return;
348
349 if ( !frame )
350 {
351 return;
352 }
353 int index = mFrameItems.indexOf( frame );
354 if ( index == -1 )
355 {
356 return;
357 }
358
359 mFrameItems.removeAt( index );
360 if ( !mFrameItems.isEmpty() )
361 {
362 if ( resizeMode() != QgsLayoutMultiFrame::RepeatOnEveryPage && !mIsRecalculatingSize )
363 {
364 //removing a frame forces the multi frame to UseExistingFrames resize mode
365 //otherwise the frame may not actually be removed, leading to confusing ui behavior
367 emit changed();
369 }
370 }
371}
372
373void QgsLayoutMultiFrame::handlePageChange()
374{
375 if ( mLayout->pageCollection()->pageCount() < 1 )
376 {
377 return;
378 }
379
381 {
382 return;
383 }
384
385 //remove items beginning on non-existing pages
386 for ( int i = mFrameItems.size() - 1; i >= 0; --i )
387 {
389 int page = mLayout->pageCollection()->predictPageNumberForPoint( frame->pos() );
390 if ( page >= mLayout->pageCollection()->pageCount() )
391 {
392 removeFrame( i );
393 }
394 }
395
396 if ( !mFrameItems.empty() )
397 {
398 //page number of the last item
399 QgsLayoutFrame *lastFrame = mFrameItems.last();
400 int lastItemPage = mLayout->pageCollection()->predictPageNumberForPoint( lastFrame->pos() );
401
402 for ( int i = lastItemPage + 1; i < mLayout->pageCollection()->pageCount(); ++i )
403 {
404 //copy last frame to current page
405 auto newFrame = std::make_unique< QgsLayoutFrame >( mLayout, this );
406
407 newFrame->attemptSetSceneRect( QRectF( lastFrame->pos().x(), mLayout->pageCollection()->page( i )->pos().y() + lastFrame->pagePos().y(), lastFrame->rect().width(), lastFrame->rect().height() ) );
408 lastFrame = newFrame.get();
409 addFrame( newFrame.release(), false );
410 }
411 }
412
414 update();
415}
416
417void QgsLayoutMultiFrame::removeFrame( int i, const bool removeEmptyPages )
418{
419 if ( i >= mFrameItems.count() )
420 {
421 return;
422 }
423
424 QgsLayoutFrame *frameItem = mFrameItems.at( i );
425 if ( mLayout )
426 {
427 mIsRecalculatingSize = true;
428 int pageNumber = frameItem->page();
429 //remove item, but don't create undo command
430 mLayout->undoStack()->blockCommands( true );
431 mLayout->removeLayoutItem( frameItem );
432 //if frame was the only item on the page, remove the page
433 if ( removeEmptyPages && mLayout->pageCollection()->pageIsEmpty( pageNumber ) )
434 {
435 mLayout->pageCollection()->deletePage( pageNumber );
436 }
437 mLayout->undoStack()->blockCommands( false );
438 mIsRecalculatingSize = false;
439 }
440
441 if ( i >= mFrameItems.count() )
442 {
443 return;
444 }
445
446 mFrameItems.removeAt( i );
447}
448
450{
451 for ( QgsLayoutFrame *frame : std::as_const( mFrameItems ) )
452 {
453 frame->update();
454 }
455}
456
458{
459 mBlockUpdates = true;
460 ResizeMode bkResizeMode = mResizeMode;
462 mLayout->undoStack()->blockCommands( true );
463 for ( QgsLayoutFrame *frame : std::as_const( mFrameItems ) )
464 {
465 mLayout->removeLayoutItem( frame );
466 }
467 mLayout->undoStack()->blockCommands( false );
468 mFrameItems.clear();
469 mResizeMode = bkResizeMode;
470 mBlockUpdates = false;
471}
472
474{
475 if ( i < 0 || i >= mFrameItems.size() )
476 {
477 return nullptr;
478 }
479 return mFrameItems.at( i );
480}
481
483{
484 return mFrameItems.indexOf( frame );
485}
486
487bool QgsLayoutMultiFrame::writeXml( QDomElement &parentElement, QDomDocument &doc, const QgsReadWriteContext &context, bool includeFrames ) const
488{
489 QDomElement element = doc.createElement( u"LayoutMultiFrame"_s );
490 element.setAttribute( u"resizeMode"_s, mResizeMode );
491 element.setAttribute( u"uuid"_s, mUuid );
492 element.setAttribute( u"templateUuid"_s, mUuid );
493 element.setAttribute( u"type"_s, type() );
494
496 {
497 if ( !frame )
498 continue;
499
500 QDomElement childItem = doc.createElement( u"childFrame"_s );
501 childItem.setAttribute( u"uuid"_s, frame->uuid() );
502 childItem.setAttribute( u"templateUuid"_s, frame->uuid() );
503
504 if ( includeFrames )
505 {
506 frame->writeXml( childItem, doc, context );
507 }
508
509 element.appendChild( childItem );
510 }
511
512 writeObjectPropertiesToElement( element, doc, context );
513 writePropertiesToElement( element, doc, context );
514 parentElement.appendChild( element );
515 return true;
516}
517
518bool QgsLayoutMultiFrame::readXml( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context, bool includeFrames )
519{
520 if ( element.nodeName() != "LayoutMultiFrame"_L1 )
521 {
522 return false;
523 }
524
525 mBlockUndoCommands = true;
526 mLayout->undoStack()->blockCommands( true );
527
528 readObjectPropertiesFromElement( element, doc, context );
529
530 mUuid = element.attribute( u"uuid"_s, QUuid::createUuid().toString() );
531 mTemplateUuid = element.attribute( u"templateUuid"_s, QUuid::createUuid().toString() );
532 mResizeMode = static_cast< ResizeMode >( element.attribute( u"resizeMode"_s, u"0"_s ).toInt() );
533
534 deleteFrames();
535 mFrameUuids.clear();
536 mFrameTemplateUuids.clear();
537 QDomNodeList elementNodes = element.elementsByTagName( u"childFrame"_s );
538 for ( int i = 0; i < elementNodes.count(); ++i )
539 {
540 QDomNode elementNode = elementNodes.at( i );
541 if ( !elementNode.isElement() )
542 continue;
543
544 QDomElement frameElement = elementNode.toElement();
545
546 QString uuid = frameElement.attribute( u"uuid"_s );
547 mFrameUuids << uuid;
548 QString templateUuid = frameElement.attribute( u"templateUuid"_s );
549 mFrameTemplateUuids << templateUuid;
550
551 if ( includeFrames )
552 {
553 QDomNodeList frameNodes = frameElement.elementsByTagName( u"LayoutItem"_s );
554 if ( !frameNodes.isEmpty() )
555 {
556 QDomElement frameItemElement = frameNodes.at( 0 ).toElement();
557 auto newFrame = std::make_unique< QgsLayoutFrame >( mLayout, this );
558 newFrame->readXml( frameItemElement, doc, context );
559 addFrame( newFrame.release(), false );
560 }
561 }
562 }
563
564 bool result = readPropertiesFromElement( element, doc, context );
565
566 mBlockUndoCommands = false;
567 mLayout->undoStack()->blockCommands( false );
568 return result;
569}
570
571bool QgsLayoutMultiFrame::writePropertiesToElement( QDomElement &, QDomDocument &, const QgsReadWriteContext & ) const
572{
573 return true;
574}
575
576bool QgsLayoutMultiFrame::readPropertiesFromElement( const QDomElement &, const QDomDocument &, const QgsReadWriteContext & )
577{
578 return true;
579}
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.