Skip to content

Commit e3616f0

Browse files
committed
[FEATURE][layout] Add actions to auto resize items in a selection
Options are resize to narrowest, widest, shortest and tallest.
1 parent 5141c05 commit e3616f0

File tree

10 files changed

+319
-2
lines changed

10 files changed

+319
-2
lines changed

python/core/layout/qgslayoutaligner.sip

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ class QgsLayoutAligner
4444
DistributeBottom,
4545
};
4646

47+
enum Resize
48+
{
49+
ResizeNarrowest,
50+
ResizeWidest,
51+
ResizeShortest,
52+
ResizeTallest,
53+
};
54+
4755
static void alignItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Alignment alignment );
4856
%Docstring
4957
Aligns a set of ``items`` from a ``layout`` in place.
@@ -58,6 +66,13 @@ class QgsLayoutAligner
5866
The ``distribution`` argument specifies the method to use when distributing the items.
5967
%End
6068

69+
static void resizeItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Resize resize );
70+
%Docstring
71+
Resizes a set of ``items`` from a ``layout`` in place.
72+
73+
The ``resize`` argument specifies the method to use when resizing the items.
74+
%End
75+
6176
};
6277

6378
/************************************************************************

python/gui/layout/qgslayoutview.sip

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,21 @@ class QgsLayoutView: QGraphicsView
131131
%Docstring
132132
Aligns all selected items using the specified ``alignment``.
133133
.. seealso:: distributeSelectedItems()
134+
.. seealso:: resizeSelectedItems()
134135
%End
135136

136137
void distributeSelectedItems( QgsLayoutAligner::Distribution distribution );
137138
%Docstring
138139
Distributes all selected items using the specified ``distribution``.
139-
see alignSelectedItems()
140+
.. seealso:: alignSelectedItems()
141+
.. seealso:: resizeSelectedItems()
142+
%End
143+
144+
void resizeSelectedItems( QgsLayoutAligner::Resize resize );
145+
%Docstring
146+
Resizes all selected items using the specified ``resize`` mode.
147+
.. seealso:: alignSelectedItems()
148+
.. seealso:: distributeSelectedItems()
140149
%End
141150

142151
public slots:

src/app/layout/qgslayoutdesignerdialog.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,17 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
205205
distributeToolButton->setDefaultAction( mActionDistributeLeft );
206206
mActionsToolbar->addWidget( distributeToolButton );
207207

208+
QToolButton *resizeToolButton = new QToolButton( this );
209+
resizeToolButton->setPopupMode( QToolButton::InstantPopup );
210+
resizeToolButton->setAutoRaise( true );
211+
resizeToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
212+
resizeToolButton->addAction( mActionResizeNarrowest );
213+
resizeToolButton->addAction( mActionResizeWidest );
214+
resizeToolButton->addAction( mActionResizeShortest );
215+
resizeToolButton->addAction( mActionResizeTallest );
216+
resizeToolButton->setDefaultAction( mActionResizeNarrowest );
217+
mActionsToolbar->addWidget( resizeToolButton );
218+
208219
mAddItemTool = new QgsLayoutViewToolAddItem( mView );
209220
mPanTool = new QgsLayoutViewToolPan( mView );
210221
mPanTool->setAction( mActionPan );
@@ -287,6 +298,22 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
287298
{
288299
mView->distributeSelectedItems( QgsLayoutAligner::DistributeBottom );
289300
} );
301+
connect( mActionResizeNarrowest, &QAction::triggered, this, [ = ]
302+
{
303+
mView->resizeSelectedItems( QgsLayoutAligner::ResizeNarrowest );
304+
} );
305+
connect( mActionResizeWidest, &QAction::triggered, this, [ = ]
306+
{
307+
mView->resizeSelectedItems( QgsLayoutAligner::ResizeWidest );
308+
} );
309+
connect( mActionResizeShortest, &QAction::triggered, this, [ = ]
310+
{
311+
mView->resizeSelectedItems( QgsLayoutAligner::ResizeShortest );
312+
} );
313+
connect( mActionResizeTallest, &QAction::triggered, this, [ = ]
314+
{
315+
mView->resizeSelectedItems( QgsLayoutAligner::ResizeTallest );
316+
} );
290317

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

src/core/layout/qgslayoutaligner.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,74 @@ void QgsLayoutAligner::distributeItems( QgsLayout *layout, const QList<QgsLayout
176176
layout->undoStack()->endMacro();
177177
}
178178

179+
void QgsLayoutAligner::resizeItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Resize resize )
180+
{
181+
if ( items.size() < 2 )
182+
return;
183+
184+
auto collectSize = [resize]( QgsLayoutItem * item )->double
185+
{
186+
QRectF itemBBox = item->sceneBoundingRect();
187+
switch ( resize )
188+
{
189+
case ResizeNarrowest:
190+
case ResizeWidest:
191+
return itemBBox.width();
192+
case ResizeShortest:
193+
case ResizeTallest:
194+
return itemBBox.height();
195+
}
196+
// no warnings
197+
return itemBBox.width();
198+
};
199+
200+
double newSize = collectSize( items.at( 0 ) );
201+
for ( QgsLayoutItem *item : items )
202+
{
203+
double size = collectSize( item );
204+
switch ( resize )
205+
{
206+
case ResizeNarrowest:
207+
case ResizeShortest:
208+
newSize = std::min( size, newSize );
209+
break;
210+
case ResizeTallest:
211+
case ResizeWidest:
212+
newSize = std::max( size, newSize );
213+
break;
214+
}
215+
}
216+
217+
auto resizeItemToSize = [layout, resize]( QgsLayoutItem * item, double size )
218+
{
219+
QSizeF newSize = item->rect().size();
220+
switch ( resize )
221+
{
222+
case ResizeNarrowest:
223+
case ResizeWidest:
224+
newSize.setWidth( size );
225+
break;
226+
case ResizeTallest:
227+
case ResizeShortest:
228+
newSize.setHeight( size );
229+
break;
230+
}
231+
232+
// need to keep item units
233+
QgsLayoutSize newSizeWithUnits = layout->convertFromLayoutUnits( newSize, item->sizeWithUnits().units() );
234+
item->attemptResize( newSizeWithUnits );
235+
};
236+
237+
layout->undoStack()->beginMacro( undoText( resize ) );
238+
for ( QgsLayoutItem *item : items )
239+
{
240+
layout->undoStack()->beginCommand( item, QString() );
241+
resizeItemToSize( item, newSize );
242+
layout->undoStack()->endCommand();
243+
}
244+
layout->undoStack()->endMacro();
245+
}
246+
179247
QRectF QgsLayoutAligner::boundingRectOfItems( const QList<QgsLayoutItem *> &items )
180248
{
181249
if ( items.empty() )
@@ -235,6 +303,22 @@ QString QgsLayoutAligner::undoText( Distribution distribution )
235303
return QString(); //no warnings
236304
}
237305

306+
QString QgsLayoutAligner::undoText( QgsLayoutAligner::Resize resize )
307+
{
308+
switch ( resize )
309+
{
310+
case ResizeNarrowest:
311+
return QObject::tr( "Resized items to narrowest" );
312+
case ResizeWidest:
313+
return QObject::tr( "Resized items to widest" );
314+
case ResizeShortest:
315+
return QObject::tr( "Resized items to shortest" );
316+
case ResizeTallest:
317+
return QObject::tr( "Resized items to tallest" );
318+
}
319+
return QString(); //no warnings
320+
}
321+
238322
QString QgsLayoutAligner::undoText( Alignment alignment )
239323
{
240324
switch ( alignment )

src/core/layout/qgslayoutaligner.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ class CORE_EXPORT QgsLayoutAligner
6060
DistributeBottom, //!< Distribute bottom edges
6161
};
6262

63+
//! Resize options
64+
enum Resize
65+
{
66+
ResizeNarrowest, //!< Resize width to match narrowest width
67+
ResizeWidest, //!< Resize width to match widest width
68+
ResizeShortest, //!< Resize height to match shortest height
69+
ResizeTallest, //!< Resize height to match tallest height
70+
};
71+
6372
/**
6473
* Aligns a set of \a items from a \a layout in place.
6574
*
@@ -74,6 +83,13 @@ class CORE_EXPORT QgsLayoutAligner
7483
*/
7584
static void distributeItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Distribution distribution );
7685

86+
/**
87+
* Resizes a set of \a items from a \a layout in place.
88+
*
89+
* The \a resize argument specifies the method to use when resizing the items.
90+
*/
91+
static void resizeItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Resize resize );
92+
7793
private:
7894

7995
/**
@@ -84,6 +100,7 @@ class CORE_EXPORT QgsLayoutAligner
84100

85101
static QString undoText( Alignment alignment );
86102
static QString undoText( Distribution distribution );
103+
static QString undoText( Resize resize );
87104

88105

89106

src/gui/layout/qgslayoutview.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,12 @@ void QgsLayoutView::distributeSelectedItems( QgsLayoutAligner::Distribution dist
242242
QgsLayoutAligner::distributeItems( currentLayout(), selectedItems, distribution );
243243
}
244244

245+
void QgsLayoutView::resizeSelectedItems( QgsLayoutAligner::Resize resize )
246+
{
247+
const QList<QgsLayoutItem *> selectedItems = currentLayout()->selectedLayoutItems();
248+
QgsLayoutAligner::resizeItems( currentLayout(), selectedItems, resize );
249+
}
250+
245251
void QgsLayoutView::zoomFull()
246252
{
247253
fitInView( scene()->sceneRect(), Qt::KeepAspectRatio );

src/gui/layout/qgslayoutview.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,24 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView
161161
/**
162162
* Aligns all selected items using the specified \a alignment.
163163
* \see distributeSelectedItems()
164+
* \see resizeSelectedItems()
164165
*/
165166
void alignSelectedItems( QgsLayoutAligner::Alignment alignment );
166167

167168
/**
168169
* Distributes all selected items using the specified \a distribution.
169-
* see alignSelectedItems()
170+
* \see alignSelectedItems()
171+
* \see resizeSelectedItems()
170172
*/
171173
void distributeSelectedItems( QgsLayoutAligner::Distribution distribution );
172174

175+
/**
176+
* Resizes all selected items using the specified \a resize mode.
177+
* \see alignSelectedItems()
178+
* \see distributeSelectedItems()
179+
*/
180+
void resizeSelectedItems( QgsLayoutAligner::Resize resize );
181+
173182
public slots:
174183

175184
/**

src/ui/layout/qgslayoutdesignerbase.ui

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,16 @@
180180
<addaction name="mActionDistributeVCenter"/>
181181
<addaction name="mActionDistributeBottom"/>
182182
</widget>
183+
<widget class="QMenu" name="menuResize">
184+
<property name="title">
185+
<string>Re&amp;size</string>
186+
</property>
187+
<addaction name="mActionResizeNarrowest"/>
188+
<addaction name="mActionResizeWidest"/>
189+
<addaction name="separator"/>
190+
<addaction name="mActionResizeShortest"/>
191+
<addaction name="mActionResizeTallest"/>
192+
</widget>
183193
<addaction name="mActionRaiseItems"/>
184194
<addaction name="mActionLowerItems"/>
185195
<addaction name="mActionMoveItemsToTop"/>
@@ -190,6 +200,7 @@
190200
<addaction name="separator"/>
191201
<addaction name="menuAlign_Items"/>
192202
<addaction name="menu_Distribute_Items"/>
203+
<addaction name="menuResize"/>
193204
</widget>
194205
<addaction name="mLayoutMenu"/>
195206
<addaction name="menuEdit"/>
@@ -835,6 +846,54 @@
835846
<string>Distributes bottom edges of items equidistantly</string>
836847
</property>
837848
</action>
849+
<action name="mActionResizeNarrowest">
850+
<property name="icon">
851+
<iconset resource="../../../images/images.qrc">
852+
<normaloff>:/images/themes/default/mActionAlignLeft.svg</normaloff>:/images/themes/default/mActionAlignLeft.svg</iconset>
853+
</property>
854+
<property name="text">
855+
<string>&amp;Narrowest</string>
856+
</property>
857+
<property name="toolTip">
858+
<string>Resizes item width to match the narrowest selected item</string>
859+
</property>
860+
</action>
861+
<action name="mActionResizeWidest">
862+
<property name="icon">
863+
<iconset resource="../../../images/images.qrc">
864+
<normaloff>:/images/themes/default/mActionAlignHCenter.svg</normaloff>:/images/themes/default/mActionAlignHCenter.svg</iconset>
865+
</property>
866+
<property name="text">
867+
<string>&amp;Widest</string>
868+
</property>
869+
<property name="toolTip">
870+
<string>Resizes item width to match the widest selected item</string>
871+
</property>
872+
</action>
873+
<action name="mActionResizeShortest">
874+
<property name="icon">
875+
<iconset resource="../../../images/images.qrc">
876+
<normaloff>:/images/themes/default/mActionAlignRight.svg</normaloff>:/images/themes/default/mActionAlignRight.svg</iconset>
877+
</property>
878+
<property name="text">
879+
<string>&amp;Shortest</string>
880+
</property>
881+
<property name="toolTip">
882+
<string>Resizes item height to match the shortest selected item</string>
883+
</property>
884+
</action>
885+
<action name="mActionResizeTallest">
886+
<property name="icon">
887+
<iconset resource="../../../images/images.qrc">
888+
<normaloff>:/images/themes/default/mActionAlignTop.svg</normaloff>:/images/themes/default/mActionAlignTop.svg</iconset>
889+
</property>
890+
<property name="text">
891+
<string>&amp;Tallest</string>
892+
</property>
893+
<property name="toolTip">
894+
<string>Resizes item height to match the tallest selected item</string>
895+
</property>
896+
</action>
838897
</widget>
839898
<resources>
840899
<include location="../../../images/images.qrc"/>

tests/src/python/test_qgslayoutaligner.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,47 @@ def testDistribute(self):
188188
self.assertEqual(item3.positionWithUnits().units(), QgsUnitTypes.LayoutCentimeters)
189189
self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
190190

191+
def testResize(self):
192+
p = QgsProject()
193+
l = QgsLayout(p)
194+
195+
# add some items
196+
item1 = QgsLayoutItemMap(l)
197+
item1.attemptMove(QgsLayoutPoint(4, 8, QgsUnitTypes.LayoutMillimeters))
198+
item1.attemptResize(QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
199+
l.addItem(item1)
200+
item2 = QgsLayoutItemMap(l)
201+
item2.attemptMove(QgsLayoutPoint(7, 10, QgsUnitTypes.LayoutMillimeters))
202+
item2.attemptResize(QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
203+
l.addItem(item2)
204+
item3 = QgsLayoutItemMap(l)
205+
item3.attemptMove(QgsLayoutPoint(0.8, 1.2, QgsUnitTypes.LayoutCentimeters))
206+
item3.attemptResize(QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
207+
l.addItem(item3)
208+
209+
QgsLayoutAligner.resizeItems(l, [item1, item2, item3], QgsLayoutAligner.ResizeNarrowest)
210+
self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(10, 12, QgsUnitTypes.LayoutMillimeters))
211+
self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
212+
self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.0, 1.6, QgsUnitTypes.LayoutCentimeters))
213+
l.undoStack().stack().undo()
214+
215+
QgsLayoutAligner.resizeItems(l, [item1, item2, item3], QgsLayoutAligner.ResizeWidest)
216+
self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 12, QgsUnitTypes.LayoutMillimeters))
217+
self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(18, 9, QgsUnitTypes.LayoutMillimeters))
218+
self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
219+
l.undoStack().stack().undo()
220+
221+
QgsLayoutAligner.resizeItems(l, [item1, item2, item3], QgsLayoutAligner.ResizeShortest)
222+
self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 9, QgsUnitTypes.LayoutMillimeters))
223+
self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 9, QgsUnitTypes.LayoutMillimeters))
224+
self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 0.9, QgsUnitTypes.LayoutCentimeters))
225+
l.undoStack().stack().undo()
226+
227+
QgsLayoutAligner.resizeItems(l, [item1, item2, item3], QgsLayoutAligner.ResizeTallest)
228+
self.assertEqual(item1.sizeWithUnits(), QgsLayoutSize(18, 16, QgsUnitTypes.LayoutMillimeters))
229+
self.assertEqual(item2.sizeWithUnits(), QgsLayoutSize(10, 16, QgsUnitTypes.LayoutMillimeters))
230+
self.assertEqual(item3.sizeWithUnits(), QgsLayoutSize(1.8, 1.6, QgsUnitTypes.LayoutCentimeters))
231+
191232

192233
if __name__ == '__main__':
193234
unittest.main()

0 commit comments

Comments
 (0)