From c6c9c6fabf97a3131f331cee576056029f3ff227 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 26 Jul 2017 19:44:09 +1000 Subject: [PATCH] Show guide positions in rulers --- .../core/layout/qgslayoutguidecollection.sip | 5 +- src/core/layout/qgslayoutguidecollection.cpp | 12 ++- src/core/layout/qgslayoutguidecollection.h | 5 +- src/gui/layout/qgslayoutruler.cpp | 78 ++++++++++++++++++- src/gui/layout/qgslayoutruler.h | 10 +++ src/gui/layout/qgslayoutview.cpp | 25 ++++++ tests/src/python/test_qgslayoutguides.py | 5 ++ 7 files changed, 129 insertions(+), 11 deletions(-) diff --git a/python/core/layout/qgslayoutguidecollection.sip b/python/core/layout/qgslayoutguidecollection.sip index 49c8f541c46..2c94b9c9fbb 100644 --- a/python/core/layout/qgslayoutguidecollection.sip +++ b/python/core/layout/qgslayoutguidecollection.sip @@ -181,10 +181,11 @@ class QgsLayoutGuideCollection : QAbstractTableModel Updates the position (and visibility) of all guide line items. %End - QList< QgsLayoutGuide * > guides( QgsLayoutGuide::Orientation orientation ); + QList< QgsLayoutGuide * > guides( QgsLayoutGuide::Orientation orientation, int page = -1 ); %Docstring Returns the list of guides contained in the collection with the specified - ``orientation``. + ``orientation`` and on a matching ``page``. + If ``page`` is -1, guides from all pages will be returned. :rtype: list of QgsLayoutGuide %End diff --git a/src/core/layout/qgslayoutguidecollection.cpp b/src/core/layout/qgslayoutguidecollection.cpp index c69a7b9a0ea..334c6efc386 100644 --- a/src/core/layout/qgslayoutguidecollection.cpp +++ b/src/core/layout/qgslayoutguidecollection.cpp @@ -117,10 +117,10 @@ double QgsLayoutGuide::layoutPosition() const switch ( mOrientation ) { case Horizontal: - return mLineItem->line().y1(); + return mLineItem->mapToScene( mLineItem->line().p1() ).y(); case Vertical: - return mLineItem->line().x1(); + return mLineItem->mapToScene( mLineItem->line().p1() ).x(); } return -999; // avoid warning } @@ -237,6 +237,7 @@ bool QgsLayoutGuideCollection::setData( const QModelIndex &index, const QVariant m.setLength( newPos ); whileBlocking( guide )->setPosition( m ); guide->update(); + emit dataChanged( index, index, QVector() << role ); return true; } case PositionRole: @@ -250,6 +251,7 @@ bool QgsLayoutGuideCollection::setData( const QModelIndex &index, const QVariant m.setLength( newPos ); whileBlocking( guide )->setPosition( m ); guide->update(); + emit dataChanged( index, index, QVector() << role ); return true; } case UnitsRole: @@ -263,6 +265,7 @@ bool QgsLayoutGuideCollection::setData( const QModelIndex &index, const QVariant m.setUnits( static_cast< QgsUnitTypes::LayoutUnit >( units ) ); whileBlocking( guide )->setPosition( m ); guide->update(); + emit dataChanged( index, index, QVector() << role ); return true; } } @@ -325,12 +328,13 @@ void QgsLayoutGuideCollection::update() } } -QList QgsLayoutGuideCollection::guides( QgsLayoutGuide::Orientation orientation ) +QList QgsLayoutGuideCollection::guides( QgsLayoutGuide::Orientation orientation, int page ) { QList res; Q_FOREACH ( QgsLayoutGuide *guide, mGuides ) { - if ( guide->orientation() == orientation && guide->item()->isVisible() ) + if ( guide->orientation() == orientation && guide->item()->isVisible() && + ( page < 0 || page == guide->page() ) ) res << guide; } return res; diff --git a/src/core/layout/qgslayoutguidecollection.h b/src/core/layout/qgslayoutguidecollection.h index 87dc06b4a45..3eecbe2c647 100644 --- a/src/core/layout/qgslayoutguidecollection.h +++ b/src/core/layout/qgslayoutguidecollection.h @@ -207,9 +207,10 @@ class CORE_EXPORT QgsLayoutGuideCollection : public QAbstractTableModel /** * Returns the list of guides contained in the collection with the specified - * \a orientation. + * \a orientation and on a matching \a page. + * If \a page is -1, guides from all pages will be returned. */ - QList< QgsLayoutGuide * > guides( QgsLayoutGuide::Orientation orientation ); + QList< QgsLayoutGuide * > guides( QgsLayoutGuide::Orientation orientation, int page = -1 ); private: diff --git a/src/gui/layout/qgslayoutruler.cpp b/src/gui/layout/qgslayoutruler.cpp index 28397e1f978..a67c19cb7f6 100644 --- a/src/gui/layout/qgslayoutruler.cpp +++ b/src/gui/layout/qgslayoutruler.cpp @@ -16,6 +16,7 @@ #include "qgslayout.h" #include "qgis.h" #include "qgslayoutview.h" +#include "qgslogger.h" #include #include #include @@ -52,6 +53,20 @@ QgsLayoutRuler::QgsLayoutRuler( QWidget *parent, Qt::Orientation orientation ) mPixelsBetweenLineAndText = mRulerMinSize / 10; mTextBaseline = mRulerMinSize / 1.667; mMinSpacingVerticalLabels = mRulerMinSize / 5; + + double guideMarkerSize = mRulerFontMetrics->width( "*" ); + switch ( mOrientation ) + { + case Qt::Horizontal: + mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) << + QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ); + break; + + case Qt::Vertical: + mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) << + QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 ); + break; + } } QSize QgsLayoutRuler::minimumSizeHint() const @@ -70,6 +85,8 @@ void QgsLayoutRuler::paintEvent( QPaintEvent *event ) QgsLayout *layout = mView->currentLayout(); QPainter p( this ); + drawGuideMarkers( &p, layout ); + QTransform t = mTransform.inverted(); p.setFont( mRulerFont ); // keep same default color, but lower opacity a tad @@ -260,6 +277,56 @@ void QgsLayoutRuler::drawMarkerPos( QPainter *painter ) } } +void QgsLayoutRuler::drawGuideMarkers( QPainter *p, QgsLayout *layout ) +{ + QList< int > visiblePageNumbers = mView->visiblePageNumbers(); + QList< QgsLayoutGuide * > guides = layout->guides().guides( mOrientation == Qt::Horizontal ? QgsLayoutGuide::Vertical : QgsLayoutGuide::Horizontal ); + p->save(); + p->setRenderHint( QPainter::Antialiasing, true ); + p->setBrush( QBrush( QColor( 255, 0, 0 ) ) ); + p->setPen( Qt::NoPen ); + Q_FOREACH ( QgsLayoutGuide *guide, guides ) + { + if ( visiblePageNumbers.contains( guide->page() ) ) + { + QPointF point; + switch ( mOrientation ) + { + case Qt::Horizontal: + point = QPointF( guide->layoutPosition(), 0 ); + break; + + case Qt::Vertical: + point = QPointF( 0, guide->layoutPosition() ); + break; + } + drawGuideAtPos( p, convertLayoutPointToLocal( point ) ); + } + } + p->restore(); +} + +void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos ) +{ + switch ( mOrientation ) + { + case Qt::Horizontal: + { + painter->translate( pos.x(), 0 ); + painter->drawPolygon( mGuideMarker ); + painter->translate( -pos.x(), 0 ); + break; + } + case Qt::Vertical: + { + painter->translate( 0, pos.y() ); + painter->drawPolygon( mGuideMarker ); + painter->translate( 0, -pos.y() ); + break; + } + } +} + void QgsLayoutRuler::createTemporaryGuideItem() { mGuideItem.reset( new QGraphicsLineItem() ); @@ -279,6 +346,12 @@ QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint ) const return mView->mapToScene( viewPoint ); } +QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint ) const +{ + QPoint viewPoint = mView->mapFromScene( layoutPoint ); + return mapFromGlobal( mView->mapToGlobal( viewPoint ) ); +} + void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos, const QString &text ) { painter->save(); @@ -467,20 +540,19 @@ void QgsLayoutRuler::mouseMoveEvent( QMouseEvent *event ) linePen.setColor( QColor( 255, 0, 0, 225 ) ); } mGuideItem->setPen( linePen ); - switch ( mOrientation ) { case Qt::Horizontal: { //mouse is creating a horizontal ruler, so don't show x coordinate - mGuideItem->setLine( 0, displayPos.y(), page->rect().width(), displayPos.y() ); + mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() ); displayPos.setX( 0 ); break; } case Qt::Vertical: { //mouse is creating a vertical ruler, so don't show a y coordinate - mGuideItem->setLine( displayPos.x(), 0, displayPos.x(), page->rect().height() ); + mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() ); displayPos.setY( 0 ); break; } diff --git a/src/gui/layout/qgslayoutruler.h b/src/gui/layout/qgslayoutruler.h index b87fdbc8fec..d7047e94d9f 100644 --- a/src/gui/layout/qgslayoutruler.h +++ b/src/gui/layout/qgslayoutruler.h @@ -111,6 +111,9 @@ class GUI_EXPORT QgsLayoutRuler: public QWidget bool mCreatingGuide = false; std::unique_ptr< QGraphicsLineItem > mGuideItem; + //! Polygon for drawing guide markers + QPolygonF mGuideMarker; + //! Calculates the optimum labeled units for ruler so that labels are a good distance apart int optimumScale( double minPixelDiff, int &magnitude, int &multiple ); @@ -133,10 +136,17 @@ class GUI_EXPORT QgsLayoutRuler: public QWidget //! Draw current marker pos on ruler void drawMarkerPos( QPainter *painter ); + void drawGuideMarkers( QPainter *painter, QgsLayout *layout ); + + //! Draw a guide marker on the ruler + void drawGuideAtPos( QPainter *painter, QPoint pos ); + void createTemporaryGuideItem(); QPointF convertLocalPointToLayout( QPoint localPoint ) const; + QPoint convertLayoutPointToLocal( QPointF layoutPoint ) const; + }; diff --git a/src/gui/layout/qgslayoutview.cpp b/src/gui/layout/qgslayoutview.cpp index 2d42a732250..b81c25d85fb 100644 --- a/src/gui/layout/qgslayoutview.cpp +++ b/src/gui/layout/qgslayoutview.cpp @@ -73,6 +73,19 @@ void QgsLayoutView::setCurrentLayout( QgsLayout *layout ) mSnapMarker->hide(); layout->addItem( mSnapMarker.get() ); + if ( mHorizontalRuler ) + { + connect( &layout->guides(), &QAbstractItemModel::dataChanged, mHorizontalRuler, [ = ] { mHorizontalRuler->update(); } ); + connect( &layout->guides(), &QAbstractItemModel::rowsInserted, mHorizontalRuler, [ = ] { mHorizontalRuler->update(); } ); + connect( &layout->guides(), &QAbstractItemModel::rowsRemoved, mHorizontalRuler, [ = ] { mHorizontalRuler->update(); } ); + } + if ( mVerticalRuler ) + { + connect( &layout->guides(), &QAbstractItemModel::dataChanged, mVerticalRuler, [ = ] { mVerticalRuler->update(); } ); + connect( &layout->guides(), &QAbstractItemModel::rowsInserted, mVerticalRuler, [ = ] { mVerticalRuler->update(); } ); + connect( &layout->guides(), &QAbstractItemModel::rowsRemoved, mVerticalRuler, [ = ] { mVerticalRuler->update(); } ); + } + //emit layoutSet, so that designer dialogs can update for the new layout emit layoutSet( layout ); } @@ -148,6 +161,12 @@ void QgsLayoutView::setHorizontalRuler( QgsLayoutRuler *ruler ) { mHorizontalRuler = ruler; ruler->setLayoutView( this ); + if ( QgsLayout *layout = currentLayout() ) + { + connect( &layout->guides(), &QAbstractItemModel::dataChanged, ruler, [ = ] { mHorizontalRuler->update(); } ); + connect( &layout->guides(), &QAbstractItemModel::rowsInserted, ruler, [ = ] { mHorizontalRuler->update(); } ); + connect( &layout->guides(), &QAbstractItemModel::rowsRemoved, ruler, [ = ] { mHorizontalRuler->update(); } ); + } viewChanged(); } @@ -155,6 +174,12 @@ void QgsLayoutView::setVerticalRuler( QgsLayoutRuler *ruler ) { mVerticalRuler = ruler; ruler->setLayoutView( this ); + if ( QgsLayout *layout = currentLayout() ) + { + connect( &layout->guides(), &QAbstractItemModel::dataChanged, ruler, [ = ] { mVerticalRuler->update(); } ); + connect( &layout->guides(), &QAbstractItemModel::rowsInserted, ruler, [ = ] { mVerticalRuler->update(); } ); + connect( &layout->guides(), &QAbstractItemModel::rowsRemoved, ruler, [ = ] { mVerticalRuler->update(); } ); + } viewChanged(); } diff --git a/tests/src/python/test_qgslayoutguides.py b/tests/src/python/test_qgslayoutguides.py index 9e39b678233..3e41e79111a 100644 --- a/tests/src/python/test_qgslayoutguides.py +++ b/tests/src/python/test_qgslayoutguides.py @@ -149,7 +149,12 @@ class TestQgsLayoutGuide(unittest.TestCase): self.assertEqual(guides.data(guides.index(2, 0), QgsLayoutGuideCollection.UnitsRole), QgsUnitTypes.LayoutMillimeters) self.assertEqual(guides.data(guides.index(2, 0), QgsLayoutGuideCollection.PageRole), 1) self.assertEqual(guides.guides(QgsLayoutGuide.Horizontal), [g1, g2]) + self.assertEqual(guides.guides(QgsLayoutGuide.Horizontal, 0), [g1, g2]) + self.assertEqual(guides.guides(QgsLayoutGuide.Horizontal, 1), []) self.assertEqual(guides.guides(QgsLayoutGuide.Vertical), [g3]) + self.assertEqual(guides.guides(QgsLayoutGuide.Vertical, 0), []) + self.assertEqual(guides.guides(QgsLayoutGuide.Vertical, 1), [g3]) + self.assertEqual(guides.guides(QgsLayoutGuide.Vertical, 2), []) def testDeleteRows(self): p = QgsProject()