Add tools to create node based items

This commit is contained in:
Nyall Dawson 2017-10-17 17:39:55 +10:00
parent 6b629e199e
commit 44fe2a8d08
19 changed files with 537 additions and 43 deletions

View File

@ -28,7 +28,7 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator, QgsLayoutUndoOb
ZGuide,
ZSmartGuide,
ZMouseHandles,
ZMapTool,
ZViewTool,
ZSnapIndicator,
};

View File

@ -22,6 +22,19 @@ class QgsLayoutNodesItem: QgsLayoutItem
%End
public:
void setNodes( const QPolygonF &nodes );
%Docstring
Sets the ``nodes`` the shape consists of.
.. seealso:: nodes()
%End
QPolygonF nodes() const;
%Docstring
Returns the nodes the shape consists of.
.. seealso:: setNodes()
:rtype: QPolygonF
%End
bool addNode( QPointF point, bool checkArea = true, double radius = 10 );
%Docstring
Add a node in current shape.
@ -95,12 +108,6 @@ Returns the number of nodes in the shape.
Deselects any selected nodes.
%End
QPolygonF nodes() const;
%Docstring
Returns the nodes the shape consists of.
:rtype: QPolygonF
%End
protected:
QgsLayoutNodesItem( QgsLayout *layout );

View File

@ -295,6 +295,7 @@
%Include layout/qgslayoutview.sip
%Include layout/qgslayoutviewtool.sip
%Include layout/qgslayoutviewtooladditem.sip
%Include layout/qgslayoutviewtooladdnodeitem.sip
%Include layout/qgslayoutviewtoolpan.sip
%Include layout/qgslayoutviewtoolselect.sip
%Include layout/qgslayoutviewtooltemporarykeypan.sip

View File

@ -35,13 +35,15 @@ class QgsLayoutItemAbstractGuiMetadata
typedef QFlags<QgsLayoutItemAbstractGuiMetadata::Flag> Flags;
QgsLayoutItemAbstractGuiMetadata( int type, const QString &visibleName, const QString &groupId = QString(), Flags flags = 0 );
QgsLayoutItemAbstractGuiMetadata( int type, const QString &visibleName, const QString &groupId = QString(), bool isNodeBased = false, Flags flags = 0 );
%Docstring
Constructor for QgsLayoutItemAbstractGuiMetadata with the specified class ``type``.
``visibleName`` should be set to a translated, user visible name identifying the corresponding layout item.
An optional ``groupId`` can be set, which allows grouping of related layout item classes. See QgsLayoutItemGuiMetadata for details.
If ``isNodeBased`` is true, then the corresponding item is a node based item.
%End
virtual ~QgsLayoutItemAbstractGuiMetadata();
@ -64,6 +66,12 @@ class QgsLayoutItemAbstractGuiMetadata
:rtype: str
%End
bool isNodeBased() const;
%Docstring
Returns true if the associated item is a node based item.
:rtype: bool
%End
QString visibleName() const;
%Docstring
Returns a translated, user visible name identifying the corresponding layout item.
@ -86,9 +94,18 @@ class QgsLayoutItemAbstractGuiMetadata
%Docstring
Creates a rubber band for use when creating layout items of this type. Can return None if no rubber band
should be created. The default behavior is to create a rectangular rubber band.
.. seealso:: createNodeRubberBand()
:rtype: QgsLayoutViewRubberBand
%End
virtual QAbstractGraphicsShapeItem *createNodeRubberBand( QgsLayoutView *view ) /Factory/;
%Docstring
Creates a rubber band for use when creating layout node based items of this type. Can return None if no rubber band
should be created. The default behavior is to return None.
.. seealso:: createRubberBand()
:rtype: QAbstractGraphicsShapeItem
%End
virtual QgsLayoutItem *createItem( QgsLayout *layout ) /Factory/;
%Docstring
Creates an instance of the corresponding item type.
@ -100,6 +117,7 @@ class QgsLayoutItemAbstractGuiMetadata
class QgsLayoutItemGuiGroup
{
%Docstring
@ -219,6 +237,7 @@ class QgsLayoutItemGuiRegistry : QObject
%End
QList< int > itemMetadataIds() const;
%Docstring
Returns a list of available item metadata ids handled by the registry.

View File

@ -0,0 +1,66 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/layout/qgslayoutviewtooladdnodeitem.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsLayoutViewToolAddNodeItem : QgsLayoutViewTool
{
%Docstring
Layout view tool for adding node based items to a layout.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslayoutviewtooladdnodeitem.h"
%End
public:
QgsLayoutViewToolAddNodeItem( QgsLayoutView *view /TransferThis/ );
int itemMetadataId() const;
%Docstring
Returns the item metadata id for items created by the tool.
.. seealso:: setItemMetadataId()
:rtype: int
%End
void setItemMetadataId( int metadataId );
%Docstring
Sets the item metadata ``metadataId`` for items created by the tool.
The ``metadataId`` associates the current tool behavior with a metadata entry
from QgsLayoutItemGuiRegistry.
.. seealso:: itemMetadataId()
%End
virtual void layoutPressEvent( QgsLayoutViewMouseEvent *event );
virtual void layoutMoveEvent( QgsLayoutViewMouseEvent *event );
virtual void layoutReleaseEvent( QgsLayoutViewMouseEvent *event );
virtual void deactivate();
signals:
void createdItem();
%Docstring
Emitted when an item has been created using the tool.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/layout/qgslayoutviewtooladdnodeitem.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -15,6 +15,7 @@
#include "qgslayoutapputils.h"
#include "qgsgui.h"
#include "qgslayout.h"
#include "qgslayoutitemguiregistry.h"
#include "qgslayoutitemregistry.h"
#include "qgslayoutviewrubberband.h"
@ -59,35 +60,56 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
return new QgsLayoutShapeWidget( qobject_cast< QgsLayoutItemShape * >( item ) );
};
registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutShape, QObject::tr( "Rectangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), createShapeWidget, createRubberBand, QStringLiteral( "shapes" ), 0, []( QgsLayout * layout )->QgsLayoutItem*
registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutShape, QObject::tr( "Rectangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), createShapeWidget, createRubberBand, QStringLiteral( "shapes" ), false, 0, []( QgsLayout * layout )->QgsLayoutItem*
{
std::unique_ptr< QgsLayoutItemShape > shape = qgis::make_unique< QgsLayoutItemShape >( layout );
shape->setShapeType( QgsLayoutItemShape::Rectangle );
return shape.release();
} ) );
registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutShape, QObject::tr( "Ellipse" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), createShapeWidget, createEllipseBand, QStringLiteral( "shapes" ), 0, []( QgsLayout * layout )->QgsLayoutItem*
registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutShape, QObject::tr( "Ellipse" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), createShapeWidget, createEllipseBand, QStringLiteral( "shapes" ), false, 0, []( QgsLayout * layout )->QgsLayoutItem*
{
std::unique_ptr< QgsLayoutItemShape > shape = qgis::make_unique< QgsLayoutItemShape >( layout );
shape->setShapeType( QgsLayoutItemShape::Ellipse );
return shape.release();
} ) );
registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutShape, QObject::tr( "Triangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicTriangle.svg" ) ), createShapeWidget, createTriangleBand, QStringLiteral( "shapes" ), 0, []( QgsLayout * layout )->QgsLayoutItem*
registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutShape, QObject::tr( "Triangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicTriangle.svg" ) ), createShapeWidget, createTriangleBand, QStringLiteral( "shapes" ), false, 0, []( QgsLayout * layout )->QgsLayoutItem*
{
std::unique_ptr< QgsLayoutItemShape > shape = qgis::make_unique< QgsLayoutItemShape >( layout );
shape->setShapeType( QgsLayoutItemShape::Triangle );
return shape.release();
} ) );
registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutPolygon, QObject::tr( "Polygon" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPolygon.svg" ) ),
[ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
std::unique_ptr< QgsLayoutItemGuiMetadata > polygonMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >(
QgsLayoutItemRegistry::LayoutPolygon, QObject::tr( "Polygon" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPolygon.svg" ) ),
[ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
{
return nullptr;
//return new QgsLayoutMapWidget( qobject_cast< QgsLayoutItemMap * >( item ) );
}, createRubberBand, QStringLiteral( "nodes" ) ) );
registry->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutPolyline, QObject::tr( "Polyline" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPolyline.svg" ) ),
[ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
}, createRubberBand, QStringLiteral( "nodes" ), true );
polygonMetadata->setNodeRubberBandCreationFunction( []( QgsLayoutView * )->QGraphicsPolygonItem*
{
std::unique_ptr< QGraphicsPolygonItem > band = qgis::make_unique< QGraphicsPolygonItem >();
band->setBrush( Qt::NoBrush );
band->setPen( QPen( QBrush( QColor( 227, 22, 22, 200 ) ), 0 ) );
band->setZValue( QgsLayout::ZViewTool );
return band.release();
} );
registry->addLayoutItemGuiMetadata( polygonMetadata.release() );
std::unique_ptr< QgsLayoutItemGuiMetadata > polylineMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata>(
QgsLayoutItemRegistry::LayoutPolyline, QObject::tr( "Polyline" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPolyline.svg" ) ),
[ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
{
return nullptr;
//return new QgsLayoutMapWidget( qobject_cast< QgsLayoutItemMap * >( item ) );
}, createRubberBand, QStringLiteral( "nodes" ) ) );
}, createRubberBand, QStringLiteral( "nodes" ), true );
polylineMetadata->setNodeRubberBandCreationFunction( []( QgsLayoutView * )->QGraphicsPathItem*
{
std::unique_ptr< QGraphicsPathItem > band = qgis::make_unique< QGraphicsPathItem >();
band->setPen( QPen( QBrush( QColor( 227, 22, 22, 200 ) ), 0 ) );
band->setZValue( QgsLayout::ZViewTool );
return band.release();
} );
registry->addLayoutItemGuiMetadata( polylineMetadata.release() );
}

View File

@ -24,6 +24,7 @@
#include "qgslayoutappmenuprovider.h"
#include "qgslayoutview.h"
#include "qgslayoutviewtooladditem.h"
#include "qgslayoutviewtooladdnodeitem.h"
#include "qgslayoutviewtoolpan.h"
#include "qgslayoutviewtoolzoom.h"
#include "qgslayoutviewtoolselect.h"
@ -227,6 +228,7 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
mActionsToolbar->addWidget( resizeToolButton );
mAddItemTool = new QgsLayoutViewToolAddItem( mView );
mAddNodeItemTool = new QgsLayoutViewToolAddNodeItem( mView );
mPanTool = new QgsLayoutViewToolPan( mView );
mPanTool->setAction( mActionPan );
mToolsActionGroup->addAction( mActionPan );
@ -241,6 +243,7 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [ = ] { mView->setTool( mSelectTool ); } );
// after creating an item with the add item tool, switch immediately to select tool
connect( mAddItemTool, &QgsLayoutViewToolAddItem::createdItem, this, [ = ] { mView->setTool( mSelectTool ); } );
connect( mAddNodeItemTool, &QgsLayoutViewToolAddNodeItem::createdItem, this, [ = ] { mView->setTool( mSelectTool ); } );
//Ctrl+= should also trigger zoom in
QShortcut *ctrlEquals = new QShortcut( QKeySequence( QStringLiteral( "Ctrl+=" ) ), this );
@ -847,6 +850,7 @@ void QgsLayoutDesignerDialog::itemTypeAdded( int id )
QString name = QgsGui::layoutItemGuiRegistry()->itemMetadata( id )->visibleName();
QString groupId = QgsGui::layoutItemGuiRegistry()->itemMetadata( id )->groupId();
bool nodeBased = QgsGui::layoutItemGuiRegistry()->itemMetadata( id )->isNodeBased();
QToolButton *groupButton = nullptr;
QMenu *itemSubmenu = nullptr;
if ( !groupId.isEmpty() )
@ -905,9 +909,9 @@ void QgsLayoutDesignerDialog::itemTypeAdded( int id )
else
mToolsToolbar->addAction( action );
connect( action, &QAction::triggered, this, [this, id]()
connect( action, &QAction::triggered, this, [this, id, nodeBased]()
{
activateNewItemCreationTool( id );
activateNewItemCreationTool( id, nodeBased );
} );
}
@ -1084,12 +1088,19 @@ void QgsLayoutDesignerDialog::restoreWindowState()
}
}
void QgsLayoutDesignerDialog::activateNewItemCreationTool( int id )
void QgsLayoutDesignerDialog::activateNewItemCreationTool( int id, bool nodeBasedItem )
{
mAddItemTool->setItemMetadataId( id );
if ( mView )
if ( !nodeBasedItem )
{
mView->setTool( mAddItemTool );
mAddItemTool->setItemMetadataId( id );
if ( mView )
mView->setTool( mAddItemTool );
}
else
{
mAddNodeItemTool->setItemMetadataId( id );
if ( mView )
mView->setTool( mAddNodeItemTool );
}
}
@ -1123,7 +1134,7 @@ void QgsLayoutDesignerDialog::initializeRegistry()
return new QgsLayoutPagePropertiesWidget( nullptr, item );
} );
QgsGui::layoutItemGuiRegistry()->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutPage, QObject::tr( "Page" ), QIcon(), createPageWidget, nullptr, QString(), QgsLayoutItemAbstractGuiMetadata::FlagNoCreationTools ) );
QgsGui::layoutItemGuiRegistry()->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutPage, QObject::tr( "Page" ), QIcon(), createPageWidget, nullptr, QString(), false, QgsLayoutItemAbstractGuiMetadata::FlagNoCreationTools ) );
}

View File

@ -24,6 +24,7 @@
class QgsLayoutDesignerDialog;
class QgsLayoutView;
class QgsLayoutViewToolAddItem;
class QgsLayoutViewToolAddNodeItem;
class QgsLayoutViewToolPan;
class QgsLayoutViewToolZoom;
class QgsLayoutViewToolSelect;
@ -270,6 +271,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
static QList<double> sStatusZoomLevelsList;
QgsLayoutViewToolAddItem *mAddItemTool = nullptr;
QgsLayoutViewToolAddNodeItem *mAddNodeItemTool = nullptr;
QgsLayoutViewToolPan *mPanTool = nullptr;
QgsLayoutViewToolZoom *mZoomTool = nullptr;
QgsLayoutViewToolSelect *mSelectTool = nullptr;
@ -313,7 +315,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
void restoreWindowState();
//! Switch to new item creation tool, for a new item of the specified \a id.
void activateNewItemCreationTool( int id );
void activateNewItemCreationTool( int id, bool nodeBasedItem );
void createLayoutPropertiesWidget();

View File

@ -51,7 +51,7 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
ZGuide = 9998, //!< Z-value for page guides
ZSmartGuide = 9999, //!< Z-value for smart (item bounds based) guides
ZMouseHandles = 10000, //!< Z-value for mouse handles
ZMapTool = 10001, //!< Z-value for temporary map tool items
ZViewTool = 10001, //!< Z-value for temporary view tool items
ZSnapIndicator = 10002, //!< Z-value for snapping indicator
};

View File

@ -23,21 +23,23 @@
#include <cmath>
#include <QStyleOptionGraphicsItem>
void QgsLayoutNodesItem::setNodes( const QPolygonF &nodes )
{
mPolygon = nodes;
updateSceneRect();
}
QgsLayoutNodesItem::QgsLayoutNodesItem( QgsLayout *layout )
: QgsLayoutItem( layout )
{
// no cache - the node based items cannot reliably determine their real bounds (e.g. due to mitred corners).
// this blocks use of the pixmap based cache for these
setCacheMode( QGraphicsItem::NoCache );
init();
}
QgsLayoutNodesItem::QgsLayoutNodesItem( const QPolygonF &polygon,
QgsLayout *layout )
: QgsLayoutItem( layout )
{
// no cache - the node based items cannot reliably determine their real bounds (e.g. due to mitred corners).
// this blocks use of the pixmap based cache for these
setCacheMode( QGraphicsItem::NoCache );
init();
const QRectF boundingRect = polygon.boundingRect();
attemptSetSceneRect( boundingRect );
@ -46,6 +48,15 @@ QgsLayoutNodesItem::QgsLayoutNodesItem( const QPolygonF &polygon,
mPolygon = polygon.translated( -topLeft );
}
void QgsLayoutNodesItem::init()
{
// no cache - the node based items cannot reliably determine their real bounds (e.g. due to mitred corners).
// this blocks use of the pixmap based cache for these
setCacheMode( QGraphicsItem::NoCache );
setBackgroundEnabled( false );
setFrameEnabled( false );
}
void QgsLayoutNodesItem::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *style )
{
QPainter *painter = context.painter();
@ -309,6 +320,8 @@ void QgsLayoutNodesItem::updateSceneRect()
emit sizePositionChanged();
}
bool QgsLayoutNodesItem::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
{
// style

View File

@ -32,6 +32,18 @@ class CORE_EXPORT QgsLayoutNodesItem: public QgsLayoutItem
public:
/**
* Sets the \a nodes the shape consists of.
* \see nodes()
*/
void setNodes( const QPolygonF &nodes );
/**
* Returns the nodes the shape consists of.
* \see setNodes()
*/
QPolygonF nodes() const { return mPolygon; }
/**
* Add a node in current shape.
* \param point is the location of the new node (in scene coordinates)
@ -95,11 +107,6 @@ class CORE_EXPORT QgsLayoutNodesItem: public QgsLayoutItem
*/
void deselectNode() { mSelectedNode = -1; }
/**
* Returns the nodes the shape consists of.
*/
QPolygonF nodes() const { return mPolygon; }
protected:
/**
@ -154,6 +161,8 @@ class CORE_EXPORT QgsLayoutNodesItem: public QgsLayoutItem
private:
void init();
//! The index of the node currently selected.
int mSelectedNode = -1;

View File

@ -170,6 +170,7 @@ SET(QGIS_GUI_SRCS
layout/qgslayoutviewrubberband.cpp
layout/qgslayoutviewtool.cpp
layout/qgslayoutviewtooladditem.cpp
layout/qgslayoutviewtooladdnodeitem.cpp
layout/qgslayoutviewtoolpan.cpp
layout/qgslayoutviewtoolselect.cpp
layout/qgslayoutviewtooltemporarykeypan.cpp
@ -673,6 +674,7 @@ SET(QGIS_GUI_MOC_HDRS
layout/qgslayoutview.h
layout/qgslayoutviewtool.h
layout/qgslayoutviewtooladditem.h
layout/qgslayoutviewtooladdnodeitem.h
layout/qgslayoutviewtoolpan.h
layout/qgslayoutviewtoolselect.h
layout/qgslayoutviewtooltemporarykeypan.h

View File

@ -25,6 +25,11 @@ QgsLayoutViewRubberBand *QgsLayoutItemAbstractGuiMetadata::createRubberBand( Qgs
return new QgsLayoutViewRectangularRubberBand( view );
}
QAbstractGraphicsShapeItem *QgsLayoutItemAbstractGuiMetadata::createNodeRubberBand( QgsLayoutView *view )
{
return nullptr;
}
QgsLayoutItem *QgsLayoutItemAbstractGuiMetadata::createItem( QgsLayout * )
{
return nullptr;
@ -105,6 +110,14 @@ QgsLayoutViewRubberBand *QgsLayoutItemGuiRegistry::createItemRubberBand( int met
return mMetadata[metadataId]->createRubberBand( view );
}
QAbstractGraphicsShapeItem *QgsLayoutItemGuiRegistry::createNodeItemRubberBand( int metadataId, QgsLayoutView *view )
{
if ( !mMetadata.contains( metadataId ) )
return nullptr;
return mMetadata[metadataId]->createNodeRubberBand( view );
}
QList<int> QgsLayoutItemGuiRegistry::itemMetadataIds() const
{
return mMetadata.keys();

View File

@ -60,10 +60,13 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata
* \a visibleName should be set to a translated, user visible name identifying the corresponding layout item.
*
* An optional \a groupId can be set, which allows grouping of related layout item classes. See QgsLayoutItemGuiMetadata for details.
*
* If \a isNodeBased is true, then the corresponding item is a node based item.
*/
QgsLayoutItemAbstractGuiMetadata( int type, const QString &visibleName, const QString &groupId = QString(), Flags flags = 0 )
QgsLayoutItemAbstractGuiMetadata( int type, const QString &visibleName, const QString &groupId = QString(), bool isNodeBased = false, Flags flags = 0 )
: mType( type )
, mGroupId( groupId )
, mIsNodeBased( isNodeBased )
, mName( visibleName )
, mFlags( flags )
{}
@ -85,6 +88,11 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata
*/
QString groupId() const { return mGroupId; }
/**
* Returns true if the associated item is a node based item.
*/
bool isNodeBased() const { return mIsNodeBased; }
/**
* Returns a translated, user visible name identifying the corresponding layout item.
*/
@ -103,9 +111,17 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata
/**
* Creates a rubber band for use when creating layout items of this type. Can return nullptr if no rubber band
* should be created. The default behavior is to create a rectangular rubber band.
* \see createNodeRubberBand()
*/
virtual QgsLayoutViewRubberBand *createRubberBand( QgsLayoutView *view ) SIP_FACTORY;
/**
* Creates a rubber band for use when creating layout node based items of this type. Can return nullptr if no rubber band
* should be created. The default behavior is to return nullptr.
* \see createRubberBand()
*/
virtual QAbstractGraphicsShapeItem *createNodeRubberBand( QgsLayoutView *view ) SIP_FACTORY;
/**
* Creates an instance of the corresponding item type.
*/
@ -115,6 +131,7 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata
int mType = -1;
QString mGroupId;
bool mIsNodeBased = false;
QString mName;
Flags mFlags;
@ -126,6 +143,9 @@ typedef std::function<QgsLayoutItemBaseWidget *( QgsLayoutItem * )> QgsLayoutIte
//! Layout rubber band creation function
typedef std::function<QgsLayoutViewRubberBand *( QgsLayoutView * )> QgsLayoutItemRubberBandFunc SIP_SKIP;
//! Layout node based rubber band creation function
typedef std::function<QAbstractGraphicsShapeItem *( QgsLayoutView * )> QgsLayoutNodeItemRubberBandFunc SIP_SKIP;
#ifndef SIP_RUN
/**
@ -146,12 +166,16 @@ class GUI_EXPORT QgsLayoutItemGuiMetadata : public QgsLayoutItemAbstractGuiMetad
* \a visibleName should be set to a translated, user visible name identifying the corresponding layout item.
*
* An optional \a groupId can be set, which allows grouping of related layout item classes. See QgsLayoutItemGuiMetadata for details.
*
* If \a isNodeBased is true, then the corresponding item is a node based item.
*/
QgsLayoutItemGuiMetadata( int type, const QString &visibleName, const QIcon &creationIcon,
QgsLayoutItemWidgetFunc pfWidget = nullptr,
QgsLayoutItemRubberBandFunc pfRubberBand = nullptr, const QString &groupId = QString(), QgsLayoutItemAbstractGuiMetadata::Flags flags = 0,
QgsLayoutItemRubberBandFunc pfRubberBand = nullptr, const QString &groupId = QString(),
bool isNodeBased = false,
QgsLayoutItemAbstractGuiMetadata::Flags flags = 0,
QgsLayoutItemCreateFunc pfCreateFunc = nullptr )
: QgsLayoutItemAbstractGuiMetadata( type, visibleName, groupId, flags )
: QgsLayoutItemAbstractGuiMetadata( type, visibleName, groupId, isNodeBased, flags )
, mIcon( creationIcon )
, mWidgetFunc( pfWidget )
, mRubberBandFunc( pfRubberBand )
@ -182,6 +206,18 @@ class GUI_EXPORT QgsLayoutItemGuiMetadata : public QgsLayoutItemAbstractGuiMetad
*/
void setRubberBandCreationFunction( QgsLayoutItemRubberBandFunc function ) { mRubberBandFunc = function; }
/**
* Returns the classes' node based rubber band creation function.
* \see setNodeRubberBandCreationFunction()
*/
QgsLayoutNodeItemRubberBandFunc nodeRubberBandCreationFunction() const { return mNodeRubberBandFunc; }
/**
* Sets the classes' node based rubber band creation \a function.
* \see nodeRubberBandCreationFunction()
*/
void setNodeRubberBandCreationFunction( QgsLayoutNodeItemRubberBandFunc function ) { mNodeRubberBandFunc = function; }
/**
* Returns the classes' item creation function.
* \see setItemCreationFunction()
@ -197,12 +233,14 @@ class GUI_EXPORT QgsLayoutItemGuiMetadata : public QgsLayoutItemAbstractGuiMetad
QIcon creationIcon() const override { return mIcon.isNull() ? QgsLayoutItemAbstractGuiMetadata::creationIcon() : mIcon; }
QgsLayoutItemBaseWidget *createItemWidget( QgsLayoutItem *item ) override { return mWidgetFunc ? mWidgetFunc( item ) : nullptr; }
QgsLayoutViewRubberBand *createRubberBand( QgsLayoutView *view ) override { return mRubberBandFunc ? mRubberBandFunc( view ) : nullptr; }
QAbstractGraphicsShapeItem *createNodeRubberBand( QgsLayoutView *view ) override { return mNodeRubberBandFunc ? mNodeRubberBandFunc( view ) : nullptr; }
QgsLayoutItem *createItem( QgsLayout *layout ) override;
protected:
QIcon mIcon;
QgsLayoutItemWidgetFunc mWidgetFunc = nullptr;
QgsLayoutItemRubberBandFunc mRubberBandFunc = nullptr;
QgsLayoutNodeItemRubberBandFunc mNodeRubberBandFunc = nullptr;
QgsLayoutItemCreateFunc mCreateFunc = nullptr;
};
@ -329,9 +367,18 @@ class GUI_EXPORT QgsLayoutItemGuiRegistry : public QObject
/**
* Creates a new rubber band item for the specified item \a metadataId and destination \a view.
* \note not available from Python bindings
* \see createNodeItemRubberBand()
*/
QgsLayoutViewRubberBand *createItemRubberBand( int metadataId, QgsLayoutView *view ) const SIP_SKIP;
/**
* Creates a rubber band for the specified item \a metadataId and destination \a view.
* Can return nullptr if no node based rubber band should be created or is applicable for the item.
* \see createItemRubberBand()
* \note not available from Python bindings
*/
QAbstractGraphicsShapeItem *createNodeItemRubberBand( int metadataId, QgsLayoutView *view ) SIP_SKIP;
/**
* Returns a list of available item metadata ids handled by the registry.
*/

View File

@ -149,7 +149,7 @@ void QgsLayoutViewRectangularRubberBand::start( QPointF position, Qt::KeyboardMo
mRubberBandStartPos = position;
t.translate( position.x(), position.y() );
mRubberBandItem->setTransform( t );
mRubberBandItem->setZValue( QgsLayout::ZMapTool );
mRubberBandItem->setZValue( QgsLayout::ZViewTool );
layout()->addItem( mRubberBandItem );
layout()->update();
}
@ -214,7 +214,7 @@ void QgsLayoutViewEllipticalRubberBand::start( QPointF position, Qt::KeyboardMod
mRubberBandStartPos = position;
t.translate( position.x(), position.y() );
mRubberBandItem->setTransform( t );
mRubberBandItem->setZValue( QgsLayout::ZMapTool );
mRubberBandItem->setZValue( QgsLayout::ZViewTool );
layout()->addItem( mRubberBandItem );
layout()->update();
}
@ -283,7 +283,7 @@ void QgsLayoutViewTriangleRubberBand::start( QPointF position, Qt::KeyboardModif
mRubberBandStartPos = position;
t.translate( position.x(), position.y() );
mRubberBandItem->setTransform( t );
mRubberBandItem->setZValue( QgsLayout::ZMapTool );
mRubberBandItem->setZValue( QgsLayout::ZViewTool );
layout()->addItem( mRubberBandItem );
layout()->update();
}

View File

@ -0,0 +1,172 @@
/***************************************************************************
qgslayoutviewtooladdnodeitem.cpp
----------------------------
Date : July 2017
Copyright : (C) 2017 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 "qgslayoutviewtooladdnodeitem.h"
#include "qgsapplication.h"
#include "qgscursors.h"
#include "qgslayoutview.h"
#include "qgslayout.h"
#include "qgslayoutitemregistry.h"
#include "qgslayoutviewmouseevent.h"
#include "qgslogger.h"
#include "qgslayoutviewrubberband.h"
#include "qgsgui.h"
#include "qgslayoutitemguiregistry.h"
#include "qgslayoutnewitempropertiesdialog.h"
#include "qgssettings.h"
#include "qgslayoututils.h"
#include "qgslayoutitemnodeitem.h"
#include <QGraphicsRectItem>
#include <QPen>
#include <QBrush>
#include <QMouseEvent>
QgsLayoutViewToolAddNodeItem::QgsLayoutViewToolAddNodeItem( QgsLayoutView *view )
: QgsLayoutViewTool( view, tr( "Add item" ) )
{
setFlags( QgsLayoutViewTool::FlagSnaps );
QPixmap crosshairQPixmap = QPixmap( ( const char ** )( cross_hair_cursor ) );
setCursor( QCursor( crosshairQPixmap, 8, 8 ) );
}
void QgsLayoutViewToolAddNodeItem::setItemMetadataId( int metadataId )
{
mItemMetadataId = metadataId;
}
void QgsLayoutViewToolAddNodeItem::layoutPressEvent( QgsLayoutViewMouseEvent *event )
{
if ( event->button() == Qt::LeftButton )
{
if ( !mRubberBand )
{
mPolygon.clear();
mRubberBand.reset( QgsGui::layoutItemGuiRegistry()->createNodeItemRubberBand( mItemMetadataId, view() ) );
if ( mRubberBand )
layout()->addItem( mRubberBand.get() );
}
if ( mRubberBand )
{
//add a new node
addNode( event->snappedPoint() );
}
}
else if ( event->button() == Qt::RightButton && mRubberBand )
{
// finish up
// last (temporary) point is removed
mPolygon.remove( mPolygon.count() - 1 );
QgsLayoutItem *item = QgsGui::layoutItemGuiRegistry()->createItem( mItemMetadataId, layout() );
if ( !item )
return;
if ( QgsLayoutNodesItem *nodesItem = qobject_cast< QgsLayoutNodesItem * >( item ) )
nodesItem->setNodes( mPolygon );
layout()->addLayoutItem( item );
layout()->setSelectedItem( item );
emit createdItem();
}
else
{
event->ignore();
mRubberBand.reset();
}
}
void QgsLayoutViewToolAddNodeItem::layoutMoveEvent( QgsLayoutViewMouseEvent *event )
{
if ( mRubberBand )
{
moveTemporaryNode( event->snappedPoint(), event->modifiers() );
}
else
{
event->ignore();
}
}
void QgsLayoutViewToolAddNodeItem::layoutReleaseEvent( QgsLayoutViewMouseEvent *event )
{
if ( !mRubberBand )
{
event->ignore();
return;
}
}
void QgsLayoutViewToolAddNodeItem::deactivate()
{
if ( mRubberBand )
{
// canceled mid operation
mRubberBand.reset();
}
QgsLayoutViewTool::deactivate();
}
void QgsLayoutViewToolAddNodeItem::addNode( QPointF scenePoint )
{
mPolygon.append( scenePoint );
if ( mPolygon.size() == 1 )
mPolygon.append( scenePoint );
setRubberBandNodes();
}
void QgsLayoutViewToolAddNodeItem::moveTemporaryNode( QPointF scenePoint, Qt::KeyboardModifiers modifiers )
{
if ( mPolygon.isEmpty() )
return;
if ( mPolygon.size() > 1 && ( modifiers & Qt::ShiftModifier ) )
{
QPointF start = mPolygon.at( mPolygon.size() - 2 );
QLineF newLine = QLineF( start, scenePoint );
//movement is contrained to 45 degree angles
double angle = QgsLayoutUtils::snappedAngle( newLine.angle() );
newLine.setAngle( angle );
scenePoint = newLine.p2();
}
mPolygon.replace( mPolygon.size() - 1, scenePoint );
setRubberBandNodes();
}
void QgsLayoutViewToolAddNodeItem::setRubberBandNodes()
{
if ( QGraphicsPolygonItem *polygonItem = dynamic_cast< QGraphicsPolygonItem *>( mRubberBand.get() ) )
{
polygonItem->setPolygon( mPolygon );
}
else if ( QGraphicsPathItem *polylineItem = dynamic_cast< QGraphicsPathItem *>( mRubberBand.get() ) )
{
// rebuild a new qpainter path
QPainterPath path;
path.addPolygon( mPolygon );
polylineItem->setPath( path );
}
}
int QgsLayoutViewToolAddNodeItem::itemMetadataId() const
{
return mItemMetadataId;
}

View File

@ -0,0 +1,82 @@
/***************************************************************************
qgslayoutviewtooladdnodeitem.h
--------------------------
Date : October 2017
Copyright : (C) 2017 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 QGSLAYOUTVIEWTOOLADDNODEITEM_H
#define QGSLAYOUTVIEWTOOLADDNODEITEM_H
#include "qgis.h"
#include "qgis_gui.h"
#include "qgslayoutviewtool.h"
#include <memory>
#include <QAbstractGraphicsShapeItem>
/**
* \ingroup gui
* Layout view tool for adding node based items to a layout.
* \since QGIS 3.0
*/
class GUI_EXPORT QgsLayoutViewToolAddNodeItem : public QgsLayoutViewTool
{
Q_OBJECT
public:
QgsLayoutViewToolAddNodeItem( QgsLayoutView *view SIP_TRANSFERTHIS );
/**
* Returns the item metadata id for items created by the tool.
* \see setItemMetadataId()
*/
int itemMetadataId() const;
/**
* Sets the item metadata \a metadataId for items created by the tool.
*
* The \a metadataId associates the current tool behavior with a metadata entry
* from QgsLayoutItemGuiRegistry.
*
* \see itemMetadataId()
*/
void setItemMetadataId( int metadataId );
void layoutPressEvent( QgsLayoutViewMouseEvent *event ) override;
void layoutMoveEvent( QgsLayoutViewMouseEvent *event ) override;
void layoutReleaseEvent( QgsLayoutViewMouseEvent *event ) override;
void deactivate() override;
signals:
/**
* Emitted when an item has been created using the tool.
*/
void createdItem();
private:
int mItemMetadataId = -1;
//! Rubber band item
std::unique_ptr< QAbstractGraphicsShapeItem > mRubberBand;
QPolygonF mPolygon;
void addNode( QPointF scenePoint );
void moveTemporaryNode( QPointF scenePoint, Qt::KeyboardModifiers modifiers );
void setRubberBandNodes();
};
#endif // QGSLAYOUTVIEWTOOLADDNODEITEM_H

View File

@ -76,6 +76,15 @@ class TestQgsLayoutPolygon(unittest.TestCase):
p = QgsLayoutItemPolygon(polygon, self.layout)
self.assertEqual(p.nodes(), polygon)
polygon = QPolygonF()
polygon.append(QPointF(0.0, 0.0))
polygon.append(QPointF(1000.0, 0.0))
polygon.append(QPointF(2000.0, 100.0))
polygon.append(QPointF(1000.0, 200.0))
p.setNodes(polygon)
self.assertEqual(p.nodes(), polygon)
def testDisplayName(self):
"""Test if displayName is valid"""

View File

@ -64,6 +64,25 @@ class TestQgsLayoutPolyline(unittest.TestCase):
style = QgsLineSymbol.createSimple(props)
self.polyline.setSymbol(style)
def testNodes(self):
polygon = QPolygonF()
polygon.append(QPointF(0.0, 0.0))
polygon.append(QPointF(100.0, 0.0))
polygon.append(QPointF(200.0, 100.0))
polygon.append(QPointF(100.0, 200.0))
p = QgsLayoutItemPolyline(polygon, self.layout)
self.assertEqual(p.nodes(), polygon)
polygon = QPolygonF()
polygon.append(QPointF(0.0, 0.0))
polygon.append(QPointF(1000.0, 0.0))
polygon.append(QPointF(2000.0, 100.0))
polygon.append(QPointF(1000.0, 200.0))
p.setNodes(polygon)
self.assertEqual(p.nodes(), polygon)
def testDisplayName(self):
"""Test if displayName is valid"""