QGIS API Documentation  3.6.0-Noosa (5873452)
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 
22 void QgsLayoutAligner::alignItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Alignment alignment )
23 {
24  if ( !layout || items.size() < 2 )
25  {
26  return;
27  }
28 
29  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  QgsLayoutPoint newPos = layout->convertFromLayoutUnits( shifted, item->positionWithUnits().units() );
88  item->attemptMove( newPos );
89 
90  layout->undoStack()->endCommand();
91  }
92  layout->undoStack()->endMacro();
93 }
94 
95 void QgsLayoutAligner::distributeItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Distribution distribution )
96 {
97  if ( items.size() < 2 )
98  return;
99 
100  auto collectReferenceCoord = [distribution]( QgsLayoutItem * item )->double
101  {
102  QRectF itemBBox = item->sceneBoundingRect();
103  switch ( distribution )
104  {
105  case DistributeLeft:
106  return itemBBox.left();
107  case DistributeHCenter:
108  return itemBBox.center().x();
109  case DistributeRight:
110  return itemBBox.right();
111  case DistributeTop:
112  return itemBBox.top();
113  case DistributeVCenter:
114  return itemBBox.center().y();
115  case DistributeBottom:
116  return itemBBox.bottom();
117  }
118  // no warnings
119  return itemBBox.left();
120  };
121 
122 
123  double minCoord = std::numeric_limits<double>::max();
124  double maxCoord = std::numeric_limits<double>::lowest();
125  QMap< double, QgsLayoutItem * > itemCoords;
126  for ( QgsLayoutItem *item : items )
127  {
128  double refCoord = collectReferenceCoord( item );
129  minCoord = std::min( minCoord, refCoord );
130  maxCoord = std::max( maxCoord, refCoord );
131  itemCoords.insert( refCoord, item );
132  }
133 
134  double step = ( maxCoord - minCoord ) / ( items.size() - 1 );
135 
136  auto distributeItemToCoord = [layout, distribution]( QgsLayoutItem * item, double refCoord )
137  {
138  QPointF shifted = item->pos();
139  switch ( distribution )
140  {
141  case DistributeLeft:
142  shifted.setX( refCoord );
143  break;
144  case DistributeHCenter:
145  shifted.setX( refCoord - item->rect().width() / 2.0 );
146  break;
147  case DistributeRight:
148  shifted.setX( refCoord - item->rect().width() );
149  break;
150  case DistributeTop:
151  shifted.setY( refCoord );
152  break;
153  case DistributeVCenter:
154  shifted.setY( refCoord - item->rect().height() / 2.0 );
155  break;
156  case DistributeBottom:
157  shifted.setY( refCoord - item->rect().height() );
158  break;
159  }
160 
161  // need to keep item units
162  QgsLayoutPoint newPos = layout->convertFromLayoutUnits( shifted, item->positionWithUnits().units() );
163  item->attemptMove( newPos );
164  };
165 
166 
167  layout->undoStack()->beginMacro( undoText( distribution ) );
168  double currentVal = minCoord;
169  for ( auto itemIt = itemCoords.constBegin(); itemIt != itemCoords.constEnd(); ++itemIt )
170  {
171  layout->undoStack()->beginCommand( itemIt.value(), QString() );
172  distributeItemToCoord( itemIt.value(), currentVal );
173  layout->undoStack()->endCommand();
174 
175  currentVal += step;
176  }
177  layout->undoStack()->endMacro();
178 }
179 
180 void QgsLayoutAligner::resizeItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Resize resize )
181 {
182  if ( !( items.size() >= 2 || ( items.size() == 1 && resize == ResizeToSquare ) ) )
183  return;
184 
185  auto collectSize = [resize]( QgsLayoutItem * item )->double
186  {
187  QRectF itemBBox = item->sceneBoundingRect();
188  switch ( resize )
189  {
190  case ResizeNarrowest:
191  case ResizeWidest:
192  case ResizeToSquare:
193  return itemBBox.width();
194  case ResizeShortest:
195  case ResizeTallest:
196  return itemBBox.height();
197  }
198  // no warnings
199  return itemBBox.width();
200  };
201 
202  double newSize = collectSize( items.at( 0 ) );
203  for ( QgsLayoutItem *item : items )
204  {
205  double size = collectSize( item );
206  switch ( resize )
207  {
208  case ResizeNarrowest:
209  case ResizeShortest:
210  newSize = std::min( size, newSize );
211  break;
212  case ResizeTallest:
213  case ResizeWidest:
214  newSize = std::max( size, newSize );
215  break;
216  case ResizeToSquare:
217  break;
218  }
219  }
220 
221  auto resizeItemToSize = [layout, resize]( QgsLayoutItem * item, double size )
222  {
223  QSizeF newSize = item->rect().size();
224  switch ( resize )
225  {
226  case ResizeNarrowest:
227  case ResizeWidest:
228  newSize.setWidth( size );
229  break;
230  case ResizeTallest:
231  case ResizeShortest:
232  newSize.setHeight( size );
233  break;
234  case ResizeToSquare:
235  {
236  if ( newSize.width() > newSize.height() )
237  newSize.setHeight( newSize.width() );
238  else
239  newSize.setWidth( newSize.height() );
240  break;
241  }
242  }
243 
244  // need to keep item units
245  QgsLayoutSize newSizeWithUnits = layout->convertFromLayoutUnits( newSize, item->sizeWithUnits().units() );
246  item->attemptResize( newSizeWithUnits );
247  };
248 
249  layout->undoStack()->beginMacro( undoText( resize ) );
250  for ( QgsLayoutItem *item : items )
251  {
252  layout->undoStack()->beginCommand( item, QString() );
253  resizeItemToSize( item, newSize );
254  layout->undoStack()->endCommand();
255  }
256  layout->undoStack()->endMacro();
257 }
258 
259 QRectF QgsLayoutAligner::boundingRectOfItems( const QList<QgsLayoutItem *> &items )
260 {
261  if ( items.empty() )
262  {
263  return QRectF();
264  }
265 
266  auto it = items.constBegin();
267  //set the box to the first item
268  QgsLayoutItem *currentItem = *it;
269  it++;
270  double minX = currentItem->pos().x();
271  double minY = currentItem->pos().y();
272  double maxX = minX + currentItem->rect().width();
273  double maxY = minY + currentItem->rect().height();
274 
275  double currentMinX, currentMinY, currentMaxX, currentMaxY;
276 
277  for ( ; it != items.constEnd(); ++it )
278  {
279  currentItem = *it;
280  currentMinX = currentItem->pos().x();
281  currentMinY = currentItem->pos().y();
282  currentMaxX = currentMinX + currentItem->rect().width();
283  currentMaxY = currentMinY + currentItem->rect().height();
284 
285  if ( currentMinX < minX )
286  minX = currentMinX;
287  if ( currentMaxX > maxX )
288  maxX = currentMaxX;
289  if ( currentMinY < minY )
290  minY = currentMinY;
291  if ( currentMaxY > maxY )
292  maxY = currentMaxY;
293  }
294 
295  return QRectF( QPointF( minX, minY ), QPointF( maxX, maxY ) );
296 }
297 
298 QString QgsLayoutAligner::undoText( Distribution distribution )
299 {
300  switch ( distribution )
301  {
302  case DistributeLeft:
303  return QObject::tr( "Distribute Items by Left" );
304  case DistributeHCenter:
305  return QObject::tr( "Distribute Items by Center" );
306  case DistributeRight:
307  return QObject::tr( "Distribute Items by Right" );
308  case DistributeTop:
309  return QObject::tr( "Distribute Items by Top" );
310  case DistributeVCenter:
311  return QObject::tr( "Distribute Items by Vertical Center" );
312  case DistributeBottom:
313  return QObject::tr( "Distribute Items by Bottom" );
314  }
315  return QString(); //no warnings
316 }
317 
318 QString QgsLayoutAligner::undoText( QgsLayoutAligner::Resize resize )
319 {
320  switch ( resize )
321  {
322  case ResizeNarrowest:
323  return QObject::tr( "Resize Items to Narrowest" );
324  case ResizeWidest:
325  return QObject::tr( "Resize Items to Widest" );
326  case ResizeShortest:
327  return QObject::tr( "Resize Items to Shortest" );
328  case ResizeTallest:
329  return QObject::tr( "Resize Items to Tallest" );
330  case ResizeToSquare:
331  return QObject::tr( "Resize Items to Square" );
332  }
333  return QString(); //no warnings
334 }
335 
336 QString QgsLayoutAligner::undoText( Alignment alignment )
337 {
338  switch ( alignment )
339  {
340  case AlignLeft:
341  return QObject::tr( "Align Items to Left" );
342  case AlignHCenter:
343  return QObject::tr( "Align Items to Center" );
344  case AlignRight:
345  return QObject::tr( "Align Items to Right" );
346  case AlignTop:
347  return QObject::tr( "Align Items to Top" );
348  case AlignVCenter:
349  return QObject::tr( "Align Items to Vertical Center" );
350  case AlignBottom:
351  return QObject::tr( "Align Items to Bottom" );
352  }
353  return QString(); //no warnings
354 }
Base class for graphical items within a QgsLayout.
Align vertical centers.
QgsLayoutUndoStack * undoStack()
Returns a pointer to the layout&#39;s undo stack, which manages undo/redo states for the layout and it&#39;s ...
Definition: qgslayout.cpp:683
Resize items to square.
Resize height to match shortest height.
static void alignItems(QgsLayout *layout, const QList< QgsLayoutItem * > &items, Alignment alignment)
Aligns a set of items from a layout in place.
Distribute vertical centers.
This class provides a method of storing points, consisting of an x and y coordinate, for use in QGIS layouts.
void endCommand()
Saves final state of an object and pushes the active command to the undo history. ...
Distribution
Distribution options.
static void distributeItems(QgsLayout *layout, const QList< QgsLayoutItem * > &items, Distribution distribution)
Distributes a set of items from a layout in place.
Resize height to match tallest height.
Distribute right edges.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void beginMacro(const QString &commandText)
Starts a macro command, with the given descriptive commandText.
Resize width to match widest width.
void beginCommand(QgsLayoutUndoObjectInterface *object, const QString &commandText, int id=0)
Begins a new undo command for the specified object.
Align horizontal centers.
QgsLayoutMeasurement convertFromLayoutUnits(double length, QgsUnitTypes::LayoutUnit unit) const
Converts a length measurement from the layout&#39;s native units to a specified target unit...
Definition: qgslayout.cpp:342
Alignment
Alignment options.
Distribute horizontal centers.
Resize
Resize options.
void endMacro()
Ends a macro command.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:40
static void resizeItems(QgsLayout *layout, const QList< QgsLayoutItem * > &items, Resize resize)
Resizes a set of items from a layout in place.
Distribute bottom edges.
Resize width to match narrowest width.