Skip to content
Permalink
Browse files

[FEATURE] Add a dock widget showing snap guides for current page

Allows creation of snap lines at specific locations, and
adjusting position of existing guides to exact coordinates
  • Loading branch information
nyalldawson committed Jul 25, 2017
1 parent f5126b0 commit ef6727518db686eb26cb3c27c352b8fd5a786d48
@@ -145,6 +145,7 @@ class QgsLayoutGuideCollection : QAbstractListModel
PositionRole,
UnitsRole,
PageRole,
LayoutPositionRole,
};

QgsLayoutGuideCollection( QgsLayout *layout );
@@ -203,6 +204,11 @@ class QgsLayoutGuideProxyModel : QSortFilterProxyModel
Page numbers begin at 0.
%End

void setPage( int page );
%Docstring
Sets the current ``page`` for filtering matching guides. Page numbers begin at 0.
%End

virtual bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const;

virtual bool lessThan( const QModelIndex &left, const QModelIndex &right ) const;
@@ -78,6 +78,20 @@ class QgsLayoutSnapper
:rtype: QPointF
%End

double snapPointToGuides( double original, QgsLayoutGuide::Orientation orientation, double scaleFactor, bool &snapped /Out/ ) const;
%Docstring
Snaps a layout coordinate ``point`` to the grid. If ``point``
was snapped, ``snapped`` will be set to true.

The ``scaleFactor`` argument should be set to the transformation from
scalar transform from layout coordinates to pixels, i.e. the
graphics view transform().m11() value.

If snapToGrid() is disabled, this method will return the point
unchanged.
:rtype: float
%End

};

/************************************************************************
@@ -103,6 +103,15 @@ class QgsLayoutView: QGraphicsView
:rtype: QgsLayoutViewMenuProvider
%End

int currentPage() const;
%Docstring
Returns the page visible in the view. This method
considers the page at the center of the view as the current visible
page.
.. seealso:: pageChanged()
:rtype: int
%End

public slots:

void zoomFull();
@@ -147,9 +156,9 @@ class QgsLayoutView: QGraphicsView

void emitZoomLevelChanged();

void updateRulers();
void viewChanged();
%Docstring
Updates associated rulers after view extent or zoom has changed.
Updates associated rulers and other widgets after view extent or zoom has changed.
This should be called after calling any of the QGraphicsView
base class methods which alter the view's zoom level or extent,
i.e. QGraphicsView.fitInView().
@@ -182,6 +191,14 @@ class QgsLayoutView: QGraphicsView
the layout coordinate system.
%End

void pageChanged( int page );
%Docstring
Emitted when the page visible in the view is changed. This signal
considers the page at the center of the view as the current visible
page.
.. seealso:: currentPage()
%End

protected:
virtual void mousePressEvent( QMouseEvent *event );

@@ -157,6 +157,7 @@ SET(QGIS_APP_SRCS

layout/qgslayoutaddpagesdialog.cpp
layout/qgslayoutdesignerdialog.cpp
layout/qgslayoutguidewidget.cpp
layout/qgslayoutappmenuprovider.cpp
layout/qgslayoutpagepropertieswidget.cpp
layout/qgslayoutpropertieswidget.cpp
@@ -339,6 +340,7 @@ SET (QGIS_APP_MOC_HDRS
layout/qgslayoutaddpagesdialog.h
layout/qgslayoutappmenuprovider.h
layout/qgslayoutdesignerdialog.h
layout/qgslayoutguidewidget.h
layout/qgslayoutpagepropertieswidget.h
layout/qgslayoutpropertieswidget.h

@@ -37,6 +37,7 @@
#include "qgspanelwidget.h"
#include "qgsdockwidget.h"
#include "qgslayoutpagepropertieswidget.h"
#include "qgslayoutguidewidget.h"
#include <QShortcut>
#include <QComboBox>
#include <QLineEdit>
@@ -257,8 +258,20 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
mItemDock->setWidget( mItemPropertiesStack );
mPanelsMenu->addAction( mItemDock->toggleViewAction() );

mGuideDock = new QgsDockWidget( tr( "Guides" ), this );
mGuideDock->setObjectName( QStringLiteral( "GuideDock" ) );
mGuideDock->setMinimumWidth( minDockWidth );
mGuideStack = new QgsPanelWidgetStack();
mGuideDock->setWidget( mGuideStack );
mPanelsMenu->addAction( mGuideDock->toggleViewAction() );
connect( mActionManageGuides, &QAction::triggered, this, [ = ]
{
mGuideDock->setUserVisible( true );
} );

addDockWidget( Qt::RightDockWidgetArea, mItemDock );
addDockWidget( Qt::RightDockWidgetArea, mGeneralDock );
addDockWidget( Qt::RightDockWidgetArea, mGuideDock );

createLayoutPropertiesWidget();

@@ -317,6 +330,7 @@ void QgsLayoutDesignerDialog::showItemOptions( QgsLayoutItem *item )
delete mItemPropertiesStack->takeMainPanel();
widget->setDockMode( true );
mItemPropertiesStack->setMainPanel( widget.release() );
mItemDock->setUserVisible( true );
}

void QgsLayoutDesignerDialog::open()
@@ -615,13 +629,19 @@ void QgsLayoutDesignerDialog::createLayoutPropertiesWidget()
return;
}

// update composition widget
// update layout based widgets
QgsLayoutPropertiesWidget *oldCompositionWidget = qobject_cast<QgsLayoutPropertiesWidget *>( mGeneralPropertiesStack->takeMainPanel() );
delete oldCompositionWidget;
QgsLayoutGuideWidget *oldGuideWidget = qobject_cast<QgsLayoutGuideWidget *>( mGuideStack->takeMainPanel() );
delete oldGuideWidget;

QgsLayoutPropertiesWidget *widget = new QgsLayoutPropertiesWidget( mGeneralDock, mLayout );
widget->setDockMode( true );
mGeneralPropertiesStack->setMainPanel( widget );

QgsLayoutGuideWidget *guideWidget = new QgsLayoutGuideWidget( mGuideDock, mLayout, mView );
guideWidget->setDockMode( true );
mGuideStack->setMainPanel( guideWidget );
}

void QgsLayoutDesignerDialog::initializeRegistry()
@@ -193,6 +193,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
QgsPanelWidgetStack *mItemPropertiesStack = nullptr;
QgsDockWidget *mGeneralDock = nullptr;
QgsPanelWidgetStack *mGeneralPropertiesStack = nullptr;
QgsDockWidget *mGuideDock = nullptr;
QgsPanelWidgetStack *mGuideStack = nullptr;

//! Save window state
void saveWindowState();
@@ -286,6 +286,12 @@ QgsLayoutGuideProxyModel::QgsLayoutGuideProxyModel( QObject *parent, QgsLayoutGu
sort( 0 );
}

void QgsLayoutGuideProxyModel::setPage( int page )
{
mPage = page;
invalidateFilter();
}

bool QgsLayoutGuideProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
{
QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
@@ -175,6 +175,7 @@ class CORE_EXPORT QgsLayoutGuideCollection : public QAbstractListModel
PositionRole, //!< Guide position role
UnitsRole, //!< Guide position units role
PageRole, //!< Guide page role
LayoutPositionRole, //!< Guide position in layout coordinates
};

/**
@@ -234,6 +235,11 @@ class CORE_EXPORT QgsLayoutGuideProxyModel : public QSortFilterProxyModel
*/
explicit QgsLayoutGuideProxyModel( QObject *parent SIP_TRANSFERTHIS, QgsLayoutGuide::Orientation orientation, int page );

/**
* Sets the current \a page for filtering matching guides. Page numbers begin at 0.
*/
void setPage( int page );

bool filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const override;
bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override;

@@ -35,6 +35,21 @@ QPointF QgsLayoutSnapper::snapPoint( QPointF point, double scaleFactor, bool &sn
return res;
}

bool snappedToHozGuides = false;
double newX = snapPointToGuides( point.x(), QgsLayoutGuide::Vertical, scaleFactor, snappedToHozGuides );
if ( snappedToHozGuides )
{
snapped = true;
point.setX( newX );
}
bool snappedToVertGuides = false;
double newY = snapPointToGuides( point.y(), QgsLayoutGuide::Horizontal, scaleFactor, snappedToVertGuides );
if ( snappedToVertGuides )
{
snapped = true;
point.setY( newY );
}

return point;
}

@@ -88,3 +103,35 @@ QPointF QgsLayoutSnapper::snapPointToGrid( QPointF point, double scaleFactor, bo

return QPointF( xSnapped, ySnapped );
}

double QgsLayoutSnapper::snapPointToGuides( double original, QgsLayoutGuide::Orientation orientation, double scaleFactor, bool &snapped ) const
{
snapped = false;

//convert snap tolerance from pixels to layout units
double alignThreshold = mTolerance / scaleFactor;

double bestPos = original;
double smallestDiff = DBL_MAX;

Q_FOREACH ( QgsLayoutGuide *guide, mLayout->guides().guides( orientation ) )
{
double guidePos = guide->layoutPosition();
double diff = qAbs( original - guidePos );
if ( diff < smallestDiff )
{
smallestDiff = diff;
bestPos = guidePos;
}
}

if ( smallestDiff <= alignThreshold )
{
snapped = true;
return bestPos;
}
else
{
return original;
}
}
@@ -19,6 +19,7 @@
#include "qgis_core.h"
#include "qgslayoutmeasurement.h"
#include "qgslayoutpoint.h"
#include "qgslayoutguidecollection.h"
#include <QPen>

class QgsLayout;
@@ -88,6 +89,19 @@ class CORE_EXPORT QgsLayoutSnapper
*/
QPointF snapPointToGrid( QPointF point, double scaleFactor, bool &snapped SIP_OUT ) const;

/**
* Snaps a layout coordinate \a point to the grid. If \a point
* was snapped, \a snapped will be set to true.
*
* The \a scaleFactor argument should be set to the transformation from
* scalar transform from layout coordinates to pixels, i.e. the
* graphics view transform().m11() value.
*
* If snapToGrid() is disabled, this method will return the point
* unchanged.
*/
double snapPointToGuides( double original, QgsLayoutGuide::Orientation orientation, double scaleFactor, bool &snapped SIP_OUT ) const;

private:

QgsLayout *mLayout = nullptr;
@@ -61,8 +61,8 @@ void QgsLayoutView::setCurrentLayout( QgsLayout *layout )
{
setScene( layout );

connect( layout->pageCollection(), &QgsLayoutPageCollection::changed, this, &QgsLayoutView::updateRulers );
updateRulers();
connect( layout->pageCollection(), &QgsLayoutPageCollection::changed, this, &QgsLayoutView::viewChanged );
viewChanged();

mSnapMarker.reset( new QgsLayoutViewSnapMarker() );
mSnapMarker->hide();
@@ -114,7 +114,7 @@ void QgsLayoutView::scaleSafe( double scale )
scale = qBound( MIN_VIEW_SCALE, scale, MAX_VIEW_SCALE );
setTransform( QTransform::fromScale( scale, scale ) );
emit zoomLevelChanged();
updateRulers();
viewChanged();
}

void QgsLayoutView::setZoomLevel( double level )
@@ -136,21 +136,21 @@ void QgsLayoutView::setZoomLevel( double level )
setTransform( QTransform::fromScale( mmLevel, mmLevel ) );
}
emit zoomLevelChanged();
updateRulers();
viewChanged();
}

void QgsLayoutView::setHorizontalRuler( QgsLayoutRuler *ruler )
{
mHorizontalRuler = ruler;
ruler->setLayoutView( this );
updateRulers();
viewChanged();
}

void QgsLayoutView::setVerticalRuler( QgsLayoutRuler *ruler )
{
mVerticalRuler = ruler;
ruler->setLayoutView( this );
updateRulers();
viewChanged();
}

void QgsLayoutView::setMenuProvider( QgsLayoutViewMenuProvider *provider )
@@ -166,7 +166,7 @@ QgsLayoutViewMenuProvider *QgsLayoutView::menuProvider() const
void QgsLayoutView::zoomFull()
{
fitInView( scene()->sceneRect(), Qt::KeepAspectRatio );
updateRulers();
viewChanged();
emit zoomLevelChanged();
}

@@ -189,7 +189,7 @@ void QgsLayoutView::zoomWidth()

fitInView( targetRect, Qt::KeepAspectRatio );
emit zoomLevelChanged();
updateRulers();
viewChanged();
}

void QgsLayoutView::zoomIn()
@@ -363,15 +363,16 @@ void QgsLayoutView::resizeEvent( QResizeEvent *event )
{
QGraphicsView::resizeEvent( event );
emit zoomLevelChanged();
viewChanged();
}

void QgsLayoutView::scrollContentsBy( int dx, int dy )
{
QGraphicsView::scrollContentsBy( dx, dy );
updateRulers();
viewChanged();
}

void QgsLayoutView::updateRulers()
void QgsLayoutView::viewChanged()
{
if ( mHorizontalRuler )
{
@@ -381,6 +382,21 @@ void QgsLayoutView::updateRulers()
{
mVerticalRuler->setSceneTransform( viewportTransform() );
}

// determine page at center of view
QRect viewportRect( 0, 0, viewport()->width(), viewport()->height() );
QRectF visibleRect = mapToScene( viewportRect ).boundingRect();
QPointF centerVisible = visibleRect.center();

if ( currentLayout() && currentLayout()->pageCollection() )
{
int newPage = currentLayout()->pageCollection()->pageNumberForPoint( centerVisible );
if ( newPage != mCurrentPage )
{
mCurrentPage = newPage;
emit pageChanged( mCurrentPage );
}
}
}

void QgsLayoutView::wheelZoom( QWheelEvent *event )

0 comments on commit ef67275

Please sign in to comment.
You can’t perform that action at this time.