Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgslayoutitem.cpp
3 : : -------------------
4 : : begin : June 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 "qgslayoutitem.h"
18 : : #include "qgslayout.h"
19 : : #include "qgslayoututils.h"
20 : : #include "qgspagesizeregistry.h"
21 : : #include "qgslayoutitemundocommand.h"
22 : : #include "qgslayoutmodel.h"
23 : : #include "qgssymbollayerutils.h"
24 : : #include "qgslayoutitemgroup.h"
25 : : #include "qgspainting.h"
26 : : #include "qgslayouteffect.h"
27 : : #include "qgslayoutundostack.h"
28 : : #include "qgslayoutpagecollection.h"
29 : : #include "qgslayoutitempage.h"
30 : : #include "qgsimageoperation.h"
31 : : #include "qgsexpressioncontextutils.h"
32 : :
33 : : #include <QPainter>
34 : : #include <QStyleOptionGraphicsItem>
35 : : #include <QUuid>
36 : :
37 : : #define CACHE_SIZE_LIMIT 5000
38 : :
39 : 0 : QgsLayoutItemRenderContext::QgsLayoutItemRenderContext( QgsRenderContext &context, double viewScaleFactor )
40 : 0 : : mRenderContext( context )
41 : 0 : , mViewScaleFactor( viewScaleFactor )
42 : : {
43 : 0 : }
44 : :
45 : :
46 : :
47 : 0 : QgsLayoutItem::QgsLayoutItem( QgsLayout *layout, bool manageZValue )
48 : 0 : : QgsLayoutObject( layout )
49 : 0 : , QGraphicsRectItem( nullptr )
50 : 0 : , mUuid( QUuid::createUuid().toString() )
51 : 0 : {
52 : 0 : setZValue( QgsLayout::ZItem );
53 : :
54 : : // needed to access current view transform during paint operations
55 : 0 : setFlags( flags() | QGraphicsItem::ItemUsesExtendedStyleOption | QGraphicsItem::ItemIsSelectable );
56 : :
57 : 0 : setCacheMode( QGraphicsItem::DeviceCoordinateCache );
58 : :
59 : : //record initial position
60 : 0 : QgsUnitTypes::LayoutUnit initialUnits = layout ? layout->units() : QgsUnitTypes::LayoutMillimeters;
61 : 0 : mItemPosition = QgsLayoutPoint( scenePos().x(), scenePos().y(), initialUnits );
62 : 0 : mItemSize = QgsLayoutSize( rect().width(), rect().height(), initialUnits );
63 : :
64 : : // required to initially setup background/frame style
65 : 0 : refreshBackgroundColor( false );
66 : 0 : refreshFrame( false );
67 : :
68 : 0 : initConnectionsToLayout();
69 : :
70 : : //let z-Value be managed by layout
71 : 0 : if ( mLayout && manageZValue )
72 : : {
73 : 0 : mLayoutManagesZValue = true;
74 : 0 : mLayout->itemsModel()->addItemAtTop( this );
75 : 0 : }
76 : : else
77 : : {
78 : 0 : mLayoutManagesZValue = false;
79 : : }
80 : :
81 : : // Setup layout effect
82 : 0 : mEffect.reset( new QgsLayoutEffect() );
83 : 0 : if ( mLayout )
84 : : {
85 : 0 : mEffect->setEnabled( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagUseAdvancedEffects );
86 : 0 : connect( &mLayout->renderContext(), &QgsLayoutRenderContext::flagsChanged, this, [ = ]( QgsLayoutRenderContext::Flags flags )
87 : : {
88 : 0 : mEffect->setEnabled( flags & QgsLayoutRenderContext::FlagUseAdvancedEffects );
89 : 0 : } );
90 : 0 : }
91 : 0 : setGraphicsEffect( mEffect.get() );
92 : 0 : }
93 : :
94 : 0 : QgsLayoutItem::~QgsLayoutItem()
95 : 0 : {
96 : 0 : cleanup();
97 : 0 : }
98 : :
99 : 0 : void QgsLayoutItem::cleanup()
100 : : {
101 : 0 : if ( mLayout && mLayoutManagesZValue )
102 : : {
103 : 0 : mLayout->itemsModel()->removeItem( this );
104 : 0 : }
105 : 0 : }
106 : :
107 : 0 : QString QgsLayoutItem::displayName() const
108 : : {
109 : : //return id, if it's not empty
110 : 0 : if ( !id().isEmpty() )
111 : : {
112 : 0 : return id();
113 : : }
114 : :
115 : : //for unnamed items, default to item type
116 : 0 : if ( QgsLayoutItemAbstractMetadata *metadata = QgsApplication::layoutItemRegistry()->itemMetadata( type() ) )
117 : : {
118 : 0 : return tr( "<%1>" ).arg( metadata->visibleName() );
119 : : }
120 : :
121 : 0 : return tr( "<item>" );
122 : 0 : }
123 : :
124 : 0 : int QgsLayoutItem::type() const
125 : : {
126 : 0 : return QgsLayoutItemRegistry::LayoutItem;
127 : : }
128 : :
129 : 0 : QgsLayoutItem::Flags QgsLayoutItem::itemFlags() const
130 : : {
131 : 0 : return QgsLayoutItem::Flags();
132 : : }
133 : :
134 : 0 : void QgsLayoutItem::setId( const QString &id )
135 : : {
136 : 0 : if ( id == mId )
137 : : {
138 : 0 : return;
139 : : }
140 : :
141 : 0 : if ( !shouldBlockUndoCommands() )
142 : 0 : mLayout->undoStack()->beginCommand( this, tr( "Change Item ID" ) );
143 : :
144 : 0 : mId = id;
145 : :
146 : 0 : if ( !shouldBlockUndoCommands() )
147 : 0 : mLayout->undoStack()->endCommand();
148 : :
149 : 0 : setToolTip( id );
150 : :
151 : : //inform model that id data has changed
152 : 0 : if ( mLayout )
153 : : {
154 : 0 : mLayout->itemsModel()->updateItemDisplayName( this );
155 : 0 : }
156 : :
157 : 0 : emit changed();
158 : 0 : }
159 : :
160 : 0 : void QgsLayoutItem::setSelected( bool selected )
161 : : {
162 : 0 : QGraphicsRectItem::setSelected( selected );
163 : : //inform model that id data has changed
164 : 0 : if ( mLayout )
165 : : {
166 : 0 : mLayout->itemsModel()->updateItemSelectStatus( this );
167 : 0 : }
168 : 0 : }
169 : :
170 : 0 : void QgsLayoutItem::setVisibility( const bool visible )
171 : : {
172 : 0 : if ( visible == isVisible() )
173 : : {
174 : : //nothing to do
175 : 0 : return;
176 : : }
177 : :
178 : 0 : std::unique_ptr< QgsAbstractLayoutUndoCommand > command;
179 : 0 : if ( !shouldBlockUndoCommands() )
180 : : {
181 : 0 : command.reset( createCommand( visible ? tr( "Show Item" ) : tr( "Hide Item" ), 0 ) );
182 : 0 : command->saveBeforeState();
183 : 0 : }
184 : :
185 : 0 : QGraphicsItem::setVisible( visible );
186 : :
187 : 0 : if ( command )
188 : : {
189 : 0 : command->saveAfterState();
190 : 0 : mLayout->undoStack()->push( command.release() );
191 : 0 : }
192 : :
193 : : //inform model that visibility has changed
194 : 0 : if ( mLayout )
195 : : {
196 : 0 : mLayout->itemsModel()->updateItemVisibility( this );
197 : 0 : }
198 : 0 : }
199 : :
200 : 0 : void QgsLayoutItem::setLocked( const bool locked )
201 : : {
202 : 0 : if ( locked == mIsLocked )
203 : : {
204 : 0 : return;
205 : : }
206 : :
207 : 0 : if ( !shouldBlockUndoCommands() )
208 : 0 : mLayout->undoStack()->beginCommand( this, locked ? tr( "Lock Item" ) : tr( "Unlock Item" ) );
209 : :
210 : 0 : mIsLocked = locked;
211 : :
212 : 0 : if ( !shouldBlockUndoCommands() )
213 : 0 : mLayout->undoStack()->endCommand();
214 : :
215 : : //inform model that id data has changed
216 : 0 : if ( mLayout )
217 : : {
218 : 0 : mLayout->itemsModel()->updateItemLockStatus( this );
219 : 0 : }
220 : :
221 : 0 : update();
222 : 0 : emit lockChanged();
223 : 0 : }
224 : :
225 : 0 : bool QgsLayoutItem::isGroupMember() const
226 : : {
227 : 0 : return !mParentGroupUuid.isEmpty() && mLayout && static_cast< bool >( mLayout->itemByUuid( mParentGroupUuid ) );
228 : : }
229 : :
230 : 0 : QgsLayoutItemGroup *QgsLayoutItem::parentGroup() const
231 : : {
232 : 0 : if ( !mLayout || mParentGroupUuid.isEmpty() )
233 : 0 : return nullptr;
234 : :
235 : 0 : return qobject_cast< QgsLayoutItemGroup * >( mLayout->itemByUuid( mParentGroupUuid ) );
236 : 0 : }
237 : :
238 : 0 : void QgsLayoutItem::setParentGroup( QgsLayoutItemGroup *group )
239 : : {
240 : 0 : if ( !group )
241 : 0 : mParentGroupUuid.clear();
242 : : else
243 : 0 : mParentGroupUuid = group->uuid();
244 : 0 : setFlag( QGraphicsItem::ItemIsSelectable, !static_cast< bool>( group ) ); //item in groups cannot be selected
245 : 0 : }
246 : :
247 : 0 : QgsLayoutItem::ExportLayerBehavior QgsLayoutItem::exportLayerBehavior() const
248 : : {
249 : 0 : return CanGroupWithAnyOtherItem;
250 : : }
251 : :
252 : 0 : int QgsLayoutItem::numberExportLayers() const
253 : : {
254 : 0 : return 0;
255 : : }
256 : :
257 : 0 : void QgsLayoutItem::startLayeredExport()
258 : : {
259 : :
260 : 0 : }
261 : :
262 : 0 : void QgsLayoutItem::stopLayeredExport()
263 : : {
264 : :
265 : 0 : }
266 : :
267 : 0 : bool QgsLayoutItem::nextExportPart()
268 : : {
269 : : Q_NOWARN_DEPRECATED_PUSH
270 : 0 : if ( !mLayout || mLayout->renderContext().currentExportLayer() == -1 )
271 : 0 : return false;
272 : :
273 : : // QGIS 4- return false from base class implementation
274 : :
275 : 0 : const int layers = numberExportLayers();
276 : 0 : return mLayout->renderContext().currentExportLayer() < layers;
277 : : Q_NOWARN_DEPRECATED_POP
278 : 0 : }
279 : :
280 : 0 : QgsLayoutItem::ExportLayerDetail QgsLayoutItem::exportLayerDetails() const
281 : : {
282 : 0 : return QgsLayoutItem::ExportLayerDetail();
283 : : }
284 : :
285 : 0 : void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget * )
286 : : {
287 : 0 : if ( !painter || !painter->device() || !shouldDrawItem() )
288 : : {
289 : 0 : return;
290 : : }
291 : :
292 : : //TODO - remember to disable saving/restoring on graphics view!!
293 : :
294 : 0 : if ( shouldDrawDebugRect() )
295 : : {
296 : 0 : drawDebugRect( painter );
297 : 0 : return;
298 : : }
299 : :
300 : 0 : bool previewRender = !mLayout || mLayout->renderContext().isPreviewRender();
301 : 0 : double destinationDpi = previewRender ? QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter ) * 25.4 : mLayout->renderContext().dpi();
302 : 0 : bool useImageCache = false;
303 : 0 : bool forceRasterOutput = containsAdvancedEffects() && ( !mLayout || !( mLayout->renderContext().flags() & QgsLayoutRenderContext::FlagForceVectorOutput ) );
304 : :
305 : 0 : if ( useImageCache || forceRasterOutput )
306 : : {
307 : 0 : double widthInPixels = 0;
308 : 0 : double heightInPixels = 0;
309 : :
310 : 0 : if ( previewRender )
311 : : {
312 : 0 : widthInPixels = boundingRect().width() * QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
313 : 0 : heightInPixels = boundingRect().height() * QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
314 : 0 : }
315 : : else
316 : : {
317 : 0 : double layoutUnitsToPixels = mLayout ? mLayout->convertFromLayoutUnits( 1, QgsUnitTypes::LayoutPixels ).length() : destinationDpi / 25.4;
318 : 0 : widthInPixels = boundingRect().width() * layoutUnitsToPixels;
319 : 0 : heightInPixels = boundingRect().height() * layoutUnitsToPixels;
320 : : }
321 : :
322 : : // limit size of image for better performance
323 : 0 : if ( previewRender && ( widthInPixels > CACHE_SIZE_LIMIT || heightInPixels > CACHE_SIZE_LIMIT ) )
324 : : {
325 : 0 : double scale = 1.0;
326 : 0 : if ( widthInPixels > heightInPixels )
327 : : {
328 : 0 : scale = widthInPixels / CACHE_SIZE_LIMIT;
329 : 0 : widthInPixels = CACHE_SIZE_LIMIT;
330 : 0 : heightInPixels /= scale;
331 : 0 : }
332 : : else
333 : : {
334 : 0 : scale = heightInPixels / CACHE_SIZE_LIMIT;
335 : 0 : heightInPixels = CACHE_SIZE_LIMIT;
336 : 0 : widthInPixels /= scale;
337 : : }
338 : 0 : destinationDpi = destinationDpi / scale;
339 : 0 : }
340 : :
341 : 0 : if ( previewRender && !mItemCachedImage.isNull() && qgsDoubleNear( mItemCacheDpi, destinationDpi ) )
342 : : {
343 : : // can reuse last cached image
344 : 0 : QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( mLayout, painter, destinationDpi );
345 : 0 : QgsScopedQPainterState painterState( painter );
346 : 0 : preparePainter( painter );
347 : 0 : double cacheScale = destinationDpi / mItemCacheDpi;
348 : 0 : painter->scale( cacheScale / context.scaleFactor(), cacheScale / context.scaleFactor() );
349 : 0 : painter->drawImage( boundingRect().x() * context.scaleFactor() / cacheScale,
350 : 0 : boundingRect().y() * context.scaleFactor() / cacheScale, mItemCachedImage );
351 : : return;
352 : 0 : }
353 : : else
354 : : {
355 : 0 : QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
356 : 0 : image.fill( Qt::transparent );
357 : 0 : image.setDotsPerMeterX( 1000 * destinationDpi * 25.4 );
358 : 0 : image.setDotsPerMeterY( 1000 * destinationDpi * 25.4 );
359 : 0 : QPainter p( &image );
360 : :
361 : 0 : preparePainter( &p );
362 : 0 : QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( mLayout, &p, destinationDpi );
363 : 0 : context.setExpressionContext( createExpressionContext() );
364 : : // painter is already scaled to dots
365 : : // need to translate so that item origin is at 0,0 in painter coordinates (not bounding rect origin)
366 : 0 : p.translate( -boundingRect().x() * context.scaleFactor(), -boundingRect().y() * context.scaleFactor() );
367 : : // scale to layout units for background and frame rendering
368 : 0 : p.scale( context.scaleFactor(), context.scaleFactor() );
369 : 0 : drawBackground( context );
370 : 0 : p.scale( 1 / context.scaleFactor(), 1 / context.scaleFactor() );
371 : 0 : double viewScale = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
372 : 0 : QgsLayoutItemRenderContext itemRenderContext( context, viewScale );
373 : 0 : draw( itemRenderContext );
374 : 0 : p.scale( context.scaleFactor(), context.scaleFactor() );
375 : 0 : drawFrame( context );
376 : 0 : p.scale( 1 / context.scaleFactor(), 1 / context.scaleFactor() );
377 : 0 : p.end();
378 : :
379 : 0 : QgsImageOperation::multiplyOpacity( image, mEvaluatedOpacity );
380 : :
381 : 0 : QgsScopedQPainterState painterState( painter );
382 : : // scale painter from mm to dots
383 : 0 : painter->scale( 1.0 / context.scaleFactor(), 1.0 / context.scaleFactor() );
384 : 0 : painter->drawImage( boundingRect().x() * context.scaleFactor(),
385 : 0 : boundingRect().y() * context.scaleFactor(), image );
386 : :
387 : 0 : if ( previewRender )
388 : : {
389 : 0 : mItemCacheDpi = destinationDpi;
390 : 0 : mItemCachedImage = image;
391 : 0 : }
392 : 0 : }
393 : 0 : }
394 : : else
395 : : {
396 : : // no caching or flattening
397 : 0 : QgsScopedQPainterState painterState( painter );
398 : 0 : preparePainter( painter );
399 : 0 : QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( mLayout, painter, destinationDpi );
400 : 0 : context.setExpressionContext( createExpressionContext() );
401 : 0 : drawBackground( context );
402 : :
403 : : // scale painter from mm to dots
404 : 0 : painter->scale( 1.0 / context.scaleFactor(), 1.0 / context.scaleFactor() );
405 : 0 : double viewScale = QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
406 : 0 : QgsLayoutItemRenderContext itemRenderContext( context, viewScale );
407 : 0 : draw( itemRenderContext );
408 : :
409 : 0 : painter->scale( context.scaleFactor(), context.scaleFactor() );
410 : 0 : drawFrame( context );
411 : 0 : }
412 : 0 : }
413 : :
414 : 0 : void QgsLayoutItem::setReferencePoint( const QgsLayoutItem::ReferencePoint point )
415 : : {
416 : 0 : if ( point == mReferencePoint )
417 : : {
418 : 0 : return;
419 : : }
420 : :
421 : 0 : mReferencePoint = point;
422 : :
423 : : //also need to adjust stored position
424 : 0 : updateStoredItemPosition();
425 : 0 : refreshItemPosition();
426 : 0 : }
427 : :
428 : 0 : void QgsLayoutItem::attemptResize( const QgsLayoutSize &s, bool includesFrame )
429 : : {
430 : 0 : if ( !mLayout )
431 : : {
432 : 0 : mItemSize = s;
433 : 0 : setRect( 0, 0, s.width(), s.height() );
434 : 0 : return;
435 : : }
436 : :
437 : 0 : QgsLayoutSize size = s;
438 : :
439 : 0 : if ( includesFrame )
440 : : {
441 : : //adjust position to account for frame size
442 : 0 : double bleed = mLayout->convertFromLayoutUnits( estimatedFrameBleed(), size.units() ).length();
443 : 0 : size.setWidth( size.width() - 2 * bleed );
444 : 0 : size.setHeight( size.height() - 2 * bleed );
445 : 0 : }
446 : :
447 : 0 : QgsLayoutSize evaluatedSize = applyDataDefinedSize( size );
448 : 0 : QSizeF targetSizeLayoutUnits = mLayout->convertToLayoutUnits( evaluatedSize );
449 : 0 : QSizeF actualSizeLayoutUnits = applyMinimumSize( targetSizeLayoutUnits );
450 : 0 : actualSizeLayoutUnits = applyFixedSize( actualSizeLayoutUnits );
451 : 0 : actualSizeLayoutUnits = applyItemSizeConstraint( actualSizeLayoutUnits );
452 : :
453 : 0 : if ( actualSizeLayoutUnits == rect().size() )
454 : : {
455 : 0 : return;
456 : : }
457 : :
458 : 0 : QgsLayoutSize actualSizeTargetUnits = mLayout->convertFromLayoutUnits( actualSizeLayoutUnits, size.units() );
459 : 0 : mItemSize = actualSizeTargetUnits;
460 : :
461 : 0 : setRect( 0, 0, actualSizeLayoutUnits.width(), actualSizeLayoutUnits.height() );
462 : 0 : refreshItemPosition();
463 : 0 : emit sizePositionChanged();
464 : 0 : }
465 : :
466 : 0 : void QgsLayoutItem::attemptMove( const QgsLayoutPoint &p, bool useReferencePoint, bool includesFrame, int page )
467 : : {
468 : 0 : if ( !mLayout )
469 : : {
470 : 0 : mItemPosition = p;
471 : 0 : setPos( p.toQPointF() );
472 : 0 : return;
473 : : }
474 : :
475 : 0 : QgsLayoutPoint point = p;
476 : 0 : if ( page >= 0 )
477 : : {
478 : 0 : point = mLayout->pageCollection()->pagePositionToAbsolute( page, p );
479 : 0 : }
480 : :
481 : 0 : if ( includesFrame )
482 : : {
483 : : //adjust position to account for frame size
484 : 0 : double bleed = mLayout->convertFromLayoutUnits( estimatedFrameBleed(), point.units() ).length();
485 : 0 : point.setX( point.x() + bleed );
486 : 0 : point.setY( point.y() + bleed );
487 : 0 : }
488 : :
489 : 0 : QgsLayoutPoint evaluatedPoint = point;
490 : 0 : if ( !useReferencePoint )
491 : : {
492 : 0 : evaluatedPoint = topLeftToReferencePoint( point );
493 : 0 : }
494 : :
495 : 0 : evaluatedPoint = applyDataDefinedPosition( evaluatedPoint );
496 : 0 : QPointF evaluatedPointLayoutUnits = mLayout->convertToLayoutUnits( evaluatedPoint );
497 : 0 : QPointF topLeftPointLayoutUnits = adjustPointForReferencePosition( evaluatedPointLayoutUnits, rect().size(), mReferencePoint );
498 : 0 : if ( topLeftPointLayoutUnits == scenePos() && point.units() == mItemPosition.units() )
499 : : {
500 : : //TODO - add test for second condition
501 : 0 : return;
502 : : }
503 : :
504 : 0 : QgsLayoutPoint referencePointTargetUnits = mLayout->convertFromLayoutUnits( evaluatedPointLayoutUnits, point.units() );
505 : 0 : mItemPosition = referencePointTargetUnits;
506 : 0 : setScenePos( topLeftPointLayoutUnits );
507 : 0 : emit sizePositionChanged();
508 : 0 : }
509 : :
510 : 0 : void QgsLayoutItem::attemptSetSceneRect( const QRectF &rect, bool includesFrame )
511 : : {
512 : 0 : QPointF newPos = rect.topLeft();
513 : :
514 : 0 : blockSignals( true );
515 : : // translate new size to current item units
516 : 0 : QgsLayoutSize newSize = mLayout->convertFromLayoutUnits( rect.size(), mItemSize.units() );
517 : 0 : attemptResize( newSize, includesFrame );
518 : :
519 : : // translate new position to current item units
520 : 0 : QgsLayoutPoint itemPos = mLayout->convertFromLayoutUnits( newPos, mItemPosition.units() );
521 : 0 : attemptMove( itemPos, false, includesFrame );
522 : 0 : blockSignals( false );
523 : 0 : emit sizePositionChanged();
524 : 0 : }
525 : :
526 : 0 : void QgsLayoutItem::attemptMoveBy( double deltaX, double deltaY )
527 : : {
528 : 0 : if ( !mLayout )
529 : : {
530 : 0 : moveBy( deltaX, deltaY );
531 : 0 : return;
532 : : }
533 : :
534 : 0 : QgsLayoutPoint itemPos = positionWithUnits();
535 : 0 : QgsLayoutPoint deltaPos = mLayout->convertFromLayoutUnits( QPointF( deltaX, deltaY ), itemPos.units() );
536 : 0 : itemPos.setX( itemPos.x() + deltaPos.x() );
537 : 0 : itemPos.setY( itemPos.y() + deltaPos.y() );
538 : 0 : attemptMove( itemPos );
539 : 0 : }
540 : :
541 : 0 : int QgsLayoutItem::page() const
542 : : {
543 : 0 : if ( !mLayout )
544 : 0 : return -1;
545 : :
546 : 0 : return mLayout->pageCollection()->pageNumberForPoint( pos() );
547 : 0 : }
548 : :
549 : 0 : QPointF QgsLayoutItem::pagePos() const
550 : : {
551 : 0 : QPointF p = positionAtReferencePoint( mReferencePoint );
552 : :
553 : 0 : if ( !mLayout )
554 : 0 : return p;
555 : :
556 : : // try to get page
557 : 0 : QgsLayoutItemPage *pageItem = mLayout->pageCollection()->page( page() );
558 : 0 : if ( !pageItem )
559 : 0 : return p;
560 : :
561 : 0 : p.ry() -= pageItem->pos().y();
562 : 0 : return p;
563 : 0 : }
564 : :
565 : 0 : QgsLayoutPoint QgsLayoutItem::pagePositionWithUnits() const
566 : : {
567 : 0 : QPointF p = pagePos();
568 : 0 : if ( !mLayout )
569 : 0 : return QgsLayoutPoint( p );
570 : :
571 : 0 : return mLayout->convertFromLayoutUnits( p, mItemPosition.units() );
572 : 0 : }
573 : :
574 : 0 : void QgsLayoutItem::setScenePos( const QPointF destinationPos )
575 : : {
576 : : //since setPos does not account for item rotation, use difference between
577 : : //current scenePos (which DOES account for rotation) and destination pos
578 : : //to calculate how much the item needs to move
579 : 0 : if ( auto *lParentItem = parentItem() )
580 : 0 : setPos( pos() + ( destinationPos - scenePos() ) + lParentItem->scenePos() );
581 : : else
582 : 0 : setPos( pos() + ( destinationPos - scenePos() ) );
583 : 0 : }
584 : :
585 : 0 : bool QgsLayoutItem::shouldBlockUndoCommands() const
586 : : {
587 : 0 : return !mLayout || mLayout != scene() || mBlockUndoCommands;
588 : : }
589 : :
590 : 0 : bool QgsLayoutItem::shouldDrawItem() const
591 : : {
592 : 0 : if ( mLayout && QgsLayoutUtils::itemIsAClippingSource( this ) )
593 : 0 : return false;
594 : :
595 : 0 : if ( !mLayout || mLayout->renderContext().isPreviewRender() )
596 : : {
597 : : //preview mode so OK to draw item
598 : 0 : return true;
599 : : }
600 : :
601 : : //exporting layout, so check if item is excluded from exports
602 : 0 : return !mEvaluatedExcludeFromExports;
603 : 0 : }
604 : :
605 : 0 : double QgsLayoutItem::itemRotation() const
606 : : {
607 : 0 : return mItemRotation;
608 : : }
609 : :
610 : 0 : bool QgsLayoutItem::writeXml( QDomElement &parentElement, QDomDocument &doc, const QgsReadWriteContext &context ) const
611 : : {
612 : 0 : QDomElement element = doc.createElement( QStringLiteral( "LayoutItem" ) );
613 : 0 : element.setAttribute( QStringLiteral( "type" ), QString::number( type() ) );
614 : :
615 : 0 : element.setAttribute( QStringLiteral( "uuid" ), mUuid );
616 : 0 : element.setAttribute( QStringLiteral( "templateUuid" ), mUuid );
617 : 0 : element.setAttribute( QStringLiteral( "id" ), mId );
618 : 0 : element.setAttribute( QStringLiteral( "referencePoint" ), QString::number( static_cast< int >( mReferencePoint ) ) );
619 : 0 : element.setAttribute( QStringLiteral( "position" ), mItemPosition.encodePoint() );
620 : 0 : element.setAttribute( QStringLiteral( "positionOnPage" ), pagePositionWithUnits().encodePoint() );
621 : 0 : element.setAttribute( QStringLiteral( "size" ), mItemSize.encodeSize() );
622 : 0 : element.setAttribute( QStringLiteral( "itemRotation" ), QString::number( mItemRotation ) );
623 : 0 : element.setAttribute( QStringLiteral( "groupUuid" ), mParentGroupUuid );
624 : :
625 : 0 : element.setAttribute( QStringLiteral( "zValue" ), QString::number( zValue() ) );
626 : 0 : element.setAttribute( QStringLiteral( "visibility" ), isVisible() );
627 : : //position lock for mouse moves/resizes
628 : 0 : if ( mIsLocked )
629 : : {
630 : 0 : element.setAttribute( QStringLiteral( "positionLock" ), QStringLiteral( "true" ) );
631 : 0 : }
632 : : else
633 : : {
634 : 0 : element.setAttribute( QStringLiteral( "positionLock" ), QStringLiteral( "false" ) );
635 : : }
636 : :
637 : : //frame
638 : 0 : if ( mFrame )
639 : : {
640 : 0 : element.setAttribute( QStringLiteral( "frame" ), QStringLiteral( "true" ) );
641 : 0 : }
642 : : else
643 : : {
644 : 0 : element.setAttribute( QStringLiteral( "frame" ), QStringLiteral( "false" ) );
645 : : }
646 : :
647 : : //background
648 : 0 : if ( mBackground )
649 : : {
650 : 0 : element.setAttribute( QStringLiteral( "background" ), QStringLiteral( "true" ) );
651 : 0 : }
652 : : else
653 : : {
654 : 0 : element.setAttribute( QStringLiteral( "background" ), QStringLiteral( "false" ) );
655 : : }
656 : :
657 : : //frame color
658 : 0 : QDomElement frameColorElem = doc.createElement( QStringLiteral( "FrameColor" ) );
659 : 0 : frameColorElem.setAttribute( QStringLiteral( "red" ), QString::number( mFrameColor.red() ) );
660 : 0 : frameColorElem.setAttribute( QStringLiteral( "green" ), QString::number( mFrameColor.green() ) );
661 : 0 : frameColorElem.setAttribute( QStringLiteral( "blue" ), QString::number( mFrameColor.blue() ) );
662 : 0 : frameColorElem.setAttribute( QStringLiteral( "alpha" ), QString::number( mFrameColor.alpha() ) );
663 : 0 : element.appendChild( frameColorElem );
664 : 0 : element.setAttribute( QStringLiteral( "outlineWidthM" ), mFrameWidth.encodeMeasurement() );
665 : 0 : element.setAttribute( QStringLiteral( "frameJoinStyle" ), QgsSymbolLayerUtils::encodePenJoinStyle( mFrameJoinStyle ) );
666 : :
667 : : //background color
668 : 0 : QDomElement bgColorElem = doc.createElement( QStringLiteral( "BackgroundColor" ) );
669 : 0 : bgColorElem.setAttribute( QStringLiteral( "red" ), QString::number( mBackgroundColor.red() ) );
670 : 0 : bgColorElem.setAttribute( QStringLiteral( "green" ), QString::number( mBackgroundColor.green() ) );
671 : 0 : bgColorElem.setAttribute( QStringLiteral( "blue" ), QString::number( mBackgroundColor.blue() ) );
672 : 0 : bgColorElem.setAttribute( QStringLiteral( "alpha" ), QString::number( mBackgroundColor.alpha() ) );
673 : 0 : element.appendChild( bgColorElem );
674 : :
675 : : //blend mode
676 : 0 : element.setAttribute( QStringLiteral( "blendMode" ), QgsPainting::getBlendModeEnum( mBlendMode ) );
677 : :
678 : : //opacity
679 : 0 : element.setAttribute( QStringLiteral( "opacity" ), QString::number( mOpacity ) );
680 : :
681 : 0 : element.setAttribute( QStringLiteral( "excludeFromExports" ), mExcludeFromExports );
682 : :
683 : 0 : writeObjectPropertiesToElement( element, doc, context );
684 : :
685 : 0 : writePropertiesToElement( element, doc, context );
686 : 0 : parentElement.appendChild( element );
687 : :
688 : : return true;
689 : 0 : }
690 : :
691 : 0 : bool QgsLayoutItem::readXml( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context )
692 : : {
693 : 0 : if ( element.nodeName() != QLatin1String( "LayoutItem" ) )
694 : : {
695 : 0 : return false;
696 : : }
697 : :
698 : 0 : readObjectPropertiesFromElement( element, doc, context );
699 : :
700 : 0 : mBlockUndoCommands = true;
701 : 0 : mUuid = element.attribute( QStringLiteral( "uuid" ), QUuid::createUuid().toString() );
702 : 0 : setId( element.attribute( QStringLiteral( "id" ) ) );
703 : 0 : mReferencePoint = static_cast< ReferencePoint >( element.attribute( QStringLiteral( "referencePoint" ) ).toInt() );
704 : 0 : setItemRotation( element.attribute( QStringLiteral( "itemRotation" ), QStringLiteral( "0" ) ).toDouble() );
705 : 0 : attemptMove( QgsLayoutPoint::decodePoint( element.attribute( QStringLiteral( "position" ) ) ) );
706 : 0 : attemptResize( QgsLayoutSize::decodeSize( element.attribute( QStringLiteral( "size" ) ) ) );
707 : :
708 : 0 : mParentGroupUuid = element.attribute( QStringLiteral( "groupUuid" ) );
709 : 0 : if ( !mParentGroupUuid.isEmpty() )
710 : : {
711 : 0 : if ( QgsLayoutItemGroup *group = parentGroup() )
712 : : {
713 : 0 : group->addItem( this );
714 : 0 : }
715 : 0 : }
716 : 0 : mTemplateUuid = element.attribute( QStringLiteral( "templateUuid" ) );
717 : :
718 : : //position lock for mouse moves/resizes
719 : 0 : QString positionLock = element.attribute( QStringLiteral( "positionLock" ) );
720 : 0 : if ( positionLock.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
721 : : {
722 : 0 : setLocked( true );
723 : 0 : }
724 : : else
725 : : {
726 : 0 : setLocked( false );
727 : : }
728 : : //visibility
729 : 0 : setVisibility( element.attribute( QStringLiteral( "visibility" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) );
730 : 0 : setZValue( element.attribute( QStringLiteral( "zValue" ) ).toDouble() );
731 : :
732 : : //frame
733 : 0 : QString frame = element.attribute( QStringLiteral( "frame" ) );
734 : 0 : if ( frame.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
735 : : {
736 : 0 : mFrame = true;
737 : 0 : }
738 : : else
739 : : {
740 : 0 : mFrame = false;
741 : : }
742 : :
743 : : //frame
744 : 0 : QString background = element.attribute( QStringLiteral( "background" ) );
745 : 0 : if ( background.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
746 : : {
747 : 0 : mBackground = true;
748 : 0 : }
749 : : else
750 : : {
751 : 0 : mBackground = false;
752 : : }
753 : :
754 : : //pen
755 : 0 : mFrameWidth = QgsLayoutMeasurement::decodeMeasurement( element.attribute( QStringLiteral( "outlineWidthM" ) ) );
756 : 0 : mFrameJoinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( element.attribute( QStringLiteral( "frameJoinStyle" ), QStringLiteral( "miter" ) ) );
757 : 0 : QDomNodeList frameColorList = element.elementsByTagName( QStringLiteral( "FrameColor" ) );
758 : 0 : if ( !frameColorList.isEmpty() )
759 : : {
760 : 0 : QDomElement frameColorElem = frameColorList.at( 0 ).toElement();
761 : 0 : bool redOk = false;
762 : 0 : bool greenOk = false;
763 : 0 : bool blueOk = false;
764 : 0 : bool alphaOk = false;
765 : : int penRed, penGreen, penBlue, penAlpha;
766 : :
767 : 0 : penRed = frameColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
768 : 0 : penGreen = frameColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
769 : 0 : penBlue = frameColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
770 : 0 : penAlpha = frameColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
771 : :
772 : 0 : if ( redOk && greenOk && blueOk && alphaOk )
773 : : {
774 : 0 : mFrameColor = QColor( penRed, penGreen, penBlue, penAlpha );
775 : 0 : }
776 : 0 : }
777 : 0 : refreshFrame( false );
778 : :
779 : : //brush
780 : 0 : QDomNodeList bgColorList = element.elementsByTagName( QStringLiteral( "BackgroundColor" ) );
781 : 0 : if ( !bgColorList.isEmpty() )
782 : : {
783 : 0 : QDomElement bgColorElem = bgColorList.at( 0 ).toElement();
784 : : bool redOk, greenOk, blueOk, alphaOk;
785 : : int bgRed, bgGreen, bgBlue, bgAlpha;
786 : 0 : bgRed = bgColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
787 : 0 : bgGreen = bgColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
788 : 0 : bgBlue = bgColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
789 : 0 : bgAlpha = bgColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
790 : 0 : if ( redOk && greenOk && blueOk && alphaOk )
791 : : {
792 : 0 : mBackgroundColor = QColor( bgRed, bgGreen, bgBlue, bgAlpha );
793 : 0 : setBrush( QBrush( mBackgroundColor, Qt::SolidPattern ) );
794 : 0 : }
795 : : //apply any data defined settings
796 : 0 : refreshBackgroundColor( false );
797 : 0 : }
798 : :
799 : : //blend mode
800 : 0 : setBlendMode( QgsPainting::getCompositionMode( static_cast< QgsPainting::BlendMode >( element.attribute( QStringLiteral( "blendMode" ), QStringLiteral( "0" ) ).toUInt() ) ) );
801 : :
802 : : //opacity
803 : 0 : if ( element.hasAttribute( QStringLiteral( "opacity" ) ) )
804 : : {
805 : 0 : setItemOpacity( element.attribute( QStringLiteral( "opacity" ), QStringLiteral( "1" ) ).toDouble() );
806 : 0 : }
807 : : else
808 : : {
809 : 0 : setItemOpacity( 1.0 - element.attribute( QStringLiteral( "transparency" ), QStringLiteral( "0" ) ).toInt() / 100.0 );
810 : : }
811 : :
812 : 0 : mExcludeFromExports = element.attribute( QStringLiteral( "excludeFromExports" ), QStringLiteral( "0" ) ).toInt();
813 : 0 : mEvaluatedExcludeFromExports = mExcludeFromExports;
814 : :
815 : 0 : bool result = readPropertiesFromElement( element, doc, context );
816 : :
817 : 0 : mBlockUndoCommands = false;
818 : :
819 : 0 : emit changed();
820 : 0 : update();
821 : 0 : return result;
822 : 0 : }
823 : :
824 : 0 : void QgsLayoutItem::finalizeRestoreFromXml()
825 : : {
826 : 0 : }
827 : :
828 : 0 : QgsAbstractLayoutUndoCommand *QgsLayoutItem::createCommand( const QString &text, int id, QUndoCommand *parent )
829 : : {
830 : 0 : return new QgsLayoutItemUndoCommand( this, text, id, parent );
831 : 0 : }
832 : :
833 : 0 : void QgsLayoutItem::setFrameEnabled( bool drawFrame )
834 : : {
835 : 0 : if ( drawFrame == mFrame )
836 : : {
837 : : //no change
838 : 0 : return;
839 : : }
840 : :
841 : 0 : mFrame = drawFrame;
842 : 0 : refreshFrame( true );
843 : 0 : emit frameChanged();
844 : 0 : }
845 : :
846 : 0 : void QgsLayoutItem::setFrameStrokeColor( const QColor &color )
847 : : {
848 : 0 : if ( mFrameColor == color )
849 : : {
850 : : //no change
851 : 0 : return;
852 : : }
853 : 0 : mFrameColor = color;
854 : : // apply any datadefined overrides
855 : 0 : refreshFrame( true );
856 : 0 : emit frameChanged();
857 : 0 : }
858 : :
859 : 0 : void QgsLayoutItem::setFrameStrokeWidth( const QgsLayoutMeasurement width )
860 : : {
861 : 0 : if ( mFrameWidth == width )
862 : : {
863 : : //no change
864 : 0 : return;
865 : : }
866 : 0 : mFrameWidth = width;
867 : 0 : refreshFrame();
868 : 0 : emit frameChanged();
869 : 0 : }
870 : :
871 : 0 : void QgsLayoutItem::setFrameJoinStyle( const Qt::PenJoinStyle style )
872 : : {
873 : 0 : if ( mFrameJoinStyle == style )
874 : : {
875 : : //no change
876 : 0 : return;
877 : : }
878 : 0 : mFrameJoinStyle = style;
879 : :
880 : 0 : QPen itemPen = pen();
881 : 0 : itemPen.setJoinStyle( mFrameJoinStyle );
882 : 0 : setPen( itemPen );
883 : 0 : emit frameChanged();
884 : 0 : }
885 : :
886 : 0 : void QgsLayoutItem::setBackgroundEnabled( bool drawBackground )
887 : : {
888 : 0 : mBackground = drawBackground;
889 : 0 : update();
890 : 0 : }
891 : :
892 : 0 : void QgsLayoutItem::setBackgroundColor( const QColor &color )
893 : : {
894 : 0 : mBackgroundColor = color;
895 : : // apply any datadefined overrides
896 : 0 : refreshBackgroundColor( true );
897 : 0 : }
898 : :
899 : 0 : void QgsLayoutItem::setBlendMode( const QPainter::CompositionMode mode )
900 : : {
901 : 0 : mBlendMode = mode;
902 : : // Update the item effect to use the new blend mode
903 : 0 : refreshBlendMode();
904 : 0 : }
905 : :
906 : 0 : void QgsLayoutItem::setItemOpacity( double opacity )
907 : : {
908 : 0 : mOpacity = opacity;
909 : 0 : refreshOpacity( mItemCachedImage.isNull() );
910 : 0 : if ( !mItemCachedImage.isNull() )
911 : 0 : invalidateCache();
912 : 0 : }
913 : :
914 : 0 : bool QgsLayoutItem::excludeFromExports() const
915 : : {
916 : 0 : return mExcludeFromExports;
917 : : }
918 : :
919 : 0 : void QgsLayoutItem::setExcludeFromExports( bool exclude )
920 : : {
921 : 0 : mExcludeFromExports = exclude;
922 : 0 : refreshDataDefinedProperty( QgsLayoutObject::ExcludeFromExports );
923 : 0 : }
924 : :
925 : 0 : bool QgsLayoutItem::containsAdvancedEffects() const
926 : : {
927 : 0 : return itemFlags() & Flag::FlagOverridesPaint ? false : mEvaluatedOpacity < 1.0;
928 : : }
929 : :
930 : 0 : bool QgsLayoutItem::requiresRasterization() const
931 : : {
932 : 0 : return ( itemFlags() & Flag::FlagOverridesPaint && itemOpacity() < 1.0 ) ||
933 : 0 : blendMode() != QPainter::CompositionMode_SourceOver;
934 : : }
935 : :
936 : 0 : double QgsLayoutItem::estimatedFrameBleed() const
937 : : {
938 : 0 : if ( !frameEnabled() )
939 : : {
940 : 0 : return 0;
941 : : }
942 : :
943 : 0 : return pen().widthF() / 2.0;
944 : 0 : }
945 : :
946 : 0 : QRectF QgsLayoutItem::rectWithFrame() const
947 : : {
948 : 0 : double frameBleed = estimatedFrameBleed();
949 : 0 : return rect().adjusted( -frameBleed, -frameBleed, frameBleed, frameBleed );
950 : : }
951 : :
952 : 0 : void QgsLayoutItem::moveContent( double, double )
953 : : {
954 : :
955 : 0 : }
956 : :
957 : 0 : void QgsLayoutItem::setMoveContentPreviewOffset( double, double )
958 : : {
959 : :
960 : 0 : }
961 : :
962 : 0 : void QgsLayoutItem::zoomContent( double, QPointF )
963 : : {
964 : :
965 : 0 : }
966 : :
967 : 0 : void QgsLayoutItem::beginCommand( const QString &commandText, UndoCommand command )
968 : : {
969 : 0 : if ( !mLayout )
970 : 0 : return;
971 : :
972 : 0 : mLayout->undoStack()->beginCommand( this, commandText, command );
973 : 0 : }
974 : :
975 : 0 : void QgsLayoutItem::endCommand()
976 : : {
977 : 0 : if ( mLayout )
978 : 0 : mLayout->undoStack()->endCommand();
979 : 0 : }
980 : :
981 : 0 : void QgsLayoutItem::cancelCommand()
982 : : {
983 : 0 : if ( mLayout )
984 : 0 : mLayout->undoStack()->cancelCommand();
985 : 0 : }
986 : :
987 : 0 : QgsLayoutPoint QgsLayoutItem::applyDataDefinedPosition( const QgsLayoutPoint &position )
988 : : {
989 : 0 : if ( !mLayout )
990 : : {
991 : 0 : return position;
992 : : }
993 : :
994 : 0 : QgsExpressionContext context = createExpressionContext();
995 : 0 : double evaluatedX = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PositionX, context, position.x() );
996 : 0 : double evaluatedY = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PositionY, context, position.y() );
997 : 0 : return QgsLayoutPoint( evaluatedX, evaluatedY, position.units() );
998 : 0 : }
999 : :
1000 : 0 : void QgsLayoutItem::applyDataDefinedOrientation( double &width, double &height, const QgsExpressionContext &context )
1001 : : {
1002 : 0 : bool ok = false;
1003 : 0 : QString orientationString = mDataDefinedProperties.valueAsString( QgsLayoutObject::PaperOrientation, context, QString(), &ok );
1004 : 0 : if ( ok && !orientationString.isEmpty() )
1005 : : {
1006 : 0 : QgsLayoutItemPage::Orientation orientation = QgsLayoutUtils::decodePaperOrientation( orientationString, ok );
1007 : 0 : if ( ok )
1008 : : {
1009 : 0 : double heightD = 0.0, widthD = 0.0;
1010 : 0 : switch ( orientation )
1011 : : {
1012 : : case QgsLayoutItemPage::Portrait:
1013 : : {
1014 : 0 : heightD = std::max( height, width );
1015 : 0 : widthD = std::min( height, width );
1016 : 0 : break;
1017 : : }
1018 : : case QgsLayoutItemPage::Landscape:
1019 : : {
1020 : 0 : heightD = std::min( height, width );
1021 : 0 : widthD = std::max( height, width );
1022 : 0 : break;
1023 : : }
1024 : : }
1025 : 0 : width = widthD;
1026 : 0 : height = heightD;
1027 : 0 : }
1028 : 0 : }
1029 : 0 : }
1030 : :
1031 : 0 : QgsLayoutSize QgsLayoutItem::applyDataDefinedSize( const QgsLayoutSize &size )
1032 : : {
1033 : 0 : if ( !mLayout )
1034 : : {
1035 : 0 : return size;
1036 : : }
1037 : :
1038 : 0 : if ( !mDataDefinedProperties.isActive( QgsLayoutObject::PresetPaperSize ) &&
1039 : 0 : !mDataDefinedProperties.isActive( QgsLayoutObject::ItemWidth ) &&
1040 : 0 : !mDataDefinedProperties.isActive( QgsLayoutObject::ItemHeight ) &&
1041 : 0 : !mDataDefinedProperties.isActive( QgsLayoutObject::PaperOrientation ) )
1042 : 0 : return size;
1043 : :
1044 : :
1045 : 0 : QgsExpressionContext context = createExpressionContext();
1046 : :
1047 : : // lowest priority is page size
1048 : 0 : QString pageSize = mDataDefinedProperties.valueAsString( QgsLayoutObject::PresetPaperSize, context );
1049 : 0 : QgsPageSize matchedSize;
1050 : 0 : double evaluatedWidth = size.width();
1051 : 0 : double evaluatedHeight = size.height();
1052 : 0 : if ( QgsApplication::pageSizeRegistry()->decodePageSize( pageSize, matchedSize ) )
1053 : : {
1054 : 0 : QgsLayoutSize convertedSize = mLayout->renderContext().measurementConverter().convert( matchedSize.size, size.units() );
1055 : 0 : evaluatedWidth = convertedSize.width();
1056 : 0 : evaluatedHeight = convertedSize.height();
1057 : 0 : }
1058 : :
1059 : : // highest priority is dd width/height
1060 : 0 : evaluatedWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::ItemWidth, context, evaluatedWidth );
1061 : 0 : evaluatedHeight = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::ItemHeight, context, evaluatedHeight );
1062 : :
1063 : : //which is finally overwritten by data defined orientation
1064 : 0 : applyDataDefinedOrientation( evaluatedWidth, evaluatedHeight, context );
1065 : :
1066 : 0 : return QgsLayoutSize( evaluatedWidth, evaluatedHeight, size.units() );
1067 : 0 : }
1068 : :
1069 : 0 : double QgsLayoutItem::applyDataDefinedRotation( const double rotation )
1070 : : {
1071 : 0 : if ( !mLayout )
1072 : : {
1073 : 0 : return rotation;
1074 : : }
1075 : :
1076 : 0 : QgsExpressionContext context = createExpressionContext();
1077 : 0 : double evaluatedRotation = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::ItemRotation, context, rotation );
1078 : 0 : return evaluatedRotation;
1079 : 0 : }
1080 : :
1081 : 0 : void QgsLayoutItem::refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property )
1082 : : {
1083 : : //update data defined properties and update item to match
1084 : :
1085 : : //evaluate width and height first, since they may affect position if non-top-left reference point set
1086 : 0 : if ( property == QgsLayoutObject::ItemWidth || property == QgsLayoutObject::ItemHeight ||
1087 : 0 : property == QgsLayoutObject::AllProperties )
1088 : : {
1089 : 0 : refreshItemSize();
1090 : 0 : }
1091 : 0 : if ( property == QgsLayoutObject::PositionX || property == QgsLayoutObject::PositionY ||
1092 : 0 : property == QgsLayoutObject::AllProperties )
1093 : : {
1094 : 0 : refreshItemPosition();
1095 : 0 : }
1096 : 0 : if ( property == QgsLayoutObject::ItemRotation || property == QgsLayoutObject::AllProperties )
1097 : : {
1098 : 0 : refreshItemRotation();
1099 : 0 : }
1100 : 0 : if ( property == QgsLayoutObject::Opacity || property == QgsLayoutObject::AllProperties )
1101 : : {
1102 : 0 : refreshOpacity( false );
1103 : 0 : }
1104 : 0 : if ( property == QgsLayoutObject::FrameColor || property == QgsLayoutObject::AllProperties )
1105 : : {
1106 : 0 : refreshFrame( false );
1107 : 0 : }
1108 : 0 : if ( property == QgsLayoutObject::BackgroundColor || property == QgsLayoutObject::AllProperties )
1109 : : {
1110 : 0 : refreshBackgroundColor( false );
1111 : 0 : }
1112 : 0 : if ( property == QgsLayoutObject::BlendMode || property == QgsLayoutObject::AllProperties )
1113 : : {
1114 : 0 : refreshBlendMode();
1115 : 0 : }
1116 : 0 : if ( property == QgsLayoutObject::ExcludeFromExports || property == QgsLayoutObject::AllProperties )
1117 : : {
1118 : 0 : bool exclude = mExcludeFromExports;
1119 : : //data defined exclude from exports set?
1120 : 0 : mEvaluatedExcludeFromExports = mDataDefinedProperties.valueAsBool( QgsLayoutObject::ExcludeFromExports, createExpressionContext(), exclude );
1121 : 0 : }
1122 : :
1123 : 0 : update();
1124 : 0 : }
1125 : :
1126 : 0 : void QgsLayoutItem::setItemRotation( double angle, const bool adjustPosition )
1127 : : {
1128 : 0 : if ( angle >= 360.0 || angle <= -360.0 )
1129 : : {
1130 : 0 : angle = std::fmod( angle, 360.0 );
1131 : 0 : }
1132 : :
1133 : 0 : QPointF point = adjustPosition ? positionAtReferencePoint( QgsLayoutItem::Middle )
1134 : 0 : : pos();
1135 : 0 : double rotationRequired = angle - rotation();
1136 : 0 : rotateItem( rotationRequired, point );
1137 : :
1138 : 0 : mItemRotation = angle;
1139 : 0 : }
1140 : :
1141 : 0 : void QgsLayoutItem::updateStoredItemPosition()
1142 : : {
1143 : 0 : QPointF layoutPosReferencePoint = positionAtReferencePoint( mReferencePoint );
1144 : 0 : mItemPosition = mLayout->convertFromLayoutUnits( layoutPosReferencePoint, mItemPosition.units() );
1145 : 0 : }
1146 : :
1147 : 0 : void QgsLayoutItem::rotateItem( const double angle, const QPointF transformOrigin )
1148 : : {
1149 : 0 : double evaluatedAngle = angle + rotation();
1150 : 0 : evaluatedAngle = QgsLayoutUtils::normalizedAngle( evaluatedAngle, true );
1151 : 0 : mItemRotation = evaluatedAngle;
1152 : :
1153 : 0 : QPointF itemTransformOrigin = mapFromScene( transformOrigin );
1154 : :
1155 : 0 : refreshItemRotation( &itemTransformOrigin );
1156 : 0 : }
1157 : :
1158 : 0 : QgsExpressionContext QgsLayoutItem::createExpressionContext() const
1159 : : {
1160 : 0 : QgsExpressionContext context = QgsLayoutObject::createExpressionContext();
1161 : 0 : context.appendScope( QgsExpressionContextUtils::layoutItemScope( this ) );
1162 : 0 : return context;
1163 : 0 : }
1164 : :
1165 : 0 : bool QgsLayoutItem::accept( QgsStyleEntityVisitorInterface *visitor ) const
1166 : : {
1167 : : Q_UNUSED( visitor );
1168 : 0 : return true;
1169 : : }
1170 : :
1171 : 0 : QgsGeometry QgsLayoutItem::clipPath() const
1172 : : {
1173 : 0 : return QgsGeometry();
1174 : : }
1175 : :
1176 : 0 : void QgsLayoutItem::refresh()
1177 : : {
1178 : 0 : QgsLayoutObject::refresh();
1179 : 0 : refreshItemSize();
1180 : :
1181 : 0 : refreshDataDefinedProperty();
1182 : 0 : }
1183 : :
1184 : 0 : void QgsLayoutItem::invalidateCache()
1185 : : {
1186 : 0 : if ( !mItemCachedImage.isNull() )
1187 : : {
1188 : 0 : mItemCachedImage = QImage();
1189 : 0 : mItemCacheDpi = -1;
1190 : 0 : update();
1191 : 0 : }
1192 : 0 : }
1193 : :
1194 : 0 : void QgsLayoutItem::redraw()
1195 : : {
1196 : 0 : update();
1197 : 0 : }
1198 : :
1199 : 0 : void QgsLayoutItem::drawDebugRect( QPainter *painter )
1200 : : {
1201 : 0 : if ( !painter )
1202 : : {
1203 : 0 : return;
1204 : : }
1205 : :
1206 : 0 : QgsScopedQPainterState painterState( painter );
1207 : 0 : painter->setRenderHint( QPainter::Antialiasing, false );
1208 : 0 : painter->setPen( Qt::NoPen );
1209 : 0 : painter->setBrush( QColor( 100, 255, 100, 200 ) );
1210 : 0 : painter->drawRect( rect() );
1211 : 0 : }
1212 : :
1213 : 0 : QPainterPath QgsLayoutItem::framePath() const
1214 : : {
1215 : 0 : QPainterPath path;
1216 : 0 : path.addRect( QRectF( 0, 0, rect().width(), rect().height() ) );
1217 : 0 : return path;
1218 : 0 : }
1219 : :
1220 : 0 : void QgsLayoutItem::drawFrame( QgsRenderContext &context )
1221 : : {
1222 : 0 : if ( !mFrame || !context.painter() )
1223 : 0 : return;
1224 : :
1225 : 0 : QPainter *p = context.painter();
1226 : :
1227 : 0 : QgsScopedQPainterState painterState( p );
1228 : :
1229 : 0 : p->setPen( pen() );
1230 : 0 : p->setBrush( Qt::NoBrush );
1231 : 0 : context.setPainterFlagsUsingContext( p );
1232 : :
1233 : 0 : p->drawPath( framePath() );
1234 : 0 : }
1235 : :
1236 : 0 : void QgsLayoutItem::drawBackground( QgsRenderContext &context )
1237 : : {
1238 : 0 : if ( !mBackground || !context.painter() )
1239 : 0 : return;
1240 : :
1241 : 0 : QgsScopedQPainterState painterState( context.painter() );
1242 : :
1243 : 0 : QPainter *p = context.painter();
1244 : 0 : p->setBrush( brush() );
1245 : 0 : p->setPen( Qt::NoPen );
1246 : 0 : context.setPainterFlagsUsingContext( p );
1247 : :
1248 : 0 : p->drawPath( framePath() );
1249 : 0 : }
1250 : :
1251 : 0 : void QgsLayoutItem::setFixedSize( const QgsLayoutSize &size )
1252 : : {
1253 : 0 : mFixedSize = size;
1254 : 0 : refreshItemSize();
1255 : 0 : }
1256 : :
1257 : 0 : void QgsLayoutItem::setMinimumSize( const QgsLayoutSize &size )
1258 : : {
1259 : 0 : mMinimumSize = size;
1260 : 0 : refreshItemSize();
1261 : 0 : }
1262 : 0 :
1263 : 0 : QSizeF QgsLayoutItem::applyItemSizeConstraint( const QSizeF targetSize )
1264 : : {
1265 : 0 : return targetSize;
1266 : : }
1267 : :
1268 : 0 : void QgsLayoutItem::refreshItemSize()
1269 : : {
1270 : 0 : attemptResize( mItemSize );
1271 : 0 : }
1272 : :
1273 : 0 : void QgsLayoutItem::refreshItemPosition()
1274 : : {
1275 : 0 : attemptMove( mItemPosition );
1276 : 0 : }
1277 : :
1278 : 0 : QPointF QgsLayoutItem::itemPositionAtReferencePoint( const ReferencePoint reference, const QSizeF size ) const
1279 : 0 : {
1280 : 0 : switch ( reference )
1281 : 0 : {
1282 : : case UpperMiddle:
1283 : 0 : return QPointF( size.width() / 2.0, 0 );
1284 : 0 : case UpperRight:
1285 : 0 : return QPointF( size.width(), 0 );
1286 : 0 : case MiddleLeft:
1287 : 0 : return QPointF( 0, size.height() / 2.0 );
1288 : 0 : case Middle:
1289 : 0 : return QPointF( size.width() / 2.0, size.height() / 2.0 );
1290 : 0 : case MiddleRight:
1291 : 0 : return QPointF( size.width(), size.height() / 2.0 );
1292 : : case LowerLeft:
1293 : 0 : return QPointF( 0, size.height() );
1294 : : case LowerMiddle:
1295 : 0 : return QPointF( size.width() / 2.0, size.height() );
1296 : : case LowerRight:
1297 : 0 : return QPointF( size.width(), size.height() );
1298 : : case UpperLeft:
1299 : 0 : return QPointF( 0, 0 );
1300 : : }
1301 : : // no warnings
1302 : 0 : return QPointF( 0, 0 );
1303 : 0 : }
1304 : :
1305 : 0 : QPointF QgsLayoutItem::adjustPointForReferencePosition( const QPointF position, const QSizeF size, const ReferencePoint reference ) const
1306 : : {
1307 : 0 : QPointF itemPosition = mapFromScene( position ); //need to map from scene to handle item rotation
1308 : 0 : QPointF adjustedPointInsideItem = itemPosition - itemPositionAtReferencePoint( reference, size );
1309 : 0 : return mapToScene( adjustedPointInsideItem );
1310 : : }
1311 : :
1312 : 0 : QPointF QgsLayoutItem::positionAtReferencePoint( const QgsLayoutItem::ReferencePoint reference ) const
1313 : : {
1314 : 0 : QPointF pointWithinItem = itemPositionAtReferencePoint( reference, rect().size() );
1315 : 0 : return mapToScene( pointWithinItem );
1316 : : }
1317 : :
1318 : 0 : QgsLayoutPoint QgsLayoutItem::topLeftToReferencePoint( const QgsLayoutPoint &point ) const
1319 : : {
1320 : 0 : QPointF topLeft = mLayout->convertToLayoutUnits( point );
1321 : 0 : QPointF refPoint = topLeft + itemPositionAtReferencePoint( mReferencePoint, rect().size() );
1322 : 0 : return mLayout->convertFromLayoutUnits( refPoint, point.units() );
1323 : : }
1324 : :
1325 : 0 : bool QgsLayoutItem::writePropertiesToElement( QDomElement &, QDomDocument &, const QgsReadWriteContext & ) const
1326 : : {
1327 : 0 : return true;
1328 : : }
1329 : :
1330 : 0 : bool QgsLayoutItem::readPropertiesFromElement( const QDomElement &, const QDomDocument &, const QgsReadWriteContext & )
1331 : : {
1332 : :
1333 : 0 : return true;
1334 : : }
1335 : :
1336 : 0 : void QgsLayoutItem::initConnectionsToLayout()
1337 : : {
1338 : 0 : if ( !mLayout )
1339 : 0 : return;
1340 : :
1341 : 0 : }
1342 : :
1343 : 0 : void QgsLayoutItem::preparePainter( QPainter *painter )
1344 : : {
1345 : 0 : if ( !painter || !painter->device() )
1346 : : {
1347 : 0 : return;
1348 : : }
1349 : :
1350 : 0 : painter->setRenderHint( QPainter::Antialiasing, shouldDrawAntialiased() );
1351 : :
1352 : : #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
1353 : 0 : painter->setRenderHint( QPainter::LosslessImageRendering, mLayout && mLayout->renderContext().testFlag( QgsLayoutRenderContext::FlagLosslessImageRendering ) );
1354 : : #endif
1355 : 0 : }
1356 : :
1357 : 0 : bool QgsLayoutItem::shouldDrawAntialiased() const
1358 : : {
1359 : 0 : if ( !mLayout )
1360 : : {
1361 : 0 : return true;
1362 : : }
1363 : 0 : return mLayout->renderContext().testFlag( QgsLayoutRenderContext::FlagAntialiasing ) && !mLayout->renderContext().testFlag( QgsLayoutRenderContext::FlagDebug );
1364 : 0 : }
1365 : :
1366 : 0 : bool QgsLayoutItem::shouldDrawDebugRect() const
1367 : : {
1368 : 0 : return mLayout && mLayout->renderContext().testFlag( QgsLayoutRenderContext::FlagDebug );
1369 : : }
1370 : :
1371 : 0 : QSizeF QgsLayoutItem::applyMinimumSize( const QSizeF targetSize )
1372 : : {
1373 : 0 : if ( !mLayout || minimumSize().isEmpty() )
1374 : : {
1375 : 0 : return targetSize;
1376 : : }
1377 : 0 : QSizeF minimumSizeLayoutUnits = mLayout->convertToLayoutUnits( minimumSize() );
1378 : 0 : return targetSize.expandedTo( minimumSizeLayoutUnits );
1379 : 0 : }
1380 : :
1381 : 0 : QSizeF QgsLayoutItem::applyFixedSize( const QSizeF targetSize )
1382 : : {
1383 : 0 : if ( !mLayout || fixedSize().isEmpty() )
1384 : : {
1385 : 0 : return targetSize;
1386 : : }
1387 : :
1388 : 0 : QSizeF size = targetSize;
1389 : 0 : QSizeF fixedSizeLayoutUnits = mLayout->convertToLayoutUnits( fixedSize() );
1390 : 0 : if ( fixedSizeLayoutUnits.width() > 0 )
1391 : 0 : size.setWidth( fixedSizeLayoutUnits.width() );
1392 : 0 : if ( fixedSizeLayoutUnits.height() > 0 )
1393 : 0 : size.setHeight( fixedSizeLayoutUnits.height() );
1394 : :
1395 : 0 : return size;
1396 : 0 : }
1397 : :
1398 : 0 : void QgsLayoutItem::refreshItemRotation( QPointF *origin )
1399 : : {
1400 : 0 : double r = mItemRotation;
1401 : :
1402 : : //data defined rotation set?
1403 : 0 : r = mDataDefinedProperties.valueAsDouble( QgsLayoutItem::ItemRotation, createExpressionContext(), r );
1404 : :
1405 : 0 : if ( qgsDoubleNear( r, rotation() ) && !origin )
1406 : : {
1407 : 0 : return;
1408 : : }
1409 : :
1410 : 0 : QPointF transformPoint = origin ? *origin : mapFromScene( positionAtReferencePoint( QgsLayoutItem::Middle ) );
1411 : :
1412 : 0 : if ( !transformPoint.isNull() )
1413 : : {
1414 : : //adjustPosition set, so shift the position of the item so that rotation occurs around item center
1415 : : //create a line from the transform point to the item's origin, in scene coordinates
1416 : 0 : QLineF refLine = QLineF( mapToScene( transformPoint ), mapToScene( QPointF( 0, 0 ) ) );
1417 : : //rotate this line by the current rotation angle
1418 : 0 : refLine.setAngle( refLine.angle() - r + rotation() );
1419 : : //get new end point of line - this is the new item position
1420 : 0 : QPointF rotatedReferencePoint = refLine.p2();
1421 : 0 : setPos( rotatedReferencePoint );
1422 : 0 : }
1423 : :
1424 : 0 : setTransformOriginPoint( 0, 0 );
1425 : 0 : QGraphicsItem::setRotation( r );
1426 : :
1427 : : //adjust stored position of item to match scene pos of reference point
1428 : 0 : updateStoredItemPosition();
1429 : 0 : emit sizePositionChanged();
1430 : :
1431 : 0 : emit rotationChanged( r );
1432 : :
1433 : : //update bounds of scene, since rotation may affect this
1434 : 0 : mLayout->updateBounds();
1435 : 0 : }
1436 : :
1437 : 0 : void QgsLayoutItem::refreshOpacity( bool updateItem )
1438 : : {
1439 : : //data defined opacity set?
1440 : 0 : double opacity = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::Opacity, createExpressionContext(), mOpacity * 100.0 );
1441 : :
1442 : : // Set the QGraphicItem's opacity
1443 : 0 : mEvaluatedOpacity = opacity / 100.0;
1444 : :
1445 : 0 : if ( itemFlags() & QgsLayoutItem::FlagOverridesPaint )
1446 : : {
1447 : : // item handles it's own painting, so it won't use the built-in opacity handling in QgsLayoutItem::paint, and
1448 : : // we have to rely on QGraphicsItem opacity to handle this
1449 : 0 : setOpacity( mEvaluatedOpacity );
1450 : 0 : }
1451 : :
1452 : 0 : if ( updateItem )
1453 : : {
1454 : 0 : update();
1455 : 0 : }
1456 : 0 : }
1457 : :
1458 : 0 : void QgsLayoutItem::refreshFrame( bool updateItem )
1459 : : {
1460 : 0 : if ( !mFrame )
1461 : : {
1462 : 0 : setPen( Qt::NoPen );
1463 : 0 : return;
1464 : : }
1465 : :
1466 : : //data defined stroke color set?
1467 : 0 : bool ok = false;
1468 : 0 : QColor frameColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::FrameColor, createExpressionContext(), mFrameColor, &ok );
1469 : 0 : QPen itemPen;
1470 : 0 : if ( ok )
1471 : : {
1472 : 0 : itemPen = QPen( frameColor );
1473 : 0 : }
1474 : : else
1475 : : {
1476 : 0 : itemPen = QPen( mFrameColor );
1477 : : }
1478 : 0 : itemPen.setJoinStyle( mFrameJoinStyle );
1479 : :
1480 : 0 : if ( mLayout )
1481 : 0 : itemPen.setWidthF( mLayout->convertToLayoutUnits( mFrameWidth ) );
1482 : : else
1483 : 0 : itemPen.setWidthF( mFrameWidth.length() );
1484 : :
1485 : 0 : setPen( itemPen );
1486 : :
1487 : 0 : if ( updateItem )
1488 : : {
1489 : 0 : update();
1490 : 0 : }
1491 : 0 : }
1492 : :
1493 : 0 : void QgsLayoutItem::refreshBackgroundColor( bool updateItem )
1494 : : {
1495 : : //data defined fill color set?
1496 : 0 : bool ok = false;
1497 : 0 : QColor backgroundColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::BackgroundColor, createExpressionContext(), mBackgroundColor, &ok );
1498 : 0 : if ( ok )
1499 : : {
1500 : 0 : setBrush( QBrush( backgroundColor, Qt::SolidPattern ) );
1501 : 0 : }
1502 : : else
1503 : : {
1504 : 0 : setBrush( QBrush( mBackgroundColor, Qt::SolidPattern ) );
1505 : : }
1506 : 0 : if ( updateItem )
1507 : : {
1508 : 0 : update();
1509 : 0 : }
1510 : 0 : }
1511 : :
1512 : 0 : void QgsLayoutItem::refreshBlendMode()
1513 : : {
1514 : 0 : QPainter::CompositionMode blendMode = mBlendMode;
1515 : :
1516 : : //data defined blend mode set?
1517 : 0 : bool ok = false;
1518 : 0 : QString blendStr = mDataDefinedProperties.valueAsString( QgsLayoutObject::BlendMode, createExpressionContext(), QString(), &ok );
1519 : 0 : if ( ok && !blendStr.isEmpty() )
1520 : : {
1521 : 0 : QString blendstr = blendStr.trimmed();
1522 : 0 : QPainter::CompositionMode blendModeD = QgsSymbolLayerUtils::decodeBlendMode( blendstr );
1523 : 0 : blendMode = blendModeD;
1524 : 0 : }
1525 : :
1526 : : // Update the item effect to use the new blend mode
1527 : 0 : mEffect->setCompositionMode( blendMode );
1528 : 0 : }
1529 : :
|