Skip to content

Commit 5141c05

Browse files
committed
[FEATURE][layout] Add item distribution actions
Adds actions for distributing selections of items by their left/center/right/top/bottom edges. Useful for quickly evenly distributing items in the layout Needs new icons
1 parent c5fcc9d commit 5141c05

File tree

10 files changed

+543
-57
lines changed

10 files changed

+543
-57
lines changed

python/core/layout/qgslayoutaligner.sip

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,22 @@ class QgsLayoutAligner
2626

2727
enum Alignment
2828
{
29-
Left,
30-
HCenter,
31-
Right,
32-
Top,
33-
VCenter,
34-
Bottom,
29+
AlignLeft,
30+
AlignHCenter,
31+
AlignRight,
32+
AlignTop,
33+
AlignVCenter,
34+
AlignBottom,
35+
};
36+
37+
enum Distribution
38+
{
39+
DistributeLeft,
40+
DistributeHCenter,
41+
DistributeRight,
42+
DistributeTop,
43+
DistributeVCenter,
44+
DistributeBottom,
3545
};
3646

3747
static void alignItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Alignment alignment );
@@ -41,6 +51,13 @@ class QgsLayoutAligner
4151
The ``alignment`` argument specifies the method to use when aligning the items.
4252
%End
4353

54+
static void distributeItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Distribution distribution );
55+
%Docstring
56+
Distributes a set of ``items`` from a ``layout`` in place.
57+
58+
The ``distribution`` argument specifies the method to use when distributing the items.
59+
%End
60+
4461
};
4562

4663
/************************************************************************

python/gui/layout/qgslayoutview.sip

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ class QgsLayoutView: QGraphicsView
130130
void alignSelectedItems( QgsLayoutAligner::Alignment alignment );
131131
%Docstring
132132
Aligns all selected items using the specified ``alignment``.
133+
.. seealso:: distributeSelectedItems()
134+
%End
135+
136+
void distributeSelectedItems( QgsLayoutAligner::Distribution distribution );
137+
%Docstring
138+
Distributes all selected items using the specified ``distribution``.
139+
see alignSelectedItems()
133140
%End
134141

135142
public slots:

src/app/layout/qgslayoutdesignerdialog.cpp

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
183183
alignToolButton->setPopupMode( QToolButton::InstantPopup );
184184
alignToolButton->setAutoRaise( true );
185185
alignToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
186-
187186
alignToolButton->addAction( mActionAlignLeft );
188187
alignToolButton->addAction( mActionAlignHCenter );
189188
alignToolButton->addAction( mActionAlignRight );
@@ -193,6 +192,19 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
193192
alignToolButton->setDefaultAction( mActionAlignLeft );
194193
mActionsToolbar->addWidget( alignToolButton );
195194

195+
QToolButton *distributeToolButton = new QToolButton( this );
196+
distributeToolButton->setPopupMode( QToolButton::InstantPopup );
197+
distributeToolButton->setAutoRaise( true );
198+
distributeToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
199+
distributeToolButton->addAction( mActionDistributeLeft );
200+
distributeToolButton->addAction( mActionDistributeHCenter );
201+
distributeToolButton->addAction( mActionDistributeRight );
202+
distributeToolButton->addAction( mActionDistributeTop );
203+
distributeToolButton->addAction( mActionDistributeVCenter );
204+
distributeToolButton->addAction( mActionDistributeBottom );
205+
distributeToolButton->setDefaultAction( mActionDistributeLeft );
206+
mActionsToolbar->addWidget( distributeToolButton );
207+
196208
mAddItemTool = new QgsLayoutViewToolAddItem( mView );
197209
mPanTool = new QgsLayoutViewToolPan( mView );
198210
mPanTool->setAction( mActionPan );
@@ -229,27 +241,51 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
229241
connect( mActionMoveItemsToBottom, &QAction::triggered, this, &QgsLayoutDesignerDialog::moveSelectedItemsToBottom );
230242
connect( mActionAlignLeft, &QAction::triggered, this, [ = ]
231243
{
232-
mView->alignSelectedItems( QgsLayoutAligner::Left );
244+
mView->alignSelectedItems( QgsLayoutAligner::AlignLeft );
233245
} );
234246
connect( mActionAlignHCenter, &QAction::triggered, this, [ = ]
235247
{
236-
mView->alignSelectedItems( QgsLayoutAligner::HCenter );
248+
mView->alignSelectedItems( QgsLayoutAligner::AlignHCenter );
237249
} );
238250
connect( mActionAlignRight, &QAction::triggered, this, [ = ]
239251
{
240-
mView->alignSelectedItems( QgsLayoutAligner::Right );
252+
mView->alignSelectedItems( QgsLayoutAligner::AlignRight );
241253
} );
242254
connect( mActionAlignTop, &QAction::triggered, this, [ = ]
243255
{
244-
mView->alignSelectedItems( QgsLayoutAligner::Top );
256+
mView->alignSelectedItems( QgsLayoutAligner::AlignTop );
245257
} );
246258
connect( mActionAlignVCenter, &QAction::triggered, this, [ = ]
247259
{
248-
mView->alignSelectedItems( QgsLayoutAligner::VCenter );
260+
mView->alignSelectedItems( QgsLayoutAligner::AlignVCenter );
249261
} );
250262
connect( mActionAlignBottom, &QAction::triggered, this, [ = ]
251263
{
252-
mView->alignSelectedItems( QgsLayoutAligner::Bottom );
264+
mView->alignSelectedItems( QgsLayoutAligner::AlignBottom );
265+
} );
266+
connect( mActionDistributeLeft, &QAction::triggered, this, [ = ]
267+
{
268+
mView->distributeSelectedItems( QgsLayoutAligner::DistributeLeft );
269+
} );
270+
connect( mActionDistributeHCenter, &QAction::triggered, this, [ = ]
271+
{
272+
mView->distributeSelectedItems( QgsLayoutAligner::DistributeHCenter );
273+
} );
274+
connect( mActionDistributeRight, &QAction::triggered, this, [ = ]
275+
{
276+
mView->distributeSelectedItems( QgsLayoutAligner::DistributeRight );
277+
} );
278+
connect( mActionDistributeTop, &QAction::triggered, this, [ = ]
279+
{
280+
mView->distributeSelectedItems( QgsLayoutAligner::DistributeTop );
281+
} );
282+
connect( mActionDistributeVCenter, &QAction::triggered, this, [ = ]
283+
{
284+
mView->distributeSelectedItems( QgsLayoutAligner::DistributeVCenter );
285+
} );
286+
connect( mActionDistributeBottom, &QAction::triggered, this, [ = ]
287+
{
288+
mView->distributeSelectedItems( QgsLayoutAligner::DistributeBottom );
253289
} );
254290

255291
connect( mActionAddPages, &QAction::triggered, this, &QgsLayoutDesignerDialog::addPages );

src/core/layout/qgslayoutaligner.cpp

Lines changed: 138 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,50 +34,50 @@ void QgsLayoutAligner::alignItems( QgsLayout *layout, const QList<QgsLayoutItem
3434
double refCoord = 0;
3535
switch ( alignment )
3636
{
37-
case Left:
37+
case AlignLeft:
3838
refCoord = itemBBox.left();
3939
break;
40-
case HCenter:
40+
case AlignHCenter:
4141
refCoord = itemBBox.center().x();
4242
break;
43-
case Right:
43+
case AlignRight:
4444
refCoord = itemBBox.right();
4545
break;
46-
case Top:
46+
case AlignTop:
4747
refCoord = itemBBox.top();
4848
break;
49-
case VCenter:
49+
case AlignVCenter:
5050
refCoord = itemBBox.center().y();
5151
break;
52-
case Bottom:
52+
case AlignBottom:
5353
refCoord = itemBBox.bottom();
5454
break;
5555
}
5656

57-
layout->undoStack()->beginMacro( QObject::tr( "Aligned items bottom" ) );
57+
layout->undoStack()->beginMacro( undoText( alignment ) );
5858
for ( QgsLayoutItem *item : items )
5959
{
6060
layout->undoStack()->beginCommand( item, QString() );
6161

6262
QPointF shifted = item->pos();
6363
switch ( alignment )
6464
{
65-
case Left:
65+
case AlignLeft:
6666
shifted.setX( refCoord );
6767
break;
68-
case HCenter:
68+
case AlignHCenter:
6969
shifted.setX( refCoord - item->rect().width() / 2.0 );
7070
break;
71-
case Right:
71+
case AlignRight:
7272
shifted.setX( refCoord - item->rect().width() );
7373
break;
74-
case Top:
74+
case AlignTop:
7575
shifted.setY( refCoord );
7676
break;
77-
case VCenter:
77+
case AlignVCenter:
7878
shifted.setY( refCoord - item->rect().height() / 2.0 );
7979
break;
80-
case Bottom:
80+
case AlignBottom:
8181
shifted.setY( refCoord - item->rect().height() );
8282
break;
8383
}
@@ -91,6 +91,91 @@ void QgsLayoutAligner::alignItems( QgsLayout *layout, const QList<QgsLayoutItem
9191
layout->undoStack()->endMacro();
9292
}
9393

94+
void QgsLayoutAligner::distributeItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Distribution distribution )
95+
{
96+
if ( items.size() < 2 )
97+
return;
98+
99+
auto collectReferenceCoord = [distribution]( QgsLayoutItem * item )->double
100+
{
101+
QRectF itemBBox = item->sceneBoundingRect();
102+
switch ( distribution )
103+
{
104+
case AlignLeft:
105+
return itemBBox.left();
106+
case AlignHCenter:
107+
return itemBBox.center().x();
108+
case AlignRight:
109+
return itemBBox.right();
110+
case AlignTop:
111+
return itemBBox.top();
112+
case AlignVCenter:
113+
return itemBBox.center().y();
114+
case AlignBottom:
115+
return itemBBox.bottom();
116+
}
117+
// no warnings
118+
return itemBBox.left();
119+
};
120+
121+
122+
double minCoord = DBL_MAX;
123+
double maxCoord = -DBL_MAX;
124+
QMap< double, QgsLayoutItem * > itemCoords;
125+
for ( QgsLayoutItem *item : items )
126+
{
127+
double refCoord = collectReferenceCoord( item );
128+
minCoord = std::min( minCoord, refCoord );
129+
maxCoord = std::max( maxCoord, refCoord );
130+
itemCoords.insert( refCoord, item );
131+
}
132+
133+
double step = ( maxCoord - minCoord ) / ( items.size() - 1 );
134+
135+
auto distributeItemToCoord = [layout, distribution]( QgsLayoutItem * item, double refCoord )
136+
{
137+
QPointF shifted = item->pos();
138+
switch ( distribution )
139+
{
140+
case DistributeLeft:
141+
shifted.setX( refCoord );
142+
break;
143+
case DistributeHCenter:
144+
shifted.setX( refCoord - item->rect().width() / 2.0 );
145+
break;
146+
case DistributeRight:
147+
shifted.setX( refCoord - item->rect().width() );
148+
break;
149+
case DistributeTop:
150+
shifted.setY( refCoord );
151+
break;
152+
case DistributeVCenter:
153+
shifted.setY( refCoord - item->rect().height() / 2.0 );
154+
break;
155+
case DistributeBottom:
156+
shifted.setY( refCoord - item->rect().height() );
157+
break;
158+
}
159+
160+
// need to keep item units
161+
QgsLayoutPoint newPos = layout->convertFromLayoutUnits( shifted, item->positionWithUnits().units() );
162+
item->attemptMove( newPos );
163+
};
164+
165+
166+
layout->undoStack()->beginMacro( undoText( distribution ) );
167+
double currentVal = minCoord;
168+
for ( auto itemIt = itemCoords.constBegin(); itemIt != itemCoords.constEnd(); ++itemIt )
169+
{
170+
layout->undoStack()->beginCommand( itemIt.value(), QString() );
171+
distributeItemToCoord( itemIt.value(), currentVal );
172+
layout->undoStack()->endCommand();
173+
174+
currentVal += step;
175+
}
176+
layout->undoStack()->endMacro();
177+
}
178+
94179
QRectF QgsLayoutAligner::boundingRectOfItems( const QList<QgsLayoutItem *> &items )
95180
{
96181
if ( items.empty() )
@@ -129,3 +214,43 @@ QRectF QgsLayoutAligner::boundingRectOfItems( const QList<QgsLayoutItem *> &item
129214

130215
return QRectF( QPointF( minX, minY ), QPointF( maxX, maxY ) );
131216
}
217+
218+
QString QgsLayoutAligner::undoText( Distribution distribution )
219+
{
220+
switch ( distribution )
221+
{
222+
case DistributeLeft:
223+
return QObject::tr( "Distributed items by left" );
224+
case DistributeHCenter:
225+
return QObject::tr( "Distributed items by center" );
226+
case DistributeRight:
227+
return QObject::tr( "Distributed items by right" );
228+
case DistributeTop:
229+
return QObject::tr( "Distributed items by top" );
230+
case DistributeVCenter:
231+
return QObject::tr( "Distributed items by vertical center" );
232+
case DistributeBottom:
233+
return QObject::tr( "Distributed items by bottom" );
234+
}
235+
return QString(); //no warnings
236+
}
237+
238+
QString QgsLayoutAligner::undoText( Alignment alignment )
239+
{
240+
switch ( alignment )
241+
{
242+
case AlignLeft:
243+
return QObject::tr( "Aligned items to left" );
244+
case AlignHCenter:
245+
return QObject::tr( "Aligned items to center" );
246+
case AlignRight:
247+
return QObject::tr( "Aligned items to right" );
248+
case AlignTop:
249+
return QObject::tr( "Aligned items to top" );
250+
case AlignVCenter:
251+
return QObject::tr( "Aligned items to vertical center" );
252+
case AlignBottom:
253+
return QObject::tr( "Aligned items to bottom" );
254+
}
255+
return QString(); //no warnings
256+
}

src/core/layout/qgslayoutaligner.h

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,23 @@ class CORE_EXPORT QgsLayoutAligner
4141
//! Alignment options
4242
enum Alignment
4343
{
44-
Left, //!< Align left edges
45-
HCenter, //!< Align horizontal centers
46-
Right, //!< Align right edges
47-
Top, //!< Align top edges
48-
VCenter, //!< Align vertical centers
49-
Bottom, //!< Align bottom edges
44+
AlignLeft, //!< Align left edges
45+
AlignHCenter, //!< Align horizontal centers
46+
AlignRight, //!< Align right edges
47+
AlignTop, //!< Align top edges
48+
AlignVCenter, //!< Align vertical centers
49+
AlignBottom, //!< Align bottom edges
50+
};
51+
52+
//! Distribution options
53+
enum Distribution
54+
{
55+
DistributeLeft, //!< Distribute left edges
56+
DistributeHCenter, //!< Distribute horizontal centers
57+
DistributeRight, //!< Distribute right edges
58+
DistributeTop, //!< Distribute top edges
59+
DistributeVCenter, //!< Distribute vertical centers
60+
DistributeBottom, //!< Distribute bottom edges
5061
};
5162

5263
/**
@@ -56,6 +67,13 @@ class CORE_EXPORT QgsLayoutAligner
5667
*/
5768
static void alignItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Alignment alignment );
5869

70+
/**
71+
* Distributes a set of \a items from a \a layout in place.
72+
*
73+
* The \a distribution argument specifies the method to use when distributing the items.
74+
*/
75+
static void distributeItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Distribution distribution );
76+
5977
private:
6078

6179
/**
@@ -64,6 +82,9 @@ class CORE_EXPORT QgsLayoutAligner
6482
*/
6583
static QRectF boundingRectOfItems( const QList< QgsLayoutItem * > &items );
6684

85+
static QString undoText( Alignment alignment );
86+
static QString undoText( Distribution distribution );
87+
6788

6889

6990
};

0 commit comments

Comments
 (0)