Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgslayoutitemgroup.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 : : * This program is free software; you can redistribute it and/or modify *
11 : : * it under the terms of the GNU General Public License as published by *
12 : : * the Free Software Foundation; either version 2 of the License, or *
13 : : * (at your option) any later version. *
14 : : * *
15 : : ***************************************************************************/
16 : :
17 : : #include "qgslayoutitemgroup.h"
18 : : #include "qgslayoutitemregistry.h"
19 : : #include "qgslayout.h"
20 : : #include "qgslayoututils.h"
21 : : #include "qgslayoutundostack.h"
22 : : #include "qgslayoutpagecollection.h"
23 : :
24 : 0 : QgsLayoutItemGroup::QgsLayoutItemGroup( QgsLayout *layout )
25 : 0 : : QgsLayoutItem( layout )
26 : 0 : {}
27 : :
28 : 0 : void QgsLayoutItemGroup::cleanup()
29 : : {
30 : : //loop through group members and remove them from the scene
31 : 0 : for ( QgsLayoutItem *item : std::as_const( mItems ) )
32 : : {
33 : 0 : if ( !item )
34 : 0 : continue;
35 : :
36 : : //inform model that we are about to remove an item from the scene
37 : 0 : if ( mLayout )
38 : 0 : mLayout->removeLayoutItem( item );
39 : : else
40 : : {
41 : 0 : item->cleanup();
42 : 0 : item->deleteLater();
43 : : }
44 : : }
45 : 0 : mItems.clear();
46 : 0 : QgsLayoutItem::cleanup();
47 : 0 : }
48 : :
49 : 0 : int QgsLayoutItemGroup::type() const
50 : : {
51 : 0 : return QgsLayoutItemRegistry::LayoutGroup;
52 : : }
53 : :
54 : 0 : QString QgsLayoutItemGroup::displayName() const
55 : : {
56 : : //return id, if it's not empty
57 : 0 : if ( !id().isEmpty() )
58 : : {
59 : 0 : return id();
60 : : }
61 : 0 : return tr( "<Group>" );
62 : 0 : }
63 : :
64 : 0 : QgsLayoutItemGroup *QgsLayoutItemGroup::create( QgsLayout *layout )
65 : : {
66 : 0 : return new QgsLayoutItemGroup( layout );
67 : 0 : }
68 : :
69 : 0 : void QgsLayoutItemGroup::addItem( QgsLayoutItem *item )
70 : : {
71 : 0 : if ( !item )
72 : : {
73 : 0 : return;
74 : : }
75 : :
76 : 0 : if ( mItems.contains( item ) )
77 : : {
78 : 0 : return;
79 : : }
80 : :
81 : 0 : mItems << QPointer< QgsLayoutItem >( item );
82 : 0 : item->setParentGroup( this );
83 : :
84 : 0 : updateBoundingRect( item );
85 : 0 : }
86 : :
87 : 0 : void QgsLayoutItemGroup::removeItems()
88 : : {
89 : 0 : for ( QgsLayoutItem *item : std::as_const( mItems ) )
90 : : {
91 : 0 : if ( !item )
92 : 0 : continue;
93 : :
94 : 0 : item->setParentGroup( nullptr );
95 : : }
96 : 0 : mItems.clear();
97 : 0 : }
98 : :
99 : 0 : QList<QgsLayoutItem *> QgsLayoutItemGroup::items() const
100 : : {
101 : 0 : QList<QgsLayoutItem *> val;
102 : 0 : for ( QgsLayoutItem *item : std::as_const( mItems ) )
103 : : {
104 : 0 : if ( !item )
105 : 0 : continue;
106 : 0 : val << item;
107 : : }
108 : 0 : return val;
109 : 0 : }
110 : :
111 : 0 : void QgsLayoutItemGroup::setVisibility( const bool visible )
112 : : {
113 : 0 : if ( !shouldBlockUndoCommands() )
114 : 0 : mLayout->undoStack()->beginMacro( tr( "Set Group Visibility" ) );
115 : : //also set visibility for all items within the group
116 : 0 : for ( QgsLayoutItem *item : std::as_const( mItems ) )
117 : : {
118 : 0 : if ( !item )
119 : 0 : continue;
120 : 0 : bool prev = item->mBlockUndoCommands;
121 : 0 : item->mBlockUndoCommands = mBlockUndoCommands;
122 : 0 : item->setVisibility( visible );
123 : 0 : item->mBlockUndoCommands = prev;
124 : : }
125 : : //lastly set visibility for group item itself
126 : 0 : QgsLayoutItem::setVisibility( visible );
127 : :
128 : 0 : if ( !shouldBlockUndoCommands() )
129 : 0 : mLayout->undoStack()->endMacro();
130 : 0 : }
131 : :
132 : 0 : void QgsLayoutItemGroup::attemptMove( const QgsLayoutPoint &point, bool useReferencePoint, bool includesFrame, int page )
133 : : {
134 : : Q_UNUSED( useReferencePoint ) //groups should always have reference point in top left
135 : 0 : if ( !mLayout )
136 : 0 : return;
137 : :
138 : 0 : if ( !shouldBlockUndoCommands() )
139 : 0 : mLayout->undoStack()->beginMacro( tr( "Move group" ) );
140 : :
141 : 0 : QPointF scenePoint;
142 : 0 : if ( page < 0 )
143 : 0 : scenePoint = mLayout->convertToLayoutUnits( point );
144 : : else
145 : 0 : scenePoint = mLayout->pageCollection()->pagePositionToLayoutPosition( page, point );
146 : :
147 : 0 : double deltaX = scenePoint.x() - pos().x();
148 : 0 : double deltaY = scenePoint.y() - pos().y();
149 : :
150 : : //also move all items within the group
151 : 0 : for ( QgsLayoutItem *item : std::as_const( mItems ) )
152 : : {
153 : 0 : if ( !item )
154 : 0 : continue;
155 : :
156 : 0 : std::unique_ptr< QgsAbstractLayoutUndoCommand > command;
157 : 0 : if ( !shouldBlockUndoCommands() )
158 : : {
159 : 0 : command.reset( createCommand( QString(), 0 ) );
160 : 0 : command->saveBeforeState();
161 : 0 : }
162 : :
163 : 0 : item->attemptMoveBy( deltaX, deltaY );
164 : :
165 : 0 : if ( command )
166 : : {
167 : 0 : command->saveAfterState();
168 : 0 : mLayout->undoStack()->push( command.release() );
169 : 0 : }
170 : 0 : }
171 : : //lastly move group item itself
172 : 0 : QgsLayoutItem::attemptMove( point, includesFrame );
173 : 0 : if ( !shouldBlockUndoCommands() )
174 : 0 : mLayout->undoStack()->endMacro();
175 : 0 : resetBoundingRect();
176 : 0 : }
177 : :
178 : 0 : void QgsLayoutItemGroup::attemptResize( const QgsLayoutSize &size, bool includesFrame )
179 : : {
180 : 0 : if ( !mLayout )
181 : 0 : return;
182 : :
183 : 0 : if ( !shouldBlockUndoCommands() )
184 : 0 : mLayout->undoStack()->beginMacro( tr( "Resize Group" ) );
185 : :
186 : 0 : QRectF oldRect = rect();
187 : 0 : QSizeF newSizeLayoutUnits = mLayout->convertToLayoutUnits( size );
188 : 0 : QRectF newRect;
189 : 0 : newRect.setSize( newSizeLayoutUnits );
190 : :
191 : : //also resize all items within the group
192 : 0 : for ( QgsLayoutItem *item : std::as_const( mItems ) )
193 : : {
194 : 0 : if ( !item )
195 : 0 : continue;
196 : :
197 : 0 : std::unique_ptr< QgsAbstractLayoutUndoCommand > command;
198 : 0 : if ( !shouldBlockUndoCommands() )
199 : : {
200 : 0 : command.reset( createCommand( QString(), 0 ) );
201 : 0 : command->saveBeforeState();
202 : 0 : }
203 : :
204 : 0 : QRectF itemRect = mapRectFromItem( item, item->rect() );
205 : 0 : QgsLayoutUtils::relativeResizeRect( itemRect, oldRect, newRect );
206 : :
207 : 0 : itemRect = itemRect.normalized();
208 : 0 : QPointF newPos = mapToScene( itemRect.topLeft() );
209 : :
210 : 0 : QgsLayoutSize itemSize = mLayout->convertFromLayoutUnits( itemRect.size(), item->sizeWithUnits().units() );
211 : 0 : item->attemptResize( itemSize, includesFrame );
212 : :
213 : : // translate new position to current item units
214 : 0 : QgsLayoutPoint itemPos = mLayout->convertFromLayoutUnits( newPos, item->positionWithUnits().units() );
215 : 0 : item->attemptMove( itemPos, false );
216 : :
217 : 0 : if ( command )
218 : : {
219 : 0 : command->saveAfterState();
220 : 0 : mLayout->undoStack()->push( command.release() );
221 : 0 : }
222 : 0 : }
223 : 0 : QgsLayoutItem::attemptResize( size );
224 : 0 : if ( !shouldBlockUndoCommands() )
225 : 0 : mLayout->undoStack()->endMacro();
226 : :
227 : 0 : resetBoundingRect();
228 : 0 : }
229 : :
230 : 0 : bool QgsLayoutItemGroup::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
231 : : {
232 : 0 : for ( QgsLayoutItem *item : mItems )
233 : : {
234 : 0 : if ( !item )
235 : 0 : continue;
236 : :
237 : 0 : QDomElement childItem = document.createElement( QStringLiteral( "ComposerItemGroupElement" ) );
238 : 0 : childItem.setAttribute( QStringLiteral( "uuid" ), item->uuid() );
239 : 0 : element.appendChild( childItem );
240 : 0 : }
241 : 0 : return true;
242 : 0 : }
243 : :
244 : 0 : bool QgsLayoutItemGroup::readPropertiesFromElement( const QDomElement &itemElement, const QDomDocument &, const QgsReadWriteContext & )
245 : : {
246 : 0 : mItemUuids.clear();
247 : :
248 : 0 : QDomNodeList elementNodes = itemElement.elementsByTagName( QStringLiteral( "ComposerItemGroupElement" ) );
249 : 0 : for ( int i = 0; i < elementNodes.count(); ++i )
250 : : {
251 : 0 : QDomNode elementNode = elementNodes.at( i );
252 : 0 : if ( !elementNode.isElement() )
253 : 0 : continue;
254 : :
255 : 0 : QString uuid = elementNode.toElement().attribute( QStringLiteral( "uuid" ) );
256 : 0 : mItemUuids << uuid;
257 : 0 : }
258 : : return true;
259 : 0 : }
260 : :
261 : 0 : void QgsLayoutItemGroup::finalizeRestoreFromXml()
262 : : {
263 : 0 : for ( const QString &uuid : std::as_const( mItemUuids ) )
264 : : {
265 : 0 : QgsLayoutItem *item = mLayout->itemByUuid( uuid, true );
266 : 0 : if ( item )
267 : : {
268 : 0 : addItem( item );
269 : 0 : }
270 : : }
271 : :
272 : 0 : resetBoundingRect();
273 : 0 : }
274 : :
275 : 0 : QgsLayoutItem::ExportLayerBehavior QgsLayoutItemGroup::exportLayerBehavior() const
276 : : {
277 : 0 : return MustPlaceInOwnLayer;
278 : : }
279 : :
280 : 0 : void QgsLayoutItemGroup::paint( QPainter *, const QStyleOptionGraphicsItem *, QWidget * )
281 : : {
282 : 0 : }
283 : :
284 : 0 : void QgsLayoutItemGroup::draw( QgsLayoutItemRenderContext & )
285 : : {
286 : : // nothing to draw here!
287 : 0 : }
288 : :
289 : 0 : void QgsLayoutItemGroup::resetBoundingRect()
290 : : {
291 : 0 : mBoundingRectangle = QRectF();
292 : 0 : for ( QgsLayoutItem *item : std::as_const( mItems ) )
293 : : {
294 : 0 : updateBoundingRect( item );
295 : : }
296 : 0 : }
297 : :
298 : 0 : void QgsLayoutItemGroup::updateBoundingRect( QgsLayoutItem *item )
299 : : {
300 : : //update extent
301 : 0 : if ( mBoundingRectangle.isEmpty() ) //we add the first item
302 : : {
303 : 0 : mBoundingRectangle = QRectF( 0, 0, item->rect().width(), item->rect().height() );
304 : 0 : setSceneRect( QRectF( item->pos().x(), item->pos().y(), item->rect().width(), item->rect().height() ) );
305 : :
306 : 0 : if ( !qgsDoubleNear( item->rotation(), 0.0 ) )
307 : : {
308 : 0 : setItemRotation( item->rotation() );
309 : 0 : }
310 : 0 : }
311 : : else
312 : : {
313 : 0 : if ( !qgsDoubleNear( item->rotation(), rotation() ) )
314 : : {
315 : : //items have mixed rotation, so reset rotation of group
316 : 0 : mBoundingRectangle = mapRectToScene( mBoundingRectangle );
317 : 0 : setItemRotation( 0 );
318 : 0 : mBoundingRectangle = mBoundingRectangle.united( item->mapRectToScene( item->rect() ) );
319 : 0 : setSceneRect( mBoundingRectangle );
320 : 0 : }
321 : : else
322 : : {
323 : : //items have same rotation, so keep rotation of group
324 : 0 : mBoundingRectangle = mBoundingRectangle.united( mapRectFromItem( item, item->rect() ) );
325 : 0 : QPointF newPos = mapToScene( mBoundingRectangle.topLeft().x(), mBoundingRectangle.topLeft().y() );
326 : 0 : mBoundingRectangle = QRectF( 0, 0, mBoundingRectangle.width(), mBoundingRectangle.height() );
327 : 0 : setSceneRect( QRectF( newPos.x(), newPos.y(), mBoundingRectangle.width(), mBoundingRectangle.height() ) );
328 : : }
329 : : }
330 : 0 : }
331 : :
332 : 0 : void QgsLayoutItemGroup::setSceneRect( const QRectF &rectangle )
333 : : {
334 : 0 : mItemPosition = mLayout->convertFromLayoutUnits( rectangle.topLeft(), positionWithUnits().units() );
335 : 0 : mItemSize = mLayout->convertFromLayoutUnits( rectangle.size(), sizeWithUnits().units() );
336 : 0 : setScenePos( rectangle.topLeft() );
337 : 0 : setRect( 0, 0, rectangle.width(), rectangle.height() );
338 : 0 : }
|