QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgslayoutaligner.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutaligner.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 "qgslayoutaligner.h"
18#include "qgslayoutitem.h"
19#include "qgslayout.h"
20#include "qgslayoutundostack.h"
21
22void QgsLayoutAligner::alignItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Alignment alignment )
23{
24 if ( !layout || items.size() < 2 )
25 {
26 return;
27 }
28
29 const QRectF itemBBox = boundingRectOfItems( items );
30 if ( !itemBBox.isValid() )
31 {
32 return;
33 }
34
35 double refCoord = 0;
36 switch ( alignment )
37 {
38 case AlignLeft:
39 refCoord = itemBBox.left();
40 break;
41 case AlignHCenter:
42 refCoord = itemBBox.center().x();
43 break;
44 case AlignRight:
45 refCoord = itemBBox.right();
46 break;
47 case AlignTop:
48 refCoord = itemBBox.top();
49 break;
50 case AlignVCenter:
51 refCoord = itemBBox.center().y();
52 break;
53 case AlignBottom:
54 refCoord = itemBBox.bottom();
55 break;
56 }
57
58 layout->undoStack()->beginMacro( undoText( alignment ) );
59 for ( QgsLayoutItem *item : items )
60 {
61 layout->undoStack()->beginCommand( item, QString() );
62
63 QPointF shifted = item->pos();
64 switch ( alignment )
65 {
66 case AlignLeft:
67 shifted.setX( refCoord );
68 break;
69 case AlignHCenter:
70 shifted.setX( refCoord - item->rect().width() / 2.0 );
71 break;
72 case AlignRight:
73 shifted.setX( refCoord - item->rect().width() );
74 break;
75 case AlignTop:
76 shifted.setY( refCoord );
77 break;
78 case AlignVCenter:
79 shifted.setY( refCoord - item->rect().height() / 2.0 );
80 break;
81 case AlignBottom:
82 shifted.setY( refCoord - item->rect().height() );
83 break;
84 }
85
86 // need to keep item units
87 const QgsLayoutPoint newPos = layout->convertFromLayoutUnits( shifted, item->positionWithUnits().units() );
88 item->attemptMove( newPos, false );
89
90 layout->undoStack()->endCommand();
91 }
92 layout->undoStack()->endMacro();
93}
94
95void QgsLayoutAligner::distributeItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Distribution distribution )
96{
97 if ( items.size() < 2 )
98 return;
99
100 // equispaced distribution doesn't follow the same approach of the other distribution types
101 if ( distribution == DistributeHSpace || distribution == DistributeVSpace )
102 {
103 distributeEquispacedItems( layout, items, distribution );
104 return;
105 }
106
107 auto collectReferenceCoord = [distribution]( QgsLayoutItem * item )->double
108 {
109 const QRectF itemBBox = item->sceneBoundingRect();
110 switch ( distribution )
111 {
112 case DistributeLeft:
113 return itemBBox.left();
115 return itemBBox.center().x();
116 case DistributeRight:
117 return itemBBox.right();
118 case DistributeTop:
119 return itemBBox.top();
121 return itemBBox.center().y();
122 case DistributeBottom:
123 return itemBBox.bottom();
124 case DistributeHSpace:
125 case DistributeVSpace:
126 // not reachable branch, just to avoid compilation warning
127 return std::numeric_limits<double>::quiet_NaN();
128 }
129 // no warnings
130 return itemBBox.left();
131 };
132
133
134 double minCoord = std::numeric_limits<double>::max();
135 double maxCoord = std::numeric_limits<double>::lowest();
136 QMap< double, QgsLayoutItem * > itemCoords;
137 for ( QgsLayoutItem *item : items )
138 {
139 const double refCoord = collectReferenceCoord( item );
140 minCoord = std::min( minCoord, refCoord );
141 maxCoord = std::max( maxCoord, refCoord );
142 itemCoords.insert( refCoord, item );
143 }
144
145 const double step = ( maxCoord - minCoord ) / ( items.size() - 1 );
146
147 auto distributeItemToCoord = [layout, distribution]( QgsLayoutItem * item, double refCoord )
148 {
149 QPointF shifted = item->pos();
150 switch ( distribution )
151 {
152 case DistributeLeft:
153 shifted.setX( refCoord );
154 break;
156 shifted.setX( refCoord - item->rect().width() / 2.0 );
157 break;
158 case DistributeRight:
159 shifted.setX( refCoord - item->rect().width() );
160 break;
161 case DistributeTop:
162 shifted.setY( refCoord );
163 break;
165 shifted.setY( refCoord - item->rect().height() / 2.0 );
166 break;
167 case DistributeBottom:
168 shifted.setY( refCoord - item->rect().height() );
169 break;
170 case DistributeHSpace:
171 case DistributeVSpace:
172 // not reachable branch, just to avoid compilation warning
173 break;
174 }
175
176 // need to keep item units
177 const QgsLayoutPoint newPos = layout->convertFromLayoutUnits( shifted, item->positionWithUnits().units() );
178 item->attemptMove( newPos, false );
179 };
180
181
182 layout->undoStack()->beginMacro( undoText( distribution ) );
183 double currentVal = minCoord;
184 for ( auto itemIt = itemCoords.constBegin(); itemIt != itemCoords.constEnd(); ++itemIt )
185 {
186 layout->undoStack()->beginCommand( itemIt.value(), QString() );
187 distributeItemToCoord( itemIt.value(), currentVal );
188 layout->undoStack()->endCommand();
189
190 currentVal += step;
191 }
192 layout->undoStack()->endMacro();
193}
194
195void QgsLayoutAligner::resizeItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Resize resize )
196{
197 if ( !( items.size() >= 2 || ( items.size() == 1 && resize == ResizeToSquare ) ) )
198 return;
199
200 auto collectSize = [resize]( QgsLayoutItem * item )->double
201 {
202 const QRectF itemBBox = item->sceneBoundingRect();
203 switch ( resize )
204 {
205 case ResizeNarrowest:
206 case ResizeWidest:
207 case ResizeToSquare:
208 return itemBBox.width();
209 case ResizeShortest:
210 case ResizeTallest:
211 return itemBBox.height();
212 }
213 // no warnings
214 return itemBBox.width();
215 };
216
217 double newSize = collectSize( items.at( 0 ) );
218 for ( QgsLayoutItem *item : items )
219 {
220 const double size = collectSize( item );
221 switch ( resize )
222 {
223 case ResizeNarrowest:
224 case ResizeShortest:
225 newSize = std::min( size, newSize );
226 break;
227 case ResizeTallest:
228 case ResizeWidest:
229 newSize = std::max( size, newSize );
230 break;
231 case ResizeToSquare:
232 break;
233 }
234 }
235
236 auto resizeItemToSize = [layout, resize]( QgsLayoutItem * item, double size )
237 {
238 QSizeF newSize = item->rect().size();
239 switch ( resize )
240 {
241 case ResizeNarrowest:
242 case ResizeWidest:
243 newSize.setWidth( size );
244 break;
245 case ResizeTallest:
246 case ResizeShortest:
247 newSize.setHeight( size );
248 break;
249 case ResizeToSquare:
250 {
251 if ( newSize.width() > newSize.height() )
252 newSize.setHeight( newSize.width() );
253 else
254 newSize.setWidth( newSize.height() );
255 break;
256 }
257 }
258
259 // need to keep item units
260 const QgsLayoutSize newSizeWithUnits = layout->convertFromLayoutUnits( newSize, item->sizeWithUnits().units() );
261 item->attemptResize( newSizeWithUnits );
262 };
263
264 layout->undoStack()->beginMacro( undoText( resize ) );
265 for ( QgsLayoutItem *item : items )
266 {
267 layout->undoStack()->beginCommand( item, QString() );
268 resizeItemToSize( item, newSize );
269 layout->undoStack()->endCommand();
270 }
271 layout->undoStack()->endMacro();
272}
273
274QRectF QgsLayoutAligner::boundingRectOfItems( const QList<QgsLayoutItem *> &items )
275{
276 if ( items.empty() )
277 {
278 return QRectF();
279 }
280
281 auto it = items.constBegin();
282 //set the box to the first item
283 QgsLayoutItem *currentItem = *it;
284 it++;
285 double minX = currentItem->pos().x();
286 double minY = currentItem->pos().y();
287 double maxX = minX + currentItem->rect().width();
288 double maxY = minY + currentItem->rect().height();
289
290 double currentMinX, currentMinY, currentMaxX, currentMaxY;
291
292 for ( ; it != items.constEnd(); ++it )
293 {
294 currentItem = *it;
295 currentMinX = currentItem->pos().x();
296 currentMinY = currentItem->pos().y();
297 currentMaxX = currentMinX + currentItem->rect().width();
298 currentMaxY = currentMinY + currentItem->rect().height();
299
300 if ( currentMinX < minX )
301 minX = currentMinX;
302 if ( currentMaxX > maxX )
303 maxX = currentMaxX;
304 if ( currentMinY < minY )
305 minY = currentMinY;
306 if ( currentMaxY > maxY )
307 maxY = currentMaxY;
308 }
309
310 return QRectF( QPointF( minX, minY ), QPointF( maxX, maxY ) );
311}
312
313QString QgsLayoutAligner::undoText( Distribution distribution )
314{
315 switch ( distribution )
316 {
317 case DistributeLeft:
318 return QObject::tr( "Distribute Items by Left" );
320 return QObject::tr( "Distribute Items by Horizontal Center" );
321 case DistributeHSpace:
322 return QObject::tr( "Distribute Horizontal Spacing Equally" );
323 case DistributeRight:
324 return QObject::tr( "Distribute Items by Right" );
325 case DistributeTop:
326 return QObject::tr( "Distribute Items by Top" );
328 return QObject::tr( "Distribute Items by Vertical Center" );
329 case DistributeVSpace:
330 return QObject::tr( "Distribute Vertical Spacing Equally" );
331 case DistributeBottom:
332 return QObject::tr( "Distribute Items by Bottom" );
333 }
334 return QString(); //no warnings
335}
336
337QString QgsLayoutAligner::undoText( QgsLayoutAligner::Resize resize )
338{
339 switch ( resize )
340 {
341 case ResizeNarrowest:
342 return QObject::tr( "Resize Items to Narrowest" );
343 case ResizeWidest:
344 return QObject::tr( "Resize Items to Widest" );
345 case ResizeShortest:
346 return QObject::tr( "Resize Items to Shortest" );
347 case ResizeTallest:
348 return QObject::tr( "Resize Items to Tallest" );
349 case ResizeToSquare:
350 return QObject::tr( "Resize Items to Square" );
351 }
352 return QString(); //no warnings
353}
354
355QString QgsLayoutAligner::undoText( Alignment alignment )
356{
357 switch ( alignment )
358 {
359 case AlignLeft:
360 return QObject::tr( "Align Items to Left" );
361 case AlignHCenter:
362 return QObject::tr( "Align Items to Center" );
363 case AlignRight:
364 return QObject::tr( "Align Items to Right" );
365 case AlignTop:
366 return QObject::tr( "Align Items to Top" );
367 case AlignVCenter:
368 return QObject::tr( "Align Items to Vertical Center" );
369 case AlignBottom:
370 return QObject::tr( "Align Items to Bottom" );
371 }
372 return QString(); //no warnings
373}
374
375void QgsLayoutAligner::distributeEquispacedItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Distribution distribution )
376{
377 double length = 0.0;
378 double minCoord = std::numeric_limits<double>::max();
379 double maxCoord = std::numeric_limits<double>::lowest();
380 QMap< double, QgsLayoutItem * > itemCoords;
381
382 for ( QgsLayoutItem *item : items )
383 {
384 const QRectF itemBBox = item->sceneBoundingRect();
385
386 const double item_min = ( distribution == DistributeHSpace ? itemBBox.left() :
387 itemBBox.top() );
388 const double item_max = ( distribution == DistributeHSpace ? itemBBox.right() :
389 itemBBox.bottom() );
390
391 minCoord = std::min( minCoord, item_min );
392 maxCoord = std::max( maxCoord, item_max );
393 length += ( item_max - item_min );
394 itemCoords.insert( item_min, item );
395 }
396 const double step = ( maxCoord - minCoord - length ) / ( items.size() - 1 );
397
398 double currentVal = minCoord;
399 layout->undoStack()->beginMacro( undoText( distribution ) );
400 for ( auto itemIt = itemCoords.constBegin(); itemIt != itemCoords.constEnd(); ++itemIt )
401 {
402 QgsLayoutItem *item = itemIt.value();
403 QPointF shifted = item->pos();
404
405 layout->undoStack()->beginCommand( itemIt.value(), QString() );
406
407 if ( distribution == DistributeHSpace )
408 {
409 shifted.setX( currentVal );
410 }
411 else
412 {
413 shifted.setY( currentVal );
414 }
415
416 const QgsLayoutPoint newPos = layout->convertFromLayoutUnits( shifted, item->positionWithUnits().units() );
417 item->attemptMove( newPos, false );
418
419 layout->undoStack()->endCommand();
420
421 currentVal += ( distribution == DistributeHSpace ? item->rect().width() :
422 item->rect().height() ) + step;
423 }
424 layout->undoStack()->endMacro();
425 return;
426}
Alignment
Alignment options.
@ AlignVCenter
Align vertical centers.
@ AlignLeft
Align left edges.
@ AlignBottom
Align bottom edges.
@ AlignRight
Align right edges.
@ AlignTop
Align top edges.
@ AlignHCenter
Align horizontal centers.
Resize
Resize options.
@ ResizeNarrowest
Resize width to match narrowest width.
@ ResizeShortest
Resize height to match shortest height.
@ ResizeTallest
Resize height to match tallest height.
@ ResizeToSquare
Resize items to square.
@ ResizeWidest
Resize width to match widest width.
static void alignItems(QgsLayout *layout, const QList< QgsLayoutItem * > &items, Alignment alignment)
Aligns a set of items from a layout in place.
static void distributeItems(QgsLayout *layout, const QList< QgsLayoutItem * > &items, Distribution distribution)
Distributes a set of items from a layout in place.
static void resizeItems(QgsLayout *layout, const QList< QgsLayoutItem * > &items, Resize resize)
Resizes a set of items from a layout in place.
Distribution
Distribution options.
@ DistributeHSpace
Distribute horizontal equispaced.
@ DistributeVCenter
Distribute vertical centers.
@ DistributeBottom
Distribute bottom edges.
@ DistributeLeft
Distribute left edges.
@ DistributeHCenter
Distribute horizontal centers.
@ DistributeRight
Distribute right edges.
@ DistributeVSpace
Distribute vertical equispaced.
@ DistributeTop
Distribute top edges.
Base class for graphical items within a QgsLayout.
QgsLayoutPoint positionWithUnits() const
Returns the item's current position, including units.
virtual void attemptMove(const QgsLayoutPoint &point, bool useReferencePoint=true, bool includesFrame=false, int page=-1)
Attempts to move the item to a specified point.
This class provides a method of storing points, consisting of an x and y coordinate,...
QgsUnitTypes::LayoutUnit units() const
Returns the units for the point.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:41
void endCommand()
Saves final state of an object and pushes the active command to the undo history.
void beginMacro(const QString &commandText)
Starts a macro command, with the given descriptive commandText.
void beginCommand(QgsLayoutUndoObjectInterface *object, const QString &commandText, int id=0)
Begins a new undo command for the specified object.
void endMacro()
Ends a macro command.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
QgsLayoutMeasurement convertFromLayoutUnits(double length, QgsUnitTypes::LayoutUnit unit) const
Converts a length measurement from the layout's native units to a specified target unit.
Definition: qgslayout.cpp:344
QgsLayoutUndoStack * undoStack()
Returns a pointer to the layout's undo stack, which manages undo/redo states for the layout and it's ...
Definition: qgslayout.cpp:686