mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
Merge pull request #5119 from nyalldawson/layout_next3
[layouts] Undo/redo framework
This commit is contained in:
commit
cea7eb8be5
@ -30,6 +30,7 @@ from qgis.core import QgsSettings
|
||||
|
||||
|
||||
class optionsDialog(QDialog, Ui_SettingsDialogPythonConsole):
|
||||
|
||||
def __init__(self, parent):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setWindowTitle(QCoreApplication.translate(
|
||||
|
@ -160,8 +160,11 @@
|
||||
%Include layout/qgslayoutmeasurementconverter.sip
|
||||
%Include layout/qgspagesizeregistry.sip
|
||||
%Include layout/qgslayoutpoint.sip
|
||||
%Include layout/qgslayoutserializableobject.sip
|
||||
%Include layout/qgslayoutsize.sip
|
||||
%Include layout/qgslayoutsnapper.sip
|
||||
%Include layout/qgslayoutundocommand.sip
|
||||
%Include layout/qgslayoutundostack.sip
|
||||
%Include layout/qgslayoututils.sip
|
||||
%Include metadata/qgslayermetadata.sip
|
||||
%Include metadata/qgslayermetadatavalidator.sip
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
|
||||
class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator
|
||||
class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator, QgsLayoutUndoObjectInterface
|
||||
{
|
||||
%Docstring
|
||||
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
|
||||
@ -39,8 +39,6 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator
|
||||
called on the new layout.
|
||||
%End
|
||||
|
||||
~QgsLayout();
|
||||
|
||||
void initializeDefaults();
|
||||
%Docstring
|
||||
Initializes an empty layout, e.g. by adding a default page to the layout. This should be called after creating
|
||||
@ -67,6 +65,14 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator
|
||||
.. seealso:: name()
|
||||
%End
|
||||
|
||||
|
||||
QgsLayoutItem *itemByUuid( const QString &uuid );
|
||||
%Docstring
|
||||
Returns the layout item with matching ``uuid`` unique identifier, or a None
|
||||
if a matching item could not be found.
|
||||
:rtype: QgsLayoutItem
|
||||
%End
|
||||
|
||||
void setUnits( QgsUnitTypes::LayoutUnit units );
|
||||
%Docstring
|
||||
Sets the native measurement ``units`` for the layout. These also form the default unit
|
||||
@ -247,6 +253,32 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator
|
||||
method. Ownership of the item is transferred to the layout.
|
||||
%End
|
||||
|
||||
QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const;
|
||||
%Docstring
|
||||
Returns the layout's state encapsulated in a DOM element.
|
||||
.. seealso:: readXml()
|
||||
:rtype: QDomElement
|
||||
%End
|
||||
|
||||
bool readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context );
|
||||
%Docstring
|
||||
Sets the collection's state from a DOM element. ``layoutElement`` is the DOM node corresponding to the layout.
|
||||
.. seealso:: writeXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
QgsLayoutUndoStack *undoStack();
|
||||
%Docstring
|
||||
Returns a pointer to the layout's undo stack, which manages undo/redo states for the layout
|
||||
and it's associated objects.
|
||||
:rtype: QgsLayoutUndoStack
|
||||
%End
|
||||
|
||||
|
||||
virtual QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id = 0, QUndoCommand *parent = 0 ) /Factory/;
|
||||
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
void updateBounds();
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
|
||||
class QgsLayoutGridSettings
|
||||
class QgsLayoutGridSettings : QgsLayoutSerializableObject
|
||||
{
|
||||
%Docstring
|
||||
Contains settings relating to the appearance, spacing and offset for layout grids.
|
||||
@ -27,11 +27,15 @@ class QgsLayoutGridSettings
|
||||
StyleCrosses
|
||||
};
|
||||
|
||||
QgsLayoutGridSettings();
|
||||
QgsLayoutGridSettings( QgsLayout *layout );
|
||||
%Docstring
|
||||
Constructor for QgsLayoutGridSettings.
|
||||
%End
|
||||
|
||||
virtual QString stringType() const;
|
||||
virtual QgsLayout *layout();
|
||||
|
||||
|
||||
void setResolution( const QgsLayoutMeasurement &resolution );
|
||||
%Docstring
|
||||
Sets the page/snap grid ``resolution``.
|
||||
@ -92,6 +96,22 @@ class QgsLayoutGridSettings
|
||||
:rtype: Style
|
||||
%End
|
||||
|
||||
virtual bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const;
|
||||
|
||||
%Docstring
|
||||
Stores the grid's state in a DOM element. The ``parentElement`` should refer to the parent layout's DOM element.
|
||||
.. seealso:: readXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
virtual bool readXml( const QDomElement &gridElement, const QDomDocument &document, const QgsReadWriteContext &context );
|
||||
|
||||
%Docstring
|
||||
Sets the grid's state from a DOM element. gridElement is the DOM node corresponding to the grid.
|
||||
.. seealso:: writeXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
|
@ -132,7 +132,7 @@ class QgsLayoutGuide : QObject
|
||||
|
||||
};
|
||||
|
||||
class QgsLayoutGuideCollection : QAbstractTableModel
|
||||
class QgsLayoutGuideCollection : QAbstractTableModel, QgsLayoutSerializableObject
|
||||
{
|
||||
%Docstring
|
||||
Stores and manages the snap guides used by a layout.
|
||||
@ -160,6 +160,10 @@ class QgsLayoutGuideCollection : QAbstractTableModel
|
||||
%End
|
||||
~QgsLayoutGuideCollection();
|
||||
|
||||
virtual QString stringType() const;
|
||||
virtual QgsLayout *layout();
|
||||
|
||||
|
||||
virtual int rowCount( const QModelIndex & ) const;
|
||||
|
||||
virtual int columnCount( const QModelIndex & ) const;
|
||||
@ -188,6 +192,11 @@ class QgsLayoutGuideCollection : QAbstractTableModel
|
||||
.. seealso:: clear()
|
||||
%End
|
||||
|
||||
void setGuideLayoutPosition( QgsLayoutGuide *guide, double position );
|
||||
%Docstring
|
||||
Sets the absolute ``position`` (in layout coordinates) for ``guide`` within the layout.
|
||||
%End
|
||||
|
||||
void clear();
|
||||
%Docstring
|
||||
Removes all guides from the collection.
|
||||
@ -233,6 +242,22 @@ class QgsLayoutGuideCollection : QAbstractTableModel
|
||||
.. seealso:: visible()
|
||||
%End
|
||||
|
||||
virtual bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const;
|
||||
|
||||
%Docstring
|
||||
Stores the collection's state in a DOM element. The ``parentElement`` should refer to the parent layout's DOM element.
|
||||
.. seealso:: readXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
virtual bool readXml( const QDomElement &collectionElement, const QDomDocument &document, const QgsReadWriteContext &context );
|
||||
|
||||
%Docstring
|
||||
Sets the collection's state from a DOM element. collectionElement is the DOM node corresponding to the collection.
|
||||
.. seealso:: writeXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
|
||||
|
||||
class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem
|
||||
class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInterface
|
||||
{
|
||||
%Docstring
|
||||
Base class for graphical items within a QgsLayout.
|
||||
@ -219,6 +219,9 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
virtual QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = 0 ) /Factory/;
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
virtual void refresh();
|
||||
|
@ -32,6 +32,16 @@ class QgsLayoutItemPage : QgsLayoutItem
|
||||
%Docstring
|
||||
Constructor for QgsLayoutItemPage, with the specified parent ``layout``.
|
||||
%End
|
||||
|
||||
static QgsLayoutItemPage *create( QgsLayout *layout, const QVariantMap &settings ) /Factory/;
|
||||
%Docstring
|
||||
Returns a new page item for the specified ``layout``.
|
||||
|
||||
The caller takes responsibility for deleting the returned object.
|
||||
:rtype: QgsLayoutItemPage
|
||||
%End
|
||||
|
||||
|
||||
virtual int type() const;
|
||||
virtual QString stringType() const;
|
||||
|
||||
@ -80,6 +90,9 @@ class QgsLayoutItemPage : QgsLayoutItem
|
||||
virtual void attemptResize( const QgsLayoutSize &size );
|
||||
|
||||
|
||||
virtual QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = 0 ) /Factory/;
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
virtual void redraw();
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
|
||||
|
||||
class QgsLayoutPageCollection : QObject
|
||||
class QgsLayoutPageCollection : QObject, QgsLayoutSerializableObject
|
||||
{
|
||||
%Docstring
|
||||
A manager for a collection of pages in a layout.
|
||||
@ -28,11 +28,9 @@ class QgsLayoutPageCollection : QObject
|
||||
|
||||
~QgsLayoutPageCollection();
|
||||
|
||||
QgsLayout *layout() const;
|
||||
%Docstring
|
||||
Returns the layout this collection belongs to.
|
||||
:rtype: QgsLayout
|
||||
%End
|
||||
virtual QString stringType() const;
|
||||
virtual QgsLayout *layout();
|
||||
|
||||
|
||||
QList< QgsLayoutItemPage * > pages();
|
||||
%Docstring
|
||||
@ -130,6 +128,12 @@ class QgsLayoutPageCollection : QObject
|
||||
Calling deletePage() automatically triggers a reflow() of pages.
|
||||
%End
|
||||
|
||||
QgsLayoutItemPage *takePage( QgsLayoutItemPage *page ) /TransferBack/;
|
||||
%Docstring
|
||||
Takes a ``page`` from the collection, returning ownership of the page to the caller.
|
||||
:rtype: QgsLayoutItemPage
|
||||
%End
|
||||
|
||||
void setPageStyleSymbol( QgsFillSymbol *symbol );
|
||||
%Docstring
|
||||
Sets the ``symbol`` to use for drawing pages in the collection.
|
||||
@ -211,6 +215,29 @@ class QgsLayoutPageCollection : QObject
|
||||
:rtype: float
|
||||
%End
|
||||
|
||||
virtual bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const;
|
||||
|
||||
%Docstring
|
||||
Stores the collection's state in a DOM element. The ``parentElement`` should refer to the parent layout's DOM element.
|
||||
.. seealso:: readXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
virtual bool readXml( const QDomElement &collectionElement, const QDomDocument &document, const QgsReadWriteContext &context );
|
||||
|
||||
%Docstring
|
||||
Sets the collection's state from a DOM element. collectionElement is the DOM node corresponding to the collection.
|
||||
.. seealso:: writeXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
QgsLayoutGuideCollection &guides();
|
||||
%Docstring
|
||||
Returns a reference to the collection's guide collection, which manages page snap guides.
|
||||
:rtype: QgsLayoutGuideCollection
|
||||
%End
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
void redraw();
|
||||
|
66
python/core/layout/qgslayoutserializableobject.sip
Normal file
66
python/core/layout/qgslayoutserializableobject.sip
Normal file
@ -0,0 +1,66 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/layout/qgslayoutserializableobject.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsLayoutSerializableObject : QgsLayoutUndoObjectInterface
|
||||
{
|
||||
%Docstring
|
||||
An interface for layout objects which can be stored and read from DOM elements.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgslayoutserializableobject.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
virtual ~QgsLayoutSerializableObject();
|
||||
|
||||
virtual QString stringType() const = 0;
|
||||
%Docstring
|
||||
Return the object type as a string.
|
||||
|
||||
This string must be a unique, single word, character only representation of the item type, eg "LayoutScaleBar"
|
||||
:rtype: str
|
||||
%End
|
||||
|
||||
virtual QgsLayout *layout() = 0;
|
||||
%Docstring
|
||||
Returns the layout the object belongs to.
|
||||
:rtype: QgsLayout
|
||||
%End
|
||||
|
||||
virtual bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const = 0;
|
||||
%Docstring
|
||||
Stores the objects's state in a DOM element. The ``parentElement`` should refer to the parent layout's DOM element.
|
||||
.. seealso:: readXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
virtual bool readXml( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ) = 0;
|
||||
%Docstring
|
||||
Sets the objects's state from a DOM element. ``element`` is the DOM node corresponding to the object.
|
||||
.. seealso:: writeXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
virtual QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = 0 ) /Factory/;
|
||||
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/layout/qgslayoutserializableobject.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
|
||||
class QgsLayoutSnapper
|
||||
class QgsLayoutSnapper: QgsLayoutSerializableObject
|
||||
{
|
||||
%Docstring
|
||||
Manages snapping grids and preset snap lines in a layout, and handles
|
||||
@ -26,6 +26,10 @@ class QgsLayoutSnapper
|
||||
Constructor for QgsLayoutSnapper, attached to the specified ``layout``.
|
||||
%End
|
||||
|
||||
virtual QString stringType() const;
|
||||
virtual QgsLayout *layout();
|
||||
|
||||
|
||||
void setSnapTolerance( const int snapTolerance );
|
||||
%Docstring
|
||||
Sets the snap ``tolerance`` (in pixels) to use when snapping.
|
||||
@ -106,6 +110,22 @@ class QgsLayoutSnapper
|
||||
:rtype: float
|
||||
%End
|
||||
|
||||
virtual bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const;
|
||||
|
||||
%Docstring
|
||||
Stores the snapper's state in a DOM element. The ``parentElement`` should refer to the parent layout's DOM element.
|
||||
.. seealso:: readXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
virtual bool readXml( const QDomElement &gridElement, const QDomDocument &document, const QgsReadWriteContext &context );
|
||||
|
||||
%Docstring
|
||||
Sets the snapper's state from a DOM element. snapperElement is the DOM node corresponding to the snapper.
|
||||
.. seealso:: writeXml()
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
|
132
python/core/layout/qgslayoutundocommand.sip
Normal file
132
python/core/layout/qgslayoutundocommand.sip
Normal file
@ -0,0 +1,132 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/layout/qgslayoutundocommand.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsAbstractLayoutUndoCommand: QUndoCommand
|
||||
{
|
||||
%Docstring
|
||||
Base class for commands to undo/redo layout and layout object changes.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgslayoutundocommand.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsAbstractLayoutUndoCommand( const QString &text, int id = 0, QUndoCommand *parent /TransferThis/ = 0 );
|
||||
%Docstring
|
||||
Constructor for QgsLayoutUndoCommand.
|
||||
The ``id`` argument can be used to specify an id number for the source event - this is used to determine whether QUndoCommand
|
||||
command compression can apply to the command.
|
||||
%End
|
||||
|
||||
virtual void undo();
|
||||
|
||||
virtual void redo();
|
||||
|
||||
virtual int id() const;
|
||||
|
||||
void saveBeforeState();
|
||||
%Docstring
|
||||
Saves current layout state as before state.
|
||||
.. seealso:: beforeState()
|
||||
.. seealso:: saveAfterState()
|
||||
%End
|
||||
|
||||
void saveAfterState();
|
||||
%Docstring
|
||||
Saves current layout state as after state.
|
||||
.. seealso:: afterState()
|
||||
.. seealso:: saveBeforeState()
|
||||
%End
|
||||
|
||||
QDomDocument beforeState() const;
|
||||
%Docstring
|
||||
Returns the before state for the layout.
|
||||
.. seealso:: saveBeforeState()
|
||||
.. seealso:: afterState()
|
||||
:rtype: QDomDocument
|
||||
%End
|
||||
|
||||
QDomDocument afterState() const;
|
||||
%Docstring
|
||||
Returns the after state for the layout.
|
||||
.. seealso:: saveAfterState()
|
||||
.. seealso:: beforeState()
|
||||
:rtype: QDomDocument
|
||||
%End
|
||||
|
||||
virtual bool containsChange() const;
|
||||
%Docstring
|
||||
Returns true if both the before and after states are valid and different.
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
protected:
|
||||
|
||||
virtual void saveState( QDomDocument &stateDoc ) const = 0;
|
||||
%Docstring
|
||||
Saves the state of the object to the specified ``stateDoc``.
|
||||
|
||||
Subclasses must implement this to handle encapsulating their current state into a DOM document.
|
||||
|
||||
.. seealso:: restoreState()
|
||||
%End
|
||||
|
||||
virtual void restoreState( QDomDocument &stateDoc ) = 0;
|
||||
%Docstring
|
||||
Restores the state of the object from the specified ``stateDoc``.
|
||||
|
||||
Subclasses must implement this to handle restoring their current state from the encapsulated state.
|
||||
|
||||
.. seealso:: saveState()
|
||||
%End
|
||||
|
||||
void setAfterState( const QDomDocument &stateDoc );
|
||||
%Docstring
|
||||
Manually sets the after state for the command. Generally this should not be called directly.
|
||||
%End
|
||||
|
||||
|
||||
};
|
||||
|
||||
class QgsLayoutUndoObjectInterface
|
||||
{
|
||||
%Docstring
|
||||
Interface for layout objects which support undo/redo commands.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgslayoutundocommand.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
virtual QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id = 0, QUndoCommand *parent = 0 ) = 0 /Factory/;
|
||||
%Docstring
|
||||
Creates a new layout undo command with the specified ``text`` and ``parent``.
|
||||
|
||||
The ``id`` argument can be used to specify an id number for the source event - this is used to determine whether QUndoCommand
|
||||
command compression can apply to the command.
|
||||
:rtype: QgsAbstractLayoutUndoCommand
|
||||
%End
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/layout/qgslayoutundocommand.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
99
python/core/layout/qgslayoutundostack.sip
Normal file
99
python/core/layout/qgslayoutundostack.sip
Normal file
@ -0,0 +1,99 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/layout/qgslayoutundostack.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsLayoutUndoStack
|
||||
{
|
||||
%Docstring
|
||||
An undo stack for QgsLayouts.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgslayoutundostack.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsLayoutUndoStack( QgsLayout *layout );
|
||||
%Docstring
|
||||
Constructor for QgsLayoutUndoStack, for the specified parent ``layout``.
|
||||
%End
|
||||
|
||||
void beginMacro( const QString &commandText );
|
||||
%Docstring
|
||||
Starts a macro command, with the given descriptive ``commandText``.
|
||||
|
||||
Any commands added to the stack (either via direct manipulation of
|
||||
stack() or via beginCommand()/endCommand() calls) between a
|
||||
beginMacro() and endMacro() block are collapsed into a single
|
||||
undo command, which will be applied or rolled back in a single step.
|
||||
|
||||
.. seealso:: endMacro()
|
||||
%End
|
||||
|
||||
void endMacro();
|
||||
%Docstring
|
||||
Ends a macro command. This must be called after beginMacro(), when
|
||||
all child undo commands which form part of the macro have been completed.
|
||||
|
||||
Any commands added to the stack (either via direct manipulation of
|
||||
stack() or via beginCommand()/endCommand() calls) between a
|
||||
beginMacro() and endMacro() block are collapsed into a single
|
||||
undo command, which will be applied or rolled back in a single step.
|
||||
|
||||
.. seealso:: beginMacro()
|
||||
%End
|
||||
|
||||
void beginCommand( QgsLayoutUndoObjectInterface *object, const QString &commandText, int id = 0 );
|
||||
%Docstring
|
||||
Begins a new undo command for the specified ``object``.
|
||||
|
||||
This must be followed by a call to endCommand() or cancelCommand() after the desired changes
|
||||
have been made to ``object``.
|
||||
|
||||
The ``id`` argument can be used to specify an id number for the source event - this is used to determine whether QUndoCommand
|
||||
command compression can apply to the command.
|
||||
|
||||
.. seealso:: endCommand()
|
||||
.. seealso:: cancelCommand()
|
||||
%End
|
||||
|
||||
void endCommand();
|
||||
%Docstring
|
||||
Saves final state of an object and pushes the active command to the undo history.
|
||||
.. seealso:: beginCommand()
|
||||
.. seealso:: cancelCommand()
|
||||
%End
|
||||
|
||||
void cancelCommand();
|
||||
%Docstring
|
||||
Cancels the active command, discarding it without pushing to the undo history.
|
||||
.. seealso:: endCommand()
|
||||
.. seealso:: cancelCommand()
|
||||
%End
|
||||
|
||||
QUndoStack *stack();
|
||||
%Docstring
|
||||
Returns a pointer to the internal QUndoStack.
|
||||
:rtype: QUndoStack
|
||||
%End
|
||||
|
||||
private:
|
||||
QgsLayoutUndoStack( const QgsLayoutUndoStack &other );
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/layout/qgslayoutundostack.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
@ -44,6 +44,7 @@
|
||||
#include <QDesktopWidget>
|
||||
#include <QSlider>
|
||||
#include <QLabel>
|
||||
#include <QUndoView>
|
||||
|
||||
|
||||
//add some nice zoom levels for zoom comboboxes
|
||||
@ -286,15 +287,28 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
|
||||
mGuideDock->setUserVisible( true );
|
||||
} );
|
||||
|
||||
mUndoDock = new QgsDockWidget( tr( "Command history" ), this );
|
||||
mUndoDock->setObjectName( QStringLiteral( "CommandDock" ) );
|
||||
mPanelsMenu->addAction( mUndoDock->toggleViewAction() );
|
||||
mUndoView = new QUndoView( this );
|
||||
mUndoDock->setWidget( mUndoView );
|
||||
|
||||
addDockWidget( Qt::RightDockWidgetArea, mItemDock );
|
||||
addDockWidget( Qt::RightDockWidgetArea, mGeneralDock );
|
||||
addDockWidget( Qt::RightDockWidgetArea, mGuideDock );
|
||||
addDockWidget( Qt::RightDockWidgetArea, mUndoDock );
|
||||
|
||||
createLayoutPropertiesWidget();
|
||||
|
||||
mUndoDock->show();
|
||||
mItemDock->show();
|
||||
mGeneralDock->show();
|
||||
|
||||
mActionUndo->setEnabled( false );
|
||||
mActionRedo->setEnabled( false );
|
||||
|
||||
tabifyDockWidget( mGeneralDock, mUndoDock );
|
||||
tabifyDockWidget( mItemDock, mUndoDock );
|
||||
tabifyDockWidget( mGeneralDock, mItemDock );
|
||||
|
||||
restoreWindowState();
|
||||
@ -325,6 +339,12 @@ void QgsLayoutDesignerDialog::setCurrentLayout( QgsLayout *layout )
|
||||
mActionShowGuides->setChecked( mLayout->guides().visible() );
|
||||
mActionSnapGuides->setChecked( mLayout->snapper().snapToGuides() );
|
||||
|
||||
connect( mLayout->undoStack()->stack(), &QUndoStack::canUndoChanged, mActionUndo, &QAction::setEnabled );
|
||||
connect( mLayout->undoStack()->stack(), &QUndoStack::canRedoChanged, mActionRedo, &QAction::setEnabled );
|
||||
connect( mActionUndo, &QAction::triggered, mLayout->undoStack()->stack(), &QUndoStack::undo );
|
||||
connect( mActionRedo, &QAction::triggered, mLayout->undoStack()->stack(), &QUndoStack::redo );
|
||||
mUndoView->setStack( mLayout->undoStack()->stack() );
|
||||
|
||||
createLayoutPropertiesWidget();
|
||||
}
|
||||
|
||||
@ -357,8 +377,14 @@ void QgsLayoutDesignerDialog::showItemOptions( QgsLayoutItem *item )
|
||||
|
||||
delete mItemPropertiesStack->takeMainPanel();
|
||||
widget->setDockMode( true );
|
||||
connect( item, &QgsLayoutItem::destroyed, widget.get(), [this]
|
||||
{
|
||||
delete mItemPropertiesStack->takeMainPanel();
|
||||
} );
|
||||
|
||||
mItemPropertiesStack->setMainPanel( widget.release() );
|
||||
mItemDock->setUserVisible( true );
|
||||
|
||||
}
|
||||
|
||||
void QgsLayoutDesignerDialog::open()
|
||||
@ -612,12 +638,17 @@ void QgsLayoutDesignerDialog::addPages()
|
||||
|
||||
}
|
||||
|
||||
if ( dlg.numberPages() > 1 )
|
||||
mLayout->undoStack()->beginMacro( tr( "Add pages" ) );
|
||||
for ( int i = 0; i < dlg.numberPages(); ++i )
|
||||
{
|
||||
QgsLayoutItemPage *page = new QgsLayoutItemPage( mLayout );
|
||||
page->setPageSize( dlg.pageSize() );
|
||||
mLayout->pageCollection()->insertPage( page, firstPagePosition + i );
|
||||
}
|
||||
if ( dlg.numberPages() > 1 )
|
||||
mLayout->undoStack()->endMacro();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ class QgsLayoutAppMenuProvider;
|
||||
class QgsLayoutItem;
|
||||
class QgsPanelWidgetStack;
|
||||
class QgsDockWidget;
|
||||
class QUndoView;
|
||||
|
||||
class QgsAppLayoutDesignerInterface : public QgsLayoutDesignerInterface
|
||||
{
|
||||
@ -206,6 +207,9 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
|
||||
QgsDockWidget *mGuideDock = nullptr;
|
||||
QgsPanelWidgetStack *mGuideStack = nullptr;
|
||||
|
||||
QUndoView *mUndoView = nullptr;
|
||||
QgsDockWidget *mUndoDock = nullptr;
|
||||
|
||||
//! Save window state
|
||||
void saveWindowState();
|
||||
|
||||
|
@ -72,22 +72,26 @@ void QgsLayoutGuideWidget::addVerticalGuide()
|
||||
|
||||
void QgsLayoutGuideWidget::deleteHorizontalGuide()
|
||||
{
|
||||
mLayout->undoStack()->beginMacro( tr( "Remove horizontal guides" ) );
|
||||
Q_FOREACH ( const QModelIndex &index, mHozGuidesTableView->selectionModel()->selectedIndexes() )
|
||||
{
|
||||
mHozGuidesTableView->closePersistentEditor( index );
|
||||
if ( index.column() == 0 )
|
||||
mHozProxyModel->removeRow( index.row() );
|
||||
}
|
||||
mLayout->undoStack()->endMacro();
|
||||
}
|
||||
|
||||
void QgsLayoutGuideWidget::deleteVerticalGuide()
|
||||
{
|
||||
mLayout->undoStack()->beginMacro( tr( "Remove vertical guides" ) );
|
||||
Q_FOREACH ( const QModelIndex &index, mVertGuidesTableView->selectionModel()->selectedIndexes() )
|
||||
{
|
||||
mVertGuidesTableView->closePersistentEditor( index );
|
||||
if ( index.column() == 0 )
|
||||
mVertProxyModel->removeRow( index.row() );
|
||||
}
|
||||
mLayout->undoStack()->endMacro();
|
||||
}
|
||||
|
||||
void QgsLayoutGuideWidget::pageChanged( int page )
|
||||
@ -123,8 +127,10 @@ void QgsLayoutGuideWidget::clearAll()
|
||||
mVertGuidesTableView->closePersistentEditor( index );
|
||||
}
|
||||
|
||||
mLayout->undoStack()->beginMacro( tr( "Remove all guides" ) );
|
||||
mVertProxyModel->removeRows( 0, mVertProxyModel->rowCount() );
|
||||
mHozProxyModel->removeRows( 0, mHozProxyModel->rowCount() );
|
||||
mLayout->undoStack()->endMacro();
|
||||
}
|
||||
|
||||
void QgsLayoutGuideWidget::applyToAll()
|
||||
|
@ -128,7 +128,9 @@ void QgsLayoutPagePropertiesWidget::orientationChanged( int )
|
||||
|
||||
void QgsLayoutPagePropertiesWidget::updatePageSize()
|
||||
{
|
||||
mPage->layout()->undoStack()->beginCommand( mPage, tr( "Changed page size" ), 1 + mPage->layout()->pageCollection()->pageNumber( mPage ) );
|
||||
mPage->setPageSize( QgsLayoutSize( mWidthSpin->value(), mHeightSpin->value(), mSizeUnitsComboBox->unit() ) );
|
||||
mPage->layout()->undoStack()->endCommand();
|
||||
mPage->layout()->pageCollection()->reflow();
|
||||
}
|
||||
|
||||
@ -147,8 +149,6 @@ void QgsLayoutPagePropertiesWidget::showCurrentPageSize()
|
||||
if ( !pageSize.isEmpty() )
|
||||
{
|
||||
mPageSizeComboBox->setCurrentIndex( mPageSizeComboBox->findData( pageSize ) );
|
||||
mWidthSpin->setEnabled( false );
|
||||
mHeightSpin->setEnabled( false );
|
||||
mLockAspectRatio->setEnabled( false );
|
||||
mLockAspectRatio->setLocked( false );
|
||||
mSizeUnitsComboBox->setEnabled( false );
|
||||
@ -158,8 +158,6 @@ void QgsLayoutPagePropertiesWidget::showCurrentPageSize()
|
||||
{
|
||||
// custom
|
||||
mPageSizeComboBox->setCurrentIndex( mPageSizeComboBox->count() - 1 );
|
||||
mWidthSpin->setEnabled( true );
|
||||
mHeightSpin->setEnabled( true );
|
||||
mLockAspectRatio->setEnabled( true );
|
||||
mSizeUnitsComboBox->setEnabled( true );
|
||||
mPageOrientationComboBox->setEnabled( false );
|
||||
|
@ -359,14 +359,19 @@ SET(QGIS_CORE_SRCS
|
||||
layout/qgslayoutitempage.cpp
|
||||
layout/qgslayoutitemregistry.cpp
|
||||
layout/qgslayoutitemshape.cpp
|
||||
layout/qgslayoutitemundocommand.cpp
|
||||
layout/qgslayoutmeasurement.cpp
|
||||
layout/qgslayoutmeasurementconverter.cpp
|
||||
layout/qgslayoutobject.cpp
|
||||
layout/qgslayoutpagecollection.cpp
|
||||
layout/qgslayoutserializableobject.cpp
|
||||
layout/qgslayoutsnapper.cpp
|
||||
layout/qgslayoutundocommand.cpp
|
||||
layout/qgslayoutundostack.cpp
|
||||
layout/qgslayoututils.cpp
|
||||
layout/qgspagesizeregistry.cpp
|
||||
layout/qgslayoutpoint.cpp
|
||||
layout/qgslayoutserializableobject.cpp
|
||||
layout/qgslayoutsize.cpp
|
||||
|
||||
pal/costcalculator.cpp
|
||||
@ -935,12 +940,16 @@ SET(QGIS_CORE_HDRS
|
||||
|
||||
layout/qgslayoutcontext.h
|
||||
layout/qgslayoutgridsettings.h
|
||||
layout/qgslayoutitemundocommand.h
|
||||
layout/qgslayoutmeasurement.h
|
||||
layout/qgslayoutmeasurementconverter.h
|
||||
layout/qgspagesizeregistry.h
|
||||
layout/qgslayoutpoint.h
|
||||
layout/qgslayoutserializableobject.h
|
||||
layout/qgslayoutsize.h
|
||||
layout/qgslayoutsnapper.h
|
||||
layout/qgslayoutundocommand.h
|
||||
layout/qgslayoutundostack.h
|
||||
layout/qgslayoututils.h
|
||||
|
||||
metadata/qgslayermetadata.h
|
||||
|
@ -49,7 +49,7 @@ QgsComposerLabel::QgsComposerLabel( QgsComposition *composition )
|
||||
, mMarginX( 1.0 )
|
||||
, mMarginY( 1.0 )
|
||||
, mFontColor( QColor( 0, 0, 0 ) )
|
||||
, mHAlignment( Qt::AlignLeft )
|
||||
, mHAlignment( Qt::AlignJustify )
|
||||
, mVAlignment( Qt::AlignTop )
|
||||
, mDistanceArea( nullptr )
|
||||
{
|
||||
|
@ -71,7 +71,7 @@ class CORE_EXPORT QgsComposerLabel: public QgsComposerItem
|
||||
* \param a alignment
|
||||
* \returns void
|
||||
*/
|
||||
void setHAlign( Qt::AlignmentFlag a ) {mHAlignment = a;}
|
||||
void setHAlign( Qt::AlignmentFlag a ) { mHAlignment = a; }
|
||||
|
||||
/** Mutator for the vertical alignment of the label
|
||||
* \param a alignment
|
||||
|
@ -17,30 +17,28 @@
|
||||
#include "qgslayout.h"
|
||||
#include "qgslayoutpagecollection.h"
|
||||
#include "qgslayoutguidecollection.h"
|
||||
#include "qgsreadwritecontext.h"
|
||||
#include "qgsproject.h"
|
||||
|
||||
QgsLayout::QgsLayout( QgsProject *project )
|
||||
: QGraphicsScene()
|
||||
, mProject( project )
|
||||
, mSnapper( QgsLayoutSnapper( this ) )
|
||||
, mGridSettings( this )
|
||||
, mPageCollection( new QgsLayoutPageCollection( this ) )
|
||||
, mGuideCollection( new QgsLayoutGuideCollection( this, mPageCollection.get() ) )
|
||||
, mUndoStack( new QgsLayoutUndoStack( this ) )
|
||||
{
|
||||
// just to make sure - this should be the default, but maybe it'll change in some future Qt version...
|
||||
setBackgroundBrush( Qt::NoBrush );
|
||||
}
|
||||
|
||||
QgsLayout::~QgsLayout()
|
||||
{
|
||||
// delete guide collection FIRST, since it depends on the page collection
|
||||
mGuideCollection.reset();
|
||||
}
|
||||
|
||||
void QgsLayout::initializeDefaults()
|
||||
{
|
||||
// default to a A4 landscape page
|
||||
QgsLayoutItemPage *page = new QgsLayoutItemPage( this );
|
||||
page->setPageSize( QgsLayoutSize( 297, 210, QgsUnitTypes::LayoutMillimeters ) );
|
||||
mPageCollection->addPage( page );
|
||||
mUndoStack->stack()->clear();
|
||||
}
|
||||
|
||||
QgsProject *QgsLayout::project() const
|
||||
@ -48,6 +46,21 @@ QgsProject *QgsLayout::project() const
|
||||
return mProject;
|
||||
}
|
||||
|
||||
QgsLayoutItem *QgsLayout::itemByUuid( const QString &uuid )
|
||||
{
|
||||
QList<QgsLayoutItem *> itemList;
|
||||
layoutItems( itemList );
|
||||
Q_FOREACH ( QgsLayoutItem *item, itemList )
|
||||
{
|
||||
if ( item->uuid() == uuid )
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
double QgsLayout::convertToLayoutUnits( const QgsLayoutMeasurement &measurement ) const
|
||||
{
|
||||
return mContext.measurementConverter().convert( measurement, mUnits ).length();
|
||||
@ -80,12 +93,12 @@ QgsLayoutPoint QgsLayout::convertFromLayoutUnits( const QPointF &point, const Qg
|
||||
|
||||
QgsLayoutGuideCollection &QgsLayout::guides()
|
||||
{
|
||||
return *mGuideCollection;
|
||||
return mPageCollection->guides();
|
||||
}
|
||||
|
||||
const QgsLayoutGuideCollection &QgsLayout::guides() const
|
||||
{
|
||||
return *mGuideCollection;
|
||||
return mPageCollection->guides();
|
||||
}
|
||||
|
||||
QgsExpressionContext QgsLayout::createExpressionContext() const
|
||||
@ -196,6 +209,109 @@ void QgsLayout::addLayoutItem( QgsLayoutItem *item )
|
||||
updateBounds();
|
||||
}
|
||||
|
||||
QgsLayoutUndoStack *QgsLayout::undoStack()
|
||||
{
|
||||
return mUndoStack.get();
|
||||
}
|
||||
|
||||
const QgsLayoutUndoStack *QgsLayout::undoStack() const
|
||||
{
|
||||
return mUndoStack.get();
|
||||
}
|
||||
|
||||
///@cond PRIVATE
|
||||
class QgsLayoutUndoCommand: public QgsAbstractLayoutUndoCommand
|
||||
{
|
||||
public:
|
||||
|
||||
QgsLayoutUndoCommand( QgsLayout *layout, const QString &text, int id, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr )
|
||||
: QgsAbstractLayoutUndoCommand( text, id, parent )
|
||||
, mLayout( layout )
|
||||
{}
|
||||
|
||||
protected:
|
||||
|
||||
void saveState( QDomDocument &stateDoc ) const override
|
||||
{
|
||||
stateDoc.clear();
|
||||
QDomElement documentElement = stateDoc.createElement( QStringLiteral( "UndoState" ) );
|
||||
mLayout->writeXmlLayoutSettings( documentElement, stateDoc, QgsReadWriteContext() );
|
||||
stateDoc.appendChild( documentElement );
|
||||
}
|
||||
|
||||
void restoreState( QDomDocument &stateDoc ) override
|
||||
{
|
||||
if ( !mLayout )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mLayout->readXmlLayoutSettings( stateDoc.documentElement().firstChild().toElement(), stateDoc, QgsReadWriteContext() );
|
||||
mLayout->project()->setDirty( true );
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
QgsLayout *mLayout = nullptr;
|
||||
};
|
||||
///@endcond
|
||||
|
||||
QgsAbstractLayoutUndoCommand *QgsLayout::createCommand( const QString &text, int id, QUndoCommand *parent )
|
||||
{
|
||||
return new QgsLayoutUndoCommand( this, text, id, parent );
|
||||
}
|
||||
|
||||
void QgsLayout::writeXmlLayoutSettings( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
|
||||
{
|
||||
mCustomProperties.writeXml( element, document );
|
||||
element.setAttribute( QStringLiteral( "name" ), mName );
|
||||
element.setAttribute( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( mUnits ) );
|
||||
}
|
||||
|
||||
QDomElement QgsLayout::writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
QDomElement element = document.createElement( QStringLiteral( "Layout" ) );
|
||||
auto save = [&]( const QgsLayoutSerializableObject * object )->bool
|
||||
{
|
||||
return object->writeXml( element, document, context );
|
||||
};
|
||||
save( &mSnapper );
|
||||
save( &mGridSettings );
|
||||
save( mPageCollection.get() );
|
||||
|
||||
writeXmlLayoutSettings( element, document, context );
|
||||
return element;
|
||||
}
|
||||
|
||||
bool QgsLayout::readXmlLayoutSettings( const QDomElement &layoutElement, const QDomDocument &, const QgsReadWriteContext & )
|
||||
{
|
||||
mCustomProperties.readXml( layoutElement );
|
||||
setName( layoutElement.attribute( QStringLiteral( "name" ) ) );
|
||||
setUnits( QgsUnitTypes::decodeLayoutUnit( layoutElement.attribute( QStringLiteral( "units" ) ) ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsLayout::readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context )
|
||||
{
|
||||
if ( layoutElement.nodeName() != QString( "Layout" ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto restore = [&]( QgsLayoutSerializableObject * object )->bool
|
||||
{
|
||||
return object->readXml( layoutElement, document, context );
|
||||
};
|
||||
|
||||
readXmlLayoutSettings( layoutElement, document, context );
|
||||
|
||||
restore( mPageCollection.get() );
|
||||
restore( &mSnapper );
|
||||
restore( &mGridSettings );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QgsLayout::updateBounds()
|
||||
{
|
||||
setSceneRect( layoutBounds( false, 0.05 ) );
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "qgslayoutpagecollection.h"
|
||||
#include "qgslayoutgridsettings.h"
|
||||
#include "qgslayoutguidecollection.h"
|
||||
#include "qgslayoutundostack.h"
|
||||
|
||||
class QgsLayoutItemMap;
|
||||
|
||||
@ -33,7 +34,7 @@ class QgsLayoutItemMap;
|
||||
* \brief Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContextGenerator
|
||||
class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContextGenerator, public QgsLayoutUndoObjectInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@ -59,8 +60,6 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
|
||||
*/
|
||||
QgsLayout( QgsProject *project );
|
||||
|
||||
~QgsLayout();
|
||||
|
||||
/**
|
||||
* Initializes an empty layout, e.g. by adding a default page to the layout. This should be called after creating
|
||||
* a new layout.
|
||||
@ -86,6 +85,31 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
|
||||
*/
|
||||
void setName( const QString &name ) { mName = name; }
|
||||
|
||||
/**
|
||||
* Returns a list of layout items of a specific type.
|
||||
* \note not available in Python bindings
|
||||
*/
|
||||
template<class T> void layoutItems( QList<T *> &itemList ) SIP_SKIP
|
||||
{
|
||||
itemList.clear();
|
||||
QList<QGraphicsItem *> graphicsItemList = items();
|
||||
QList<QGraphicsItem *>::iterator itemIt = graphicsItemList.begin();
|
||||
for ( ; itemIt != graphicsItemList.end(); ++itemIt )
|
||||
{
|
||||
T *item = dynamic_cast<T *>( *itemIt );
|
||||
if ( item )
|
||||
{
|
||||
itemList.push_back( item );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the layout item with matching \a uuid unique identifier, or a nullptr
|
||||
* if a matching item could not be found.
|
||||
*/
|
||||
QgsLayoutItem *itemByUuid( const QString &uuid );
|
||||
|
||||
/**
|
||||
* Sets the native measurement \a units for the layout. These also form the default unit
|
||||
* for measurements for the layout.
|
||||
@ -285,6 +309,33 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
|
||||
*/
|
||||
void addLayoutItem( QgsLayoutItem *item SIP_TRANSFER );
|
||||
|
||||
/**
|
||||
* Returns the layout's state encapsulated in a DOM element.
|
||||
* \see readXml()
|
||||
*/
|
||||
QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const;
|
||||
|
||||
/**
|
||||
* Sets the collection's state from a DOM element. \a layoutElement is the DOM node corresponding to the layout.
|
||||
* \see writeXml()
|
||||
*/
|
||||
bool readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context );
|
||||
|
||||
/**
|
||||
* Returns a pointer to the layout's undo stack, which manages undo/redo states for the layout
|
||||
* and it's associated objects.
|
||||
*/
|
||||
QgsLayoutUndoStack *undoStack();
|
||||
|
||||
/**
|
||||
* Returns a pointer to the layout's undo stack, which manages undo/redo states for the layout
|
||||
* and it's associated objects.
|
||||
*/
|
||||
SIP_SKIP const QgsLayoutUndoStack *undoStack() const;
|
||||
|
||||
QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id = 0, QUndoCommand *parent = nullptr ) SIP_FACTORY override;
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
@ -313,9 +364,14 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
|
||||
QgsLayoutGridSettings mGridSettings;
|
||||
|
||||
std::unique_ptr< QgsLayoutPageCollection > mPageCollection;
|
||||
std::unique_ptr< QgsLayoutUndoStack > mUndoStack;
|
||||
|
||||
std::unique_ptr< QgsLayoutGuideCollection > mGuideCollection;
|
||||
//! Writes only the layout settings (not member settings like grid settings, etc) to XML
|
||||
void writeXmlLayoutSettings( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const;
|
||||
//! Reads only the layout settings (not member settings like grid settings, etc) from XML
|
||||
bool readXmlLayoutSettings( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context );
|
||||
|
||||
friend class QgsLayoutUndoCommand;
|
||||
};
|
||||
|
||||
#endif //QGSLAYOUT_H
|
||||
|
@ -15,11 +15,76 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgslayoutgridsettings.h"
|
||||
#include "qgsreadwritecontext.h"
|
||||
#include "qgslayout.h"
|
||||
#include "qgsproject.h"
|
||||
|
||||
QgsLayoutGridSettings::QgsLayoutGridSettings()
|
||||
QgsLayoutGridSettings::QgsLayoutGridSettings( QgsLayout *layout )
|
||||
: mGridResolution( QgsLayoutMeasurement( 10 ) )
|
||||
, mLayout( layout )
|
||||
{
|
||||
mGridPen = QPen( QColor( 190, 190, 190, 100 ), 0 );
|
||||
mGridPen.setCosmetic( true );
|
||||
}
|
||||
|
||||
QgsLayout *QgsLayoutGridSettings::layout()
|
||||
{
|
||||
return mLayout;
|
||||
}
|
||||
|
||||
void QgsLayoutGridSettings::setResolution( const QgsLayoutMeasurement &resolution )
|
||||
{
|
||||
mLayout->undoStack()->beginCommand( this, QObject::tr( "Grid resolution changed" ), UndoGridResolution );
|
||||
mGridResolution = resolution;
|
||||
mLayout->undoStack()->endCommand();
|
||||
}
|
||||
|
||||
void QgsLayoutGridSettings::setOffset( const QgsLayoutPoint offset )
|
||||
{
|
||||
mLayout->undoStack()->beginCommand( this, QObject::tr( "Grid offset changed" ), UndoGridOffset );
|
||||
mGridOffset = offset;
|
||||
mLayout->undoStack()->endCommand();
|
||||
}
|
||||
|
||||
bool QgsLayoutGridSettings::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const
|
||||
{
|
||||
QDomElement element = document.createElement( QStringLiteral( "Grid" ) );
|
||||
|
||||
element.setAttribute( QStringLiteral( "resolution" ), mGridResolution.length() );
|
||||
element.setAttribute( QStringLiteral( "resUnits" ), QgsUnitTypes::encodeUnit( mGridResolution.units() ) );
|
||||
|
||||
element.setAttribute( QStringLiteral( "offsetX" ), mGridOffset.x() );
|
||||
element.setAttribute( QStringLiteral( "offsetY" ), mGridOffset.y() );
|
||||
element.setAttribute( QStringLiteral( "offsetUnits" ), QgsUnitTypes::encodeUnit( mGridOffset.units() ) );
|
||||
|
||||
parentElement.appendChild( element );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsLayoutGridSettings::readXml( const QDomElement &e, const QDomDocument &, const QgsReadWriteContext & )
|
||||
{
|
||||
QDomElement element = e;
|
||||
if ( element.nodeName() != QStringLiteral( "Grid" ) )
|
||||
{
|
||||
element = element.firstChildElement( QStringLiteral( "Grid" ) );
|
||||
}
|
||||
|
||||
if ( element.nodeName() != QStringLiteral( "Grid" ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double res = element.attribute( QStringLiteral( "resolution" ), QStringLiteral( "10" ) ).toDouble();
|
||||
QgsUnitTypes::LayoutUnit resUnit = QgsUnitTypes::decodeLayoutUnit( element.attribute( QStringLiteral( "resUnits" ) ) );
|
||||
mGridResolution = QgsLayoutMeasurement( res, resUnit );
|
||||
|
||||
double offsetX = element.attribute( QStringLiteral( "offsetX" ) ).toDouble();
|
||||
double offsetY = element.attribute( QStringLiteral( "offsetY" ) ).toDouble();
|
||||
QgsUnitTypes::LayoutUnit offsetUnit = QgsUnitTypes::decodeLayoutUnit( element.attribute( QStringLiteral( "offsetUnits" ) ) );
|
||||
mGridOffset = QgsLayoutPoint( offsetX, offsetY, offsetUnit );
|
||||
|
||||
mLayout->pageCollection()->redraw();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,12 @@
|
||||
#include "qgis_core.h"
|
||||
#include "qgslayoutmeasurement.h"
|
||||
#include "qgslayoutpoint.h"
|
||||
#include "qgslayoutundocommand.h"
|
||||
#include "qgslayoutserializableobject.h"
|
||||
#include <QPen>
|
||||
|
||||
class QgsLayout;
|
||||
class QgsReadWriteContext;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -29,7 +32,7 @@ class QgsLayout;
|
||||
* \brief Contains settings relating to the appearance, spacing and offset for layout grids.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayoutGridSettings
|
||||
class CORE_EXPORT QgsLayoutGridSettings : public QgsLayoutSerializableObject
|
||||
{
|
||||
|
||||
public:
|
||||
@ -45,14 +48,17 @@ class CORE_EXPORT QgsLayoutGridSettings
|
||||
/**
|
||||
* Constructor for QgsLayoutGridSettings.
|
||||
*/
|
||||
QgsLayoutGridSettings();
|
||||
QgsLayoutGridSettings( QgsLayout *layout );
|
||||
|
||||
QString stringType() const override { return QStringLiteral( "LayoutGrid" ); }
|
||||
QgsLayout *layout() override;
|
||||
|
||||
/**
|
||||
* Sets the page/snap grid \a resolution.
|
||||
* \see resolution()
|
||||
* \see setOffset()
|
||||
*/
|
||||
void setResolution( const QgsLayoutMeasurement &resolution ) { mGridResolution = resolution; }
|
||||
void setResolution( const QgsLayoutMeasurement &resolution );
|
||||
|
||||
/**
|
||||
* Returns the page/snap grid resolution.
|
||||
@ -66,7 +72,7 @@ class CORE_EXPORT QgsLayoutGridSettings
|
||||
* \see offset()
|
||||
* \see setResolution()
|
||||
*/
|
||||
void setOffset( const QgsLayoutPoint offset ) { mGridOffset = offset; }
|
||||
void setOffset( const QgsLayoutPoint offset );
|
||||
|
||||
/**
|
||||
* Returns the offset of the page/snap grid.
|
||||
@ -103,12 +109,33 @@ class CORE_EXPORT QgsLayoutGridSettings
|
||||
*/
|
||||
Style style() const { return mGridStyle; }
|
||||
|
||||
/**
|
||||
* Stores the grid's state in a DOM element. The \a parentElement should refer to the parent layout's DOM element.
|
||||
* \see readXml()
|
||||
*/
|
||||
bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const override;
|
||||
|
||||
/**
|
||||
* Sets the grid's state from a DOM element. gridElement is the DOM node corresponding to the grid.
|
||||
* \see writeXml()
|
||||
*/
|
||||
bool readXml( const QDomElement &gridElement, const QDomDocument &document, const QgsReadWriteContext &context ) override;
|
||||
|
||||
private:
|
||||
|
||||
// Used for 'collapsing' undo commands
|
||||
enum UndoCommand
|
||||
{
|
||||
UndoGridResolution = 1,
|
||||
UndoGridOffset,
|
||||
};
|
||||
|
||||
QgsLayoutMeasurement mGridResolution;
|
||||
QgsLayoutPoint mGridOffset;
|
||||
QPen mGridPen;
|
||||
Style mGridStyle = StyleLines;
|
||||
QgsLayout *mLayout = nullptr;
|
||||
|
||||
};
|
||||
|
||||
#endif //QGSLAYOUTGRIDSETTINGS_H
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
#include "qgslayoutguidecollection.h"
|
||||
#include "qgslayout.h"
|
||||
#include "qgsproject.h"
|
||||
#include "qgsreadwritecontext.h"
|
||||
#include <QGraphicsLineItem>
|
||||
|
||||
|
||||
@ -205,6 +207,11 @@ QgsLayoutGuideCollection::~QgsLayoutGuideCollection()
|
||||
qDeleteAll( mGuides );
|
||||
}
|
||||
|
||||
QgsLayout *QgsLayoutGuideCollection::layout()
|
||||
{
|
||||
return mLayout;
|
||||
}
|
||||
|
||||
int QgsLayoutGuideCollection::rowCount( const QModelIndex & ) const
|
||||
{
|
||||
return mGuides.count();
|
||||
@ -279,8 +286,10 @@ bool QgsLayoutGuideCollection::setData( const QModelIndex &index, const QVariant
|
||||
|
||||
QgsLayoutMeasurement m = guide->position();
|
||||
m.setLength( newPos );
|
||||
mLayout->undoStack()->beginCommand( mPageCollection, tr( "Guide moved" ), Move + index.row() );
|
||||
whileBlocking( guide )->setPosition( m );
|
||||
guide->update();
|
||||
mLayout->undoStack()->endCommand();
|
||||
emit dataChanged( index, index, QVector<int>() << role );
|
||||
return true;
|
||||
}
|
||||
@ -293,11 +302,28 @@ bool QgsLayoutGuideCollection::setData( const QModelIndex &index, const QVariant
|
||||
|
||||
QgsLayoutMeasurement m = guide->position();
|
||||
m.setLength( newPos );
|
||||
mLayout->undoStack()->beginCommand( mPageCollection, tr( "Guide moved" ), Move + index.row() );
|
||||
whileBlocking( guide )->setPosition( m );
|
||||
guide->update();
|
||||
mLayout->undoStack()->endCommand();
|
||||
emit dataChanged( index, index, QVector<int>() << role );
|
||||
return true;
|
||||
}
|
||||
|
||||
case LayoutPositionRole:
|
||||
{
|
||||
bool ok = false;
|
||||
double newPos = value.toDouble( &ok );
|
||||
if ( !ok )
|
||||
return false;
|
||||
|
||||
mLayout->undoStack()->beginCommand( mPageCollection, tr( "Guide moved" ), Move + index.row() );
|
||||
whileBlocking( guide )->setLayoutPosition( newPos );
|
||||
mLayout->undoStack()->endCommand();
|
||||
emit dataChanged( index, index, QVector<int>() << role );
|
||||
return true;
|
||||
}
|
||||
|
||||
case UnitsRole:
|
||||
{
|
||||
bool ok = false;
|
||||
@ -307,8 +333,10 @@ bool QgsLayoutGuideCollection::setData( const QModelIndex &index, const QVariant
|
||||
|
||||
QgsLayoutMeasurement m = guide->position();
|
||||
m.setUnits( static_cast< QgsUnitTypes::LayoutUnit >( units ) );
|
||||
mLayout->undoStack()->beginCommand( mPageCollection, tr( "Guide moved" ), Move + index.row() );
|
||||
whileBlocking( guide )->setPosition( m );
|
||||
guide->update();
|
||||
mLayout->undoStack()->endCommand();
|
||||
emit dataChanged( index, index, QVector<int>() << role );
|
||||
return true;
|
||||
}
|
||||
@ -340,12 +368,16 @@ bool QgsLayoutGuideCollection::removeRows( int row, int count, const QModelIndex
|
||||
if ( parent.isValid() )
|
||||
return false;
|
||||
|
||||
if ( !mBlockUndoCommands )
|
||||
mLayout->undoStack()->beginCommand( mPageCollection, tr( "Guide(s) removed" ), Remove + row );
|
||||
beginRemoveRows( parent, row, row + count - 1 );
|
||||
for ( int i = 0; i < count; ++ i )
|
||||
{
|
||||
delete mGuides.takeAt( row );
|
||||
}
|
||||
endRemoveRows();
|
||||
if ( !mBlockUndoCommands )
|
||||
mLayout->undoStack()->endCommand();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -353,9 +385,13 @@ void QgsLayoutGuideCollection::addGuide( QgsLayoutGuide *guide )
|
||||
{
|
||||
guide->setLayout( mLayout );
|
||||
|
||||
if ( !mBlockUndoCommands )
|
||||
mLayout->undoStack()->beginCommand( mPageCollection, tr( "Guide created" ) );
|
||||
beginInsertRows( QModelIndex(), mGuides.count(), mGuides.count() );
|
||||
mGuides.append( guide );
|
||||
endInsertRows();
|
||||
if ( !mBlockUndoCommands )
|
||||
mLayout->undoStack()->endCommand();
|
||||
|
||||
QModelIndex index = createIndex( mGuides.length() - 1, 0 );
|
||||
connect( guide, &QgsLayoutGuide::positionChanged, this, [ this, index ]
|
||||
@ -373,16 +409,29 @@ void QgsLayoutGuideCollection::removeGuide( QgsLayoutGuide *guide )
|
||||
removeRow( row );
|
||||
}
|
||||
|
||||
void QgsLayoutGuideCollection::setGuideLayoutPosition( QgsLayoutGuide *guide, double position )
|
||||
{
|
||||
int row = mGuides.indexOf( guide );
|
||||
if ( row < 0 )
|
||||
return;
|
||||
|
||||
setData( index( row, 0 ), position, LayoutPositionRole );
|
||||
}
|
||||
|
||||
void QgsLayoutGuideCollection::clear()
|
||||
{
|
||||
mLayout->undoStack()->beginCommand( mPageCollection, tr( "Guides cleared" ) );
|
||||
beginResetModel();
|
||||
qDeleteAll( mGuides );
|
||||
mGuides.clear();
|
||||
endResetModel();
|
||||
mLayout->undoStack()->endCommand();
|
||||
}
|
||||
|
||||
void QgsLayoutGuideCollection::applyGuidesToAllOtherPages( int sourcePage )
|
||||
{
|
||||
mLayout->undoStack()->beginCommand( mPageCollection, tr( "Guides applied" ) );
|
||||
mBlockUndoCommands = true;
|
||||
QgsLayoutItemPage *page = mPageCollection->page( sourcePage );
|
||||
// remove other page's guides
|
||||
Q_FOREACH ( QgsLayoutGuide *guide, mGuides )
|
||||
@ -408,6 +457,8 @@ void QgsLayoutGuideCollection::applyGuidesToAllOtherPages( int sourcePage )
|
||||
}
|
||||
}
|
||||
}
|
||||
mLayout->undoStack()->endCommand();
|
||||
mBlockUndoCommands = false;
|
||||
}
|
||||
|
||||
void QgsLayoutGuideCollection::update()
|
||||
@ -448,19 +499,76 @@ bool QgsLayoutGuideCollection::visible() const
|
||||
|
||||
void QgsLayoutGuideCollection::setVisible( bool visible )
|
||||
{
|
||||
mLayout->undoStack()->beginCommand( mPageCollection, tr( "Guide visibility changed" ) );
|
||||
mGuidesVisible = visible;
|
||||
mLayout->undoStack()->endCommand();
|
||||
update();
|
||||
}
|
||||
|
||||
void QgsLayoutGuideCollection::pageAboutToBeRemoved( int pageNumber )
|
||||
{
|
||||
mBlockUndoCommands = true;
|
||||
Q_FOREACH ( QgsLayoutGuide *guide, guidesOnPage( pageNumber ) )
|
||||
{
|
||||
removeGuide( guide );
|
||||
}
|
||||
mBlockUndoCommands = false;
|
||||
}
|
||||
|
||||
bool QgsLayoutGuideCollection::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const
|
||||
{
|
||||
QDomElement element = document.createElement( QStringLiteral( "GuideCollection" ) );
|
||||
element.setAttribute( QStringLiteral( "visible" ), mGuidesVisible );
|
||||
Q_FOREACH ( QgsLayoutGuide *guide, mGuides )
|
||||
{
|
||||
QDomElement guideElement = document.createElement( QStringLiteral( "Guide" ) );
|
||||
guideElement.setAttribute( QStringLiteral( "orientation" ), guide->orientation() );
|
||||
guideElement.setAttribute( QStringLiteral( "page" ), mPageCollection->pageNumber( guide->page() ) );
|
||||
guideElement.setAttribute( QStringLiteral( "position" ), guide->position().length() );
|
||||
guideElement.setAttribute( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( guide->position().units() ) );
|
||||
element.appendChild( guideElement );
|
||||
}
|
||||
|
||||
parentElement.appendChild( element );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsLayoutGuideCollection::readXml( const QDomElement &e, const QDomDocument &, const QgsReadWriteContext & )
|
||||
{
|
||||
QDomElement element = e;
|
||||
if ( element.nodeName() != QStringLiteral( "GuideCollection" ) )
|
||||
{
|
||||
element = element.firstChildElement( QStringLiteral( "GuideCollection" ) );
|
||||
}
|
||||
|
||||
if ( element.nodeName() != QStringLiteral( "GuideCollection" ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mBlockUndoCommands = true;
|
||||
beginResetModel();
|
||||
qDeleteAll( mGuides );
|
||||
mGuides.clear();
|
||||
|
||||
mGuidesVisible = element.attribute( QStringLiteral( "visible" ), QStringLiteral( "0" ) ) != QLatin1String( "0" );
|
||||
QDomNodeList guideNodeList = element.elementsByTagName( QStringLiteral( "Guide" ) );
|
||||
for ( int i = 0; i < guideNodeList.size(); ++i )
|
||||
{
|
||||
QDomElement element = guideNodeList.at( i ).toElement();
|
||||
QgsLayoutGuide::Orientation orientation = static_cast< QgsLayoutGuide::Orientation >( element.attribute( QStringLiteral( "orientation" ), QStringLiteral( "0" ) ).toInt() );
|
||||
double pos = element.attribute( QStringLiteral( "position" ), QStringLiteral( "0" ) ).toDouble();
|
||||
QgsUnitTypes::LayoutUnit unit = QgsUnitTypes::decodeLayoutUnit( element.attribute( QStringLiteral( "units" ) ) );
|
||||
int page = element.attribute( QStringLiteral( "page" ), QStringLiteral( "0" ) ).toInt();
|
||||
std::unique_ptr< QgsLayoutGuide > guide( new QgsLayoutGuide( orientation, QgsLayoutMeasurement( pos, unit ), mPageCollection->page( page ) ) );
|
||||
guide->update();
|
||||
addGuide( guide.release() );
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
mBlockUndoCommands = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// QgsLayoutGuideProxyModel
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "qgslayoutmeasurement.h"
|
||||
#include "qgslayoutpoint.h"
|
||||
#include "qgslayoutitempage.h"
|
||||
#include "qgslayoutserializableobject.h"
|
||||
#include <QPen>
|
||||
#include <QAbstractListModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
@ -28,6 +29,9 @@
|
||||
|
||||
class QgsLayout;
|
||||
class QgsLayoutPageCollection;
|
||||
class QDomElement;
|
||||
class QDomDocument;
|
||||
class QgsReadWriteContext;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -168,7 +172,7 @@ class CORE_EXPORT QgsLayoutGuide : public QObject
|
||||
* \brief Stores and manages the snap guides used by a layout.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayoutGuideCollection : public QAbstractTableModel
|
||||
class CORE_EXPORT QgsLayoutGuideCollection : public QAbstractTableModel, public QgsLayoutSerializableObject
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
@ -192,6 +196,9 @@ class CORE_EXPORT QgsLayoutGuideCollection : public QAbstractTableModel
|
||||
QgsLayoutGuideCollection( QgsLayout *layout, QgsLayoutPageCollection *pageCollection );
|
||||
~QgsLayoutGuideCollection();
|
||||
|
||||
QString stringType() const override { return QStringLiteral( "LayoutGuideCollection" ); }
|
||||
QgsLayout *layout() override;
|
||||
|
||||
int rowCount( const QModelIndex & ) const override;
|
||||
int columnCount( const QModelIndex & ) const override;
|
||||
QVariant data( const QModelIndex &index, int role ) const override;
|
||||
@ -214,6 +221,11 @@ class CORE_EXPORT QgsLayoutGuideCollection : public QAbstractTableModel
|
||||
*/
|
||||
void removeGuide( QgsLayoutGuide *guide );
|
||||
|
||||
/**
|
||||
* Sets the absolute \a position (in layout coordinates) for \a guide within the layout.
|
||||
*/
|
||||
void setGuideLayoutPosition( QgsLayoutGuide *guide, double position );
|
||||
|
||||
/**
|
||||
* Removes all guides from the collection.
|
||||
* \see removeGuide()
|
||||
@ -256,12 +268,30 @@ class CORE_EXPORT QgsLayoutGuideCollection : public QAbstractTableModel
|
||||
*/
|
||||
void setVisible( bool visible );
|
||||
|
||||
/**
|
||||
* Stores the collection's state in a DOM element. The \a parentElement should refer to the parent layout's DOM element.
|
||||
* \see readXml()
|
||||
*/
|
||||
bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const override;
|
||||
|
||||
/**
|
||||
* Sets the collection's state from a DOM element. collectionElement is the DOM node corresponding to the collection.
|
||||
* \see writeXml()
|
||||
*/
|
||||
bool readXml( const QDomElement &collectionElement, const QDomDocument &document, const QgsReadWriteContext &context ) override;
|
||||
|
||||
private slots:
|
||||
|
||||
void pageAboutToBeRemoved( int pageNumber );
|
||||
|
||||
private:
|
||||
|
||||
enum UndoRoles
|
||||
{
|
||||
Move = 10000,
|
||||
Remove = 20000,
|
||||
};
|
||||
|
||||
QgsLayout *mLayout = nullptr;
|
||||
QgsLayoutPageCollection *mPageCollection = nullptr;
|
||||
|
||||
@ -269,6 +299,9 @@ class CORE_EXPORT QgsLayoutGuideCollection : public QAbstractTableModel
|
||||
int mHeaderSize = 0;
|
||||
|
||||
bool mGuidesVisible = true;
|
||||
bool mBlockUndoCommands = false;
|
||||
|
||||
friend class QgsLayoutGuideCollectionUndoCommand;
|
||||
|
||||
};
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "qgslayout.h"
|
||||
#include "qgslayoututils.h"
|
||||
#include "qgspagesizeregistry.h"
|
||||
#include "qgslayoutitemundocommand.h"
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <QUuid>
|
||||
@ -260,6 +261,11 @@ bool QgsLayoutItem::readXml( const QDomElement &itemElem, const QDomDocument &do
|
||||
return readPropertiesFromElement( itemElem, doc, context );
|
||||
}
|
||||
|
||||
QgsAbstractLayoutUndoCommand *QgsLayoutItem::createCommand( const QString &text, int id, QUndoCommand *parent )
|
||||
{
|
||||
return new QgsLayoutItemUndoCommand( this, text, id, parent );
|
||||
}
|
||||
|
||||
QgsLayoutPoint QgsLayoutItem::applyDataDefinedPosition( const QgsLayoutPoint &position )
|
||||
{
|
||||
if ( !mLayout )
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "qgslayoutsize.h"
|
||||
#include "qgslayoutpoint.h"
|
||||
#include "qgsrendercontext.h"
|
||||
#include "qgslayoutundocommand.h"
|
||||
#include <QGraphicsRectItem>
|
||||
|
||||
class QgsLayout;
|
||||
@ -33,7 +34,7 @@ class QPainter;
|
||||
* \brief Base class for graphical items within a QgsLayout.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectItem
|
||||
class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectItem, public QgsLayoutUndoObjectInterface
|
||||
{
|
||||
#ifdef SIP_RUN
|
||||
#include <qgslayoutitemshape.h>
|
||||
@ -227,6 +228,8 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
|
||||
*/
|
||||
virtual bool readXml( const QDomElement &itemElement, const QDomDocument &document, const QgsReadWriteContext &context );
|
||||
|
||||
QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = nullptr ) override SIP_FACTORY;
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "qgslayoututils.h"
|
||||
#include "qgspagesizeregistry.h"
|
||||
#include "qgssymbollayerutils.h"
|
||||
#include "qgslayoutitemundocommand.h"
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
|
||||
@ -42,6 +43,12 @@ QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout )
|
||||
mGrid->setParentItem( this );
|
||||
}
|
||||
|
||||
QgsLayoutItemPage *QgsLayoutItemPage::create( QgsLayout *layout, const QVariantMap &settings )
|
||||
{
|
||||
Q_UNUSED( settings );
|
||||
return new QgsLayoutItemPage( layout );
|
||||
}
|
||||
|
||||
void QgsLayoutItemPage::setPageSize( const QgsLayoutSize &size )
|
||||
{
|
||||
attemptResize( size );
|
||||
@ -119,6 +126,37 @@ void QgsLayoutItemPage::attemptResize( const QgsLayoutSize &size )
|
||||
mLayout->guides().update();
|
||||
}
|
||||
|
||||
///@cond PRIVATE
|
||||
class QgsLayoutItemPageUndoCommand: public QgsLayoutItemUndoCommand
|
||||
{
|
||||
public:
|
||||
|
||||
QgsLayoutItemPageUndoCommand( QgsLayoutItemPage *page, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr )
|
||||
: QgsLayoutItemUndoCommand( page, text, id, parent )
|
||||
{}
|
||||
|
||||
void restoreState( QDomDocument &stateDoc ) override
|
||||
{
|
||||
QgsLayoutItemUndoCommand::restoreState( stateDoc );
|
||||
layout()->pageCollection()->reflow();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
QgsLayoutItem *recreateItem( int, QgsLayout *layout ) override
|
||||
{
|
||||
QgsLayoutItemPage *page = new QgsLayoutItemPage( layout );
|
||||
layout->pageCollection()->addPage( page );
|
||||
return page;
|
||||
}
|
||||
};
|
||||
///@endcond
|
||||
|
||||
QgsAbstractLayoutUndoCommand *QgsLayoutItemPage::createCommand( const QString &text, int id, QUndoCommand *parent )
|
||||
{
|
||||
return new QgsLayoutItemPageUndoCommand( this, text, id, parent );
|
||||
}
|
||||
|
||||
void QgsLayoutItemPage::redraw()
|
||||
{
|
||||
QgsLayoutItem::redraw();
|
||||
|
@ -69,6 +69,15 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem
|
||||
* Constructor for QgsLayoutItemPage, with the specified parent \a layout.
|
||||
*/
|
||||
explicit QgsLayoutItemPage( QgsLayout *layout SIP_TRANSFERTHIS );
|
||||
|
||||
/**
|
||||
* Returns a new page item for the specified \a layout.
|
||||
*
|
||||
* The caller takes responsibility for deleting the returned object.
|
||||
*/
|
||||
static QgsLayoutItemPage *create( QgsLayout *layout, const QVariantMap &settings ) SIP_FACTORY;
|
||||
|
||||
|
||||
int type() const override { return QgsLayoutItemRegistry::LayoutPage; }
|
||||
QString stringType() const override { return QStringLiteral( "ItemPaper" ); }
|
||||
|
||||
@ -110,6 +119,8 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem
|
||||
|
||||
void attemptResize( const QgsLayoutSize &size ) override;
|
||||
|
||||
QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = nullptr ) override SIP_FACTORY;
|
||||
|
||||
public slots:
|
||||
|
||||
void redraw() override;
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
#include "qgslayoutitemregistry.h"
|
||||
#include "qgslayoutitemshape.h"
|
||||
#include "qgslayoutitempage.h"
|
||||
#include "qgsgloweffect.h"
|
||||
#include "qgseffectstack.h"
|
||||
#include <QPainter>
|
||||
@ -42,7 +43,7 @@ bool QgsLayoutItemRegistry::populate()
|
||||
};
|
||||
|
||||
addLayoutItemType( new QgsLayoutItemMetadata( 101, QStringLiteral( "temp type" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLabel.svg" ) ), createTemporaryItem ) );
|
||||
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPage, QStringLiteral( "Page" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileNew.svg" ) ), nullptr ) );
|
||||
addLayoutItemType( new QgsLayoutItemMetadata( LayoutPage, QStringLiteral( "Page" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileNew.svg" ) ), QgsLayoutItemPage::create ) );
|
||||
|
||||
addLayoutItemType( new QgsLayoutItemMetadata( LayoutRectangle, QStringLiteral( "Rectangle" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), QgsLayoutItemRectangularShape::create ) );
|
||||
addLayoutItemType( new QgsLayoutItemMetadata( LayoutEllipse, QStringLiteral( "Ellipse" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), QgsLayoutItemEllipseShape::create ) );
|
||||
|
123
src/core/layout/qgslayoutitemundocommand.cpp
Normal file
123
src/core/layout/qgslayoutitemundocommand.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
/***************************************************************************
|
||||
qgslayoutitemundocommand.cpp
|
||||
------------------------
|
||||
begin : July 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 "qgslayoutitemundocommand.h"
|
||||
#include "qgslayoutitem.h"
|
||||
#include "qgsreadwritecontext.h"
|
||||
#include "qgslayout.h"
|
||||
#include "qgsproject.h"
|
||||
|
||||
///@cond PRIVATE
|
||||
QgsLayoutItemUndoCommand::QgsLayoutItemUndoCommand( QgsLayoutItem *item, const QString &text, int id, QUndoCommand *parent )
|
||||
: QgsAbstractLayoutUndoCommand( text, id, parent )
|
||||
, mItemUuid( item->uuid() )
|
||||
, mLayout( item->layout() )
|
||||
, mItemType( item->type() )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool QgsLayoutItemUndoCommand::mergeWith( const QUndoCommand *command )
|
||||
{
|
||||
if ( command->id() == 0 )
|
||||
return false;
|
||||
|
||||
const QgsLayoutItemUndoCommand *c = dynamic_cast<const QgsLayoutItemUndoCommand *>( command );
|
||||
if ( !c )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
setAfterState( c->afterState() );
|
||||
return true;
|
||||
}
|
||||
|
||||
void QgsLayoutItemUndoCommand::saveState( QDomDocument &stateDoc ) const
|
||||
{
|
||||
stateDoc.clear();
|
||||
QDomElement documentElement = stateDoc.createElement( QStringLiteral( "ItemState" ) );
|
||||
|
||||
QgsLayoutItem *item = mLayout->itemByUuid( mItemUuid );
|
||||
Q_ASSERT_X( item, "QgsLayoutItemUndoCommand::saveState", "could not retrieve item for saving state" );
|
||||
|
||||
item->writeXml( documentElement, stateDoc, QgsReadWriteContext() );
|
||||
stateDoc.appendChild( documentElement );
|
||||
}
|
||||
|
||||
void QgsLayoutItemUndoCommand::restoreState( QDomDocument &stateDoc )
|
||||
{
|
||||
// find item by uuid...
|
||||
QgsLayoutItem *item = mLayout->itemByUuid( mItemUuid );
|
||||
if ( !item )
|
||||
{
|
||||
// uh oh - it's been deleted! we need to create a new instance
|
||||
item = recreateItem( mItemType, mLayout );
|
||||
}
|
||||
|
||||
item->readXml( stateDoc.documentElement().firstChild().toElement(), stateDoc, QgsReadWriteContext() );
|
||||
mLayout->project()->setDirty( true );
|
||||
}
|
||||
|
||||
QgsLayoutItem *QgsLayoutItemUndoCommand::recreateItem( int itemType, QgsLayout *layout )
|
||||
{
|
||||
QgsLayoutItem *item = QgsApplication::layoutItemRegistry()->createItem( itemType, layout );
|
||||
mLayout->addLayoutItem( item );
|
||||
return item;
|
||||
}
|
||||
|
||||
QString QgsLayoutItemUndoCommand::itemUuid() const
|
||||
{
|
||||
return mItemUuid;
|
||||
}
|
||||
|
||||
QgsLayout *QgsLayoutItemUndoCommand::layout() const
|
||||
{
|
||||
return mLayout;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// QgsLayoutItemDeleteUndoCommand
|
||||
//
|
||||
|
||||
QgsLayoutItemDeleteUndoCommand::QgsLayoutItemDeleteUndoCommand( QgsLayoutItem *item, const QString &text, int id, QUndoCommand *parent )
|
||||
: QgsLayoutItemUndoCommand( item, text, id, parent )
|
||||
{
|
||||
saveBeforeState();
|
||||
}
|
||||
|
||||
bool QgsLayoutItemDeleteUndoCommand::mergeWith( const QUndoCommand * )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void QgsLayoutItemDeleteUndoCommand::redo()
|
||||
{
|
||||
if ( mFirstRun )
|
||||
{
|
||||
mFirstRun = false;
|
||||
return;
|
||||
}
|
||||
|
||||
QgsLayoutItem *item = layout()->itemByUuid( itemUuid() );
|
||||
Q_ASSERT_X( item, "QgsLayoutItemDeleteUndoCommand::redo", "could not find item to re-delete!" );
|
||||
|
||||
layout()->removeItem( item );
|
||||
item->deleteLater();
|
||||
}
|
||||
|
||||
///@endcond
|
112
src/core/layout/qgslayoutitemundocommand.h
Normal file
112
src/core/layout/qgslayoutitemundocommand.h
Normal file
@ -0,0 +1,112 @@
|
||||
/***************************************************************************
|
||||
qgslayoutitemundocommand.h
|
||||
----------------------
|
||||
begin : July 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 QGSLAYOUTITEMUNDOCOMMAND_H
|
||||
#define QGSLAYOUTITEMUNDOCOMMAND_H
|
||||
|
||||
#include "qgslayoutundocommand.h"
|
||||
#include "qgis_core.h"
|
||||
|
||||
class QgsLayout;
|
||||
class QgsLayoutItem;
|
||||
|
||||
SIP_NO_FILE
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* An undo command subclass for layout item undo commands.
|
||||
*
|
||||
* QgsLayoutItemUndoCommand is a specific layout undo command which is
|
||||
* designed for use with QgsLayoutItems. It automatically handles
|
||||
* recreating a deleted item when the undo stack rolls back past
|
||||
* the item deletion command.
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayoutItemUndoCommand: public QgsAbstractLayoutUndoCommand
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsLayoutItemUndoCommand.
|
||||
* \param item associated layout item
|
||||
* \param text undo command descriptive text
|
||||
* \param id optional undo command id, used for automatic command merging
|
||||
* \param parent command
|
||||
*/
|
||||
QgsLayoutItemUndoCommand( QgsLayoutItem *item, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr );
|
||||
|
||||
bool mergeWith( const QUndoCommand *command ) override;
|
||||
|
||||
/**
|
||||
* Returns the layout associated with this command.
|
||||
*/
|
||||
QgsLayout *layout() const;
|
||||
|
||||
/**
|
||||
* Returns the associated item's UUID, which uniquely identifies the item
|
||||
* within the layout.
|
||||
*/
|
||||
QString itemUuid() const;
|
||||
|
||||
protected:
|
||||
|
||||
void saveState( QDomDocument &stateDoc ) const override;
|
||||
void restoreState( QDomDocument &stateDoc ) override;
|
||||
|
||||
virtual QgsLayoutItem *recreateItem( int itemType, QgsLayout *layout ) SIP_FACTORY;
|
||||
|
||||
private:
|
||||
|
||||
QString mItemUuid;
|
||||
QgsLayout *mLayout = nullptr;
|
||||
int mItemType = 0;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* An undo command subclass for layout item deletion undo commands.
|
||||
*
|
||||
* QgsLayoutItemDeleteUndoCommand is a specific layout undo command which handles
|
||||
* layout item deletion. When applied (e.g. as a result of a 'redo' action),
|
||||
* the associated layout item is deleted and removed from the layout.
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayoutItemDeleteUndoCommand: public QgsLayoutItemUndoCommand
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsLayoutItemDeleteUndoCommand.
|
||||
* \param item associated layout item
|
||||
* \param text undo command descriptive text
|
||||
* \param id optional undo command id, used for automatic command merging
|
||||
* \param parent command
|
||||
*/
|
||||
QgsLayoutItemDeleteUndoCommand( QgsLayoutItem *item, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr );
|
||||
bool mergeWith( const QUndoCommand *command ) override;
|
||||
void redo() override;
|
||||
|
||||
};
|
||||
|
||||
///@endcond
|
||||
|
||||
#endif
|
@ -16,10 +16,15 @@
|
||||
|
||||
#include "qgslayoutpagecollection.h"
|
||||
#include "qgslayout.h"
|
||||
#include "qgsreadwritecontext.h"
|
||||
#include "qgsproject.h"
|
||||
#include "qgslayoutitemundocommand.h"
|
||||
#include "qgssymbollayerutils.h"
|
||||
|
||||
QgsLayoutPageCollection::QgsLayoutPageCollection( QgsLayout *layout )
|
||||
: QObject( layout )
|
||||
, mLayout( layout )
|
||||
, mGuideCollection( new QgsLayoutGuideCollection( layout, this ) )
|
||||
{
|
||||
createDefaultPageStyleSymbol();
|
||||
}
|
||||
@ -135,6 +140,83 @@ double QgsLayoutPageCollection::pageShadowWidth() const
|
||||
return spaceBetweenPages() / 2;
|
||||
}
|
||||
|
||||
bool QgsLayoutPageCollection::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
QDomElement element = document.createElement( QStringLiteral( "PageCollection" ) );
|
||||
|
||||
QDomElement pageStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mPageStyleSymbol.get(), document, context );
|
||||
element.appendChild( pageStyleElem );
|
||||
|
||||
for ( const QgsLayoutItemPage *page : mPages )
|
||||
{
|
||||
page->writeXml( element, document, context );
|
||||
}
|
||||
|
||||
mGuideCollection->writeXml( element, document, context );
|
||||
|
||||
parentElement.appendChild( element );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsLayoutPageCollection::readXml( const QDomElement &e, const QDomDocument &document, const QgsReadWriteContext &context )
|
||||
{
|
||||
QDomElement element = e;
|
||||
if ( element.nodeName() != QStringLiteral( "PageCollection" ) )
|
||||
{
|
||||
element = element.firstChildElement( QStringLiteral( "PageCollection" ) );
|
||||
}
|
||||
|
||||
if ( element.nodeName() != QStringLiteral( "PageCollection" ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mBlockUndoCommands = true;
|
||||
|
||||
int i = 0;
|
||||
for ( QgsLayoutItemPage *page : qgsAsConst( mPages ) )
|
||||
{
|
||||
emit pageAboutToBeRemoved( i );
|
||||
mLayout->removeItem( page );
|
||||
page->deleteLater();
|
||||
++i;
|
||||
}
|
||||
mPages.clear();
|
||||
|
||||
QDomElement pageStyleSymbolElem = element.firstChildElement( QStringLiteral( "symbol" ) );
|
||||
if ( !pageStyleSymbolElem.isNull() )
|
||||
{
|
||||
mPageStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( pageStyleSymbolElem, context ) );
|
||||
}
|
||||
|
||||
QDomNodeList pageList = element.elementsByTagName( QStringLiteral( "LayoutItem" ) );
|
||||
for ( int i = 0; i < pageList.size(); ++i )
|
||||
{
|
||||
QDomElement pageElement = pageList.at( i ).toElement();
|
||||
std::unique_ptr< QgsLayoutItemPage > page( new QgsLayoutItemPage( mLayout ) );
|
||||
page->readXml( pageElement, document, context );
|
||||
mPages.append( page.get() );
|
||||
mLayout->addItem( page.release() );
|
||||
}
|
||||
|
||||
reflow();
|
||||
|
||||
mGuideCollection->readXml( element, document, context );
|
||||
|
||||
mBlockUndoCommands = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
QgsLayoutGuideCollection &QgsLayoutPageCollection::guides()
|
||||
{
|
||||
return *mGuideCollection;
|
||||
}
|
||||
|
||||
const QgsLayoutGuideCollection &QgsLayoutPageCollection::guides() const
|
||||
{
|
||||
return *mGuideCollection;
|
||||
}
|
||||
|
||||
void QgsLayoutPageCollection::redraw()
|
||||
{
|
||||
Q_FOREACH ( QgsLayoutItemPage *page, mPages )
|
||||
@ -143,7 +225,7 @@ void QgsLayoutPageCollection::redraw()
|
||||
}
|
||||
}
|
||||
|
||||
QgsLayout *QgsLayoutPageCollection::layout() const
|
||||
QgsLayout *QgsLayoutPageCollection::layout()
|
||||
{
|
||||
return mLayout;
|
||||
}
|
||||
@ -194,13 +276,20 @@ QList<int> QgsLayoutPageCollection::visiblePageNumbers( QRectF region ) const
|
||||
|
||||
void QgsLayoutPageCollection::addPage( QgsLayoutItemPage *page )
|
||||
{
|
||||
if ( !mBlockUndoCommands )
|
||||
mLayout->undoStack()->beginCommand( this, tr( "Add page" ) );
|
||||
mPages.append( page );
|
||||
mLayout->addItem( page );
|
||||
reflow();
|
||||
if ( !mBlockUndoCommands )
|
||||
mLayout->undoStack()->endCommand();
|
||||
}
|
||||
|
||||
void QgsLayoutPageCollection::insertPage( QgsLayoutItemPage *page, int beforePage )
|
||||
{
|
||||
if ( !mBlockUndoCommands )
|
||||
mLayout->undoStack()->beginCommand( this, tr( "Add page" ) );
|
||||
|
||||
if ( beforePage < 0 )
|
||||
beforePage = 0;
|
||||
|
||||
@ -214,6 +303,8 @@ void QgsLayoutPageCollection::insertPage( QgsLayoutItemPage *page, int beforePag
|
||||
}
|
||||
mLayout->addItem( page );
|
||||
reflow();
|
||||
if ( ! mBlockUndoCommands )
|
||||
mLayout->undoStack()->endCommand();
|
||||
}
|
||||
|
||||
void QgsLayoutPageCollection::deletePage( int pageNumber )
|
||||
@ -221,11 +312,21 @@ void QgsLayoutPageCollection::deletePage( int pageNumber )
|
||||
if ( pageNumber < 0 || pageNumber >= mPages.count() )
|
||||
return;
|
||||
|
||||
if ( !mBlockUndoCommands )
|
||||
{
|
||||
mLayout->undoStack()->beginMacro( tr( "Remove page" ) );
|
||||
mLayout->undoStack()->beginCommand( this, tr( "Remove page" ) );
|
||||
}
|
||||
emit pageAboutToBeRemoved( pageNumber );
|
||||
QgsLayoutItemPage *page = mPages.takeAt( pageNumber );
|
||||
mLayout->removeItem( page );
|
||||
page->deleteLater();
|
||||
reflow();
|
||||
if ( ! mBlockUndoCommands )
|
||||
{
|
||||
mLayout->undoStack()->endCommand();
|
||||
mLayout->undoStack()->endMacro();
|
||||
}
|
||||
}
|
||||
|
||||
void QgsLayoutPageCollection::deletePage( QgsLayoutItemPage *page )
|
||||
@ -233,10 +334,26 @@ void QgsLayoutPageCollection::deletePage( QgsLayoutItemPage *page )
|
||||
if ( !mPages.contains( page ) )
|
||||
return;
|
||||
|
||||
if ( !mBlockUndoCommands )
|
||||
{
|
||||
mLayout->undoStack()->beginMacro( tr( "Remove page" ) );
|
||||
mLayout->undoStack()->beginCommand( this, tr( "Remove page" ) );
|
||||
}
|
||||
emit pageAboutToBeRemoved( mPages.indexOf( page ) );
|
||||
mPages.removeAll( page );
|
||||
page->deleteLater();
|
||||
reflow();
|
||||
if ( !mBlockUndoCommands )
|
||||
{
|
||||
mLayout->undoStack()->endCommand();
|
||||
mLayout->undoStack()->endMacro();
|
||||
}
|
||||
}
|
||||
|
||||
QgsLayoutItemPage *QgsLayoutPageCollection::takePage( QgsLayoutItemPage *page )
|
||||
{
|
||||
mPages.removeAll( page );
|
||||
return page;
|
||||
}
|
||||
|
||||
void QgsLayoutPageCollection::createDefaultPageStyleSymbol()
|
||||
|
@ -21,10 +21,12 @@
|
||||
#include "qgis_sip.h"
|
||||
#include "qgssymbol.h"
|
||||
#include "qgslayoutitempage.h"
|
||||
#include "qgslayoutserializableobject.h"
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
|
||||
class QgsLayout;
|
||||
class QgsLayoutGuideCollection;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -32,7 +34,7 @@ class QgsLayout;
|
||||
* \brief A manager for a collection of pages in a layout.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayoutPageCollection : public QObject
|
||||
class CORE_EXPORT QgsLayoutPageCollection : public QObject, public QgsLayoutSerializableObject
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
@ -46,10 +48,8 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject
|
||||
|
||||
~QgsLayoutPageCollection();
|
||||
|
||||
/**
|
||||
* Returns the layout this collection belongs to.
|
||||
*/
|
||||
QgsLayout *layout() const;
|
||||
QString stringType() const override { return QStringLiteral( "LayoutPageCollection" ); }
|
||||
QgsLayout *layout() override;
|
||||
|
||||
/**
|
||||
* Returns a list of pages in the collection.
|
||||
@ -141,6 +141,11 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject
|
||||
*/
|
||||
void deletePage( QgsLayoutItemPage *page );
|
||||
|
||||
/**
|
||||
* Takes a \a page from the collection, returning ownership of the page to the caller.
|
||||
*/
|
||||
QgsLayoutItemPage *takePage( QgsLayoutItemPage *page ) SIP_TRANSFERBACK;
|
||||
|
||||
/**
|
||||
* Sets the \a symbol to use for drawing pages in the collection.
|
||||
*
|
||||
@ -211,6 +216,28 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject
|
||||
*/
|
||||
double pageShadowWidth() const;
|
||||
|
||||
/**
|
||||
* Stores the collection's state in a DOM element. The \a parentElement should refer to the parent layout's DOM element.
|
||||
* \see readXml()
|
||||
*/
|
||||
bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const override;
|
||||
|
||||
/**
|
||||
* Sets the collection's state from a DOM element. collectionElement is the DOM node corresponding to the collection.
|
||||
* \see writeXml()
|
||||
*/
|
||||
bool readXml( const QDomElement &collectionElement, const QDomDocument &document, const QgsReadWriteContext &context ) override;
|
||||
|
||||
/**
|
||||
* Returns a reference to the collection's guide collection, which manages page snap guides.
|
||||
*/
|
||||
QgsLayoutGuideCollection &guides();
|
||||
|
||||
/**
|
||||
* Returns a reference to the collection's guide collection, which manages page snap guides.
|
||||
*/
|
||||
SIP_SKIP const QgsLayoutGuideCollection &guides() const;
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
@ -237,12 +264,18 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject
|
||||
|
||||
QgsLayout *mLayout = nullptr;
|
||||
|
||||
std::unique_ptr< QgsLayoutGuideCollection > mGuideCollection;
|
||||
|
||||
//! Symbol for drawing pages
|
||||
std::unique_ptr< QgsFillSymbol > mPageStyleSymbol;
|
||||
|
||||
QList< QgsLayoutItemPage * > mPages;
|
||||
|
||||
bool mBlockUndoCommands = false;
|
||||
|
||||
void createDefaultPageStyleSymbol();
|
||||
|
||||
friend class QgsLayoutPageCollectionUndoCommand;
|
||||
};
|
||||
|
||||
#endif //QGSLAYOUTPAGECOLLECTION_H
|
||||
|
81
src/core/layout/qgslayoutserializableobject.cpp
Normal file
81
src/core/layout/qgslayoutserializableobject.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
/***************************************************************************
|
||||
qgslayoutserializableobject.cpp
|
||||
-------------------------------
|
||||
begin : July 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 "qgslayoutserializableobject.h"
|
||||
#include "qgsreadwritecontext.h"
|
||||
#include "qgslayout.h"
|
||||
#include "qgsproject.h"
|
||||
|
||||
///@cond PRIVATE
|
||||
class QgsLayoutSerializableObjectUndoCommand: public QgsAbstractLayoutUndoCommand
|
||||
{
|
||||
public:
|
||||
|
||||
QgsLayoutSerializableObjectUndoCommand( QgsLayoutSerializableObject *object, const QString &text, int id, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr )
|
||||
: QgsAbstractLayoutUndoCommand( text, id, parent )
|
||||
, mObject( object )
|
||||
{}
|
||||
|
||||
bool mergeWith( const QUndoCommand *command ) override
|
||||
{
|
||||
if ( command->id() == 0 )
|
||||
return false;
|
||||
|
||||
const QgsLayoutSerializableObjectUndoCommand *c = dynamic_cast<const QgsLayoutSerializableObjectUndoCommand *>( command );
|
||||
if ( !c )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( mObject->stringType() != c->mObject->stringType() )
|
||||
return false;
|
||||
|
||||
setAfterState( c->afterState() );
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void saveState( QDomDocument &stateDoc ) const override
|
||||
{
|
||||
stateDoc.clear();
|
||||
QDomElement documentElement = stateDoc.createElement( QStringLiteral( "UndoState" ) );
|
||||
mObject->writeXml( documentElement, stateDoc, QgsReadWriteContext() );
|
||||
stateDoc.appendChild( documentElement );
|
||||
}
|
||||
void restoreState( QDomDocument &stateDoc ) override
|
||||
{
|
||||
if ( !mObject )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mObject->readXml( stateDoc.documentElement().firstChild().toElement(), stateDoc, QgsReadWriteContext() );
|
||||
mObject->layout()->project()->setDirty( true );
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
QgsLayoutSerializableObject *mObject = nullptr;
|
||||
};
|
||||
///@endcond
|
||||
|
||||
|
||||
QgsAbstractLayoutUndoCommand *QgsLayoutSerializableObject::createCommand( const QString &text, int id, QUndoCommand *parent )
|
||||
{
|
||||
return new QgsLayoutSerializableObjectUndoCommand( this, text, id, parent );
|
||||
}
|
73
src/core/layout/qgslayoutserializableobject.h
Normal file
73
src/core/layout/qgslayoutserializableobject.h
Normal file
@ -0,0 +1,73 @@
|
||||
/***************************************************************************
|
||||
qgslayoutserializableobject.h
|
||||
-----------------------------
|
||||
begin : July 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 QGSLAYOUTSERIALIZABLEOBJECT_H
|
||||
#define QGSLAYOUTSERIALIZABLEOBJECT_H
|
||||
|
||||
#include "qgis.h"
|
||||
#include "qgis_core.h"
|
||||
#include "qgslayoutundocommand.h"
|
||||
|
||||
class QDomElement;
|
||||
class QDomDocument;
|
||||
class QgsReadWriteContext;
|
||||
class QgsAbstractLayoutUndoCommand;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* An interface for layout objects which can be stored and read from DOM elements.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayoutSerializableObject : public QgsLayoutUndoObjectInterface
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~QgsLayoutSerializableObject() = default;
|
||||
|
||||
/**
|
||||
* Return the object type as a string.
|
||||
*
|
||||
* This string must be a unique, single word, character only representation of the item type, eg "LayoutScaleBar"
|
||||
*/
|
||||
virtual QString stringType() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the layout the object belongs to.
|
||||
*/
|
||||
virtual QgsLayout *layout() = 0;
|
||||
|
||||
/**
|
||||
* Stores the objects's state in a DOM element. The \a parentElement should refer to the parent layout's DOM element.
|
||||
* \see readXml()
|
||||
*/
|
||||
virtual bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const = 0;
|
||||
|
||||
/**
|
||||
* Sets the objects's state from a DOM element. \a element is the DOM node corresponding to the object.
|
||||
* \see writeXml()
|
||||
*/
|
||||
virtual bool readXml( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ) = 0;
|
||||
|
||||
QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = nullptr ) override SIP_FACTORY;
|
||||
|
||||
private:
|
||||
|
||||
friend class QgsLayoutSerializableObjectUndoCommand;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSLAYOUTSERIALIZABLEOBJECT_H
|
@ -16,12 +16,34 @@
|
||||
|
||||
#include "qgslayoutsnapper.h"
|
||||
#include "qgslayout.h"
|
||||
#include "qgsreadwritecontext.h"
|
||||
#include "qgsproject.h"
|
||||
|
||||
QgsLayoutSnapper::QgsLayoutSnapper( QgsLayout *layout )
|
||||
: mLayout( layout )
|
||||
{
|
||||
}
|
||||
|
||||
QgsLayout *QgsLayoutSnapper::layout()
|
||||
{
|
||||
return mLayout;
|
||||
}
|
||||
|
||||
void QgsLayoutSnapper::setSnapTolerance( const int snapTolerance )
|
||||
{
|
||||
mTolerance = snapTolerance;
|
||||
}
|
||||
|
||||
void QgsLayoutSnapper::setSnapToGrid( bool enabled )
|
||||
{
|
||||
mSnapToGrid = enabled;
|
||||
}
|
||||
|
||||
void QgsLayoutSnapper::setSnapToGuides( bool enabled )
|
||||
{
|
||||
mSnapToGuides = enabled;
|
||||
}
|
||||
|
||||
QPointF QgsLayoutSnapper::snapPoint( QPointF point, double scaleFactor, bool &snapped ) const
|
||||
{
|
||||
snapped = false;
|
||||
@ -146,3 +168,36 @@ double QgsLayoutSnapper::snapPointToGuides( double original, QgsLayoutGuide::Ori
|
||||
return original;
|
||||
}
|
||||
}
|
||||
|
||||
bool QgsLayoutSnapper::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const
|
||||
{
|
||||
QDomElement element = document.createElement( QStringLiteral( "Snapper" ) );
|
||||
|
||||
element.setAttribute( QStringLiteral( "tolerance" ), mTolerance );
|
||||
element.setAttribute( QStringLiteral( "snapToGrid" ), mSnapToGrid );
|
||||
element.setAttribute( QStringLiteral( "snapToGuides" ), mSnapToGuides );
|
||||
|
||||
parentElement.appendChild( element );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsLayoutSnapper::readXml( const QDomElement &e, const QDomDocument &, const QgsReadWriteContext & )
|
||||
{
|
||||
QDomElement element = e;
|
||||
if ( element.nodeName() != QStringLiteral( "Snapper" ) )
|
||||
{
|
||||
element = element.firstChildElement( QStringLiteral( "Snapper" ) );
|
||||
}
|
||||
|
||||
if ( element.nodeName() != QStringLiteral( "Snapper" ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mTolerance = element.attribute( QStringLiteral( "tolerance" ), QStringLiteral( "5" ) ).toInt();
|
||||
mSnapToGrid = element.attribute( QStringLiteral( "snapToGrid" ), QStringLiteral( "0" ) ) != QLatin1String( "0" );
|
||||
mSnapToGuides = element.attribute( QStringLiteral( "snapToGuides" ), QStringLiteral( "0" ) ) != QLatin1String( "0" );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,9 +20,11 @@
|
||||
#include "qgslayoutmeasurement.h"
|
||||
#include "qgslayoutpoint.h"
|
||||
#include "qgslayoutguidecollection.h"
|
||||
#include "qgslayoutserializableobject.h"
|
||||
#include <QPen>
|
||||
|
||||
class QgsLayout;
|
||||
class QgsReadWriteContext;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -31,7 +33,7 @@ class QgsLayout;
|
||||
* snapping points to the nearest grid coordinate/snap line when possible.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayoutSnapper
|
||||
class CORE_EXPORT QgsLayoutSnapper: public QgsLayoutSerializableObject
|
||||
{
|
||||
|
||||
public:
|
||||
@ -41,11 +43,14 @@ class CORE_EXPORT QgsLayoutSnapper
|
||||
*/
|
||||
QgsLayoutSnapper( QgsLayout *layout );
|
||||
|
||||
QString stringType() const override { return QStringLiteral( "LayoutSnapper" ); }
|
||||
QgsLayout *layout() override;
|
||||
|
||||
/**
|
||||
* Sets the snap \a tolerance (in pixels) to use when snapping.
|
||||
* \see snapTolerance()
|
||||
*/
|
||||
void setSnapTolerance( const int snapTolerance ) { mTolerance = snapTolerance; }
|
||||
void setSnapTolerance( const int snapTolerance );
|
||||
|
||||
/**
|
||||
* Returns the snap tolerance (in pixels) to use when snapping.
|
||||
@ -63,7 +68,7 @@ class CORE_EXPORT QgsLayoutSnapper
|
||||
* Sets whether snapping to grid is \a enabled.
|
||||
* \see snapToGrid()
|
||||
*/
|
||||
void setSnapToGrid( bool enabled ) { mSnapToGrid = enabled; }
|
||||
void setSnapToGrid( bool enabled );
|
||||
|
||||
/**
|
||||
* Returns true if snapping to guides is enabled.
|
||||
@ -75,7 +80,7 @@ class CORE_EXPORT QgsLayoutSnapper
|
||||
* Sets whether snapping to guides is \a enabled.
|
||||
* \see snapToGuides()
|
||||
*/
|
||||
void setSnapToGuides( bool enabled ) { mSnapToGuides = enabled; }
|
||||
void setSnapToGuides( bool enabled );
|
||||
|
||||
/**
|
||||
* Snaps a layout coordinate \a point. If \a point was snapped, \a snapped will be set to true.
|
||||
@ -115,14 +120,36 @@ class CORE_EXPORT QgsLayoutSnapper
|
||||
*/
|
||||
double snapPointToGuides( double original, QgsLayoutGuide::Orientation orientation, double scaleFactor, bool &snapped SIP_OUT ) const;
|
||||
|
||||
/**
|
||||
* Stores the snapper's state in a DOM element. The \a parentElement should refer to the parent layout's DOM element.
|
||||
* \see readXml()
|
||||
*/
|
||||
bool writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const override;
|
||||
|
||||
/**
|
||||
* Sets the snapper's state from a DOM element. snapperElement is the DOM node corresponding to the snapper.
|
||||
* \see writeXml()
|
||||
*/
|
||||
bool readXml( const QDomElement &gridElement, const QDomDocument &document, const QgsReadWriteContext &context ) override;
|
||||
|
||||
private:
|
||||
|
||||
// Used for 'collapsing' undo commands
|
||||
enum UndoCommand
|
||||
{
|
||||
UndoTolerance = 1,
|
||||
UndoSnapToGrid,
|
||||
UndoSnapToGuides,
|
||||
};
|
||||
|
||||
QgsLayout *mLayout = nullptr;
|
||||
|
||||
int mTolerance = 5;
|
||||
bool mSnapToGrid = false;
|
||||
bool mSnapToGuides = true;
|
||||
|
||||
friend class QgsLayoutSnapperUndoCommand;
|
||||
|
||||
};
|
||||
|
||||
#endif //QGSLAYOUTSNAPPER_H
|
||||
|
59
src/core/layout/qgslayoutundocommand.cpp
Normal file
59
src/core/layout/qgslayoutundocommand.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
/***************************************************************************
|
||||
qgslayoutundocommand.cpp
|
||||
------------------------
|
||||
begin : July 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 "qgslayoutundocommand.h"
|
||||
|
||||
|
||||
QgsAbstractLayoutUndoCommand::QgsAbstractLayoutUndoCommand( const QString &text, int id, QUndoCommand *parent )
|
||||
: QUndoCommand( text, parent )
|
||||
, mId( id )
|
||||
{}
|
||||
|
||||
void QgsAbstractLayoutUndoCommand::undo()
|
||||
{
|
||||
restoreState( mBeforeState );
|
||||
}
|
||||
|
||||
void QgsAbstractLayoutUndoCommand::redo()
|
||||
{
|
||||
if ( mFirstRun )
|
||||
{
|
||||
mFirstRun = false;
|
||||
return;
|
||||
}
|
||||
restoreState( mAfterState );
|
||||
}
|
||||
|
||||
void QgsAbstractLayoutUndoCommand::saveBeforeState()
|
||||
{
|
||||
saveState( mBeforeState );
|
||||
}
|
||||
|
||||
void QgsAbstractLayoutUndoCommand::saveAfterState()
|
||||
{
|
||||
saveState( mAfterState );
|
||||
}
|
||||
|
||||
bool QgsAbstractLayoutUndoCommand::containsChange() const
|
||||
{
|
||||
return !( mBeforeState.isNull() || mAfterState.isNull() || mBeforeState.toString() == mAfterState.toString() );
|
||||
}
|
||||
|
||||
void QgsAbstractLayoutUndoCommand::setAfterState( const QDomDocument &stateDoc )
|
||||
{
|
||||
mAfterState = stateDoc;
|
||||
}
|
142
src/core/layout/qgslayoutundocommand.h
Normal file
142
src/core/layout/qgslayoutundocommand.h
Normal file
@ -0,0 +1,142 @@
|
||||
/***************************************************************************
|
||||
qgslayoutundocommand.h
|
||||
----------------------
|
||||
begin : July 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 QGSLAYOUTUNDOCOMMAND_H
|
||||
#define QGSLAYOUTUNDOCOMMAND_H
|
||||
|
||||
#include <QUndoCommand>
|
||||
#include "qgis.h"
|
||||
#include <QDomDocument>
|
||||
|
||||
#include "qgis_core.h"
|
||||
|
||||
class QgsLayout;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* Base class for commands to undo/redo layout and layout object changes.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsAbstractLayoutUndoCommand: public QUndoCommand
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsLayoutUndoCommand.
|
||||
* The \a id argument can be used to specify an id number for the source event - this is used to determine whether QUndoCommand
|
||||
* command compression can apply to the command.
|
||||
*/
|
||||
QgsAbstractLayoutUndoCommand( const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr );
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
int id() const override { return mId; }
|
||||
|
||||
/**
|
||||
* Saves current layout state as before state.
|
||||
* \see beforeState()
|
||||
* \see saveAfterState()
|
||||
*/
|
||||
void saveBeforeState();
|
||||
|
||||
/**
|
||||
* Saves current layout state as after state.
|
||||
* \see afterState()
|
||||
* \see saveBeforeState()
|
||||
*/
|
||||
void saveAfterState();
|
||||
|
||||
/**
|
||||
* Returns the before state for the layout.
|
||||
* \see saveBeforeState()
|
||||
* \see afterState()
|
||||
*/
|
||||
QDomDocument beforeState() const { return mBeforeState.cloneNode().toDocument(); }
|
||||
|
||||
/**
|
||||
* Returns the after state for the layout.
|
||||
* \see saveAfterState()
|
||||
* \see beforeState()
|
||||
*/
|
||||
QDomDocument afterState() const { return mAfterState.cloneNode().toDocument(); }
|
||||
|
||||
/**
|
||||
* Returns true if both the before and after states are valid and different.
|
||||
*/
|
||||
virtual bool containsChange() const;
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Saves the state of the object to the specified \a stateDoc.
|
||||
*
|
||||
* Subclasses must implement this to handle encapsulating their current state into a DOM document.
|
||||
*
|
||||
* \see restoreState()
|
||||
*/
|
||||
virtual void saveState( QDomDocument &stateDoc ) const = 0;
|
||||
|
||||
/**
|
||||
* Restores the state of the object from the specified \a stateDoc.
|
||||
*
|
||||
* Subclasses must implement this to handle restoring their current state from the encapsulated state.
|
||||
*
|
||||
* \see saveState()
|
||||
*/
|
||||
virtual void restoreState( QDomDocument &stateDoc ) = 0;
|
||||
|
||||
/**
|
||||
* Manually sets the after state for the command. Generally this should not be called directly.
|
||||
*/
|
||||
void setAfterState( const QDomDocument &stateDoc );
|
||||
|
||||
//! Flag to prevent the first redo() if the command is pushed to the undo stack
|
||||
bool mFirstRun = true;
|
||||
|
||||
private:
|
||||
|
||||
//! XML that saves the state before executing the command
|
||||
QDomDocument mBeforeState;
|
||||
|
||||
//! XML containing the state after executing the command
|
||||
QDomDocument mAfterState;
|
||||
|
||||
//! Command id
|
||||
int mId = 0;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* Interface for layout objects which support undo/redo commands.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayoutUndoObjectInterface
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Creates a new layout undo command with the specified \a text and \a parent.
|
||||
*
|
||||
* The \a id argument can be used to specify an id number for the source event - this is used to determine whether QUndoCommand
|
||||
* command compression can apply to the command.
|
||||
*/
|
||||
virtual QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id = 0, QUndoCommand *parent = nullptr ) = 0 SIP_FACTORY;
|
||||
};
|
||||
|
||||
|
||||
#endif // QGSLAYOUTUNDOCOMMAND_H
|
74
src/core/layout/qgslayoutundostack.cpp
Normal file
74
src/core/layout/qgslayoutundostack.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
/***************************************************************************
|
||||
qgslayoutundostack.cpp
|
||||
------------------------
|
||||
begin : July 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 "qgslayoutundostack.h"
|
||||
#include "qgslayout.h"
|
||||
#include "qgsproject.h"
|
||||
#include <QUndoStack>
|
||||
|
||||
QgsLayoutUndoStack::QgsLayoutUndoStack( QgsLayout *layout )
|
||||
: mLayout( layout )
|
||||
, mUndoStack( new QUndoStack( layout ) )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void QgsLayoutUndoStack::beginMacro( const QString &commandText )
|
||||
{
|
||||
mUndoStack->beginMacro( commandText );
|
||||
}
|
||||
|
||||
void QgsLayoutUndoStack::endMacro()
|
||||
{
|
||||
mUndoStack->endMacro();
|
||||
}
|
||||
|
||||
void QgsLayoutUndoStack::beginCommand( QgsLayoutUndoObjectInterface *object, const QString &commandText, int id )
|
||||
{
|
||||
if ( !object )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mActiveCommand.reset( object->createCommand( commandText, id, nullptr ) );
|
||||
mActiveCommand->saveBeforeState();
|
||||
}
|
||||
|
||||
void QgsLayoutUndoStack::endCommand()
|
||||
{
|
||||
if ( !mActiveCommand )
|
||||
return;
|
||||
|
||||
mActiveCommand->saveAfterState();
|
||||
if ( mActiveCommand->containsChange() ) //protect against empty commands
|
||||
{
|
||||
mUndoStack->push( mActiveCommand.release() );
|
||||
|
||||
mLayout->project()->setDirty( true );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsLayoutUndoStack::cancelCommand()
|
||||
{
|
||||
mActiveCommand.reset();
|
||||
}
|
||||
|
||||
QUndoStack *QgsLayoutUndoStack::stack()
|
||||
{
|
||||
return mUndoStack.get();
|
||||
|
||||
}
|
114
src/core/layout/qgslayoutundostack.h
Normal file
114
src/core/layout/qgslayoutundostack.h
Normal file
@ -0,0 +1,114 @@
|
||||
/***************************************************************************
|
||||
qgslayoutundostack.h
|
||||
----------------------
|
||||
begin : July 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 QGSLAYOUTUNDOSTACK_H
|
||||
#define QGSLAYOUTUNDOSTACK_H
|
||||
|
||||
#include "qgis.h"
|
||||
#include "qgis_core.h"
|
||||
#include "qgslayoutundocommand.h"
|
||||
#include <memory>
|
||||
|
||||
class QgsLayout;
|
||||
class QUndoStack;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* An undo stack for QgsLayouts.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsLayoutUndoStack
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsLayoutUndoStack, for the specified parent \a layout.
|
||||
*/
|
||||
QgsLayoutUndoStack( QgsLayout *layout );
|
||||
|
||||
/**
|
||||
* Starts a macro command, with the given descriptive \a commandText.
|
||||
*
|
||||
* Any commands added to the stack (either via direct manipulation of
|
||||
* stack() or via beginCommand()/endCommand() calls) between a
|
||||
* beginMacro() and endMacro() block are collapsed into a single
|
||||
* undo command, which will be applied or rolled back in a single step.
|
||||
*
|
||||
* \see endMacro()
|
||||
*/
|
||||
void beginMacro( const QString &commandText );
|
||||
|
||||
/**
|
||||
* Ends a macro command. This must be called after beginMacro(), when
|
||||
* all child undo commands which form part of the macro have been completed.
|
||||
*
|
||||
* Any commands added to the stack (either via direct manipulation of
|
||||
* stack() or via beginCommand()/endCommand() calls) between a
|
||||
* beginMacro() and endMacro() block are collapsed into a single
|
||||
* undo command, which will be applied or rolled back in a single step.
|
||||
*
|
||||
* \see beginMacro()
|
||||
*/
|
||||
void endMacro();
|
||||
|
||||
/**
|
||||
* Begins a new undo command for the specified \a object.
|
||||
*
|
||||
* This must be followed by a call to endCommand() or cancelCommand() after the desired changes
|
||||
* have been made to \a object.
|
||||
*
|
||||
* The \a id argument can be used to specify an id number for the source event - this is used to determine whether QUndoCommand
|
||||
* command compression can apply to the command.
|
||||
*
|
||||
* \see endCommand()
|
||||
* \see cancelCommand()
|
||||
*/
|
||||
void beginCommand( QgsLayoutUndoObjectInterface *object, const QString &commandText, int id = 0 );
|
||||
|
||||
/**
|
||||
* Saves final state of an object and pushes the active command to the undo history.
|
||||
* \see beginCommand()
|
||||
* \see cancelCommand()
|
||||
*/
|
||||
void endCommand();
|
||||
|
||||
/**
|
||||
* Cancels the active command, discarding it without pushing to the undo history.
|
||||
* \see endCommand()
|
||||
* \see cancelCommand()
|
||||
*/
|
||||
void cancelCommand();
|
||||
|
||||
/**
|
||||
* Returns a pointer to the internal QUndoStack.
|
||||
*/
|
||||
QUndoStack *stack();
|
||||
|
||||
private:
|
||||
|
||||
QgsLayout *mLayout = nullptr;
|
||||
|
||||
std::unique_ptr< QUndoStack > mUndoStack;
|
||||
|
||||
std::unique_ptr< QgsAbstractLayoutUndoCommand > mActiveCommand;
|
||||
|
||||
#ifdef SIP_RUN
|
||||
QgsLayoutUndoStack( const QgsLayoutUndoStack &other );
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // QGSLAYOUTUNDOCOMMAND_H
|
@ -624,13 +624,13 @@ void QgsLayoutRuler::mouseMoveEvent( QMouseEvent *event )
|
||||
{
|
||||
case Qt::Horizontal:
|
||||
{
|
||||
mDraggingGuide->setLayoutPosition( displayPos.x() );
|
||||
mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.x() );
|
||||
displayPos.setY( 0 );
|
||||
break;
|
||||
}
|
||||
case Qt::Vertical:
|
||||
{
|
||||
mDraggingGuide->setLayoutPosition( displayPos.y() );
|
||||
mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.y() );
|
||||
displayPos.setX( 0 );
|
||||
break;
|
||||
}
|
||||
@ -679,8 +679,11 @@ void QgsLayoutRuler::mousePressEvent( QMouseEvent *event )
|
||||
if ( !mDraggingGuide )
|
||||
{
|
||||
// if no guide at the point, then we're creating one
|
||||
mCreatingGuide = true;
|
||||
createTemporaryGuideItem();
|
||||
if ( mView->currentLayout()->pageCollection()->pageCount() > 0 )
|
||||
{
|
||||
mCreatingGuide = true;
|
||||
createTemporaryGuideItem();
|
||||
}
|
||||
}
|
||||
switch ( mOrientation )
|
||||
{
|
||||
|
@ -62,6 +62,8 @@
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="mActionUndo"/>
|
||||
<addaction name="mActionRedo"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="mToolsToolbar">
|
||||
<property name="windowTitle">
|
||||
@ -134,7 +136,15 @@
|
||||
<addaction name="mPanelsMenu"/>
|
||||
<addaction name="mActionToggleFullScreen"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuEdit">
|
||||
<property name="title">
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
<addaction name="mActionUndo"/>
|
||||
<addaction name="mActionRedo"/>
|
||||
</widget>
|
||||
<addaction name="mLayoutMenu"/>
|
||||
<addaction name="menuEdit"/>
|
||||
<addaction name="mMenuView"/>
|
||||
<addaction name="mItemMenu"/>
|
||||
</widget>
|
||||
@ -395,6 +405,37 @@
|
||||
<string>Layout Properties…</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionUndo">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normalon>:/images/themes/default/mActionUndo.svg</normalon>
|
||||
</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Undo</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Revert last change</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Z</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionRedo">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionRedo.svg</normaloff>:/images/themes/default/mActionRedo.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Redo</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Restore last change</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+Z</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../../images/images.qrc"/>
|
||||
|
@ -39,6 +39,8 @@ class TestQgsLayout: public QObject
|
||||
void referenceMap();
|
||||
void bounds();
|
||||
void addItem();
|
||||
void layoutItems();
|
||||
void layoutItemByUuid();
|
||||
|
||||
private:
|
||||
QString mReport;
|
||||
@ -360,6 +362,61 @@ void TestQgsLayout::addItem()
|
||||
QGSCOMPARENEAR( l.sceneRect().height(), 171, 0.001 );
|
||||
}
|
||||
|
||||
void TestQgsLayout::layoutItems()
|
||||
{
|
||||
QgsProject p;
|
||||
QgsLayout l( &p );
|
||||
l.pageCollection()->deletePage( 0 );
|
||||
|
||||
QgsLayoutItemRectangularShape *shape1 = new QgsLayoutItemRectangularShape( &l );
|
||||
l.addLayoutItem( shape1 );
|
||||
|
||||
QgsLayoutItemRectangularShape *shape2 = new QgsLayoutItemRectangularShape( &l );
|
||||
l.addLayoutItem( shape2 );
|
||||
|
||||
QgsLayoutItemMap *map1 = new QgsLayoutItemMap( &l );
|
||||
l.addLayoutItem( map1 );
|
||||
|
||||
QList< QgsLayoutItem * > items;
|
||||
l.layoutItems( items );
|
||||
QCOMPARE( items.count(), 3 );
|
||||
QVERIFY( items.contains( shape1 ) );
|
||||
QVERIFY( items.contains( shape2 ) );
|
||||
QVERIFY( items.contains( map1 ) );
|
||||
|
||||
QList< QgsLayoutItemRectangularShape * > shapes;
|
||||
l.layoutItems( shapes );
|
||||
QCOMPARE( shapes.count(), 2 );
|
||||
QVERIFY( shapes.contains( shape1 ) );
|
||||
QVERIFY( shapes.contains( shape2 ) );
|
||||
|
||||
QList< QgsLayoutItemMap * > maps;
|
||||
l.layoutItems( maps );
|
||||
QCOMPARE( maps.count(), 1 );
|
||||
QVERIFY( maps.contains( map1 ) );
|
||||
}
|
||||
|
||||
void TestQgsLayout::layoutItemByUuid()
|
||||
{
|
||||
QgsProject p;
|
||||
QgsLayout l( &p );
|
||||
l.pageCollection()->deletePage( 0 );
|
||||
|
||||
QgsLayoutItemRectangularShape *shape1 = new QgsLayoutItemRectangularShape( &l );
|
||||
l.addLayoutItem( shape1 );
|
||||
|
||||
QgsLayoutItemRectangularShape *shape2 = new QgsLayoutItemRectangularShape( &l );
|
||||
l.addLayoutItem( shape2 );
|
||||
|
||||
QgsLayoutItemMap *map1 = new QgsLayoutItemMap( &l );
|
||||
l.addLayoutItem( map1 );
|
||||
|
||||
QVERIFY( !l.itemByUuid( QStringLiteral( "xxx" ) ) );
|
||||
QCOMPARE( l.itemByUuid( shape1->uuid() ), shape1 );
|
||||
QCOMPARE( l.itemByUuid( shape2->uuid() ), shape2 );
|
||||
QCOMPARE( l.itemByUuid( map1->uuid() ), map1 );
|
||||
}
|
||||
|
||||
|
||||
QGSTEST_MAIN( TestQgsLayout )
|
||||
#include "testqgslayout.moc"
|
||||
|
@ -75,6 +75,7 @@ ADD_PYTHON_TEST(PyQgsJsonUtils test_qgsjsonutils.py)
|
||||
ADD_PYTHON_TEST(PyQgsLayerMetadata test_qgslayermetadata.py)
|
||||
ADD_PYTHON_TEST(PyQgsLayerTreeMapCanvasBridge test_qgslayertreemapcanvasbridge.py)
|
||||
ADD_PYTHON_TEST(PyQgsLayerTree test_qgslayertree.py)
|
||||
ADD_PYTHON_TEST(PyQgsLayout test_qgslayout.py)
|
||||
ADD_PYTHON_TEST(PyQgsLayoutManager test_qgslayoutmanager.py)
|
||||
ADD_PYTHON_TEST(PyQgsLayoutPageCollection test_qgslayoutpagecollection.py)
|
||||
ADD_PYTHON_TEST(PyQgsLayoutView test_qgslayoutview.py)
|
||||
|
83
tests/src/python/test_qgslayout.py
Normal file
83
tests/src/python/test_qgslayout.py
Normal file
@ -0,0 +1,83 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""QGIS Unit tests for QgsLayout
|
||||
|
||||
.. note:: 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.
|
||||
"""
|
||||
__author__ = 'Nyall Dawson'
|
||||
__date__ = '18/07/2017'
|
||||
__copyright__ = 'Copyright 2017, The QGIS Project'
|
||||
# This will get replaced with a git SHA1 when you do a git archive
|
||||
__revision__ = '$Format:%H$'
|
||||
|
||||
import qgis # NOQA
|
||||
import sip
|
||||
|
||||
from qgis.core import (QgsUnitTypes,
|
||||
QgsLayout,
|
||||
QgsLayoutItemPage,
|
||||
QgsLayoutGuide,
|
||||
QgsLayoutObject,
|
||||
QgsProject,
|
||||
QgsProperty,
|
||||
QgsLayoutPageCollection,
|
||||
QgsLayoutMeasurement,
|
||||
QgsFillSymbol,
|
||||
QgsReadWriteContext)
|
||||
from qgis.PyQt.QtCore import Qt, QCoreApplication, QEvent, QPointF, QRectF
|
||||
from qgis.PyQt.QtTest import QSignalSpy
|
||||
from qgis.PyQt.QtXml import QDomDocument
|
||||
|
||||
from qgis.testing import start_app, unittest
|
||||
|
||||
start_app()
|
||||
|
||||
|
||||
class TestQgsLayout(unittest.TestCase):
|
||||
|
||||
def testReadWriteXml(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
l.setName('my layout')
|
||||
l.setUnits(QgsUnitTypes.LayoutInches)
|
||||
collection = l.pageCollection()
|
||||
|
||||
# add a page
|
||||
page = QgsLayoutItemPage(l)
|
||||
page.setPageSize('A6')
|
||||
collection.addPage(page)
|
||||
|
||||
grid = l.gridSettings()
|
||||
grid.setResolution(QgsLayoutMeasurement(5, QgsUnitTypes.LayoutPoints))
|
||||
|
||||
g1 = QgsLayoutGuide(QgsLayoutGuide.Horizontal, QgsLayoutMeasurement(5, QgsUnitTypes.LayoutCentimeters),
|
||||
l.pageCollection().page(0))
|
||||
l.guides().addGuide(g1)
|
||||
|
||||
snapper = l.snapper()
|
||||
snapper.setSnapTolerance(7)
|
||||
|
||||
doc = QDomDocument("testdoc")
|
||||
elem = l.writeXml(doc, QgsReadWriteContext())
|
||||
|
||||
l2 = QgsLayout(p)
|
||||
self.assertTrue(l2.readXml(elem, doc, QgsReadWriteContext()))
|
||||
self.assertEqual(l2.name(), 'my layout')
|
||||
self.assertEqual(l2.units(), QgsUnitTypes.LayoutInches)
|
||||
|
||||
collection2 = l2.pageCollection()
|
||||
self.assertEqual(collection2.pageCount(), 1)
|
||||
self.assertAlmostEqual(collection2.page(0).pageSize().width(), 105, 4)
|
||||
self.assertEqual(collection2.page(0).pageSize().height(), 148)
|
||||
self.assertEqual(l2.gridSettings().resolution().length(), 5.0)
|
||||
self.assertEqual(l2.gridSettings().resolution().units(), QgsUnitTypes.LayoutPoints)
|
||||
self.assertEqual(l2.guides().guidesOnPage(0)[0].orientation(), QgsLayoutGuide.Horizontal)
|
||||
self.assertEqual(l2.guides().guidesOnPage(0)[0].position().length(), 5.0)
|
||||
self.assertEqual(l2.guides().guidesOnPage(0)[0].position().units(), QgsUnitTypes.LayoutCentimeters)
|
||||
self.assertEqual(l2.snapper().snapTolerance(), 7)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -20,9 +20,11 @@ from qgis.core import (QgsProject,
|
||||
QgsLayoutMeasurement,
|
||||
QgsUnitTypes,
|
||||
QgsLayoutPoint,
|
||||
QgsLayoutItemPage)
|
||||
QgsLayoutItemPage,
|
||||
QgsReadWriteContext)
|
||||
from qgis.PyQt.QtGui import (QPen,
|
||||
QColor)
|
||||
from qgis.PyQt.QtXml import QDomDocument
|
||||
|
||||
from qgis.testing import start_app, unittest
|
||||
|
||||
@ -34,7 +36,7 @@ class TestQgsLayoutGridSettings(unittest.TestCase):
|
||||
def testGettersSetters(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
s = QgsLayoutGridSettings()
|
||||
s = QgsLayoutGridSettings(l)
|
||||
s.setResolution(QgsLayoutMeasurement(5, QgsUnitTypes.LayoutPoints))
|
||||
self.assertEqual(s.resolution().length(), 5.0)
|
||||
self.assertEqual(s.resolution().units(), QgsUnitTypes.LayoutPoints)
|
||||
@ -50,6 +52,74 @@ class TestQgsLayoutGridSettings(unittest.TestCase):
|
||||
s.setStyle(QgsLayoutGridSettings.StyleDots)
|
||||
self.assertEqual(s.style(), QgsLayoutGridSettings.StyleDots)
|
||||
|
||||
def testReadWriteXml(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
s = QgsLayoutGridSettings(l)
|
||||
s.setResolution(QgsLayoutMeasurement(5, QgsUnitTypes.LayoutPoints))
|
||||
s.setOffset(QgsLayoutPoint(6, 7, QgsUnitTypes.LayoutPixels))
|
||||
|
||||
doc = QDomDocument("testdoc")
|
||||
elem = doc.createElement("test")
|
||||
self.assertTrue(s.writeXml(elem, doc, QgsReadWriteContext()))
|
||||
|
||||
s2 = QgsLayoutGridSettings(l)
|
||||
self.assertTrue(s2.readXml(elem.firstChildElement(), doc, QgsReadWriteContext()))
|
||||
|
||||
self.assertEqual(s2.resolution().length(), 5.0)
|
||||
self.assertEqual(s2.resolution().units(), QgsUnitTypes.LayoutPoints)
|
||||
self.assertEqual(s2.offset().x(), 6.0)
|
||||
self.assertEqual(s2.offset().y(), 7.0)
|
||||
self.assertEqual(s2.offset().units(), QgsUnitTypes.LayoutPixels)
|
||||
|
||||
def testUndoRedo(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
g = l.gridSettings()
|
||||
g.setResolution(QgsLayoutMeasurement(15, QgsUnitTypes.LayoutPoints))
|
||||
|
||||
# these two commands should be 'collapsed'
|
||||
g.setOffset(QgsLayoutPoint(555, 10, QgsUnitTypes.LayoutPoints))
|
||||
g.setOffset(QgsLayoutPoint(5, 10, QgsUnitTypes.LayoutPoints))
|
||||
|
||||
# these two commands should be 'collapsed'
|
||||
g.setResolution(QgsLayoutMeasurement(45, QgsUnitTypes.LayoutInches))
|
||||
g.setResolution(QgsLayoutMeasurement(35, QgsUnitTypes.LayoutInches))
|
||||
|
||||
self.assertEqual(g.offset().x(), 5.0)
|
||||
self.assertEqual(g.offset().y(), 10.0)
|
||||
self.assertEqual(g.offset().units(), QgsUnitTypes.LayoutPoints)
|
||||
self.assertEqual(g.resolution().length(), 35.0)
|
||||
self.assertEqual(g.resolution().units(), QgsUnitTypes.LayoutInches)
|
||||
|
||||
l.undoStack().stack().undo()
|
||||
self.assertEqual(g.offset().x(), 5.0)
|
||||
self.assertEqual(g.offset().y(), 10.0)
|
||||
self.assertEqual(g.offset().units(), QgsUnitTypes.LayoutPoints)
|
||||
self.assertEqual(g.resolution().length(), 15.0)
|
||||
self.assertEqual(g.resolution().units(), QgsUnitTypes.LayoutPoints)
|
||||
|
||||
l.undoStack().stack().undo()
|
||||
self.assertEqual(g.offset().x(), 0.0)
|
||||
self.assertEqual(g.offset().y(), 0.0)
|
||||
self.assertEqual(g.offset().units(), QgsUnitTypes.LayoutMillimeters)
|
||||
self.assertEqual(g.resolution().length(), 15.0)
|
||||
self.assertEqual(g.resolution().units(), QgsUnitTypes.LayoutPoints)
|
||||
|
||||
l.undoStack().stack().redo()
|
||||
self.assertEqual(g.offset().x(), 5.0)
|
||||
self.assertEqual(g.offset().y(), 10.0)
|
||||
self.assertEqual(g.offset().units(), QgsUnitTypes.LayoutPoints)
|
||||
self.assertEqual(g.resolution().length(), 15.0)
|
||||
self.assertEqual(g.resolution().units(), QgsUnitTypes.LayoutPoints)
|
||||
|
||||
l.undoStack().stack().redo()
|
||||
self.assertEqual(g.offset().x(), 5.0)
|
||||
self.assertEqual(g.offset().y(), 10.0)
|
||||
self.assertEqual(g.offset().units(), QgsUnitTypes.LayoutPoints)
|
||||
self.assertEqual(g.resolution().length(), 35.0)
|
||||
self.assertEqual(g.resolution().units(), QgsUnitTypes.LayoutInches)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -19,7 +19,7 @@ from qgis.core import (QgsProject,
|
||||
QgsLayoutGuide,
|
||||
QgsLayoutMeasurement,
|
||||
QgsUnitTypes,
|
||||
QgsLayoutPoint,
|
||||
QgsReadWriteContext,
|
||||
QgsLayoutItemPage,
|
||||
QgsLayoutGuideCollection,
|
||||
QgsLayoutGuideProxyModel)
|
||||
@ -27,6 +27,7 @@ from qgis.PyQt.QtCore import (QModelIndex)
|
||||
from qgis.PyQt.QtGui import (QPen,
|
||||
QColor)
|
||||
from qgis.PyQt.QtTest import QSignalSpy
|
||||
from qgis.PyQt.QtXml import QDomDocument
|
||||
|
||||
from qgis.testing import start_app, unittest
|
||||
|
||||
@ -315,6 +316,55 @@ class TestQgsLayoutGuide(unittest.TestCase):
|
||||
self.assertTrue(g1.item().isVisible())
|
||||
self.assertTrue(g2.item().isVisible())
|
||||
|
||||
def testReadWriteXml(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
l.initializeDefaults()
|
||||
guides = l.guides()
|
||||
|
||||
# add some guides
|
||||
g1 = QgsLayoutGuide(QgsLayoutGuide.Horizontal, QgsLayoutMeasurement(5, QgsUnitTypes.LayoutCentimeters), l.pageCollection().page(0))
|
||||
guides.addGuide(g1)
|
||||
g2 = QgsLayoutGuide(QgsLayoutGuide.Vertical, QgsLayoutMeasurement(6, QgsUnitTypes.LayoutInches), l.pageCollection().page(0))
|
||||
guides.addGuide(g2)
|
||||
|
||||
guides.setVisible(False)
|
||||
|
||||
doc = QDomDocument("testdoc")
|
||||
elem = doc.createElement("test")
|
||||
self.assertTrue(guides.writeXml(elem, doc, QgsReadWriteContext()))
|
||||
|
||||
l2 = QgsLayout(p)
|
||||
l2.initializeDefaults()
|
||||
guides2 = l2.guides()
|
||||
|
||||
self.assertTrue(guides2.readXml(elem.firstChildElement(), doc, QgsReadWriteContext()))
|
||||
guide_list = guides2.guidesOnPage(0)
|
||||
self.assertEqual(len(guide_list), 2)
|
||||
|
||||
self.assertEqual(guide_list[0].orientation(), QgsLayoutGuide.Horizontal)
|
||||
self.assertEqual(guide_list[0].position().length(), 5.0)
|
||||
self.assertEqual(guide_list[0].position().units(), QgsUnitTypes.LayoutCentimeters)
|
||||
self.assertEqual(guide_list[1].orientation(), QgsLayoutGuide.Vertical)
|
||||
self.assertEqual(guide_list[1].position().length(), 6.0)
|
||||
self.assertEqual(guide_list[1].position().units(), QgsUnitTypes.LayoutInches)
|
||||
|
||||
def testGuideLayoutPosition(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
l.initializeDefaults()
|
||||
guides = l.guides()
|
||||
|
||||
# add some guides
|
||||
g1 = QgsLayoutGuide(QgsLayoutGuide.Horizontal, QgsLayoutMeasurement(1, QgsUnitTypes.LayoutCentimeters), l.pageCollection().page(0))
|
||||
guides.addGuide(g1)
|
||||
|
||||
# set position in layout units (mm)
|
||||
guides.setGuideLayoutPosition(g1, 50)
|
||||
|
||||
self.assertEqual(g1.position().length(), 5.0)
|
||||
self.assertEqual(g1.position().units(), QgsUnitTypes.LayoutCentimeters)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -24,9 +24,11 @@ from qgis.core import (QgsUnitTypes,
|
||||
QgsProperty,
|
||||
QgsLayoutPageCollection,
|
||||
QgsSimpleFillSymbolLayer,
|
||||
QgsFillSymbol)
|
||||
QgsFillSymbol,
|
||||
QgsReadWriteContext)
|
||||
from qgis.PyQt.QtCore import Qt, QCoreApplication, QEvent, QPointF, QRectF
|
||||
from qgis.PyQt.QtTest import QSignalSpy
|
||||
from qgis.PyQt.QtXml import QDomDocument
|
||||
|
||||
from qgis.testing import start_app, unittest
|
||||
|
||||
@ -426,6 +428,130 @@ class TestQgsLayoutPageCollection(unittest.TestCase):
|
||||
self.assertEqual(collection.visiblePages(QRectF(100, 310, 115, 615)), [page2])
|
||||
self.assertEqual(collection.visiblePageNumbers(QRectF(100, 310, 115, 115)), [1])
|
||||
|
||||
def testTakePage(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
collection = l.pageCollection()
|
||||
|
||||
# add some pages
|
||||
page = QgsLayoutItemPage(l)
|
||||
page.setPageSize('A4')
|
||||
collection.addPage(page)
|
||||
page2 = QgsLayoutItemPage(l)
|
||||
page2.setPageSize('A5')
|
||||
collection.addPage(page2)
|
||||
|
||||
self.assertEqual(collection.pageCount(), 2)
|
||||
|
||||
self.assertFalse(collection.takePage(None))
|
||||
|
||||
self.assertEqual(collection.takePage(page), page)
|
||||
self.assertFalse(sip.isdeleted(page))
|
||||
|
||||
self.assertEqual(collection.pageCount(), 1)
|
||||
self.assertEqual(collection.pages(), [page2])
|
||||
self.assertEqual(collection.page(0), page2)
|
||||
|
||||
self.assertEqual(collection.takePage(page2), page2)
|
||||
self.assertFalse(sip.isdeleted(page2))
|
||||
|
||||
self.assertEqual(collection.pageCount(), 0)
|
||||
self.assertEqual(collection.pages(), [])
|
||||
self.assertFalse(collection.page(0))
|
||||
|
||||
def testReadWriteXml(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
collection = l.pageCollection()
|
||||
|
||||
fill = QgsSimpleFillSymbolLayer()
|
||||
fill_symbol = QgsFillSymbol()
|
||||
fill_symbol.changeSymbolLayer(0, fill)
|
||||
fill.setColor(Qt.green)
|
||||
fill.setStrokeColor(Qt.red)
|
||||
fill.setStrokeWidth(6)
|
||||
collection.setPageStyleSymbol(fill_symbol)
|
||||
|
||||
# add a page
|
||||
page = QgsLayoutItemPage(l)
|
||||
page.setPageSize('A4')
|
||||
self.assertEqual(collection.pageNumber(page), -1)
|
||||
|
||||
collection.addPage(page)
|
||||
|
||||
# add a second page
|
||||
page2 = QgsLayoutItemPage(l)
|
||||
page2.setPageSize('A5')
|
||||
collection.addPage(page2)
|
||||
|
||||
doc = QDomDocument("testdoc")
|
||||
elem = doc.createElement("test")
|
||||
self.assertTrue(collection.writeXml(elem, doc, QgsReadWriteContext()))
|
||||
|
||||
l2 = QgsLayout(p)
|
||||
collection2 = l2.pageCollection()
|
||||
|
||||
self.assertTrue(collection2.readXml(elem.firstChildElement(), doc, QgsReadWriteContext()))
|
||||
|
||||
self.assertEqual(collection2.pageCount(), 2)
|
||||
self.assertEqual(collection2.page(0).pageSize().width(), 210)
|
||||
self.assertEqual(collection2.page(0).pageSize().height(), 297)
|
||||
self.assertEqual(collection2.page(1).pageSize().width(), 148)
|
||||
self.assertEqual(collection2.page(1).pageSize().height(), 210)
|
||||
|
||||
self.assertEqual(collection2.pageStyleSymbol().symbolLayer(0).color().name(), '#00ff00')
|
||||
self.assertEqual(collection2.pageStyleSymbol().symbolLayer(0).strokeColor().name(), '#ff0000')
|
||||
|
||||
def testUndoRedo(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
collection = l.pageCollection()
|
||||
|
||||
# add a page
|
||||
page = QgsLayoutItemPage(l)
|
||||
page.setPageSize('A4')
|
||||
collection.addPage(page)
|
||||
self.assertEqual(collection.pageCount(), 1)
|
||||
|
||||
l.undoStack().stack().undo()
|
||||
self.assertEqual(collection.pageCount(), 0)
|
||||
|
||||
l.undoStack().stack().redo()
|
||||
self.assertEqual(collection.pageCount(), 1)
|
||||
# make sure page is accessible
|
||||
self.assertEqual(collection.page(0).pageSize().width(), 210)
|
||||
|
||||
# add a second page
|
||||
page2 = QgsLayoutItemPage(l)
|
||||
page2.setPageSize('A5')
|
||||
collection.addPage(page2)
|
||||
|
||||
# delete page
|
||||
collection.deletePage(collection.page(0))
|
||||
self.assertEqual(collection.pageCount(), 1)
|
||||
|
||||
l.undoStack().stack().undo()
|
||||
self.assertEqual(collection.pageCount(), 2)
|
||||
# make sure pages are accessible
|
||||
self.assertEqual(collection.page(0).pageSize().width(), 210)
|
||||
self.assertEqual(collection.page(1).pageSize().width(), 148)
|
||||
|
||||
l.undoStack().stack().undo()
|
||||
self.assertEqual(collection.pageCount(), 1)
|
||||
l.undoStack().stack().undo()
|
||||
self.assertEqual(collection.pageCount(), 0)
|
||||
|
||||
l.undoStack().stack().redo()
|
||||
self.assertEqual(collection.pageCount(), 1)
|
||||
self.assertEqual(collection.page(0).pageSize().width(), 210)
|
||||
l.undoStack().stack().redo()
|
||||
self.assertEqual(collection.pageCount(), 2)
|
||||
self.assertEqual(collection.page(0).pageSize().width(), 210)
|
||||
self.assertEqual(collection.page(1).pageSize().width(), 148)
|
||||
l.undoStack().stack().redo()
|
||||
self.assertEqual(collection.pageCount(), 1)
|
||||
self.assertEqual(collection.page(0).pageSize().width(), 148)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -22,8 +22,10 @@ from qgis.core import (QgsProject,
|
||||
QgsUnitTypes,
|
||||
QgsLayoutPoint,
|
||||
QgsLayoutItemPage,
|
||||
QgsLayoutGuide)
|
||||
QgsLayoutGuide,
|
||||
QgsReadWriteContext)
|
||||
from qgis.PyQt.QtCore import QPointF
|
||||
from qgis.PyQt.QtXml import QDomDocument
|
||||
|
||||
from qgis.testing import start_app, unittest
|
||||
|
||||
@ -193,6 +195,41 @@ class TestQgsLayoutSnapper(unittest.TestCase):
|
||||
self.assertTrue(snapped)
|
||||
self.assertEqual(point, QPointF(0, 0.5))
|
||||
|
||||
def testReadWriteXml(self):
|
||||
p = QgsProject()
|
||||
l = QgsLayout(p)
|
||||
l.initializeDefaults()
|
||||
snapper = l.snapper()
|
||||
|
||||
snapper.setSnapToGrid(True)
|
||||
snapper.setSnapTolerance(1)
|
||||
snapper.setSnapToGuides(True)
|
||||
|
||||
doc = QDomDocument("testdoc")
|
||||
elem = doc.createElement("test")
|
||||
self.assertTrue(snapper.writeXml(elem, doc, QgsReadWriteContext()))
|
||||
|
||||
l2 = QgsLayout(p)
|
||||
l2.initializeDefaults()
|
||||
snapper2 = l2.snapper()
|
||||
|
||||
self.assertTrue(snapper2.readXml(elem.firstChildElement(), doc, QgsReadWriteContext()))
|
||||
self.assertTrue(snapper2.snapToGrid())
|
||||
self.assertEqual(snapper2.snapTolerance(), 1)
|
||||
self.assertTrue(snapper2.snapToGuides())
|
||||
|
||||
snapper.setSnapToGrid(False)
|
||||
snapper.setSnapTolerance(1)
|
||||
snapper.setSnapToGuides(False)
|
||||
|
||||
doc = QDomDocument("testdoc")
|
||||
elem = doc.createElement("test")
|
||||
self.assertTrue(snapper.writeXml(elem, doc, QgsReadWriteContext()))
|
||||
|
||||
self.assertTrue(snapper2.readXml(elem.firstChildElement(), doc, QgsReadWriteContext()))
|
||||
self.assertFalse(snapper2.snapToGrid())
|
||||
self.assertFalse(snapper2.snapToGuides())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user