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