Skip to content

Commit

Permalink
[FEATURE][layout] Add item distribution actions
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nyalldawson committed Oct 6, 2017
1 parent c5fcc9d commit 5141c05
Show file tree
Hide file tree
Showing 10 changed files with 543 additions and 57 deletions.
29 changes: 23 additions & 6 deletions python/core/layout/qgslayoutaligner.sip
Expand Up @@ -26,12 +26,22 @@ class QgsLayoutAligner

enum Alignment
{
Left,
HCenter,
Right,
Top,
VCenter,
Bottom,
AlignLeft,
AlignHCenter,
AlignRight,
AlignTop,
AlignVCenter,
AlignBottom,
};

enum Distribution
{
DistributeLeft,
DistributeHCenter,
DistributeRight,
DistributeTop,
DistributeVCenter,
DistributeBottom,
};

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

static void distributeItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Distribution distribution );
%Docstring
Distributes a set of ``items`` from a ``layout`` in place.

The ``distribution`` argument specifies the method to use when distributing the items.
%End

};

/************************************************************************
Expand Down
7 changes: 7 additions & 0 deletions python/gui/layout/qgslayoutview.sip
Expand Up @@ -130,6 +130,13 @@ class QgsLayoutView: QGraphicsView
void alignSelectedItems( QgsLayoutAligner::Alignment alignment );
%Docstring
Aligns all selected items using the specified ``alignment``.
.. seealso:: distributeSelectedItems()
%End

void distributeSelectedItems( QgsLayoutAligner::Distribution distribution );
%Docstring
Distributes all selected items using the specified ``distribution``.
see alignSelectedItems()
%End

public slots:
Expand Down
50 changes: 43 additions & 7 deletions src/app/layout/qgslayoutdesignerdialog.cpp
Expand Up @@ -183,7 +183,6 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
alignToolButton->setPopupMode( QToolButton::InstantPopup );
alignToolButton->setAutoRaise( true );
alignToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );

alignToolButton->addAction( mActionAlignLeft );
alignToolButton->addAction( mActionAlignHCenter );
alignToolButton->addAction( mActionAlignRight );
Expand All @@ -193,6 +192,19 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
alignToolButton->setDefaultAction( mActionAlignLeft );
mActionsToolbar->addWidget( alignToolButton );

QToolButton *distributeToolButton = new QToolButton( this );
distributeToolButton->setPopupMode( QToolButton::InstantPopup );
distributeToolButton->setAutoRaise( true );
distributeToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
distributeToolButton->addAction( mActionDistributeLeft );
distributeToolButton->addAction( mActionDistributeHCenter );
distributeToolButton->addAction( mActionDistributeRight );
distributeToolButton->addAction( mActionDistributeTop );
distributeToolButton->addAction( mActionDistributeVCenter );
distributeToolButton->addAction( mActionDistributeBottom );
distributeToolButton->setDefaultAction( mActionDistributeLeft );
mActionsToolbar->addWidget( distributeToolButton );

mAddItemTool = new QgsLayoutViewToolAddItem( mView );
mPanTool = new QgsLayoutViewToolPan( mView );
mPanTool->setAction( mActionPan );
Expand Down Expand Up @@ -229,27 +241,51 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
connect( mActionMoveItemsToBottom, &QAction::triggered, this, &QgsLayoutDesignerDialog::moveSelectedItemsToBottom );
connect( mActionAlignLeft, &QAction::triggered, this, [ = ]
{
mView->alignSelectedItems( QgsLayoutAligner::Left );
mView->alignSelectedItems( QgsLayoutAligner::AlignLeft );
} );
connect( mActionAlignHCenter, &QAction::triggered, this, [ = ]
{
mView->alignSelectedItems( QgsLayoutAligner::HCenter );
mView->alignSelectedItems( QgsLayoutAligner::AlignHCenter );
} );
connect( mActionAlignRight, &QAction::triggered, this, [ = ]
{
mView->alignSelectedItems( QgsLayoutAligner::Right );
mView->alignSelectedItems( QgsLayoutAligner::AlignRight );
} );
connect( mActionAlignTop, &QAction::triggered, this, [ = ]
{
mView->alignSelectedItems( QgsLayoutAligner::Top );
mView->alignSelectedItems( QgsLayoutAligner::AlignTop );
} );
connect( mActionAlignVCenter, &QAction::triggered, this, [ = ]
{
mView->alignSelectedItems( QgsLayoutAligner::VCenter );
mView->alignSelectedItems( QgsLayoutAligner::AlignVCenter );
} );
connect( mActionAlignBottom, &QAction::triggered, this, [ = ]
{
mView->alignSelectedItems( QgsLayoutAligner::Bottom );
mView->alignSelectedItems( QgsLayoutAligner::AlignBottom );
} );
connect( mActionDistributeLeft, &QAction::triggered, this, [ = ]
{
mView->distributeSelectedItems( QgsLayoutAligner::DistributeLeft );
} );
connect( mActionDistributeHCenter, &QAction::triggered, this, [ = ]
{
mView->distributeSelectedItems( QgsLayoutAligner::DistributeHCenter );
} );
connect( mActionDistributeRight, &QAction::triggered, this, [ = ]
{
mView->distributeSelectedItems( QgsLayoutAligner::DistributeRight );
} );
connect( mActionDistributeTop, &QAction::triggered, this, [ = ]
{
mView->distributeSelectedItems( QgsLayoutAligner::DistributeTop );
} );
connect( mActionDistributeVCenter, &QAction::triggered, this, [ = ]
{
mView->distributeSelectedItems( QgsLayoutAligner::DistributeVCenter );
} );
connect( mActionDistributeBottom, &QAction::triggered, this, [ = ]
{
mView->distributeSelectedItems( QgsLayoutAligner::DistributeBottom );
} );

connect( mActionAddPages, &QAction::triggered, this, &QgsLayoutDesignerDialog::addPages );
Expand Down
151 changes: 138 additions & 13 deletions src/core/layout/qgslayoutaligner.cpp
Expand Up @@ -34,50 +34,50 @@ void QgsLayoutAligner::alignItems( QgsLayout *layout, const QList<QgsLayoutItem
double refCoord = 0;
switch ( alignment )
{
case Left:
case AlignLeft:
refCoord = itemBBox.left();
break;
case HCenter:
case AlignHCenter:
refCoord = itemBBox.center().x();
break;
case Right:
case AlignRight:
refCoord = itemBBox.right();
break;
case Top:
case AlignTop:
refCoord = itemBBox.top();
break;
case VCenter:
case AlignVCenter:
refCoord = itemBBox.center().y();
break;
case Bottom:
case AlignBottom:
refCoord = itemBBox.bottom();
break;
}

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

QPointF shifted = item->pos();
switch ( alignment )
{
case Left:
case AlignLeft:
shifted.setX( refCoord );
break;
case HCenter:
case AlignHCenter:
shifted.setX( refCoord - item->rect().width() / 2.0 );
break;
case Right:
case AlignRight:
shifted.setX( refCoord - item->rect().width() );
break;
case Top:
case AlignTop:
shifted.setY( refCoord );
break;
case VCenter:
case AlignVCenter:
shifted.setY( refCoord - item->rect().height() / 2.0 );
break;
case Bottom:
case AlignBottom:
shifted.setY( refCoord - item->rect().height() );
break;
}
Expand All @@ -91,6 +91,91 @@ void QgsLayoutAligner::alignItems( QgsLayout *layout, const QList<QgsLayoutItem
layout->undoStack()->endMacro();
}

void QgsLayoutAligner::distributeItems( QgsLayout *layout, const QList<QgsLayoutItem *> &items, QgsLayoutAligner::Distribution distribution )
{
if ( items.size() < 2 )
return;

auto collectReferenceCoord = [distribution]( QgsLayoutItem * item )->double
{
QRectF itemBBox = item->sceneBoundingRect();
switch ( distribution )

This comment has been minimized.

Copy link
@3nids

3nids Mar 27, 2018

Member

@nyalldawson

../src/core/layout/qgslayoutaligner.cpp:115:12: warning: comparison of two values with different enumeration types in switch statement ('QgsLayoutAligner::Distribution' and 'QgsLayoutAligner::Alignment') [-Wenum-compare-switch]
case AlignBottom:

it seems there is a mix between distribution and alignment here....

{
case AlignLeft:
return itemBBox.left();
case AlignHCenter:
return itemBBox.center().x();
case AlignRight:
return itemBBox.right();
case AlignTop:
return itemBBox.top();
case AlignVCenter:
return itemBBox.center().y();
case AlignBottom:
return itemBBox.bottom();
}
// no warnings
return itemBBox.left();
};


double minCoord = DBL_MAX;
double maxCoord = -DBL_MAX;
QMap< double, QgsLayoutItem * > itemCoords;
for ( QgsLayoutItem *item : items )
{
double refCoord = collectReferenceCoord( item );
minCoord = std::min( minCoord, refCoord );
maxCoord = std::max( maxCoord, refCoord );
itemCoords.insert( refCoord, item );
}

double step = ( maxCoord - minCoord ) / ( items.size() - 1 );

auto distributeItemToCoord = [layout, distribution]( QgsLayoutItem * item, double refCoord )
{
QPointF shifted = item->pos();
switch ( distribution )
{
case DistributeLeft:
shifted.setX( refCoord );
break;
case DistributeHCenter:
shifted.setX( refCoord - item->rect().width() / 2.0 );
break;
case DistributeRight:
shifted.setX( refCoord - item->rect().width() );
break;
case DistributeTop:
shifted.setY( refCoord );
break;
case DistributeVCenter:
shifted.setY( refCoord - item->rect().height() / 2.0 );
break;
case DistributeBottom:
shifted.setY( refCoord - item->rect().height() );
break;
}

// need to keep item units
QgsLayoutPoint newPos = layout->convertFromLayoutUnits( shifted, item->positionWithUnits().units() );
item->attemptMove( newPos );
};


layout->undoStack()->beginMacro( undoText( distribution ) );
double currentVal = minCoord;
for ( auto itemIt = itemCoords.constBegin(); itemIt != itemCoords.constEnd(); ++itemIt )
{
layout->undoStack()->beginCommand( itemIt.value(), QString() );
distributeItemToCoord( itemIt.value(), currentVal );
layout->undoStack()->endCommand();

currentVal += step;
}
layout->undoStack()->endMacro();
}

QRectF QgsLayoutAligner::boundingRectOfItems( const QList<QgsLayoutItem *> &items )
{
if ( items.empty() )
Expand Down Expand Up @@ -129,3 +214,43 @@ QRectF QgsLayoutAligner::boundingRectOfItems( const QList<QgsLayoutItem *> &item

return QRectF( QPointF( minX, minY ), QPointF( maxX, maxY ) );
}

QString QgsLayoutAligner::undoText( Distribution distribution )
{
switch ( distribution )
{
case DistributeLeft:
return QObject::tr( "Distributed items by left" );
case DistributeHCenter:
return QObject::tr( "Distributed items by center" );
case DistributeRight:
return QObject::tr( "Distributed items by right" );
case DistributeTop:
return QObject::tr( "Distributed items by top" );
case DistributeVCenter:
return QObject::tr( "Distributed items by vertical center" );
case DistributeBottom:
return QObject::tr( "Distributed items by bottom" );
}
return QString(); //no warnings
}

QString QgsLayoutAligner::undoText( Alignment alignment )
{
switch ( alignment )
{
case AlignLeft:
return QObject::tr( "Aligned items to left" );
case AlignHCenter:
return QObject::tr( "Aligned items to center" );
case AlignRight:
return QObject::tr( "Aligned items to right" );
case AlignTop:
return QObject::tr( "Aligned items to top" );
case AlignVCenter:
return QObject::tr( "Aligned items to vertical center" );
case AlignBottom:
return QObject::tr( "Aligned items to bottom" );
}
return QString(); //no warnings
}
33 changes: 27 additions & 6 deletions src/core/layout/qgslayoutaligner.h
Expand Up @@ -41,12 +41,23 @@ class CORE_EXPORT QgsLayoutAligner
//! Alignment options
enum Alignment
{
Left, //!< Align left edges
HCenter, //!< Align horizontal centers
Right, //!< Align right edges
Top, //!< Align top edges
VCenter, //!< Align vertical centers
Bottom, //!< Align bottom edges
AlignLeft, //!< Align left edges
AlignHCenter, //!< Align horizontal centers
AlignRight, //!< Align right edges
AlignTop, //!< Align top edges
AlignVCenter, //!< Align vertical centers
AlignBottom, //!< Align bottom edges
};

//! Distribution options
enum Distribution
{
DistributeLeft, //!< Distribute left edges
DistributeHCenter, //!< Distribute horizontal centers
DistributeRight, //!< Distribute right edges
DistributeTop, //!< Distribute top edges
DistributeVCenter, //!< Distribute vertical centers
DistributeBottom, //!< Distribute bottom edges
};

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

/**
* Distributes a set of \a items from a \a layout in place.
*
* The \a distribution argument specifies the method to use when distributing the items.
*/
static void distributeItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Distribution distribution );

private:

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

static QString undoText( Alignment alignment );
static QString undoText( Distribution distribution );



};
Expand Down

0 comments on commit 5141c05

Please sign in to comment.