diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 0adac63293d..d18431bff75 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -157,6 +157,7 @@ %Include composer/qgscomposermultiframecommand.sip %Include composer/qgscomposertexttable.sip %Include composer/qgspaperitem.sip +%Include layout/qgslayoutaligner.sip %Include layout/qgslayoutcontext.sip %Include layout/qgslayoutgridsettings.sip %Include layout/qgslayoutmeasurement.sip diff --git a/python/core/layout/qgslayoutaligner.sip b/python/core/layout/qgslayoutaligner.sip new file mode 100644 index 00000000000..bdab531315b --- /dev/null +++ b/python/core/layout/qgslayoutaligner.sip @@ -0,0 +1,52 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutaligner.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + +class QgsLayoutAligner +{ +%Docstring + Handles aligning and distributing sets of layout items. + + QgsLayoutAligner contains methods for automatically aligning and distributing + sets of layout items, e.g. aligning a group of items to top or left sides. + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutaligner.h" +%End + public: + + enum Alignment + { + Left, + HCenter, + Right, + Top, + VCenter, + Bottom, + }; + + static void alignItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Alignment alignment ); +%Docstring + Aligns a set of ``items`` from a ``layout`` in place. + + The ``alignment`` argument specifies the method to use when aligning the items. +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/layout/qgslayoutaligner.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/layout/qgslayoutview.sip b/python/gui/layout/qgslayoutview.sip index 6227aaf9fd0..149fa11f648 100644 --- a/python/gui/layout/qgslayoutview.sip +++ b/python/gui/layout/qgslayoutview.sip @@ -127,6 +127,11 @@ class QgsLayoutView: QGraphicsView :rtype: list of int %End + void alignSelectedItems( QgsLayoutAligner::Alignment alignment ); +%Docstring + Aligns all selected items using the specified ``alignment``. +%End + public slots: void zoomFull(); diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index 731cd2855da..88e7c4273d8 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -179,6 +179,20 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla orderingToolButton->setDefaultAction( mActionRaiseItems ); mActionsToolbar->addWidget( orderingToolButton ); + QToolButton *alignToolButton = new QToolButton( this ); + alignToolButton->setPopupMode( QToolButton::InstantPopup ); + alignToolButton->setAutoRaise( true ); + alignToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly ); + + alignToolButton->addAction( mActionAlignLeft ); + alignToolButton->addAction( mActionAlignHCenter ); + alignToolButton->addAction( mActionAlignRight ); + alignToolButton->addAction( mActionAlignTop ); + alignToolButton->addAction( mActionAlignVCenter ); + alignToolButton->addAction( mActionAlignBottom ); + alignToolButton->setDefaultAction( mActionAlignLeft ); + mActionsToolbar->addWidget( alignToolButton ); + mAddItemTool = new QgsLayoutViewToolAddItem( mView ); mPanTool = new QgsLayoutViewToolPan( mView ); mPanTool->setAction( mActionPan ); @@ -213,6 +227,30 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla connect( mActionLowerItems, &QAction::triggered, this, &QgsLayoutDesignerDialog::lowerSelectedItems ); connect( mActionMoveItemsToTop, &QAction::triggered, this, &QgsLayoutDesignerDialog::moveSelectedItemsToTop ); connect( mActionMoveItemsToBottom, &QAction::triggered, this, &QgsLayoutDesignerDialog::moveSelectedItemsToBottom ); + connect( mActionAlignLeft, &QAction::triggered, this, [ = ] + { + mView->alignSelectedItems( QgsLayoutAligner::Left ); + } ); + connect( mActionAlignHCenter, &QAction::triggered, this, [ = ] + { + mView->alignSelectedItems( QgsLayoutAligner::HCenter ); + } ); + connect( mActionAlignRight, &QAction::triggered, this, [ = ] + { + mView->alignSelectedItems( QgsLayoutAligner::Right ); + } ); + connect( mActionAlignTop, &QAction::triggered, this, [ = ] + { + mView->alignSelectedItems( QgsLayoutAligner::Top ); + } ); + connect( mActionAlignVCenter, &QAction::triggered, this, [ = ] + { + mView->alignSelectedItems( QgsLayoutAligner::VCenter ); + } ); + connect( mActionAlignBottom, &QAction::triggered, this, [ = ] + { + mView->alignSelectedItems( QgsLayoutAligner::Bottom ); + } ); connect( mActionAddPages, &QAction::triggered, this, &QgsLayoutDesignerDialog::addPages ); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ccc790a5bdb..c0f5807c4a4 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -359,6 +359,7 @@ SET(QGIS_CORE_SRCS dxf/qgsdxfpallabeling.cpp layout/qgslayout.cpp + layout/qgslayoutaligner.cpp layout/qgslayoutcontext.cpp layout/qgslayoutgridsettings.cpp layout/qgslayoutguidecollection.cpp @@ -965,6 +966,7 @@ SET(QGIS_CORE_HDRS composer/qgscomposertexttable.h composer/qgspaperitem.h + layout/qgslayoutaligner.h layout/qgslayoutcontext.h layout/qgslayoutgridsettings.h layout/qgslayoutitemundocommand.h diff --git a/src/core/layout/qgslayoutaligner.cpp b/src/core/layout/qgslayoutaligner.cpp new file mode 100644 index 00000000000..1d5eefcbd22 --- /dev/null +++ b/src/core/layout/qgslayoutaligner.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** + qgslayoutaligner.cpp + -------------------- + begin : October 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgslayoutaligner.h" +#include "qgslayoutitem.h" +#include "qgslayout.h" + +void QgsLayoutAligner::alignItems( QgsLayout *layout, const QList &items, QgsLayoutAligner::Alignment alignment ) +{ + if ( !layout || items.size() < 2 ) + { + return; + } + + QRectF itemBBox = boundingRectOfItems( items ); + if ( !itemBBox.isValid() ) + { + return; + } + + double refCoord = 0; + switch ( alignment ) + { + case Left: + refCoord = itemBBox.left(); + break; + case HCenter: + refCoord = itemBBox.center().x(); + break; + case Right: + refCoord = itemBBox.right(); + break; + case Top: + refCoord = itemBBox.top(); + break; + case VCenter: + refCoord = itemBBox.center().y(); + break; + case Bottom: + refCoord = itemBBox.bottom(); + break; + } + + layout->undoStack()->beginMacro( QObject::tr( "Aligned items bottom" ) ); + for ( QgsLayoutItem *item : items ) + { + layout->undoStack()->beginCommand( item, QString() ); + + QPointF shifted = item->pos(); + switch ( alignment ) + { + case Left: + shifted.setX( refCoord ); + break; + case HCenter: + shifted.setX( refCoord - item->rect().width() / 2.0 ); + break; + case Right: + shifted.setX( refCoord - item->rect().width() ); + break; + case Top: + shifted.setY( refCoord ); + break; + case VCenter: + shifted.setY( refCoord - item->rect().height() / 2.0 ); + break; + case Bottom: + 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()->endCommand(); + } + layout->undoStack()->endMacro(); +} + +QRectF QgsLayoutAligner::boundingRectOfItems( const QList &items ) +{ + if ( items.empty() ) + { + return QRectF(); + } + + auto it = items.constBegin(); + //set the box to the first item + QgsLayoutItem *currentItem = *it; + it++; + double minX = currentItem->pos().x(); + double minY = currentItem->pos().y(); + double maxX = minX + currentItem->rect().width(); + double maxY = minY + currentItem->rect().height(); + + double currentMinX, currentMinY, currentMaxX, currentMaxY; + + for ( ; it != items.constEnd(); ++it ) + { + currentItem = *it; + currentMinX = currentItem->pos().x(); + currentMinY = currentItem->pos().y(); + currentMaxX = currentMinX + currentItem->rect().width(); + currentMaxY = currentMinY + currentItem->rect().height(); + + if ( currentMinX < minX ) + minX = currentMinX; + if ( currentMaxX > maxX ) + maxX = currentMaxX; + if ( currentMinY < minY ) + minY = currentMinY; + if ( currentMaxY > maxY ) + maxY = currentMaxY; + } + + return QRectF( QPointF( minX, minY ), QPointF( maxX, maxY ) ); +} diff --git a/src/core/layout/qgslayoutaligner.h b/src/core/layout/qgslayoutaligner.h new file mode 100644 index 00000000000..f9f4789ecbf --- /dev/null +++ b/src/core/layout/qgslayoutaligner.h @@ -0,0 +1,71 @@ +/*************************************************************************** + qgslayoutaligner.h + ------------------ + begin : October 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSLAYOUTALIGNER_H +#define QGSLAYOUTALIGNER_H + +#include "qgis_core.h" +#include +#include + +class QgsLayoutItem; +class QgsLayout; + +/** + * \ingroup core + * \class QgsLayoutAligner + * \brief Handles aligning and distributing sets of layout items. + * + * QgsLayoutAligner contains methods for automatically aligning and distributing + * sets of layout items, e.g. aligning a group of items to top or left sides. + * + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsLayoutAligner +{ + + public: + + //! 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 + }; + + /** + * Aligns a set of \a items from a \a layout in place. + * + * The \a alignment argument specifies the method to use when aligning the items. + */ + static void alignItems( QgsLayout *layout, const QList< QgsLayoutItem * > &items, Alignment alignment ); + + private: + + /** + * Returns the bounding rectangle of the selected items in + * scene coordinates. + */ + static QRectF boundingRectOfItems( const QList< QgsLayoutItem * > &items ); + + + +}; + +#endif //QGSLAYOUTALIGNER_H diff --git a/src/gui/layout/qgslayoutview.cpp b/src/gui/layout/qgslayoutview.cpp index 8a7323a5d9b..b1c3094a25a 100644 --- a/src/gui/layout/qgslayoutview.cpp +++ b/src/gui/layout/qgslayoutview.cpp @@ -230,6 +230,12 @@ QList QgsLayoutView::visiblePageNumbers() const return currentLayout()->pageCollection()->visiblePageNumbers( visibleRect ); } +void QgsLayoutView::alignSelectedItems( QgsLayoutAligner::Alignment alignment ) +{ + const QList selectedItems = currentLayout()->selectedLayoutItems(); + QgsLayoutAligner::alignItems( currentLayout(), selectedItems, alignment ); +} + void QgsLayoutView::zoomFull() { fitInView( scene()->sceneRect(), Qt::KeepAspectRatio ); diff --git a/src/gui/layout/qgslayoutview.h b/src/gui/layout/qgslayoutview.h index e0af0be540e..f869f5097d2 100644 --- a/src/gui/layout/qgslayoutview.h +++ b/src/gui/layout/qgslayoutview.h @@ -21,6 +21,7 @@ #include "qgsprevieweffect.h" // for QgsPreviewEffect::PreviewMode #include "qgis_gui.h" #include "qgslayoutitempage.h" +#include "qgslayoutaligner.h" #include #include #include @@ -157,6 +158,11 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView */ QList< int > visiblePageNumbers() const; + /** + * Aligns all selected items using the specified \a alignment. + */ + void alignSelectedItems( QgsLayoutAligner::Alignment alignment ); + public slots: /** diff --git a/src/ui/layout/qgslayoutdesignerbase.ui b/src/ui/layout/qgslayoutdesignerbase.ui index 4ae986e2c91..dc28beb15b6 100644 --- a/src/ui/layout/qgslayoutdesignerbase.ui +++ b/src/ui/layout/qgslayoutdesignerbase.ui @@ -85,7 +85,7 @@ 0 0 1083 - 25 + 42 @@ -160,8 +160,16 @@ + + + + + + + + @@ -663,6 +671,78 @@ Ctrl+Shift+[ + + + + :/images/themes/default/mActionAlignLeft.svg:/images/themes/default/mActionAlignLeft.svg + + + Align Left + + + Align selected items left + + + + + + :/images/themes/default/mActionAlignHCenter.svg:/images/themes/default/mActionAlignHCenter.svg + + + Align Center + + + Align center horizontal + + + + + + :/images/themes/default/mActionAlignRight.svg:/images/themes/default/mActionAlignRight.svg + + + Align Right + + + Align selected items right + + + + + + :/images/themes/default/mActionAlignTop.svg:/images/themes/default/mActionAlignTop.svg + + + Align Top + + + Align selected items to top + + + + + + :/images/themes/default/mActionAlignVCenter.svg:/images/themes/default/mActionAlignVCenter.svg + + + Align Center Vertical + + + Align center vertical + + + + + + :/images/themes/default/mActionAlignBottom.svg:/images/themes/default/mActionAlignBottom.svg + + + Align Bottom + + + Align selected items bottom + + @@ -691,7 +771,6 @@ -