diff --git a/python/core/auto_generated/qgsattributeeditorelement.sip.in b/python/core/auto_generated/qgsattributeeditorelement.sip.in index 7b1f30484458..067bc063e6ce 100644 --- a/python/core/auto_generated/qgsattributeeditorelement.sip.in +++ b/python/core/auto_generated/qgsattributeeditorelement.sip.in @@ -113,6 +113,20 @@ Controls if this element should be labeled with a title (field, relation or grou Controls if this element should be labeled with a title (field, relation or groupname). .. versionadded:: 2.18 +%End + + QVariantMap config() const; +%Docstring +Returns the editor configuration + +.. versionadded:: 3.18 +%End + + void setConfig( const QVariantMap &config ); +%Docstring +Sets the editor configuration + +.. versionadded:: 3.18 %End protected: @@ -351,80 +365,6 @@ Initializes the relation from the id virtual QgsAttributeEditorElement *clone( QgsAttributeEditorElement *parent ) const /Factory/; - bool showLinkButton() const /Deprecated/; -%Docstring -Determines if the "link feature" button should be shown - -.. versionadded:: 2.18 - -.. deprecated:: QGIS 3.16 - use visibleButtons() instead -%End - - void setShowLinkButton( bool showLinkButton ) /Deprecated/; -%Docstring -Determines if the "link feature" button should be shown - -.. versionadded:: 2.18 - -.. deprecated:: QGIS 3.16 - use setVisibleButtons() instead -%End - - bool showUnlinkButton() const /Deprecated/; -%Docstring -Determines if the "unlink feature" button should be shown - -.. versionadded:: 2.18 - -.. deprecated:: QGIS 3.16 - use visibleButtons() instead -%End - - void setShowUnlinkButton( bool showUnlinkButton ) /Deprecated/; -%Docstring -Determines if the "unlink feature" button should be shown - -.. versionadded:: 2.18 - -.. deprecated:: QGIS 3.16 - use setVisibleButtons() instead -%End - - void setShowSaveChildEditsButton( bool showChildEdits ) /Deprecated/; -%Docstring -Determines if the "Save child layer edits" button should be shown - -.. versionadded:: 3.14 - -.. deprecated:: QGIS 3.16 - use setVisibleButtons() instead -%End - - bool showSaveChildEditsButton() const /Deprecated/; -%Docstring -Determines if the "Save child layer edits" button should be shown - -.. versionadded:: 3.14 - -.. deprecated:: QGIS 3.16 - use visibleButtons() instead -%End - - void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ); -%Docstring -Defines the buttons which are shown - -.. versionadded:: 3.16 -%End - - QgsAttributeEditorRelation::Buttons visibleButtons() const; -%Docstring -Returns the buttons which are shown - -.. versionadded:: 3.16 -%End - bool forceSuppressFormPopup() const; %Docstring Determines the force suppress form popup status. @@ -469,6 +409,20 @@ Sets ``label`` for this element If it's empty it takes the relation id as label .. versionadded:: 3.16 +%End + + QString relationWidgetTypeId() const; +%Docstring +Returns the current relation widget type id + +.. versionadded:: 3.18 +%End + + void setRelationWidgetTypeId( const QString &relationWidgetTypeId ); +%Docstring +Sets the relation widget type + +.. versionadded:: 3.18 %End }; diff --git a/python/gui/auto_additions/qgsrelationeditorwidget.py b/python/gui/auto_additions/qgsrelationeditorwidget.py new file mode 100644 index 000000000000..3bb37c58e85a --- /dev/null +++ b/python/gui/auto_additions/qgsrelationeditorwidget.py @@ -0,0 +1,4 @@ +# The following has been generated automatically from src/gui/qgsrelationeditorwidget.h +QgsRelationEditorWidget.Button.baseClass = QgsRelationEditorWidget +QgsRelationEditorWidget.Buttons.baseClass = QgsRelationEditorWidget +Buttons = QgsRelationEditorWidget # dirty hack since SIP seems to introduce the flags in module diff --git a/python/gui/auto_generated/editorwidgets/qgsrelationwidgetwrapper.sip.in b/python/gui/auto_generated/editorwidgets/qgsrelationwidgetwrapper.sip.in index f6063540a880..737c9d33bd81 100644 --- a/python/gui/auto_generated/editorwidgets/qgsrelationwidgetwrapper.sip.in +++ b/python/gui/auto_generated/editorwidgets/qgsrelationwidgetwrapper.sip.in @@ -18,7 +18,23 @@ class QgsRelationWidgetWrapper : QgsWidgetWrapper %End public: - explicit QgsRelationWidgetWrapper( QgsVectorLayer *vl, const QgsRelation &relation, QWidget *editor = 0, QWidget *parent /TransferThis/ = 0 ); + QgsRelationWidgetWrapper( + QgsVectorLayer *vl, + const QgsRelation &relation, + QWidget *editor /Constrained/ = 0, + QWidget *parent /TransferThis,Constrained/ = 0 + ); +%Docstring +Constructor for QgsRelationWidgetWrapper +%End + + QgsRelationWidgetWrapper( + const QString &relationEditorName, + QgsVectorLayer *vl, + const QgsRelation &relation, + QWidget *editor = 0, + QWidget *parent /TransferThis/ = 0 + ); %Docstring Constructor for QgsRelationWidgetWrapper %End @@ -99,18 +115,41 @@ Determines if the "Save child layer edits" button should be shown use visibleButtons() instead %End - void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ); + void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ) /Deprecated/; %Docstring Defines the buttons which are shown .. versionadded:: 3.16 + +.. deprecated:: QGIS 3.18 + use setWidgetConfig() instead %End - QgsAttributeEditorRelation::Buttons visibleButtons() const; + QgsAttributeEditorRelation::Buttons visibleButtons() const /Deprecated/; %Docstring Returns the buttons which are shown .. versionadded:: 3.16 + +.. deprecated:: QGIS 3.18 + use widgetConfig() instead +%End + + + void setWidgetConfig( const QVariantMap &config ); +%Docstring +Will set the config of this widget wrapper to the specified config. + +:param config: The config for this wrapper + +.. versionadded:: 3.18 +%End + + QVariantMap widgetConfig() const; +%Docstring +Returns the whole widget config + +.. versionadded:: 3.18 %End bool forceSuppressFormPopup() const; diff --git a/python/gui/auto_generated/qgsabstractrelationeditorwidget.sip.in b/python/gui/auto_generated/qgsabstractrelationeditorwidget.sip.in new file mode 100644 index 000000000000..16513a642ea8 --- /dev/null +++ b/python/gui/auto_generated/qgsabstractrelationeditorwidget.sip.in @@ -0,0 +1,340 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsabstractrelationeditorwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +// this is needed for the "convert to subclass" code below to compile +%ModuleHeaderCode +#include "qgsrelationeditorwidget.h" +%End + + +class QgsAbstractRelationEditorWidget : QWidget +{ +%Docstring +Base class to build new relation widgets. + +.. versionadded:: 3.18 +%End + +%TypeHeaderCode +#include "qgsabstractrelationeditorwidget.h" +%End +%ConvertToSubClassCode + if ( qobject_cast( sipCpp ) ) + sipType = sipType_QgsRelationEditorWidget; + else + sipType = 0; +%End + public: + + + QgsAbstractRelationEditorWidget( const QVariantMap &config, QWidget *parent /TransferThis/ = 0 ); +%Docstring +Constructor +%End + + void setRelationFeature( const QgsRelation &relation, const QgsFeature &feature ); +%Docstring +Sets the ``relation`` and the ``feature`` +%End + + void setRelations( const QgsRelation &relation, const QgsRelation &nmrelation ); +%Docstring +Set the relation(s) for this widget +If only one relation is set, it will act as a simple 1:N relation widget +If both relations are set, it will act as an N:M relation widget +inserting and deleting entries on the intermediate table as required. + +:param relation: Relation referencing the edited table +:param nmrelation: Optional reference from the referencing table to a 3rd N:M table +%End + + void setFeature( const QgsFeature &feature, bool update = true ); +%Docstring +Sets the ``feature`` being edited and updates the UI unless ``update`` is set to ``False`` +%End + + virtual void setEditorContext( const QgsAttributeEditorContext &context ); +%Docstring +Sets the editor ``context`` + +.. note:: + + if context cadDockWidget is null, it won't be possible to digitize + the geometry of a referencing feature from this widget +%End + + QgsAttributeEditorContext editorContext( ) const; +%Docstring +Returns the attribute editor context. +%End + + bool showLabel() const; +%Docstring +Defines if a title label should be shown for this widget. +%End + + void setShowLabel( bool showLabel ); +%Docstring +Defines if a title label should be shown for this widget. +%End + + QVariant nmRelationId() const; +%Docstring +Determines the relation id of the second relation involved in an N:M relation. +%End + + void setNmRelationId( const QVariant &nmRelationId = QVariant() ); +%Docstring +Sets ``nmRelationId`` for the relation id of the second relation involved in an N:M relation. +If it's empty, then it's considered as a 1:M relationship. +%End + + QString label() const; +%Docstring +Determines the label of this element +%End + + void setLabel( const QString &label = QString() ); +%Docstring +Sets ``label`` for this element +If it's empty it takes the relation id as label +%End + + QgsFeature feature() const; +%Docstring +Returns the widget's current feature +%End + + bool forceSuppressFormPopup() const; +%Docstring +Determines the force suppress form popup status that is configured for this widget +%End + + void setForceSuppressFormPopup( bool forceSuppressFormPopup ); +%Docstring +Sets force suppress form popup status with ``forceSuppressFormPopup`` +configured for this widget +%End + + virtual QVariantMap config() const = 0; +%Docstring +Returns the widget configuration +%End + + virtual void setConfig( const QVariantMap &config ) = 0; +%Docstring +Defines the widget configuration +%End + + public slots: + + virtual void parentFormValueChanged( const QString &attribute, const QVariant &newValue ) = 0; +%Docstring +Called when an ``attribute`` value in the parent widget has changed to ``newValue`` +%End + + protected slots: + + void toggleEditing( bool state ); +%Docstring +Toggles editing state of the widget +%End + + void saveEdits(); +%Docstring +Saves the current modifications in the relation +%End + + void addFeature( const QgsGeometry &geometry = QgsGeometry() ); +%Docstring +Adds a new feature with given ``geometry`` +%End + + void deleteFeature( QgsFeatureId fid = QgsFeatureId() ); +%Docstring +Delete a feature with given ``fid`` +%End + + void linkFeature(); +%Docstring +Links a new feature to the relation +%End + + void onLinkFeatureDlgAccepted(); +%Docstring +Called when the link feature dialog is confirmed by the user +%End + + void unlinkFeature( QgsFeatureId fid = QgsFeatureId() ); +%Docstring +Unlinks a feature with given ``fid`` +%End + + void duplicateFeature( const QgsFeatureId &fid ); +%Docstring +Duplicates a feature +%End + + void duplicateFeatures( const QgsFeatureIds &fids ); +%Docstring +Duplicates features +%End + + protected: + + + + + void updateTitle(); +%Docstring +Updates the title contents to reflect the current state of the widget +%End + + void deleteFeatures( const QgsFeatureIds &fids ); +%Docstring +Deletes the features with ``fids`` +%End + + void unlinkFeatures( const QgsFeatureIds &fids ); +%Docstring +Unlinks the features with ``fids`` +%End + +}; + + +class QgsAbstractRelationEditorConfigWidget : QWidget +{ +%Docstring +This class should be subclassed for every configurable relation widget type. + +It implements the GUI configuration widget and transforms this to/from a configuration. + +It will only be instantiated by {:py:class:`QgsAbstractRelationEditorWidgetFactory`} + +.. versionadded:: 3.18 +%End + +%TypeHeaderCode +#include "qgsabstractrelationeditorwidget.h" +%End +%ConvertToSubClassCode + if ( qobject_cast( sipCpp ) ) + sipType = sipType_QgsRelationEditorConfigWidget; + else + sipType = 0; +%End + public: + + explicit QgsAbstractRelationEditorConfigWidget( const QgsRelation &relation, QWidget *parent /TransferThis/ ); +%Docstring +Create a new configuration widget + +:param relation: The relation for which the configuration dialog will be created +:param parent: A parent widget +%End + + virtual QVariantMap config() = 0; +%Docstring +Create a configuration from the current GUI state + +:return: A widget configuration +%End + + virtual void setConfig( const QVariantMap &config ) = 0; +%Docstring +Update the configuration widget to represent the given configuration. + +:param config: The configuration which should be represented by this widget +%End + + QgsVectorLayer *layer(); +%Docstring +Returns the layer for which this configuration widget applies + +:return: The layer +%End + + QgsRelation relation() const; +%Docstring +Returns the relation for which this configuration widget applies + +:return: The relation +%End + + +}; + + + + +class QgsAbstractRelationEditorWidgetFactory +{ +%Docstring +Factory class for creating relation widgets and their corresponding config widgets + +.. versionadded:: 3.18 +%End + +%TypeHeaderCode +#include "qgsabstractrelationeditorwidget.h" +%End + public: + + QgsAbstractRelationEditorWidgetFactory(); +%Docstring +Creates a new relation widget factory with given ``name`` +%End + + virtual ~QgsAbstractRelationEditorWidgetFactory(); + + virtual QString type() const = 0; +%Docstring +Returns the machine readable identifier name of this widget type +%End + + virtual QString name() const = 0; +%Docstring +Returns the human readable identifier name of this widget type +%End + + virtual QgsAbstractRelationEditorWidget *create( const QVariantMap &config, QWidget *parent = 0 ) const = 0 /Factory/; +%Docstring +Override this in your implementation. +Create a new relation widget. Call :py:func:`QgsEditorWidgetRegistry.create()` +instead of calling this method directly. + +:param config: The widget configuration to build the widget with +:param parent: The parent for the wrapper class and any created widget. + +:return: A new widget wrapper +%End + + virtual QgsAbstractRelationEditorConfigWidget *configWidget( const QgsRelation &relation, QWidget *parent ) const = 0 /Factory/; +%Docstring +Override this in your implementation. +Create a new configuration widget for this widget type. + +:param relation: The relation for which the widget will be created +:param parent: The parent widget of the created config widget + +:return: A configuration widget +%End +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsabstractrelationeditorwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/auto_generated/qgsgui.sip.in b/python/gui/auto_generated/qgsgui.sip.in index dd661ac36431..c10d8d43460d 100644 --- a/python/gui/auto_generated/qgsgui.sip.in +++ b/python/gui/auto_generated/qgsgui.sip.in @@ -129,6 +129,13 @@ Returns the registry of subset string editors of data providers %Docstring Returns the registry of provider source widget providers. +.. versionadded:: 3.18 +%End + + static QgsRelationWidgetRegistry *relationWidgetRegistry() /KeepReference/; +%Docstring +Returns the global relation widget registry, used for managing all known relation widget factories. + .. versionadded:: 3.18 %End diff --git a/python/gui/auto_generated/qgsrelationeditorwidget.sip.in b/python/gui/auto_generated/qgsrelationeditorwidget.sip.in index 44dc64031cbb..82b59803e3b7 100644 --- a/python/gui/auto_generated/qgsrelationeditorwidget.sip.in +++ b/python/gui/auto_generated/qgsrelationeditorwidget.sip.in @@ -10,6 +10,7 @@ + %ModuleHeaderCode // fix to allow compilation with sip that for some reason // doesn't add this include to the file where the code from @@ -20,24 +21,38 @@ -class QgsRelationEditorWidget : QgsCollapsibleGroupBox +class QgsRelationEditorWidget : QgsAbstractRelationEditorWidget { +%Docstring +The default relation widget in QGIS. Successor of the now deprecated {:py:class:`QgsRelationEditorWidget`}. + +.. versionadded:: 3.18 +%End %TypeHeaderCode #include "qgsrelationeditorwidget.h" -%End -%ConvertToSubClassCode - if ( qobject_cast( sipCpp ) ) - sipType = sipType_QgsRelationEditorWidget; - else - sipType = NULL; %End public: + enum Button + { + Link, + Unlink, + SaveChildEdits, + AddChildFeature, + DuplicateChildFeature, + DeleteChildFeature, + ZoomToChildFeature, + AllButtons + }; + typedef QFlags Buttons; + - QgsRelationEditorWidget( QWidget *parent /TransferThis/ = 0 ); + QgsRelationEditorWidget( const QVariantMap &config, QWidget *parent /TransferThis/ = 0 ); %Docstring +Constructor +:param config: widget configuration :param parent: parent widget %End @@ -51,28 +66,14 @@ Define the view mode for the dual view Gets the view mode for the dual view %End - void setRelationFeature( const QgsRelation &relation, const QgsFeature &feature ); -%Docstring -Sets the ``relation`` and the ``feature`` -%End - - void setRelations( const QgsRelation &relation, const QgsRelation &nmrelation ); + QgsIFeatureSelectionManager *featureSelectionManager(); %Docstring -Set the relation(s) for this widget -If only one relation is set, it will act as a simple 1:N relation widget -If both relations are set, it will act as an N:M relation widget -inserting and deleting entries on the intermediate table as required. - -:param relation: Relation referencing the edited table -:param nmrelation: Optional reference from the referencing table to a 3rd N:M table +The feature selection manager is responsible for the selected features +which are currently being edited. %End - void setFeature( const QgsFeature &feature, bool update = true ); -%Docstring -Sets the ``feature`` being edited and updates the UI unless ``update`` is set to ``False`` -%End + virtual void setEditorContext( const QgsAttributeEditorContext &context ); - void setEditorContext( const QgsAttributeEditorContext &context ); %Docstring Sets the editor ``context`` @@ -82,171 +83,111 @@ Sets the editor ``context`` the geometry of a referencing feature from this widget %End - QgsAttributeEditorContext editorContext( ) const; + void setVisibleButtons( const Buttons &buttons ); %Docstring -Returns the attribute editor context. - -.. versionadded:: 3.14 +Defines the buttons which are shown %End - QgsIFeatureSelectionManager *featureSelectionManager(); + Buttons visibleButtons() const; %Docstring -The feature selection manager is responsible for the selected features -which are currently being edited. +Returns the buttons which are shown %End - bool showLabel() const; + void duplicateFeature() /Deprecated/; %Docstring -Defines if a title label should be shown for this widget. +Duplicates a feature -.. versionadded:: 2.18 +.. deprecated:: QGIS 3.18 + use duplicateSelectedFeatures() instead %End - void setShowLabel( bool showLabel ); + void duplicateSelectedFeatures(); %Docstring -Defines if a title label should be shown for this widget. +Duplicates the selected features -.. versionadded:: 2.18 +.. versionadded:: 3.18 %End - bool showLinkButton() const /Deprecated/; + void unlinkSelectedFeatures(); %Docstring -Determines if the "link feature" button should be shown - -.. versionadded:: 2.18 - -.. deprecated:: QGIS 3.16 - use visibleButtons() instead +Unlinks the selected features from the relation %End - void setShowLinkButton( bool showLinkButton ) /Deprecated/; + void deleteSelectedFeatures(); %Docstring -Determines if the "link feature" button should be shown - -.. versionadded:: 2.18 - -.. deprecated:: QGIS 3.16 - use setVisibleButtons() instead +Deletes the currently selected features %End - bool showUnlinkButton() const /Deprecated/; + void zoomToSelectedFeatures(); %Docstring -Determines if the "unlink feature" button should be shown - -.. versionadded:: 2.18 - -.. deprecated:: QGIS 3.16 - use visibleButtons() instead +Zooms to the selected features %End - void setShowUnlinkButton( bool showUnlinkButton ) /Deprecated/; -%Docstring -Determines if the "unlink feature" button should be shown - -.. versionadded:: 2.18 - -.. deprecated:: QGIS 3.16 - use setVisibleButtons() instead -%End + virtual QVariantMap config() const; - void setShowSaveChildEditsButton( bool showChildEdits ) /Deprecated/; %Docstring -Determines if the "Save child layer edits" button should be shown - -.. versionadded:: 3.14 - -.. deprecated:: QGIS 3.16 - use setVisibleButtons() instead +Returns the current configuration %End - bool showSaveChildEditsButton() const /Deprecated/; -%Docstring -Determines if the "Save child layer edits" button should be shown - -.. versionadded:: 3.14 - -.. deprecated:: QGIS 3.16 - use visibleButtons() instead -%End + virtual void setConfig( const QVariantMap &config ); - void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ); %Docstring -Defines the buttons which are shown - -.. versionadded:: 3.16 +Defines the current configuration %End - QgsAttributeEditorRelation::Buttons visibleButtons() const; -%Docstring -Returns the buttons which are shown + virtual void setTitle( const QString &title ); -.. versionadded:: 3.16 -%End - - bool forceSuppressFormPopup() const; %Docstring -Determines the force suppress form popup status that is configured for this widget - -.. versionadded:: 3.16 +Sets the title of the root groupbox %End - void setForceSuppressFormPopup( bool forceSuppressFormPopup ); -%Docstring -Sets force suppress form popup status with ``forceSuppressFormPopup`` -configured for this widget + public slots: + virtual void parentFormValueChanged( const QString &attribute, const QVariant &newValue ); -.. versionadded:: 3.16 -%End - QVariant nmRelationId() const; -%Docstring -Determines the relation id of the second relation involved in an N:M relation. +}; -.. versionadded:: 3.16 -%End - void setNmRelationId( const QVariant &nmRelationId = QVariant() ); +class QgsRelationEditorConfigWidget : QgsAbstractRelationEditorConfigWidget +{ %Docstring -Sets ``nmRelationId`` for the relation id of the second relation involved in an N:M relation. -If it's empty, then it's considered as a 1:M relationship. +Creates a new configuration widget for the relation editor widget -.. versionadded:: 3.16 +.. versionadded:: 3.18 %End - QString label() const; -%Docstring -Determines the label of this element - -.. versionadded:: 3.16 +%TypeHeaderCode +#include "qgsrelationeditorwidget.h" %End + public: - void setLabel( const QString &label = QString() ); + explicit QgsRelationEditorConfigWidget( const QgsRelation &relation, QWidget *parent /TransferThis/ ); %Docstring -Sets ``label`` for this element -If it's empty it takes the relation id as label +Create a new configuration widget -.. versionadded:: 3.16 +:param relation: The relation for which the configuration dialog will be created +:param parent: A parent widget %End - QgsFeature feature() const; + QVariantMap config(); %Docstring -Returns the widget's current feature +Create a configuration from the current GUI state -.. versionadded:: 3.14 +:return: A widget configuration %End - public slots: - - void parentFormValueChanged( const QString &attribute, const QVariant &newValue ); + void setConfig( const QVariantMap &config ); %Docstring -Called when an ``attribute`` value in the parent widget has changed to ``newValue`` +Update the configuration widget to represent the given configuration. -.. versionadded:: 3.14 +:param config: The configuration which should be represented by this widget %End }; + + /************************************************************************ * This file has been generated automatically from * * * diff --git a/python/gui/auto_generated/qgsrelationwidgetregistry.sip.in b/python/gui/auto_generated/qgsrelationwidgetregistry.sip.in new file mode 100644 index 000000000000..88636f7548d5 --- /dev/null +++ b/python/gui/auto_generated/qgsrelationwidgetregistry.sip.in @@ -0,0 +1,81 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsrelationwidgetregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsRelationWidgetRegistry +{ +%Docstring +Keeps track of the registered relations widgets. New widgets can be registered, old ones deleted. +The default {:py:class:`QgsRelationEditorWidget`} is protected from removing. + +.. versionadded:: 3.18 +%End + +%TypeHeaderCode +#include "qgsrelationwidgetregistry.h" +%End + public: + + QgsRelationWidgetRegistry(); +%Docstring +Constructor +%End + + ~QgsRelationWidgetRegistry(); + + void addRelationWidget( QgsAbstractRelationEditorWidgetFactory *widgetFactory /Transfer/ ); +%Docstring +Adds a new registered relation ``widgetFactory`` +%End + + void removeRelationWidget( const QString &widgetType ); +%Docstring +Removes a registered relation widget with given ``widgetType`` +%End + + QStringList relationWidgetNames(); +%Docstring +Returns a list of names of registered relation widgets +%End + + QMap factories() const; +%Docstring +Gets access to all registered factories +%End + + QgsAbstractRelationEditorWidget *create( const QString &widgetType, const QVariantMap &config, QWidget *parent = 0 ) const /TransferBack/; +%Docstring +Create a relation widget of a given type for a given field. + +:param widgetType: The widget type to create a relation editor for +:param config: The configuration of the widget +:param parent: +%End + + QgsAbstractRelationEditorConfigWidget *createConfigWidget( const QString &widgetType, const QgsRelation &relation, QWidget *parent = 0 ) const /TransferBack/; +%Docstring +Creates a configuration widget + +:param widgetType: The widget type to create a configuration widget for +:param relation: The relation for which this widget will be created +:param parent: The parent widget for the created widget +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsrelationwidgetregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index eeba1a3a3830..d4b649a6d09f 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -181,6 +181,8 @@ %Include auto_generated/qgsrasterpyramidsoptionswidget.sip %Include auto_generated/qgsratiolockbutton.sip %Include auto_generated/qgsrelationeditorwidget.sip +%Include auto_generated/qgsabstractrelationeditorwidget.sip +%Include auto_generated/qgsrelationwidgetregistry.sip %Include auto_generated/qgsrubberband.sip %Include auto_generated/qgsscalecombobox.sip %Include auto_generated/qgsscalerangewidget.sip diff --git a/src/core/qgsattributeeditorelement.cpp b/src/core/qgsattributeeditorelement.cpp index faf208365643..911c43fe9708 100644 --- a/src/core/qgsattributeeditorelement.cpp +++ b/src/core/qgsattributeeditorelement.cpp @@ -15,6 +15,7 @@ ***************************************************************************/ #include "qgsattributeeditorelement.h" #include "qgsrelationmanager.h" +#include "qgsxmlutils.h" void QgsAttributeEditorContainer::addChildElement( QgsAttributeEditorElement *widget ) @@ -118,6 +119,11 @@ QDomElement QgsAttributeEditorElement::toDomElement( QDomDocument &doc ) const QDomElement elem = doc.createElement( typeIdentifier() ); elem.setAttribute( QStringLiteral( "name" ), mName ); elem.setAttribute( QStringLiteral( "showLabel" ), mShowLabel ); + + QDomElement elemConfig = QgsXmlUtils::writeVariant( mConfig, doc ); + elemConfig.setTagName( QStringLiteral( "config" ) ); + elem.appendChild( elemConfig ); + saveConfiguration( elem ); return elem; } @@ -132,13 +138,24 @@ void QgsAttributeEditorElement::setShowLabel( bool showLabel ) mShowLabel = showLabel; } +QVariantMap QgsAttributeEditorElement::config() const +{ + return mConfig; +} + +void QgsAttributeEditorElement::setConfig( const QVariantMap &config ) +{ + mConfig = config; +} + void QgsAttributeEditorRelation::saveConfiguration( QDomElement &elem ) const { elem.setAttribute( QStringLiteral( "relation" ), mRelation.id() ); - elem.setAttribute( QStringLiteral( "buttons" ), qgsFlagValueToKeys( mButtons ) ); + elem.setAttribute( QStringLiteral( "buttons" ), mConfig.value( QStringLiteral( "buttons" ) ).toString() ); elem.setAttribute( QStringLiteral( "forceSuppressFormPopup" ), mForceSuppressFormPopup ); elem.setAttribute( QStringLiteral( "nmRelationId" ), mNmRelationId.toString() ); elem.setAttribute( QStringLiteral( "label" ), mLabel ); + elem.setAttribute( QStringLiteral( "relationWidgetTypeId" ), mRelationWidgetTypeId ); } QString QgsAttributeEditorRelation::typeIdentifier() const @@ -146,41 +163,6 @@ QString QgsAttributeEditorRelation::typeIdentifier() const return QStringLiteral( "attributeEditorRelation" ); } -bool QgsAttributeEditorRelation::showLinkButton() const -{ - return mButtons.testFlag( Button::Link ); -} - -void QgsAttributeEditorRelation::setShowLinkButton( bool showLinkButton ) -{ - mButtons.setFlag( Button::Link, showLinkButton ); -} - -bool QgsAttributeEditorRelation::showUnlinkButton() const -{ - return mButtons.testFlag( Button::Unlink ); -} - -void QgsAttributeEditorRelation::setShowUnlinkButton( bool showUnlinkButton ) -{ - mButtons.setFlag( Button::Unlink, showUnlinkButton ); -} - -void QgsAttributeEditorRelation::setShowSaveChildEditsButton( bool showSaveChildEdits ) -{ - mButtons.setFlag( Button::SaveChildEdits, showSaveChildEdits ); -} - -bool QgsAttributeEditorRelation::showSaveChildEditsButton() const -{ - return mButtons.testFlag( Button::SaveChildEdits ); -} - -void QgsAttributeEditorRelation::setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ) -{ - mButtons = buttons; -} - void QgsAttributeEditorRelation::setForceSuppressFormPopup( bool forceSuppressFormPopup ) { mForceSuppressFormPopup = forceSuppressFormPopup; @@ -211,6 +193,16 @@ QString QgsAttributeEditorRelation::label() const return mLabel; } +QString QgsAttributeEditorRelation::relationWidgetTypeId() const +{ + return mRelationWidgetTypeId; +} + +void QgsAttributeEditorRelation::setRelationWidgetTypeId( const QString &relationWidgetTypeId ) +{ + mRelationWidgetTypeId = relationWidgetTypeId; +} + QgsAttributeEditorElement *QgsAttributeEditorQmlElement::clone( QgsAttributeEditorElement *parent ) const { QgsAttributeEditorQmlElement *element = new QgsAttributeEditorQmlElement( name(), parent ); diff --git a/src/core/qgsattributeeditorelement.h b/src/core/qgsattributeeditorelement.h index 5c98a7e87609..5c0bc48300c9 100644 --- a/src/core/qgsattributeeditorelement.h +++ b/src/core/qgsattributeeditorelement.h @@ -135,12 +135,27 @@ class CORE_EXPORT QgsAttributeEditorElement SIP_ABSTRACT */ void setShowLabel( bool showLabel ); + /** + * Returns the editor configuration + * + * \since QGIS 3.18 + */ + QVariantMap config() const; + + /** + * Sets the editor configuration + * + * \since QGIS 3.18 + */ + void setConfig( const QVariantMap &config ); + protected: #ifndef SIP_RUN AttributeEditorType mType; QString mName; QgsAttributeEditorElement *mParent = nullptr; bool mShowLabel; + QVariantMap mConfig; #endif private: @@ -413,60 +428,6 @@ class CORE_EXPORT QgsAttributeEditorRelation : public QgsAttributeEditorElement QgsAttributeEditorElement *clone( QgsAttributeEditorElement *parent ) const override SIP_FACTORY; - /** - * Determines if the "link feature" button should be shown - * \since QGIS 2.18 - * \deprecated since QGIS 3.16 use visibleButtons() instead - */ - Q_DECL_DEPRECATED bool showLinkButton() const SIP_DEPRECATED; - - /** - * Determines if the "link feature" button should be shown - * \since QGIS 2.18 - * \deprecated since QGIS 3.16 use setVisibleButtons() instead - */ - Q_DECL_DEPRECATED void setShowLinkButton( bool showLinkButton ) SIP_DEPRECATED; - - /** - * Determines if the "unlink feature" button should be shown - * \since QGIS 2.18 - * \deprecated since QGIS 3.16 use visibleButtons() instead - */ - Q_DECL_DEPRECATED bool showUnlinkButton() const SIP_DEPRECATED; - - /** - * Determines if the "unlink feature" button should be shown - * \since QGIS 2.18 - * \deprecated since QGIS 3.16 use setVisibleButtons() instead - */ - Q_DECL_DEPRECATED void setShowUnlinkButton( bool showUnlinkButton ) SIP_DEPRECATED; - - /** - * Determines if the "Save child layer edits" button should be shown - * \since QGIS 3.14 - * \deprecated since QGIS 3.16 use setVisibleButtons() instead - */ - Q_DECL_DEPRECATED void setShowSaveChildEditsButton( bool showChildEdits ) SIP_DEPRECATED; - - /** - * Determines if the "Save child layer edits" button should be shown - * \since QGIS 3.14 - * \deprecated since QGIS 3.16 use visibleButtons() instead - */ - Q_DECL_DEPRECATED bool showSaveChildEditsButton() const SIP_DEPRECATED; - - /** - * Defines the buttons which are shown - * \since QGIS 3.16 - */ - void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ); - - /** - * Returns the buttons which are shown - * \since QGIS 3.16 - */ - QgsAttributeEditorRelation::Buttons visibleButtons() const {return mButtons;} - /** * Determines the force suppress form popup status. * \since QGIS 3.16 @@ -507,6 +468,18 @@ class CORE_EXPORT QgsAttributeEditorRelation : public QgsAttributeEditorElement */ void setLabel( const QString &label = QString() ); + /** + * Returns the current relation widget type id + * \since QGIS 3.18 + */ + QString relationWidgetTypeId() const; + + /** + * Sets the relation widget type + * \since QGIS 3.18 + */ + void setRelationWidgetTypeId( const QString &relationWidgetTypeId ); + private: void saveConfiguration( QDomElement &elem ) const override; QString typeIdentifier() const override; @@ -516,6 +489,7 @@ class CORE_EXPORT QgsAttributeEditorRelation : public QgsAttributeEditorElement bool mForceSuppressFormPopup = false; QVariant mNmRelationId; QString mLabel; + QString mRelationWidgetTypeId; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QgsAttributeEditorRelation::Buttons ) diff --git a/src/core/qgseditformconfig.cpp b/src/core/qgseditformconfig.cpp index e2b20c9302fa..85aab8c571d5 100644 --- a/src/core/qgseditformconfig.cpp +++ b/src/core/qgseditformconfig.cpp @@ -24,6 +24,7 @@ #include "qgsapplication.h" #include "qgsmessagelog.h" + QgsAttributeEditorContainer::~QgsAttributeEditorContainer() { qDeleteAll( mChildren ); @@ -660,20 +661,33 @@ QgsAttributeEditorElement *QgsEditFormConfig::attributeEditorElementFromDomEleme // At this time, the relations are not loaded // So we only grab the id and delegate the rest to onRelationsLoaded() QgsAttributeEditorRelation *relElement = new QgsAttributeEditorRelation( elem.attribute( QStringLiteral( "relation" ), QStringLiteral( "[None]" ) ), parent ); - if ( elem.hasAttribute( "buttons" ) ) - { - QString buttonString = elem.attribute( QStringLiteral( "buttons" ), qgsFlagValueToKeys( QgsAttributeEditorRelation::Button::AllButtons ) ); - relElement->setVisibleButtons( qgsFlagKeysToValue( buttonString, QgsAttributeEditorRelation::Button::AllButtons ) ); - } - else + QVariantMap config = QgsXmlUtils::readVariant( elem.firstChildElement( "config" ) ).toMap(); + + // load defaults + if ( config.isEmpty() ) + config = relElement->config(); + + // pre QGIS 3.18 compatibility + if ( ! config.contains( QStringLiteral( "buttons" ) ) ) { - // pre QGIS 3.16 compatibility - QgsAttributeEditorRelation::Buttons buttons = QgsAttributeEditorRelation::Button::AllButtons; - buttons.setFlag( QgsAttributeEditorRelation::Button::Link, elem.attribute( QStringLiteral( "showLinkButton" ), QStringLiteral( "1" ) ).toInt() ); - buttons.setFlag( QgsAttributeEditorRelation::Button::Unlink, elem.attribute( QStringLiteral( "showUnlinkButton" ), QStringLiteral( "1" ) ).toInt() ); - buttons.setFlag( QgsAttributeEditorRelation::Button::SaveChildEdits, elem.attribute( QStringLiteral( "showSaveChildEditsButton" ), QStringLiteral( "1" ) ).toInt() ); - relElement->setVisibleButtons( buttons ); + if ( elem.hasAttribute( "buttons" ) ) + { + QString buttonString = elem.attribute( QStringLiteral( "buttons" ), qgsFlagValueToKeys( QgsAttributeEditorRelation::Button::AllButtons ) ); + config.insert( "buttons", qgsFlagValueToKeys( qgsFlagKeysToValue( buttonString, QgsAttributeEditorRelation::Button::AllButtons ) ) ); + } + else + { + // pre QGIS 3.16 compatibility + QgsAttributeEditorRelation::Buttons buttons = QgsAttributeEditorRelation::Button::AllButtons; + buttons.setFlag( QgsAttributeEditorRelation::Button::Link, elem.attribute( QStringLiteral( "showLinkButton" ), QStringLiteral( "1" ) ).toInt() ); + buttons.setFlag( QgsAttributeEditorRelation::Button::Unlink, elem.attribute( QStringLiteral( "showUnlinkButton" ), QStringLiteral( "1" ) ).toInt() ); + buttons.setFlag( QgsAttributeEditorRelation::Button::SaveChildEdits, elem.attribute( QStringLiteral( "showSaveChildEditsButton" ), QStringLiteral( "1" ) ).toInt() ); + config.insert( "buttons", qgsFlagValueToKeys( buttons ) ); + } } + + relElement->setConfig( config ); + if ( elem.hasAttribute( QStringLiteral( "forceSuppressFormPopup" ) ) ) { relElement->setForceSuppressFormPopup( elem.attribute( QStringLiteral( "forceSuppressFormPopup" ) ).toInt() ); @@ -698,6 +712,11 @@ QgsAttributeEditorElement *QgsEditFormConfig::attributeEditorElementFromDomEleme QString label = elem.attribute( QStringLiteral( "label" ) ); relElement->setLabel( label ); } + if ( elem.hasAttribute( "relationWidgetTypeId" ) ) + { + QString relationWidgetTypeId = elem.attribute( QStringLiteral( "relationWidgetTypeId" ) ); + relElement->setRelationWidgetTypeId( relationWidgetTypeId ); + } newElement = relElement; } else if ( elem.tagName() == QLatin1String( "attributeEditorQmlElement" ) ) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index d48da665354a..f5aa7f09fa11 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -567,6 +567,8 @@ set(QGIS_GUI_SRCS qgsrasterlayersaveasdialog.cpp qgsrasterpyramidsoptionswidget.cpp qgsrelationeditorwidget.cpp + qgsabstractrelationeditorwidget.cpp + qgsrelationwidgetregistry.cpp qgsrubberband.cpp qgsscalecombobox.cpp qgsscalerangewidget.cpp @@ -813,6 +815,8 @@ set(QGIS_GUI_HDRS qgsrasterpyramidsoptionswidget.h qgsratiolockbutton.h qgsrelationeditorwidget.h + qgsabstractrelationeditorwidget.h + qgsrelationwidgetregistry.h qgsrubberband.h qgsscalecombobox.h qgsscalerangewidget.h diff --git a/src/gui/attributeformconfig/qgsattributewidgetedit.cpp b/src/gui/attributeformconfig/qgsattributewidgetedit.cpp index 5c603ee1efe5..4ae2e7a5e642 100644 --- a/src/gui/attributeformconfig/qgsattributewidgetedit.cpp +++ b/src/gui/attributeformconfig/qgsattributewidgetedit.cpp @@ -15,6 +15,7 @@ #include "qgsattributewidgetedit.h" #include "qgsattributesformproperties.h" +#include "qgsrelationwidgetregistry.h" QgsAttributeWidgetEdit::QgsAttributeWidgetEdit( QTreeWidgetItem *item, QWidget *parent ) @@ -29,7 +30,6 @@ QgsAttributeWidgetEdit::QgsAttributeWidgetEdit( QTreeWidgetItem *item, QWidget * // common configs mShowLabelCheckBox->setChecked( itemData.showLabel() ); - switch ( itemData.type() ) { case QgsAttributesFormProperties::DnDTreeItemData::Relation: @@ -92,18 +92,18 @@ QgsAttributeWidgetRelationEditWidget::QgsAttributeWidgetRelationEditWidget( QWid : QWidget( parent ) { setupUi( this ); + + QMapIterator it( QgsGui::relationWidgetRegistry()->factories() ); + + while ( it.hasNext() ) + { + it.next(); + mWidgetTypeComboBox->addItem( it.value()->name(), it.key() ); + } } void QgsAttributeWidgetRelationEditWidget::setRelationEditorConfiguration( const QgsAttributesFormProperties::RelationEditorConfiguration &config, const QString &relationId ) { - mRelationShowLinkCheckBox->setChecked( config.buttons.testFlag( QgsAttributeEditorRelation::Button::Link ) ); - mRelationShowUnlinkCheckBox->setChecked( config.buttons.testFlag( QgsAttributeEditorRelation::Button::Unlink ) ); - mRelationShowAddChildCheckBox->setChecked( config.buttons.testFlag( QgsAttributeEditorRelation::Button::AddChildFeature ) ); - mRelationShowDuplicateChildFeatureCheckBox->setChecked( config.buttons.testFlag( QgsAttributeEditorRelation::Button::DuplicateChildFeature ) ); - mRelationShowZoomToFeatureCheckBox->setChecked( config.buttons.testFlag( QgsAttributeEditorRelation::Button::ZoomToChildFeature ) ); - mRelationDeleteChildFeatureCheckBox->setChecked( config.buttons.testFlag( QgsAttributeEditorRelation::Button::DeleteChildFeature ) ); - mRelationShowSaveChildEditsCheckBox->setChecked( config.buttons.testFlag( QgsAttributeEditorRelation::Button::SaveChildEdits ) ); - //load the combo mRelationCardinalityCombo setCardinalityCombo( tr( "Many to one relation" ) ); @@ -115,6 +115,28 @@ void QgsAttributeWidgetRelationEditWidget::setRelationEditorConfiguration( const setCardinalityCombo( QStringLiteral( "%1 (%2)" ).arg( nmrel.referencedLayer()->name(), nmrel.fieldPairs().at( 0 ).referencedField() ), nmrel.id() ); } + int widgetTypeIdx = mWidgetTypeComboBox->findData( config.mRelationWidgetType ); + mWidgetTypeComboBox->setCurrentIndex( widgetTypeIdx >= 0 ? widgetTypeIdx : 0 ); + + const QString widgetType = mWidgetTypeComboBox->currentData().toString(); + mConfigWidget = QgsGui::relationWidgetRegistry()->createConfigWidget( widgetType, relation, this ); + mConfigWidget->setConfig( config.mRelationWidgetConfig ); + mWidgetTypePlaceholderLayout->addWidget( mConfigWidget ); + + disconnect( mWidgetTypeComboBoxConnection ); + + mWidgetTypeComboBoxConnection = connect( mWidgetTypeComboBox, &QComboBox::currentTextChanged, this, [ = ]() + { + const QString widgetId = mWidgetTypeComboBox->currentData().toString(); + + mWidgetTypePlaceholderLayout->removeWidget( mConfigWidget ); + mConfigWidget->deleteLater(); + mConfigWidget = QgsGui::relationWidgetRegistry()->createConfigWidget( widgetId, relation, this ); + mConfigWidget->setConfig( config.mRelationWidgetConfig ); + mWidgetTypePlaceholderLayout->addWidget( mConfigWidget ); + update(); + } ); + mRelationCardinalityCombo->setToolTip( tr( "For a many to many (N:M) relation, the direct link has to be selected. The in-between table will be hidden." ) ); setNmRelationId( config.nmRelationId ); @@ -126,15 +148,8 @@ void QgsAttributeWidgetRelationEditWidget::setRelationEditorConfiguration( const QgsAttributesFormProperties::RelationEditorConfiguration QgsAttributeWidgetRelationEditWidget::relationEditorConfiguration() const { QgsAttributesFormProperties::RelationEditorConfiguration relEdCfg; - QgsAttributeEditorRelation::Buttons buttons; - buttons.setFlag( QgsAttributeEditorRelation::Button::Link, mRelationShowLinkCheckBox->isChecked() ); - buttons.setFlag( QgsAttributeEditorRelation::Button::Unlink, mRelationShowUnlinkCheckBox->isChecked() ); - buttons.setFlag( QgsAttributeEditorRelation::Button::AddChildFeature, mRelationShowAddChildCheckBox->isChecked() ); - buttons.setFlag( QgsAttributeEditorRelation::Button::DuplicateChildFeature, mRelationShowDuplicateChildFeatureCheckBox->isChecked() ); - buttons.setFlag( QgsAttributeEditorRelation::Button::ZoomToChildFeature, mRelationShowZoomToFeatureCheckBox->isChecked() ); - buttons.setFlag( QgsAttributeEditorRelation::Button::DeleteChildFeature, mRelationDeleteChildFeatureCheckBox->isChecked() ); - buttons.setFlag( QgsAttributeEditorRelation::Button::SaveChildEdits, mRelationShowSaveChildEditsCheckBox->isChecked() ); - relEdCfg.buttons = buttons; + relEdCfg.mRelationWidgetType = mWidgetTypeComboBox->currentData().toString(); + relEdCfg.mRelationWidgetConfig = mConfigWidget->config(); relEdCfg.nmRelationId = mRelationCardinalityCombo->currentData(); relEdCfg.forceSuppressFormPopup = mRelationForceSuppressFormPopupCheckBox->isChecked(); relEdCfg.label = mRelationLabelEdit->text(); diff --git a/src/gui/attributeformconfig/qgsattributewidgetedit.h b/src/gui/attributeformconfig/qgsattributewidgetedit.h index d5ef591b135c..b93dcda3175c 100644 --- a/src/gui/attributeformconfig/qgsattributewidgetedit.h +++ b/src/gui/attributeformconfig/qgsattributewidgetedit.h @@ -29,6 +29,7 @@ #include "qgis_gui.h" class QTreeWidgetItem; +class QgsAbstractRelationEditorConfigWidget; /** * Widget to edit the configuration (tab or group box, any field or relation, QML, …) of a form item @@ -72,6 +73,9 @@ class GUI_EXPORT QgsAttributeWidgetRelationEditWidget : public QWidget, private private: void setCardinalityCombo( const QString &cardinalityComboItem, const QVariant &auserData = QVariant() ); void setNmRelationId( const QVariant &auserData = QVariant() ); + + QMetaObject::Connection mWidgetTypeComboBoxConnection; + QgsAbstractRelationEditorConfigWidget *mConfigWidget = nullptr; }; #endif // QGSATTRIBUTEWIDGETEDIT_H diff --git a/src/gui/editorwidgets/qgsrelationwidgetwrapper.cpp b/src/gui/editorwidgets/qgsrelationwidgetwrapper.cpp index 4b75988457f7..e2a97ae0cbc7 100644 --- a/src/gui/editorwidgets/qgsrelationwidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsrelationwidgetwrapper.cpp @@ -19,12 +19,20 @@ #include "qgsattributeeditorcontext.h" #include "qgsproject.h" #include "qgsrelationmanager.h" +#include "qgsabstractrelationeditorwidget.h" +#include "qgsrelationwidgetregistry.h" +#include "qgsgui.h" #include QgsRelationWidgetWrapper::QgsRelationWidgetWrapper( QgsVectorLayer *vl, const QgsRelation &relation, QWidget *editor, QWidget *parent ) + : QgsRelationWidgetWrapper( QStringLiteral( "relation_editor" ), vl, relation, editor, parent ) +{ +} + +QgsRelationWidgetWrapper::QgsRelationWidgetWrapper( const QString &relationEditorName, QgsVectorLayer *vl, const QgsRelation &relation, QWidget *editor, QWidget *parent ) : QgsWidgetWrapper( vl, editor, parent ) , mRelation( relation ) - + , mRelationEditorId( relationEditorName ) { } @@ -34,7 +42,15 @@ QWidget *QgsRelationWidgetWrapper::createWidget( QWidget *parent ) if ( form ) connect( form, &QgsAttributeForm::widgetValueChanged, this, &QgsRelationWidgetWrapper::widgetValueChanged ); - return new QgsRelationEditorWidget( parent ); + QWidget *widget = QgsGui::instance()->relationWidgetRegistry()->create( mRelationEditorId, widgetConfig(), parent ); + + if ( !widget ) + { + QgsLogger::warning( QStringLiteral( "Failed to create relation widget \"%1\", fallback to \"basic\" relation widget" ).arg( mRelationEditorId ) ); + widget = QgsGui::instance()->relationWidgetRegistry()->create( QStringLiteral( "relation_editor" ), widgetConfig(), parent ); + } + + return widget; } void QgsRelationWidgetWrapper::setFeature( const QgsFeature &feature ) @@ -101,25 +117,17 @@ void QgsRelationWidgetWrapper::widgetValueChanged( const QString &attribute, con bool QgsRelationWidgetWrapper::showUnlinkButton() const { - Q_NOWARN_DEPRECATED_PUSH - return mWidget->showUnlinkButton(); - Q_NOWARN_DEPRECATED_POP + return visibleButtons().testFlag( QgsAttributeEditorRelation::Button::Unlink ); } void QgsRelationWidgetWrapper::setShowUnlinkButton( bool showUnlinkButton ) { - Q_NOWARN_DEPRECATED_PUSH - if ( mWidget ) - mWidget->setShowUnlinkButton( showUnlinkButton ); - Q_NOWARN_DEPRECATED_POP + setVisibleButtons( visibleButtons().setFlag( QgsAttributeEditorRelation::Unlink, showUnlinkButton ) ); } void QgsRelationWidgetWrapper::setShowSaveChildEditsButton( bool showSaveChildEditsButton ) { - Q_NOWARN_DEPRECATED_PUSH - if ( mWidget ) - mWidget->setShowSaveChildEditsButton( showSaveChildEditsButton ); - Q_NOWARN_DEPRECATED_POP + setVisibleButtons( visibleButtons().setFlag( QgsAttributeEditorRelation::SaveChildEdits, showSaveChildEditsButton ) ); } bool QgsRelationWidgetWrapper::showLabel() const @@ -139,17 +147,12 @@ void QgsRelationWidgetWrapper::setShowLabel( bool showLabel ) void QgsRelationWidgetWrapper::initWidget( QWidget *editor ) { - QgsRelationEditorWidget *w = qobject_cast( editor ); + QgsAbstractRelationEditorWidget *w = qobject_cast( editor ); // if the editor cannot be cast to relation editor, insert a new one if ( !w ) { - w = new QgsRelationEditorWidget( editor ); - w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - if ( ! editor->layout() ) - { - editor->setLayout( new QGridLayout() ); - } + w = QgsGui::instance()->relationWidgetRegistry()->create( mRelationEditorId, widgetConfig(), editor ); editor->layout()->addWidget( w ); } @@ -187,9 +190,8 @@ void QgsRelationWidgetWrapper::initWidget( QWidget *editor ) } while ( ctx ); - w->setRelations( mRelation, mNmRelation ); - w->setEditorContext( myContext ); + w->setRelations( mRelation, mNmRelation ); mWidget = w; } @@ -206,10 +208,7 @@ bool QgsRelationWidgetWrapper::showLinkButton() const void QgsRelationWidgetWrapper::setShowLinkButton( bool showLinkButton ) { - Q_NOWARN_DEPRECATED_PUSH - if ( mWidget ) - mWidget->setShowLinkButton( showLinkButton ); - Q_NOWARN_DEPRECATED_POP + setVisibleButtons( visibleButtons().setFlag( QgsAttributeEditorRelation::Link, showLinkButton ) ); } bool QgsRelationWidgetWrapper::showSaveChildEditsButton() const @@ -219,13 +218,17 @@ bool QgsRelationWidgetWrapper::showSaveChildEditsButton() const void QgsRelationWidgetWrapper::setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ) { - if ( mWidget ) - mWidget->setVisibleButtons( buttons ); + if ( ! mWidget ) + return; + QVariantMap config = mWidget->config(); + config.insert( "buttons", qgsFlagValueToKeys( buttons ) ); + + mWidget->setConfig( config ); } QgsAttributeEditorRelation::Buttons QgsRelationWidgetWrapper::visibleButtons() const { - return mWidget->visibleButtons(); + return qgsFlagKeysToValue( mWidget->config().value( QStringLiteral( "buttons" ) ).toString(), QgsAttributeEditorRelation::AllButtons ); } void QgsRelationWidgetWrapper::setForceSuppressFormPopup( bool forceSuppressFormPopup ) @@ -245,6 +248,7 @@ bool QgsRelationWidgetWrapper::forceSuppressFormPopup() const { if ( mWidget ) return mWidget->forceSuppressFormPopup(); + return false; } @@ -294,3 +298,14 @@ QString QgsRelationWidgetWrapper::label() const return mWidget->label(); return QString(); } + +void QgsRelationWidgetWrapper::setWidgetConfig( const QVariantMap &config ) +{ + if ( mWidget ) + mWidget->setConfig( config ); +} + +QVariantMap QgsRelationWidgetWrapper::widgetConfig() const +{ + return mWidget ? mWidget->config() : QVariantMap(); +} diff --git a/src/gui/editorwidgets/qgsrelationwidgetwrapper.h b/src/gui/editorwidgets/qgsrelationwidgetwrapper.h index 6c3c48fc603c..21f0e4609d71 100644 --- a/src/gui/editorwidgets/qgsrelationwidgetwrapper.h +++ b/src/gui/editorwidgets/qgsrelationwidgetwrapper.h @@ -20,7 +20,7 @@ #include "qgis_sip.h" #include "qgis_gui.h" -class QgsRelationEditorWidget; +class QgsAbstractRelationEditorWidget; /** * \ingroup gui @@ -34,7 +34,21 @@ class GUI_EXPORT QgsRelationWidgetWrapper : public QgsWidgetWrapper public: //! Constructor for QgsRelationWidgetWrapper - explicit QgsRelationWidgetWrapper( QgsVectorLayer *vl, const QgsRelation &relation, QWidget *editor = nullptr, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + QgsRelationWidgetWrapper( + QgsVectorLayer *vl, + const QgsRelation &relation, + QWidget *editor SIP_CONSTRAINED = nullptr, + QWidget *parent SIP_TRANSFERTHIS SIP_CONSTRAINED = nullptr + ); + + //! Constructor for QgsRelationWidgetWrapper + QgsRelationWidgetWrapper( + const QString &relationEditorName, + QgsVectorLayer *vl, + const QgsRelation &relation, + QWidget *editor = nullptr, + QWidget *parent SIP_TRANSFERTHIS = nullptr + ); /** * Defines if a title label should be shown for this widget. @@ -97,14 +111,31 @@ class GUI_EXPORT QgsRelationWidgetWrapper : public QgsWidgetWrapper /** * Defines the buttons which are shown * \since QGIS 3.16 + * \deprecated since QGIS 3.18 use setWidgetConfig() instead */ - void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ); + Q_DECL_DEPRECATED void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ) SIP_DEPRECATED; /** * Returns the buttons which are shown * \since QGIS 3.16 + * \deprecated since QGIS 3.18 use widgetConfig() instead + */ + Q_DECL_DEPRECATED QgsAttributeEditorRelation::Buttons visibleButtons() const SIP_DEPRECATED; + + + /** + * Will set the config of this widget wrapper to the specified config. + * + * \param config The config for this wrapper + * \since QGIS 3.18 + */ + void setWidgetConfig( const QVariantMap &config ); + + /** + * Returns the whole widget config + * \since QGIS 3.18 */ - QgsAttributeEditorRelation::Buttons visibleButtons() const; + QVariantMap widgetConfig() const; /** * Determines the force suppress form popup status that is configured for this widget @@ -187,7 +218,8 @@ class GUI_EXPORT QgsRelationWidgetWrapper : public QgsWidgetWrapper void aboutToSave() override; QgsRelation mRelation; QgsRelation mNmRelation; - QgsRelationEditorWidget *mWidget = nullptr; + QString mRelationEditorId; + QgsAbstractRelationEditorWidget *mWidget = nullptr; }; #endif // QGSRELATIONWIDGETWRAPPER_H diff --git a/src/gui/qgsabstractrelationeditorwidget.cpp b/src/gui/qgsabstractrelationeditorwidget.cpp new file mode 100644 index 000000000000..3c4048406a4b --- /dev/null +++ b/src/gui/qgsabstractrelationeditorwidget.cpp @@ -0,0 +1,598 @@ +/*************************************************************************** + qgsabstractrelationeditorwidget.cpp + ---------------------- + begin : October 2020 + copyright : (C) 2020 by Ivan Ivanov + email : ivan@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgsabstractrelationeditorwidget.h" + +#include "qgsapplication.h" +#include "qgsdistancearea.h" +#include "qgsfeatureiterator.h" +#include "qgsvectordataprovider.h" +#include "qgsexpression.h" +#include "qgsfeature.h" +#include "qgsfeatureselectiondlg.h" +#include "qgsgenericfeatureselectionmanager.h" +#include "qgsrelation.h" +#include "qgsvectorlayertools.h" +#include "qgsproject.h" +#include "qgstransactiongroup.h" +#include "qgslogger.h" +#include "qgsvectorlayerutils.h" +#include "qgsmapcanvas.h" +#include "qgsvectorlayerselectionmanager.h" +#include "qgsmaptooldigitizefeature.h" +#include "qgsexpressioncontextutils.h" +#include "qgsmessagebar.h" +#include "qgsmessagebaritem.h" + +#include +#include +#include +#include + + +QgsAbstractRelationEditorWidget::QgsAbstractRelationEditorWidget( const QVariantMap &config, QWidget *parent ) + : QWidget( parent ) +{ + Q_UNUSED( config ); +} + +void QgsAbstractRelationEditorWidget::setRelationFeature( const QgsRelation &relation, const QgsFeature &feature ) +{ + beforeSetRelationFeature( relation, feature ); + + mRelation = relation; + mFeature = feature; + + setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() ); + + updateTitle(); + afterSetRelationFeature(); + updateUi(); +} + +void QgsAbstractRelationEditorWidget::setRelations( const QgsRelation &relation, const QgsRelation &nmrelation ) +{ + + beforeSetRelations( relation, nmrelation ); + + mRelation = relation; + mNmRelation = nmrelation; + + if ( !mRelation.isValid() ) + { + afterSetRelations(); + return; + } + + mLayerInSameTransactionGroup = false; + + const auto transactionGroups = QgsProject::instance()->transactionGroups(); + for ( auto it = transactionGroups.constBegin(); it != transactionGroups.constEnd(); ++it ) + { + if ( mNmRelation.isValid() ) + { + if ( it.value()->layers().contains( mRelation.referencedLayer() ) && + it.value()->layers().contains( mRelation.referencingLayer() ) && + it.value()->layers().contains( mNmRelation.referencedLayer() ) ) + mLayerInSameTransactionGroup = true; + } + else + { + if ( it.value()->layers().contains( mRelation.referencedLayer() ) && + it.value()->layers().contains( mRelation.referencingLayer() ) ) + mLayerInSameTransactionGroup = true; + } + } + + updateTitle(); + + setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() ); + + afterSetRelations(); + updateUi(); +} + +void QgsAbstractRelationEditorWidget::setEditorContext( const QgsAttributeEditorContext &context ) +{ + mEditorContext = context; +} + +QgsAttributeEditorContext QgsAbstractRelationEditorWidget::editorContext() const +{ + return mEditorContext; +} + +void QgsAbstractRelationEditorWidget::setFeature( const QgsFeature &feature, bool update ) +{ + mFeature = feature; + + mEditorContext.setFormFeature( feature ); + + if ( update ) + updateUi(); +} + +void QgsAbstractRelationEditorWidget::setNmRelationId( const QVariant &nmRelationId ) +{ + mNmRelationId = nmRelationId; +} + +QVariant QgsAbstractRelationEditorWidget::nmRelationId() const +{ + return mNmRelationId; +} + +QString QgsAbstractRelationEditorWidget::label() const +{ + return mLabel; +} + +void QgsAbstractRelationEditorWidget::setLabel( const QString &label ) +{ + mLabel = label; + + updateTitle(); +} + +bool QgsAbstractRelationEditorWidget::showLabel() const +{ + return mShowLabel; +} + +void QgsAbstractRelationEditorWidget::setShowLabel( bool showLabel ) +{ + mShowLabel = showLabel; + + updateTitle(); +} + +void QgsAbstractRelationEditorWidget::setForceSuppressFormPopup( bool forceSuppressFormPopup ) +{ + mForceSuppressFormPopup = forceSuppressFormPopup; +} + +bool QgsAbstractRelationEditorWidget::forceSuppressFormPopup() const +{ + return mForceSuppressFormPopup; +} + +void QgsAbstractRelationEditorWidget::updateTitle() +{ + if ( mShowLabel && !mLabel.isEmpty() ) + { + setTitle( mLabel ); + } + else if ( mShowLabel && mRelation.isValid() ) + { + setTitle( mRelation.name() ); + } + else + { + setTitle( QString() ); + } +} + +QgsFeature QgsAbstractRelationEditorWidget::feature() const +{ + return mFeature; +} + +void QgsAbstractRelationEditorWidget::toggleEditing( bool state ) +{ + if ( state ) + { + mEditorContext.vectorLayerTools()->startEditing( mRelation.referencingLayer() ); + if ( mNmRelation.isValid() ) + mEditorContext.vectorLayerTools()->startEditing( mNmRelation.referencedLayer() ); + } + else + { + mEditorContext.vectorLayerTools()->stopEditing( mRelation.referencingLayer() ); + if ( mNmRelation.isValid() ) + mEditorContext.vectorLayerTools()->stopEditing( mNmRelation.referencedLayer() ); + } +} + +void QgsAbstractRelationEditorWidget::saveEdits() +{ + mEditorContext.vectorLayerTools()->saveEdits( mRelation.referencingLayer() ); + if ( mNmRelation.isValid() ) + mEditorContext.vectorLayerTools()->saveEdits( mNmRelation.referencedLayer() ); +} + +void QgsAbstractRelationEditorWidget::addFeature( const QgsGeometry &geometry ) +{ + QgsAttributeMap keyAttrs; + + const QgsVectorLayerTools *vlTools = mEditorContext.vectorLayerTools(); + + if ( mNmRelation.isValid() ) + { + // n:m Relation: first let the user create a new feature on the other table + // and autocreate a new linking feature. + QgsFeature f; + if ( vlTools->addFeature( mNmRelation.referencedLayer(), QgsAttributeMap(), geometry, &f ) ) + { + // Fields of the linking table + const QgsFields fields = mRelation.referencingLayer()->fields(); + + // Expression context for the linking table + QgsExpressionContext context = mRelation.referencingLayer()->createExpressionContext(); + + QgsAttributeMap linkAttributes; + const auto constFieldPairs = mRelation.fieldPairs(); + for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) + { + int index = fields.indexOf( fieldPair.first ); + linkAttributes.insert( index, mFeature.attribute( fieldPair.second ) ); + } + + const auto constNmFieldPairs = mNmRelation.fieldPairs(); + for ( const QgsRelation::FieldPair &fieldPair : constNmFieldPairs ) + { + int index = fields.indexOf( fieldPair.first ); + linkAttributes.insert( index, f.attribute( fieldPair.second ) ); + } + QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context ); + + mRelation.referencingLayer()->addFeature( linkFeature ); + + updateUi(); + } + } + else + { + QgsFields fields = mRelation.referencingLayer()->fields(); + + const auto constFieldPairs = mRelation.fieldPairs(); + for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) + { + keyAttrs.insert( fields.indexFromName( fieldPair.referencingField() ), mFeature.attribute( fieldPair.referencedField() ) ); + vlTools->addFeature( mRelation.referencingLayer(), keyAttrs, geometry ); + } + } +} + +void QgsAbstractRelationEditorWidget::deleteFeature( const QgsFeatureId fid ) +{ + deleteFeatures( QgsFeatureIds() << fid ); +} + +void QgsAbstractRelationEditorWidget::deleteFeatures( const QgsFeatureIds &fids ) +{ + bool deleteFeatures = true; + + QgsVectorLayer *layer; + if ( mNmRelation.isValid() ) + { + layer = mNmRelation.referencedLayer(); + + // When deleting a linked feature within an N:M relation, + // check if the feature is linked to more than just one feature. + // In case it is linked more than just once, ask the user for confirmation + // as it is likely he was not aware of the implications and might delete + // there may be several linking entries deleted along. + + QgsFeatureRequest deletedFeaturesRequest; + deletedFeaturesRequest.setFilterFids( fids ); + deletedFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry ); + deletedFeaturesRequest.setSubsetOfAttributes( QgsAttributeList() << mNmRelation.referencedFields().first() ); + + QgsFeatureIterator deletedFeatures = layer->getFeatures( deletedFeaturesRequest ); + QStringList deletedFeaturesPks; + QgsFeature feature; + while ( deletedFeatures.nextFeature( feature ) ) + { + deletedFeaturesPks.append( QgsExpression::quotedValue( feature.attribute( mNmRelation.referencedFields().first() ) ) ); + } + + QgsFeatureRequest linkingFeaturesRequest; + linkingFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry ); + linkingFeaturesRequest.setNoAttributes(); + + QString linkingFeaturesRequestExpression; + if ( !deletedFeaturesPks.empty() ) + { + linkingFeaturesRequestExpression = QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( mNmRelation.fieldPairs().first().first ), deletedFeaturesPks.join( ',' ) ); + linkingFeaturesRequest.setFilterExpression( linkingFeaturesRequestExpression ); + + QgsFeatureIterator relatedLinkingFeatures = mNmRelation.referencingLayer()->getFeatures( linkingFeaturesRequest ); + + int relatedLinkingFeaturesCount = 0; + while ( relatedLinkingFeatures.nextFeature( feature ) ) + { + relatedLinkingFeaturesCount++; + } + + if ( deletedFeaturesPks.size() == 1 && relatedLinkingFeaturesCount > 1 ) + { + QMessageBox messageBox( QMessageBox::Question, tr( "Really delete entry?" ), tr( "The entry on %1 is still linked to %2 features on %3. Do you want to delete it?" ).arg( mNmRelation.referencedLayer()->name(), QString::number( relatedLinkingFeaturesCount ), mRelation.referencedLayer()->name() ), QMessageBox::NoButton, this ); + messageBox.addButton( QMessageBox::Cancel ); + QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole ); + + messageBox.exec(); + if ( messageBox.clickedButton() != deleteButton ) + deleteFeatures = false; + } + else if ( deletedFeaturesPks.size() > 1 && relatedLinkingFeaturesCount > deletedFeaturesPks.size() ) + { + QMessageBox messageBox( QMessageBox::Question, tr( "Really delete entries?" ), tr( "The %1 entries on %2 are still linked to %3 features on %4. Do you want to delete them?" ).arg( QString::number( deletedFeaturesPks.size() ), mNmRelation.referencedLayer()->name(), QString::number( relatedLinkingFeaturesCount ), mRelation.referencedLayer()->name() ), QMessageBox::NoButton, this ); + messageBox.addButton( QMessageBox::Cancel ); + QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole ); + + messageBox.exec(); + if ( messageBox.clickedButton() != deleteButton ) + deleteFeatures = false; + } + } + } + else + { + layer = mRelation.referencingLayer(); + } + + QgsVectorLayerUtils::QgsDuplicateFeatureContext infoContext; + if ( QgsVectorLayerUtils::impactsCascadeFeatures( layer, fids, QgsProject::instance(), infoContext ) ) + { + QString childrenInfo; + int childrenCount = 0; + const auto infoContextLayers = infoContext.layers(); + for ( QgsVectorLayer *chl : infoContextLayers ) + { + childrenCount += infoContext.duplicatedFeatures( chl ).size(); + childrenInfo += ( tr( "%1 feature(s) on layer \"%2\", " ).arg( infoContext.duplicatedFeatures( chl ).size() ).arg( chl->name() ) ); + } + + // for extra safety to make sure we know that the delete can have impact on children and joins + int res = QMessageBox::question( this, tr( "Delete at least %1 feature(s) on other layer(s)" ).arg( childrenCount ), + tr( "Delete %1 feature(s) on layer \"%2\", %3 as well\nand all of its other descendants.\nDelete these features?" ).arg( fids.count() ).arg( layer->name() ).arg( childrenInfo ), + QMessageBox::Yes | QMessageBox::No ); + if ( res != QMessageBox::Yes ) + deleteFeatures = false; + } + + if ( deleteFeatures ) + { + QgsVectorLayer::DeleteContext context( true, QgsProject::instance() ); + layer->deleteFeatures( fids, &context ); + const auto contextLayers = context.handledLayers(); + if ( contextLayers.size() > 1 ) + { + int deletedCount = 0; + QString feedbackMessage; + for ( QgsVectorLayer *contextLayer : contextLayers ) + { + feedbackMessage += tr( "%1 on layer %2. " ).arg( context.handledFeatures( contextLayer ).size() ).arg( contextLayer->name() ); + deletedCount += context.handledFeatures( contextLayer ).size(); + } + mEditorContext.mainMessageBar()->pushMessage( tr( "%1 features deleted: %2" ).arg( deletedCount ).arg( feedbackMessage ), Qgis::Success ); + } + + updateUi(); + } +} + +void QgsAbstractRelationEditorWidget::linkFeature() +{ + QgsVectorLayer *layer = nullptr; + + if ( mNmRelation.isValid() ) + layer = mNmRelation.referencedLayer(); + else + layer = mRelation.referencingLayer(); + + QgsFeatureSelectionDlg *selectionDlg = new QgsFeatureSelectionDlg( layer, mEditorContext, this ); + selectionDlg->setAttribute( Qt::WA_DeleteOnClose ); + + const QString displayString = QgsVectorLayerUtils::getFeatureDisplayString( mRelation.referencedLayer(), mFeature ); + selectionDlg->setWindowTitle( tr( "Link existing child features for parent %1 \"%2\"" ).arg( mRelation.referencedLayer()->name(), displayString ) ); + + connect( selectionDlg, &QDialog::accepted, this, &QgsAbstractRelationEditorWidget::onLinkFeatureDlgAccepted ); + selectionDlg->show(); +} + +void QgsAbstractRelationEditorWidget::onLinkFeatureDlgAccepted() +{ + QgsFeatureSelectionDlg *selectionDlg = qobject_cast( sender() ); + if ( mNmRelation.isValid() ) + { + QgsFeatureIterator it = mNmRelation.referencedLayer()->getFeatures( + QgsFeatureRequest() + .setFilterFids( selectionDlg->selectedFeatures() ) + .setSubsetOfAttributes( mNmRelation.referencedFields() ) ); + + QgsFeature relatedFeature; + + QgsFeatureList newFeatures; + + // Fields of the linking table + const QgsFields fields = mRelation.referencingLayer()->fields(); + + // Expression context for the linking table + QgsExpressionContext context = mRelation.referencingLayer()->createExpressionContext(); + + QgsAttributeMap linkAttributes; + const auto constFieldPairs = mRelation.fieldPairs(); + for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) + { + int index = fields.indexOf( fieldPair.first ); + linkAttributes.insert( index, mFeature.attribute( fieldPair.second ) ); + } + + while ( it.nextFeature( relatedFeature ) ) + { + const auto constFieldPairs = mNmRelation.fieldPairs(); + for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) + { + int index = fields.indexOf( fieldPair.first ); + linkAttributes.insert( index, relatedFeature.attribute( fieldPair.second ) ); + } + const QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context ); + + newFeatures << linkFeature; + } + + mRelation.referencingLayer()->addFeatures( newFeatures ); + QgsFeatureIds ids; + const auto constNewFeatures = newFeatures; + for ( const QgsFeature &f : constNewFeatures ) + ids << f.id(); + mRelation.referencingLayer()->selectByIds( ids ); + } + else + { + QMap keys; + const auto constFieldPairs = mRelation.fieldPairs(); + for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) + { + int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() ); + QVariant val = mFeature.attribute( fieldPair.referencedField() ); + keys.insert( idx, val ); + } + + const auto constSelectedFeatures = selectionDlg->selectedFeatures(); + for ( QgsFeatureId fid : constSelectedFeatures ) + { + QMapIterator it( keys ); + while ( it.hasNext() ) + { + it.next(); + mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), it.value() ); + } + } + } + + updateUi(); +} + +void QgsAbstractRelationEditorWidget::unlinkFeature( const QgsFeatureId fid ) +{ + unlinkFeatures( QgsFeatureIds() << fid ); +} + +void QgsAbstractRelationEditorWidget::unlinkFeatures( const QgsFeatureIds &fids ) +{ + if ( mNmRelation.isValid() ) + { + QgsFeatureIterator selectedIterator = mNmRelation.referencedLayer()->getFeatures( + QgsFeatureRequest() + .setFilterFids( fids ) + .setSubsetOfAttributes( mNmRelation.referencedFields() ) ); + + QgsFeature f; + + QStringList filters; + + while ( selectedIterator.nextFeature( f ) ) + { + filters << '(' + mNmRelation.getRelatedFeaturesRequest( f ).filterExpression()->expression() + ')'; + } + + QString filter = QStringLiteral( "(%1) AND (%2)" ).arg( + mRelation.getRelatedFeaturesRequest( mFeature ).filterExpression()->expression(), + filters.join( QLatin1String( " OR " ) ) ); + + QgsFeatureIterator linkedIterator = mRelation.referencingLayer()->getFeatures( QgsFeatureRequest() + .setNoAttributes() + .setFilterExpression( filter ) ); + + QgsFeatureIds fids; + + while ( linkedIterator.nextFeature( f ) ) + { + fids << f.id(); + QgsDebugMsgLevel( FID_TO_STRING( f.id() ), 4 ); + } + + mRelation.referencingLayer()->deleteFeatures( fids ); + + updateUi(); + } + else + { + QMap keyFields; + const auto constFieldPairs = mRelation.fieldPairs(); + for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) + { + int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() ); + if ( idx < 0 ) + { + QgsDebugMsg( QStringLiteral( "referencing field %1 not found" ).arg( fieldPair.referencingField() ) ); + return; + } + QgsField fld = mRelation.referencingLayer()->fields().at( idx ); + keyFields.insert( idx, fld ); + } + + const auto constFeatureids = fids; + for ( QgsFeatureId fid : constFeatureids ) + { + QMapIterator it( keyFields ); + while ( it.hasNext() ) + { + it.next(); + mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), QVariant( it.value().type() ) ); + } + } + } +} + +void QgsAbstractRelationEditorWidget::duplicateFeature( const QgsFeatureId &fid ) +{ + duplicateFeatures( QgsFeatureIds() << fid ); +} + +void QgsAbstractRelationEditorWidget::duplicateFeatures( const QgsFeatureIds &fids ) +{ + QgsVectorLayer *layer = mRelation.referencingLayer(); + + QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) ); + QgsFeature f; + while ( fit.nextFeature( f ) ) + { + QgsVectorLayerUtils::QgsDuplicateFeatureContext duplicatedFeatureContext; + QgsVectorLayerUtils::duplicateFeature( layer, f, QgsProject::instance(), duplicatedFeatureContext ); + } +} + + +/////////////////////////////////////////////////////////////////////////////// + + +QgsAbstractRelationEditorConfigWidget::QgsAbstractRelationEditorConfigWidget( const QgsRelation &relation, QWidget *parent ) + : QWidget( parent ) + , mRelation( relation ) +{ +} + +QgsVectorLayer *QgsAbstractRelationEditorConfigWidget::layer() +{ + return mLayer; +} + +QgsRelation QgsAbstractRelationEditorConfigWidget::relation() const +{ + return mRelation; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +QgsAbstractRelationEditorWidgetFactory::QgsAbstractRelationEditorWidgetFactory() +{ +} diff --git a/src/gui/qgsabstractrelationeditorwidget.h b/src/gui/qgsabstractrelationeditorwidget.h new file mode 100644 index 000000000000..27049459cbb8 --- /dev/null +++ b/src/gui/qgsabstractrelationeditorwidget.h @@ -0,0 +1,383 @@ +/*************************************************************************** + qgsabstractrelationeditorwidget.h + ---------------------- + begin : October 2020 + copyright : (C) 2020 by Ivan Ivanov + email : ivan@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 QGSABSTRACTRELATIONEDITORWIDGET_H +#define QGSABSTRACTRELATIONEDITORWIDGET_H + +#include +#include +#include +#include +#include "qobjectuniqueptr.h" + +#include "qobjectuniqueptr.h" +#include "qgsattributeeditorcontext.h" +#include "qgscollapsiblegroupbox.h" +#include "qgsdualview.h" +#include "qgsrelation.h" +#include "qgis_sip.h" +#include "qgis_gui.h" + +#ifdef SIP_RUN +// this is needed for the "convert to subclass" code below to compile +% ModuleHeaderCode +#include "qgsrelationeditorwidget.h" +% End +#endif + +class QgsFeature; +class QgsVectorLayer; +class QgsVectorLayerTools; +class QgsMapTool; +class QgsMapToolDigitizeFeature; +class QgsBaseRelationWidget; +class QgsPropertyOverrideButton; + +/** + * Base class to build new relation widgets. + * \ingroup gui + * \class QgsAbstractRelationEditorWidget + * \since QGIS 3.18 + */ +class GUI_EXPORT QgsAbstractRelationEditorWidget : public QWidget +{ + +#ifdef SIP_RUN + SIP_CONVERT_TO_SUBCLASS_CODE + if ( qobject_cast( sipCpp ) ) + sipType = sipType_QgsRelationEditorWidget; + else + sipType = 0; + SIP_END +#endif + + Q_OBJECT + + public: + + + /** + * Constructor + */ + QgsAbstractRelationEditorWidget( const QVariantMap &config, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + /** + * Sets the \a relation and the \a feature + */ + void setRelationFeature( const QgsRelation &relation, const QgsFeature &feature ); + + /** + * Set the relation(s) for this widget + * If only one relation is set, it will act as a simple 1:N relation widget + * If both relations are set, it will act as an N:M relation widget + * inserting and deleting entries on the intermediate table as required. + * + * \param relation Relation referencing the edited table + * \param nmrelation Optional reference from the referencing table to a 3rd N:M table + */ + void setRelations( const QgsRelation &relation, const QgsRelation &nmrelation ); + + /** + * Sets the \a feature being edited and updates the UI unless \a update is set to FALSE + */ + void setFeature( const QgsFeature &feature, bool update = true ); + + /** + * Sets the editor \a context + * \note if context cadDockWidget is null, it won't be possible to digitize + * the geometry of a referencing feature from this widget + */ + virtual void setEditorContext( const QgsAttributeEditorContext &context ); + + /** + * Returns the attribute editor context. + */ + QgsAttributeEditorContext editorContext( ) const; + + /** + * Defines if a title label should be shown for this widget. + */ + bool showLabel() const; + + /** + * Defines if a title label should be shown for this widget. + */ + void setShowLabel( bool showLabel ); + + /** + * Determines the relation id of the second relation involved in an N:M relation. + */ + QVariant nmRelationId() const; + + /** + * Sets \a nmRelationId for the relation id of the second relation involved in an N:M relation. + * If it's empty, then it's considered as a 1:M relationship. + */ + void setNmRelationId( const QVariant &nmRelationId = QVariant() ); + + /** + * Determines the label of this element + */ + QString label() const; + + /** + * Sets \a label for this element + * If it's empty it takes the relation id as label + */ + void setLabel( const QString &label = QString() ); + + /** + * Returns the widget's current feature + */ + QgsFeature feature() const; + + /** + * Determines the force suppress form popup status that is configured for this widget + */ + bool forceSuppressFormPopup() const; + + /** + * Sets force suppress form popup status with \a forceSuppressFormPopup + * configured for this widget + */ + void setForceSuppressFormPopup( bool forceSuppressFormPopup ); + + /** + * Returns the widget configuration + */ + virtual QVariantMap config() const = 0; + + /** + * Defines the widget configuration + */ + virtual void setConfig( const QVariantMap &config ) = 0; + + public slots: + + /** + * Called when an \a attribute value in the parent widget has changed to \a newValue + */ + virtual void parentFormValueChanged( const QString &attribute, const QVariant &newValue ) = 0; + + protected slots: + + /** + * Toggles editing state of the widget + */ + void toggleEditing( bool state ); + + /** + * Saves the current modifications in the relation + */ + void saveEdits(); + + /** + * Adds a new feature with given \a geometry + */ + void addFeature( const QgsGeometry &geometry = QgsGeometry() ); + + /** + * Delete a feature with given \a fid + */ + void deleteFeature( QgsFeatureId fid = QgsFeatureId() ); + + /** + * Links a new feature to the relation + */ + void linkFeature(); + + /** + * Called when the link feature dialog is confirmed by the user + */ + void onLinkFeatureDlgAccepted(); + + /** + * Unlinks a feature with given \a fid + */ + void unlinkFeature( QgsFeatureId fid = QgsFeatureId() ); + + /** + * Duplicates a feature + */ + void duplicateFeature( const QgsFeatureId &fid ); + + /** + * Duplicates features + */ + void duplicateFeatures( const QgsFeatureIds &fids ); + + protected: + + QgsAttributeEditorContext mEditorContext; + QgsRelation mRelation; + QgsRelation mNmRelation; + QgsFeature mFeature; + + bool mShowLabel = true; + bool mLayerInSameTransactionGroup = false; + + bool mForceSuppressFormPopup = false; + QVariant mNmRelationId; + QString mLabel; + + /** + * Updates the title contents to reflect the current state of the widget + */ + void updateTitle(); + + /** + * Deletes the features with \a fids + */ + void deleteFeatures( const QgsFeatureIds &fids ); + + /** + * Unlinks the features with \a fids + */ + void unlinkFeatures( const QgsFeatureIds &fids ); + + private: + virtual void updateUi() {}; + virtual void setTitle( const QString &title ) { Q_UNUSED( title ); }; + virtual void beforeSetRelationFeature( const QgsRelation &newRelation, const QgsFeature &newFeature ) { Q_UNUSED( newRelation ); Q_UNUSED( newFeature ); }; + virtual void afterSetRelationFeature() {}; + virtual void beforeSetRelations( const QgsRelation &newRelation, const QgsRelation &newNmRelation ) { Q_UNUSED( newRelation ); Q_UNUSED( newNmRelation ); }; + virtual void afterSetRelations() {}; +}; + + +/** + * \ingroup gui + * This class should be subclassed for every configurable relation widget type. + * + * It implements the GUI configuration widget and transforms this to/from a configuration. + * + * It will only be instantiated by {\see QgsAbstractRelationEditorWidgetFactory} + * \since QGIS 3.18 + */ +class GUI_EXPORT QgsAbstractRelationEditorConfigWidget : public QWidget +{ + +#ifdef SIP_RUN + SIP_CONVERT_TO_SUBCLASS_CODE + if ( qobject_cast( sipCpp ) ) + sipType = sipType_QgsRelationEditorConfigWidget; + else + sipType = 0; + SIP_END +#endif + + Q_OBJECT + public: + + /** + * Create a new configuration widget + * + * \param relation The relation for which the configuration dialog will be created + * \param parent A parent widget + */ + explicit QgsAbstractRelationEditorConfigWidget( const QgsRelation &relation, QWidget *parent SIP_TRANSFERTHIS ); + + /** + * \brief Create a configuration from the current GUI state + * + * \returns A widget configuration + */ + virtual QVariantMap config() = 0; + + /** + * \brief Update the configuration widget to represent the given configuration. + * + * \param config The configuration which should be represented by this widget + */ + virtual void setConfig( const QVariantMap &config ) = 0; + + /** + * Returns the layer for which this configuration widget applies + * + * \returns The layer + */ + QgsVectorLayer *layer(); + + /** + * Returns the relation for which this configuration widget applies + * + * \returns The relation + */ + QgsRelation relation() const; + + + private: + QgsVectorLayer *mLayer = nullptr; + QgsRelation mRelation; +}; + + +/////////////////////////////// + + +/** + * Factory class for creating relation widgets and their corresponding config widgets + * \ingroup gui + * \class QgsAbstractRelationEditorWidgetFactory + * \since QGIS 3.18 + */ +class GUI_EXPORT QgsAbstractRelationEditorWidgetFactory +{ + public: + + /** + * Creates a new relation widget factory with given \a name + */ + QgsAbstractRelationEditorWidgetFactory(); + + virtual ~QgsAbstractRelationEditorWidgetFactory() = default; + + /** + * Returns the machine readable identifier name of this widget type + */ + virtual QString type() const = 0; + + /** + * Returns the human readable identifier name of this widget type + */ + virtual QString name() const = 0; + + /** + * Override this in your implementation. + * Create a new relation widget. Call QgsEditorWidgetRegistry::create() + * instead of calling this method directly. + * + * \param config The widget configuration to build the widget with + * \param parent The parent for the wrapper class and any created widget. + * + * \returns A new widget wrapper + */ + virtual QgsAbstractRelationEditorWidget *create( const QVariantMap &config, QWidget *parent = nullptr ) const = 0 SIP_FACTORY; + + /** + * Override this in your implementation. + * Create a new configuration widget for this widget type. + * + * \param relation The relation for which the widget will be created + * \param parent The parent widget of the created config widget + * + * \returns A configuration widget + */ + virtual QgsAbstractRelationEditorConfigWidget *configWidget( const QgsRelation &relation, QWidget *parent ) const = 0 SIP_FACTORY; +}; + +#endif // QGSABSTRACTRELATIONEDITORWIDGET_H diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 76752e4ad612..02fbeb046679 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -1254,7 +1254,12 @@ QList QgsAttributeForm::constraintDependencies( QgsEdi QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QgsRelation &rel, const QgsAttributeEditorContext &context ) { - QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, rel, nullptr, this ); + return setupRelationWidgetWrapper( QString(), rel, context ); +} + +QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QString &relationWidgetTypeId, const QgsRelation &rel, const QgsAttributeEditorContext &context ) +{ + QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( relationWidgetTypeId, mLayer, rel, nullptr, this ); const QVariantMap config = mLayer->editFormConfig().widgetConfig( rel.id() ); rww->setConfig( config ); rww->setContext( context ); @@ -1639,7 +1644,7 @@ void QgsAttributeForm::init() const QList relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer ); for ( const QgsRelation &rel : relations ) { - QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( rel, mContext ); + QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( QStringLiteral( "relation_editor" ), rel, mContext ); QgsAttributeFormRelationEditorWidget *formWidget = new QgsAttributeFormRelationEditorWidget( rww, this ); formWidget->createSearchWidgetWrappers( mContext ); @@ -1943,7 +1948,7 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt { const QgsAttributeEditorRelation *relDef = static_cast( widgetDef ); - QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( relDef->relation(), context ); + QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( relDef->relationWidgetTypeId(), relDef->relation(), context ); QgsAttributeFormRelationEditorWidget *formWidget = new QgsAttributeFormRelationEditorWidget( rww, this ); formWidget->createSearchWidgetWrappers( mContext ); @@ -1951,7 +1956,7 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt // This needs to be after QgsAttributeFormRelationEditorWidget creation, because the widget // does not exists yet until QgsAttributeFormRelationEditorWidget is created and the setters // below directly alter the widget and check for it. - rww->setVisibleButtons( relDef->visibleButtons() ); + rww->setWidgetConfig( relDef->config() ); rww->setShowLabel( relDef->showLabel() ); rww->setNmRelationId( relDef->nmRelationId() ); rww->setForceSuppressFormPopup( relDef->forceSuppressFormPopup() ); diff --git a/src/gui/qgsattributeform.h b/src/gui/qgsattributeform.h index 65e69311e092..446aff63dd8a 100644 --- a/src/gui/qgsattributeform.h +++ b/src/gui/qgsattributeform.h @@ -414,7 +414,8 @@ class GUI_EXPORT QgsAttributeForm : public QWidget bool currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions ); QList constraintDependencies( QgsEditorWidgetWrapper *w ); - QgsRelationWidgetWrapper *setupRelationWidgetWrapper( const QgsRelation &rel, const QgsAttributeEditorContext &context ); + Q_DECL_DEPRECATED QgsRelationWidgetWrapper *setupRelationWidgetWrapper( const QgsRelation &rel, const QgsAttributeEditorContext &context ) SIP_DEPRECATED; + QgsRelationWidgetWrapper *setupRelationWidgetWrapper( const QString &relationWidgetTypeId, const QgsRelation &rel, const QgsAttributeEditorContext &context ); QgsVectorLayer *mLayer = nullptr; QgsFeature mFeature; diff --git a/src/gui/qgsgui.cpp b/src/gui/qgsgui.cpp index c48e1c565a7d..5db19edc2fad 100644 --- a/src/gui/qgsgui.cpp +++ b/src/gui/qgsgui.cpp @@ -60,6 +60,7 @@ #include "qgscodeeditorcolorschemeregistry.h" #include "qgssubsetstringeditorproviderregistry.h" #include "qgsprovidersourcewidgetproviderregistry.h" +#include "qgsrelationwidgetregistry.h" QgsGui *QgsGui::instance() { @@ -77,6 +78,11 @@ QgsEditorWidgetRegistry *QgsGui::editorWidgetRegistry() return instance()->mEditorWidgetRegistry; } +QgsRelationWidgetRegistry *QgsGui::relationWidgetRegistry() +{ + return instance()->mRelationEditorRegistry; +} + QgsSourceSelectProviderRegistry *QgsGui::sourceSelectProviderRegistry() { return instance()->mSourceSelectProviderRegistry; @@ -198,6 +204,7 @@ QgsGui::~QgsGui() delete mCodeEditorColorSchemeRegistry; delete mSubsetStringEditorProviderRegistry; delete mProviderSourceWidgetProviderRegistry; + delete mRelationEditorRegistry; } QColor QgsGui::sampleColor( QPoint point ) @@ -261,6 +268,7 @@ QgsGui::QgsGui() mProviderSourceWidgetProviderRegistry->initializeFromProviderGuiRegistry( mProviderGuiRegistry ); mEditorWidgetRegistry = new QgsEditorWidgetRegistry(); + mRelationEditorRegistry = new QgsRelationWidgetRegistry(); mShortcutsManager = new QgsShortcutsManager(); mLayerTreeEmbeddedWidgetRegistry = new QgsLayerTreeEmbeddedWidgetRegistry(); mMapLayerActionRegistry = new QgsMapLayerActionRegistry(); diff --git a/src/gui/qgsgui.h b/src/gui/qgsgui.h index ce9c55285bb3..e52b426b7f99 100644 --- a/src/gui/qgsgui.h +++ b/src/gui/qgsgui.h @@ -42,6 +42,7 @@ class QgsCodeEditorColorSchemeRegistry; class QgsMessageBar; class QgsSubsetStringEditorProviderRegistry; class QgsProviderSourceWidgetProviderRegistry; +class QgsRelationWidgetRegistry; /** * \ingroup gui @@ -168,6 +169,12 @@ class GUI_EXPORT QgsGui : public QObject */ static QgsProviderSourceWidgetProviderRegistry *sourceWidgetProviderRegistry() SIP_KEEPREFERENCE; + /** + * Returns the global relation widget registry, used for managing all known relation widget factories. + * \since QGIS 3.18 + */ + static QgsRelationWidgetRegistry *relationWidgetRegistry() SIP_KEEPREFERENCE; + /** * Register the widget to allow its position to be automatically saved and restored when open and closed. * Use this to avoid needing to call saveGeometry() and restoreGeometry() on your widget. @@ -271,6 +278,7 @@ class GUI_EXPORT QgsGui : public QObject QgsProjectStorageGuiRegistry *mProjectStorageGuiRegistry = nullptr; QgsSubsetStringEditorProviderRegistry *mSubsetStringEditorProviderRegistry = nullptr; QgsProviderSourceWidgetProviderRegistry *mProviderSourceWidgetProviderRegistry = nullptr; + QgsRelationWidgetRegistry *mRelationEditorRegistry = nullptr; std::unique_ptr< QgsWindowManagerInterface > mWindowManager; #ifdef SIP_RUN diff --git a/src/gui/qgsrelationeditorwidget.cpp b/src/gui/qgsrelationeditorwidget.cpp index acc8743a7e52..7ed3103cadc9 100644 --- a/src/gui/qgsrelationeditorwidget.cpp +++ b/src/gui/qgsrelationeditorwidget.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - qgsrelationeditor.cpp + qgsrelationeditorwidget.cpp -------------------------------------- Date : 17.5.2013 Copyright : (C) 2013 Matthias Kuhn @@ -58,6 +58,8 @@ QgsFilteredSelectionManager::QgsFilteredSelectionManager( QgsVectorLayer *layer, } const QgsFeatureIds &QgsFilteredSelectionManager::selectedFeatureIds() const + + { return mSelectedFeatureIds; } @@ -91,12 +93,18 @@ void QgsFilteredSelectionManager::onSelectionChanged( const QgsFeatureIds &selec /// @endcond -QgsRelationEditorWidget::QgsRelationEditorWidget( QWidget *parent ) - : QgsCollapsibleGroupBox( parent ) +QgsRelationEditorWidget::QgsRelationEditorWidget( const QVariantMap &config, QWidget *parent ) + : QgsAbstractRelationEditorWidget( config, parent ) + , mButtonsVisibility( qgsFlagKeysToValue( config.value( QStringLiteral( "buttons" ) ).toString(), QgsRelationEditorWidget::Button::AllButtons ) ) { - QVBoxLayout *topLayout = new QVBoxLayout( this ); + QVBoxLayout *rootLayout = new QVBoxLayout( this ); + rootLayout->setContentsMargins( 0, 0, 0, 0 ); + + mRootCollapsibleGroupBox = new QgsCollapsibleGroupBox( QString(), this ); + rootLayout->addWidget( mRootCollapsibleGroupBox ); + + QVBoxLayout *topLayout = new QVBoxLayout( mRootCollapsibleGroupBox ); topLayout->setContentsMargins( 0, 9, 0, 0 ); - setLayout( topLayout ); // buttons QHBoxLayout *buttonLayout = new QHBoxLayout(); @@ -198,14 +206,14 @@ QgsRelationEditorWidget::QgsRelationEditorWidget( QWidget *parent ) mRelationLayout->addWidget( mDualView ); - connect( this, &QgsCollapsibleGroupBoxBasic::collapsedStateChanged, this, &QgsRelationEditorWidget::onCollapsedStateChanged ); + connect( mRootCollapsibleGroupBox, &QgsCollapsibleGroupBoxBasic::collapsedStateChanged, this, &QgsRelationEditorWidget::onCollapsedStateChanged ); connect( mViewModeButtonGroup, static_cast( &QButtonGroup::buttonClicked ), this, static_cast( &QgsRelationEditorWidget::setViewMode ) ); connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::toggleEditing ); connect( mSaveEditsButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::saveEdits ); connect( mAddFeatureButton, &QAbstractButton::clicked, this, [this]() { addFeature(); } ); connect( mAddFeatureGeometryButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::addFeatureGeometry ); - connect( mDuplicateFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::duplicateFeature ); + connect( mDuplicateFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::duplicateSelectedFeatures ); connect( mDeleteFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::deleteSelectedFeatures ); connect( mLinkFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::linkFeature ); connect( mUnlinkFeatureButton, &QAbstractButton::clicked, this, &QgsRelationEditorWidget::unlinkSelectedFeatures ); @@ -217,48 +225,6 @@ QgsRelationEditorWidget::QgsRelationEditorWidget( QWidget *parent ) updateButtons(); } -void QgsRelationEditorWidget::setRelationFeature( const QgsRelation &relation, const QgsFeature &feature ) -{ - if ( mRelation.isValid() ) - { - disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); - disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); - } - - mRelation = relation; - mFeature = feature; - - connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); - connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); - - updateTitle(); - - QgsVectorLayer *lyr = relation.referencingLayer(); - - bool canChangeAttributes = lyr->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues; - if ( canChangeAttributes && !lyr->readOnly() ) - { - mToggleEditingButton->setEnabled( true ); - updateButtons(); - } - else - { - mToggleEditingButton->setEnabled( false ); - } - - setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() ); - - // If not yet initialized, it is not (yet) visible, so we don't load it to be faster (lazy loading) - // If it is already initialized, it has been set visible before and the currently shown feature is changing - // and the widget needs updating - - if ( mVisible ) - { - QgsFeatureRequest myRequest = mRelation.getRelatedFeaturesRequest( mFeature ); - initDualView( mRelation.referencingLayer(), myRequest ); - } -} - void QgsRelationEditorWidget::initDualView( QgsVectorLayer *layer, const QgsFeatureRequest &request ) { QgsAttributeEditorContext ctx { mEditorContext }; @@ -294,76 +260,6 @@ void QgsRelationEditorWidget::initDualView( QgsVectorLayer *layer, const QgsFeat updateButtons(); } -void QgsRelationEditorWidget::setRelations( const QgsRelation &relation, const QgsRelation &nmrelation ) -{ - if ( mRelation.isValid() ) - { - disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); - disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); - } - - if ( mNmRelation.isValid() ) - { - disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); - disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); - } - - mRelation = relation; - mNmRelation = nmrelation; - - if ( !mRelation.isValid() ) - return; - - mLayerInSameTransactionGroup = false; - - const auto transactionGroups = QgsProject::instance()->transactionGroups(); - for ( auto it = transactionGroups.constBegin(); it != transactionGroups.constEnd(); ++it ) - { - if ( mNmRelation.isValid() ) - { - if ( it.value()->layers().contains( mRelation.referencedLayer() ) && - it.value()->layers().contains( mRelation.referencingLayer() ) && - it.value()->layers().contains( mNmRelation.referencedLayer() ) ) - mLayerInSameTransactionGroup = true; - } - else - { - if ( it.value()->layers().contains( mRelation.referencedLayer() ) && - it.value()->layers().contains( mRelation.referencingLayer() ) ) - mLayerInSameTransactionGroup = true; - } - } - - connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); - connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); - - if ( mNmRelation.isValid() ) - { - connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); - connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); - } - - updateTitle(); - - QgsVectorLayer *lyr = relation.referencingLayer(); - - bool canChangeAttributes = lyr->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues; - if ( canChangeAttributes && !lyr->readOnly() ) - { - mToggleEditingButton->setEnabled( true ); - } - else - { - mToggleEditingButton->setEnabled( false ); - } - - updateButtons(); - - setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() ); - - updateUi(); -} - void QgsRelationEditorWidget::setEditorContext( const QgsAttributeEditorContext &context ) { mEditorContext = context; @@ -377,32 +273,12 @@ void QgsRelationEditorWidget::setEditorContext( const QgsAttributeEditorContext updateButtons(); } -QgsAttributeEditorContext QgsRelationEditorWidget::editorContext() const -{ - return mEditorContext; -} - -QgsIFeatureSelectionManager *QgsRelationEditorWidget::featureSelectionManager() -{ - return mFeatureSelectionMgr; -} - void QgsRelationEditorWidget::setViewMode( QgsDualView::ViewMode mode ) { mDualView->setView( mode ); mViewMode = mode; } -void QgsRelationEditorWidget::setFeature( const QgsFeature &feature, bool update ) -{ - mFeature = feature; - - mEditorContext.setFormFeature( feature ); - - if ( update ) - updateUi(); -} - void QgsRelationEditorWidget::updateButtons() { bool editable = false; @@ -434,14 +310,15 @@ void QgsRelationEditorWidget::updateButtons() mSaveEditsButton->setEnabled( editable ); mToggleEditingButton->setVisible( !mLayerInSameTransactionGroup ); - mLinkFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::Link ) ); - mUnlinkFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::Unlink ) ); - mSaveEditsButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::SaveChildEdits ) && !mLayerInSameTransactionGroup ); - mAddFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::AddChildFeature ) ); - mAddFeatureGeometryButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::AddChildFeature ) && mEditorContext.mapCanvas() && mEditorContext.cadDockWidget() && spatial ); - mDuplicateFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::DuplicateChildFeature ) ); - mDeleteFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::DeleteChildFeature ) ); - mZoomToFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsAttributeEditorRelation::Button::ZoomToChildFeature ) && mEditorContext.mapCanvas() && spatial ); + + mLinkFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsRelationEditorWidget::Button::Link ) ); + mUnlinkFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsRelationEditorWidget::Button::Unlink ) ); + mSaveEditsButton->setVisible( mButtonsVisibility.testFlag( QgsRelationEditorWidget::Button::SaveChildEdits ) && !mLayerInSameTransactionGroup ); + mAddFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsRelationEditorWidget::Button::AddChildFeature ) ); + mAddFeatureGeometryButton->setVisible( mButtonsVisibility.testFlag( QgsRelationEditorWidget::Button::AddChildFeature ) && mEditorContext.mapCanvas() && mEditorContext.cadDockWidget() && spatial ); + mDuplicateFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsRelationEditorWidget::Button::DuplicateChildFeature ) ); + mDeleteFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsRelationEditorWidget::Button::DeleteChildFeature ) ); + mZoomToFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsRelationEditorWidget::Button::ZoomToChildFeature ) && mEditorContext.mapCanvas() && spatial ); } void QgsRelationEditorWidget::addFeatureGeometry() @@ -474,60 +351,6 @@ void QgsRelationEditorWidget::addFeatureGeometry() } -void QgsRelationEditorWidget::addFeature( const QgsGeometry &geometry ) -{ - QgsAttributeMap keyAttrs; - - const QgsVectorLayerTools *vlTools = mEditorContext.vectorLayerTools(); - - if ( mNmRelation.isValid() ) - { - // n:m Relation: first let the user create a new feature on the other table - // and autocreate a new linking feature. - QgsFeature f; - if ( vlTools->addFeature( mNmRelation.referencedLayer(), QgsAttributeMap(), geometry, &f ) ) - { - // Fields of the linking table - const QgsFields fields = mRelation.referencingLayer()->fields(); - - // Expression context for the linking table - QgsExpressionContext context = mRelation.referencingLayer()->createExpressionContext(); - - QgsAttributeMap linkAttributes; - const auto constFieldPairs = mRelation.fieldPairs(); - for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) - { - int index = fields.indexOf( fieldPair.first ); - linkAttributes.insert( index, mFeature.attribute( fieldPair.second ) ); - } - - const auto constNmFieldPairs = mNmRelation.fieldPairs(); - for ( const QgsRelation::FieldPair &fieldPair : constNmFieldPairs ) - { - int index = fields.indexOf( fieldPair.first ); - linkAttributes.insert( index, f.attribute( fieldPair.second ) ); - } - QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context ); - - mRelation.referencingLayer()->addFeature( linkFeature ); - - updateUi(); - } - } - else - { - QgsFields fields = mRelation.referencingLayer()->fields(); - - const auto constFieldPairs = mRelation.fieldPairs(); - for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) - { - keyAttrs.insert( fields.indexFromName( fieldPair.referencingField() ), mFeature.attribute( fieldPair.referencedField() ) ); - } - - vlTools->addFeature( mDualView->masterModel()->layer(), keyAttrs, geometry ); - } -} - void QgsRelationEditorWidget::onDigitizingCompleted( const QgsFeature &feature ) { addFeature( feature.geometry() ); @@ -535,362 +358,197 @@ void QgsRelationEditorWidget::onDigitizingCompleted( const QgsFeature &feature ) unsetMapTool(); } -void QgsRelationEditorWidget::linkFeature() +void QgsRelationEditorWidget::toggleEditing( bool state ) { - QgsVectorLayer *layer = nullptr; - - if ( mNmRelation.isValid() ) - layer = mNmRelation.referencedLayer(); - else - layer = mRelation.referencingLayer(); - - QgsFeatureSelectionDlg *selectionDlg = new QgsFeatureSelectionDlg( layer, mEditorContext, this ); - selectionDlg->setAttribute( Qt::WA_DeleteOnClose ); + QgsAbstractRelationEditorWidget::toggleEditing( state ); - const QString displayString = QgsVectorLayerUtils::getFeatureDisplayString( mRelation.referencedLayer(), mFeature ); - selectionDlg->setWindowTitle( tr( "Link existing child features for parent %1 \"%2\"" ).arg( mRelation.referencedLayer()->name(), displayString ) ); - - connect( selectionDlg, &QDialog::accepted, this, &QgsRelationEditorWidget::onLinkFeatureDlgAccepted ); - selectionDlg->show(); + updateButtons(); } -void QgsRelationEditorWidget::onLinkFeatureDlgAccepted() +void QgsRelationEditorWidget::onCollapsedStateChanged( bool collapsed ) { - QgsFeatureSelectionDlg *selectionDlg = qobject_cast( sender() ); - if ( mNmRelation.isValid() ) + if ( !collapsed ) { - QgsFeatureIterator it = mNmRelation.referencedLayer()->getFeatures( - QgsFeatureRequest() - .setFilterFids( selectionDlg->selectedFeatures() ) - .setSubsetOfAttributes( mNmRelation.referencedFields() ) ); + mVisible = true; + updateUi(); + } +} - QgsFeature relatedFeature; +void QgsRelationEditorWidget::updateUi() +{ + // If not yet initialized, it is not (yet) visible, so we don't load it to be faster (lazy loading) + // If it is already initialized, it has been set visible before and the currently shown feature is changing + // and the widget needs updating - QgsFeatureList newFeatures; + if ( mVisible ) + { + QgsFeatureRequest myRequest = mRelation.getRelatedFeaturesRequest( mFeature ); - // Fields of the linking table - const QgsFields fields = mRelation.referencingLayer()->fields(); + if ( mNmRelation.isValid() ) + { + QgsFeatureIterator it = mRelation.referencingLayer()->getFeatures( myRequest ); - // Expression context for the linking table - QgsExpressionContext context = mRelation.referencingLayer()->createExpressionContext(); + QgsFeature fet; - QgsAttributeMap linkAttributes; - const auto constFieldPairs = mRelation.fieldPairs(); - for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) - { - int index = fields.indexOf( fieldPair.first ); - linkAttributes.insert( index, mFeature.attribute( fieldPair.second ) ); - } + QStringList filters; - while ( it.nextFeature( relatedFeature ) ) - { - const auto constFieldPairs = mNmRelation.fieldPairs(); - for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) + while ( it.nextFeature( fet ) ) { - int index = fields.indexOf( fieldPair.first ); - linkAttributes.insert( index, relatedFeature.attribute( fieldPair.second ) ); + QString filter = mNmRelation.getReferencedFeatureRequest( fet ).filterExpression()->expression(); + filters << filter.prepend( '(' ).append( ')' ); } - const QgsFeature linkFeature = QgsVectorLayerUtils::createFeature( mRelation.referencingLayer(), QgsGeometry(), linkAttributes, &context ); - newFeatures << linkFeature; - } + QgsFeatureRequest nmRequest; - mRelation.referencingLayer()->addFeatures( newFeatures ); - QgsFeatureIds ids; - const auto constNewFeatures = newFeatures; - for ( const QgsFeature &f : constNewFeatures ) - ids << f.id(); - mRelation.referencingLayer()->selectByIds( ids ); - } - else - { - QMap keys; - const auto constFieldPairs = mRelation.fieldPairs(); - for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) - { - int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() ); - QVariant val = mFeature.attribute( fieldPair.referencedField() ); - keys.insert( idx, val ); - } + nmRequest.setFilterExpression( filters.join( QLatin1String( " OR " ) ) ); - const auto constSelectedFeatures = selectionDlg->selectedFeatures(); - for ( QgsFeatureId fid : constSelectedFeatures ) + initDualView( mNmRelation.referencedLayer(), nmRequest ); + } + else if ( mRelation.referencingLayer() ) { - QMapIterator it( keys ); - while ( it.hasNext() ) - { - it.next(); - mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), it.value() ); - } + initDualView( mRelation.referencingLayer(), myRequest ); } } - - updateUi(); } -void QgsRelationEditorWidget::duplicateFeature() +void QgsRelationEditorWidget::setVisibleButtons( const Buttons &buttons ) { - QgsVectorLayer *layer = mRelation.referencingLayer(); - - QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest().setFilterFids( mFeatureSelectionMgr->selectedFeatureIds() ) ); - QgsFeature f; - while ( fit.nextFeature( f ) ) - { - QgsVectorLayerUtils::QgsDuplicateFeatureContext duplicatedFeatureContext; - QgsVectorLayerUtils::duplicateFeature( layer, f, QgsProject::instance(), duplicatedFeatureContext ); - } + mButtonsVisibility = buttons; + updateButtons(); } -void QgsRelationEditorWidget::deleteFeature( const QgsFeatureId featureid ) +QgsRelationEditorWidget::Buttons QgsRelationEditorWidget::visibleButtons() const { - deleteFeatures( QgsFeatureIds() << featureid ); + Buttons buttons; + if ( mLinkFeatureButton->isVisible() ) + buttons |= Button::Link; + if ( mUnlinkFeatureButton->isVisible() ) + buttons |= Button::Unlink; + if ( mSaveEditsButton->isVisible() ) + buttons |= Button::SaveChildEdits; + if ( mAddFeatureButton->isVisible() ) + buttons |= Button::AddChildFeature; + if ( mDuplicateFeatureButton->isVisible() ) + buttons |= Button::DuplicateChildFeature; + if ( mDeleteFeatureButton->isVisible() ) + buttons |= Button::DeleteChildFeature; + if ( mZoomToFeatureButton->isVisible() ) + buttons |= Button::ZoomToChildFeature; + return buttons; } -void QgsRelationEditorWidget::deleteSelectedFeatures() +void QgsRelationEditorWidget::parentFormValueChanged( const QString &attribute, const QVariant &newValue ) { - QgsFeatureIds selectedFids = mFeatureSelectionMgr->selectedFeatureIds(); - deleteFeatures( selectedFids ); + mDualView->parentFormValueChanged( attribute, newValue ); } -void QgsRelationEditorWidget::deleteFeatures( const QgsFeatureIds &featureids ) +void QgsRelationEditorWidget::showContextMenu( QgsActionMenu *menu, const QgsFeatureId fid ) { - bool deleteFeatures = true; - - QgsVectorLayer *layer; - if ( mNmRelation.isValid() ) + if ( mRelation.referencingLayer()->isEditable() ) { - layer = mNmRelation.referencedLayer(); + QAction *qAction = nullptr; - // When deleting a linked feature within an N:M relation, - // check if the feature is linked to more than just one feature. - // In case it is linked more than just once, ask the user for confirmation - // as it is likely he was not aware of the implications and might delete - // there may be several linking entries deleted along. - - QgsFeatureRequest deletedFeaturesRequest; - deletedFeaturesRequest.setFilterFids( featureids ); - deletedFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry ); - deletedFeaturesRequest.setSubsetOfAttributes( QgsAttributeList() << mNmRelation.referencedFields().first() ); - - QgsFeatureIterator deletedFeatures = layer->getFeatures( deletedFeaturesRequest ); - QStringList deletedFeaturesPks; - QgsFeature feature; - while ( deletedFeatures.nextFeature( feature ) ) - { - deletedFeaturesPks.append( QgsExpression::quotedValue( feature.attribute( mNmRelation.referencedFields().first() ) ) ); - } + qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ), tr( "Delete Feature" ) ); + connect( qAction, &QAction::triggered, this, [this, fid]() { deleteFeature( fid ); } ); - QgsFeatureRequest linkingFeaturesRequest; - linkingFeaturesRequest.setFlags( QgsFeatureRequest::NoGeometry ); - linkingFeaturesRequest.setNoAttributes(); + qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUnlink.svg" ) ), tr( "Unlink Feature" ) ); + connect( qAction, &QAction::triggered, this, [this, fid]() { unlinkFeature( fid ); } ); + } +} - QString linkingFeaturesRequestExpression; - if ( !deletedFeaturesPks.empty() ) - { - linkingFeaturesRequestExpression = QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( mNmRelation.fieldPairs().first().first ), deletedFeaturesPks.join( ',' ) ); - linkingFeaturesRequest.setFilterExpression( linkingFeaturesRequestExpression ); +void QgsRelationEditorWidget::setMapTool( QgsMapTool *mapTool ) +{ + QgsMapCanvas *mapCanvas = mEditorContext.mapCanvas(); - QgsFeatureIterator relatedLinkingFeatures = mNmRelation.referencingLayer()->getFeatures( linkingFeaturesRequest ); + mapCanvas->setMapTool( mapTool ); + mapCanvas->window()->raise(); + mapCanvas->activateWindow(); + mapCanvas->setFocus(); + connect( mapTool, &QgsMapTool::deactivated, this, &QgsRelationEditorWidget::mapToolDeactivated ); +} - int relatedLinkingFeaturesCount = 0; - while ( relatedLinkingFeatures.nextFeature( feature ) ) - { - relatedLinkingFeaturesCount++; - } +void QgsRelationEditorWidget::unsetMapTool() +{ + QgsMapCanvas *mapCanvas = mEditorContext.mapCanvas(); - if ( deletedFeaturesPks.size() == 1 && relatedLinkingFeaturesCount > 1 ) - { - QMessageBox messageBox( QMessageBox::Question, tr( "Really delete entry?" ), tr( "The entry on %1 is still linked to %2 features on %3. Do you want to delete it?" ).arg( mNmRelation.referencedLayer()->name(), QString::number( relatedLinkingFeaturesCount ), mRelation.referencedLayer()->name() ), QMessageBox::NoButton, this ); - messageBox.addButton( QMessageBox::Cancel ); - QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole ); + // this will call mapToolDeactivated + mapCanvas->unsetMapTool( mMapToolDigitize ); - messageBox.exec(); - if ( messageBox.clickedButton() != deleteButton ) - deleteFeatures = false; - } - else if ( deletedFeaturesPks.size() > 1 && relatedLinkingFeaturesCount > deletedFeaturesPks.size() ) - { - QMessageBox messageBox( QMessageBox::Question, tr( "Really delete entries?" ), tr( "The %1 entries on %2 are still linked to %3 features on %4. Do you want to delete them?" ).arg( QString::number( deletedFeaturesPks.size() ), mNmRelation.referencedLayer()->name(), QString::number( relatedLinkingFeaturesCount ), mRelation.referencedLayer()->name() ), QMessageBox::NoButton, this ); - messageBox.addButton( QMessageBox::Cancel ); - QAbstractButton *deleteButton = messageBox.addButton( tr( "Delete" ), QMessageBox::AcceptRole ); + disconnect( mapCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationEditorWidget::onKeyPressed ); + disconnect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationEditorWidget::onDigitizingCompleted ); +} - messageBox.exec(); - if ( messageBox.clickedButton() != deleteButton ) - deleteFeatures = false; - } - } - } - else +void QgsRelationEditorWidget::onKeyPressed( QKeyEvent *e ) +{ + if ( e->key() == Qt::Key_Escape ) { - layer = mRelation.referencingLayer(); + unsetMapTool(); } +} - QgsVectorLayerUtils::QgsDuplicateFeatureContext infoContext; - if ( QgsVectorLayerUtils::impactsCascadeFeatures( layer, featureids, QgsProject::instance(), infoContext ) ) - { - QString childrenInfo; - int childrenCount = 0; - const auto infoContextLayers = infoContext.layers(); - for ( QgsVectorLayer *chl : infoContextLayers ) - { - childrenCount += infoContext.duplicatedFeatures( chl ).size(); - childrenInfo += ( tr( "%1 feature(s) on layer \"%2\", " ).arg( infoContext.duplicatedFeatures( chl ).size() ).arg( chl->name() ) ); - } - - // for extra safety to make sure we know that the delete can have impact on children and joins - int res = QMessageBox::question( this, tr( "Delete at least %1 feature(s) on other layer(s)" ).arg( childrenCount ), - tr( "Delete %1 feature(s) on layer \"%2\", %3 as well\nand all of its other descendants.\nDelete these features?" ).arg( featureids.count() ).arg( layer->name() ).arg( childrenInfo ), - QMessageBox::Yes | QMessageBox::No ); - if ( res != QMessageBox::Yes ) - deleteFeatures = false; - } +void QgsRelationEditorWidget::mapToolDeactivated() +{ + window()->setVisible( true ); + window()->raise(); + window()->activateWindow(); - if ( deleteFeatures ) + if ( mEditorContext.mainMessageBar() && mMessageBarItem ) { - QgsVectorLayer::DeleteContext context( true, QgsProject::instance() ); - layer->deleteFeatures( featureids, &context ); - const auto contextLayers = context.handledLayers(); - if ( contextLayers.size() > 1 ) - { - int deletedCount = 0; - QString feedbackMessage; - for ( QgsVectorLayer *contextLayer : contextLayers ) - { - feedbackMessage += tr( "%1 on layer %2. " ).arg( context.handledFeatures( contextLayer ).size() ).arg( contextLayer->name() ); - deletedCount += context.handledFeatures( contextLayer ).size(); - } - mEditorContext.mainMessageBar()->pushMessage( tr( "%1 features deleted: %2" ).arg( deletedCount ).arg( feedbackMessage ), Qgis::Success ); - } - - updateUi(); + mEditorContext.mainMessageBar()->popWidget( mMessageBarItem ); } + mMessageBarItem = nullptr; } -void QgsRelationEditorWidget::unlinkFeature( const QgsFeatureId featureid ) +QVariantMap QgsRelationEditorWidget::config() const { - unlinkFeatures( QgsFeatureIds() << featureid ); + return QVariantMap( {{"buttons", qgsFlagValueToKeys( visibleButtons() )}} ); } -void QgsRelationEditorWidget::unlinkSelectedFeatures() +void QgsRelationEditorWidget::setConfig( const QVariantMap &config ) { - unlinkFeatures( mFeatureSelectionMgr->selectedFeatureIds() ); + mButtonsVisibility = qgsFlagKeysToValue( config.value( QStringLiteral( "buttons" ) ).toString(), QgsRelationEditorWidget::Button::AllButtons ); + updateButtons(); } -void QgsRelationEditorWidget::zoomToSelectedFeatures() +void QgsRelationEditorWidget::setTitle( const QString &title ) { - QgsMapCanvas *c = mEditorContext.mapCanvas(); - if ( !c ) - return; - - c->zoomToFeatureIds( - mNmRelation.isValid() ? mNmRelation.referencedLayer() : mRelation.referencingLayer(), - mFeatureSelectionMgr->selectedFeatureIds() - ); + mRootCollapsibleGroupBox->setTitle( title ); } -void QgsRelationEditorWidget::unlinkFeatures( const QgsFeatureIds &featureids ) +void QgsRelationEditorWidget::beforeSetRelationFeature( const QgsRelation &newRelation, const QgsFeature &newFeature ) { - if ( mNmRelation.isValid() ) - { - QgsFeatureIterator selectedIterator = mNmRelation.referencedLayer()->getFeatures( - QgsFeatureRequest() - .setFilterFids( featureids ) - .setSubsetOfAttributes( mNmRelation.referencedFields() ) ); - - QgsFeature f; - - QStringList filters; - - while ( selectedIterator.nextFeature( f ) ) - { - filters << '(' + mNmRelation.getRelatedFeaturesRequest( f ).filterExpression()->expression() + ')'; - } - - QString filter = QStringLiteral( "(%1) AND (%2)" ).arg( - mRelation.getRelatedFeaturesRequest( mFeature ).filterExpression()->expression(), - filters.join( QLatin1String( " OR " ) ) ); + Q_UNUSED( newRelation ); + Q_UNUSED( newFeature ); - QgsFeatureIterator linkedIterator = mRelation.referencingLayer()->getFeatures( QgsFeatureRequest() - .setNoAttributes() - .setFilterExpression( filter ) ); - - QgsFeatureIds fids; + if ( ! mRelation.isValid() ) + return; - while ( linkedIterator.nextFeature( f ) ) - { - fids << f.id(); - QgsDebugMsgLevel( FID_TO_STRING( f.id() ), 4 ); - } + disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); + disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); +} - mRelation.referencingLayer()->deleteFeatures( fids ); +void QgsRelationEditorWidget::afterSetRelationFeature() +{ + mToggleEditingButton->setEnabled( false ); - updateUi(); - } - else - { - QMap keyFields; - const auto constFieldPairs = mRelation.fieldPairs(); - for ( const QgsRelation::FieldPair &fieldPair : constFieldPairs ) - { - int idx = mRelation.referencingLayer()->fields().lookupField( fieldPair.referencingField() ); - if ( idx < 0 ) - { - QgsDebugMsg( QStringLiteral( "referencing field %1 not found" ).arg( fieldPair.referencingField() ) ); - return; - } - QgsField fld = mRelation.referencingLayer()->fields().at( idx ); - keyFields.insert( idx, fld ); - } + if ( ! mRelation.isValid() ) + return; - const auto constFeatureids = featureids; - for ( QgsFeatureId fid : constFeatureids ) - { - QMapIterator it( keyFields ); - while ( it.hasNext() ) - { - it.next(); - mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), QVariant( it.value().type() ) ); - } - } - } -} + connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); + connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); -void QgsRelationEditorWidget::toggleEditing( bool state ) -{ - if ( state ) + QgsVectorLayer *vl = mRelation.referencingLayer(); + bool canChangeAttributes = vl->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues; + if ( canChangeAttributes && !vl->readOnly() ) { - mEditorContext.vectorLayerTools()->startEditing( mRelation.referencingLayer() ); - if ( mNmRelation.isValid() ) - mEditorContext.vectorLayerTools()->startEditing( mNmRelation.referencedLayer() ); + mToggleEditingButton->setEnabled( true ); + updateButtons(); } else { - mEditorContext.vectorLayerTools()->stopEditing( mRelation.referencingLayer() ); - if ( mNmRelation.isValid() ) - mEditorContext.vectorLayerTools()->stopEditing( mNmRelation.referencedLayer() ); - } - - updateButtons(); -} - -void QgsRelationEditorWidget::saveEdits() -{ - mEditorContext.vectorLayerTools()->saveEdits( mRelation.referencingLayer() ); - if ( mNmRelation.isValid() ) - mEditorContext.vectorLayerTools()->saveEdits( mNmRelation.referencedLayer() ); -} - -void QgsRelationEditorWidget::onCollapsedStateChanged( bool collapsed ) -{ - if ( !collapsed ) - { - mVisible = true; - updateUi(); + mToggleEditingButton->setEnabled( false ); } -} -void QgsRelationEditorWidget::updateUi() -{ // If not yet initialized, it is not (yet) visible, so we don't load it to be faster (lazy loading) // If it is already initialized, it has been set visible before and the currently shown feature is changing // and the widget needs updating @@ -898,213 +556,160 @@ void QgsRelationEditorWidget::updateUi() if ( mVisible ) { QgsFeatureRequest myRequest = mRelation.getRelatedFeaturesRequest( mFeature ); - - if ( mNmRelation.isValid() ) - { - QgsFeatureIterator it = mRelation.referencingLayer()->getFeatures( myRequest ); - - QgsFeature fet; - - QStringList filters; - - while ( it.nextFeature( fet ) ) - { - QString filter = mNmRelation.getReferencedFeatureRequest( fet ).filterExpression()->expression(); - filters << filter.prepend( '(' ).append( ')' ); - } - - QgsFeatureRequest nmRequest; - - nmRequest.setFilterExpression( filters.join( QLatin1String( " OR " ) ) ); - - initDualView( mNmRelation.referencedLayer(), nmRequest ); - } - else if ( mRelation.referencingLayer() ) - { - initDualView( mRelation.referencingLayer(), myRequest ); - } + initDualView( mRelation.referencingLayer(), myRequest ); } } -bool QgsRelationEditorWidget::showLinkButton() const +void QgsRelationEditorWidget::beforeSetRelations( const QgsRelation &newRelation, const QgsRelation &newNmRelation ) { - return mLinkFeatureButton->isVisible(); -} + Q_UNUSED( newRelation ); + Q_UNUSED( newNmRelation ); -void QgsRelationEditorWidget::setShowLinkButton( bool showLinkButton ) -{ - mLinkFeatureButton->setVisible( showLinkButton ); -} + if ( mRelation.isValid() ) + { + disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); + disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); + } -bool QgsRelationEditorWidget::showUnlinkButton() const -{ - return mUnlinkFeatureButton->isVisible(); + if ( mNmRelation.isValid() ) + { + disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); + disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); + } } -void QgsRelationEditorWidget::setShowSaveChildEditsButton( bool showChildEdits ) +void QgsRelationEditorWidget::afterSetRelations() { - mSaveEditsButton->setVisible( showChildEdits ); -} + if ( !mRelation.isValid() ) + return; -bool QgsRelationEditorWidget::showSaveChildEditsButton() const -{ - return mSaveEditsButton->isVisible(); -} + connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); + connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); -void QgsRelationEditorWidget::setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ) -{ - mButtonsVisibility = buttons; - updateButtons(); -} + if ( mNmRelation.isValid() ) + { + connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsRelationEditorWidget::updateButtons ); + connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsRelationEditorWidget::updateButtons ); + } -QgsAttributeEditorRelation::Buttons QgsRelationEditorWidget::visibleButtons() const -{ - QgsAttributeEditorRelation::Buttons buttons; - if ( mLinkFeatureButton->isVisible() ) - buttons |= QgsAttributeEditorRelation::Button::Link; - if ( mUnlinkFeatureButton->isVisible() ) - buttons |= QgsAttributeEditorRelation::Button::Unlink; - if ( mSaveEditsButton->isVisible() ) - buttons |= QgsAttributeEditorRelation::Button::SaveChildEdits; - if ( mAddFeatureButton->isVisible() ) - buttons |= QgsAttributeEditorRelation::Button::AddChildFeature; - if ( mDuplicateFeatureButton->isVisible() ) - buttons |= QgsAttributeEditorRelation::Button::DuplicateChildFeature; - if ( mDeleteFeatureButton->isVisible() ) - buttons |= QgsAttributeEditorRelation::Button::DeleteChildFeature; - if ( mZoomToFeatureButton->isVisible() ) - buttons |= QgsAttributeEditorRelation::Button::ZoomToChildFeature; - return buttons; + QgsVectorLayer *vl = mRelation.referencingLayer(); + bool canChangeAttributes = vl->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues; + if ( canChangeAttributes && !vl->readOnly() ) + { + mToggleEditingButton->setEnabled( true ); + } + else + { + mToggleEditingButton->setEnabled( false ); + } + + updateButtons(); } -void QgsRelationEditorWidget::setForceSuppressFormPopup( bool forceSuppressFormPopup ) +QgsIFeatureSelectionManager *QgsRelationEditorWidget::featureSelectionManager() { - mForceSuppressFormPopup = forceSuppressFormPopup; + return mFeatureSelectionMgr; } -bool QgsRelationEditorWidget::forceSuppressFormPopup() const +void QgsRelationEditorWidget::unlinkSelectedFeatures() { - return mForceSuppressFormPopup; + unlinkFeatures( mFeatureSelectionMgr->selectedFeatureIds() ); } -void QgsRelationEditorWidget::setNmRelationId( const QVariant &nmRelationId ) +void QgsRelationEditorWidget::duplicateFeature() { - mNmRelationId = nmRelationId; + duplicateFeatures( mFeatureSelectionMgr->selectedFeatureIds() ); } -QVariant QgsRelationEditorWidget::nmRelationId() const +void QgsRelationEditorWidget::duplicateSelectedFeatures() { - return mNmRelationId; + duplicateFeatures( mFeatureSelectionMgr->selectedFeatureIds() ); } -QString QgsRelationEditorWidget::label() const +void QgsRelationEditorWidget::deleteSelectedFeatures() { - return mLabel; + QgsFeatureIds selectedFids = mFeatureSelectionMgr->selectedFeatureIds(); + deleteFeatures( selectedFids ); } -void QgsRelationEditorWidget::setLabel( const QString &label ) +void QgsRelationEditorWidget::zoomToSelectedFeatures() { - mLabel = label; + QgsMapCanvas *c = mEditorContext.mapCanvas(); + if ( !c ) + return; - updateTitle(); + c->zoomToFeatureIds( + mNmRelation.isValid() ? mNmRelation.referencedLayer() : mRelation.referencingLayer(), + mFeatureSelectionMgr->selectedFeatureIds() + ); } -void QgsRelationEditorWidget::setShowUnlinkButton( bool showUnlinkButton ) -{ - mUnlinkFeatureButton->setVisible( showUnlinkButton ); -} +/////////////////////////////////////////////////////////////////////////////// -void QgsRelationEditorWidget::parentFormValueChanged( const QString &attribute, const QVariant &newValue ) -{ - mDualView->parentFormValueChanged( attribute, newValue ); -} -bool QgsRelationEditorWidget::showLabel() const +QgsRelationEditorConfigWidget::QgsRelationEditorConfigWidget( const QgsRelation &relation, QWidget *parent ) + : QgsAbstractRelationEditorConfigWidget( relation, parent ) { - return mShowLabel; + setupUi( this ); } -void QgsRelationEditorWidget::setShowLabel( bool showLabel ) +QVariantMap QgsRelationEditorConfigWidget::config() { - mShowLabel = showLabel; + QgsRelationEditorWidget::Buttons buttons; + buttons.setFlag( QgsRelationEditorWidget::Button::Link, mRelationShowLinkCheckBox->isChecked() ); + buttons.setFlag( QgsRelationEditorWidget::Button::Unlink, mRelationShowUnlinkCheckBox->isChecked() ); + buttons.setFlag( QgsRelationEditorWidget::Button::AddChildFeature, mRelationShowAddChildCheckBox->isChecked() ); + buttons.setFlag( QgsRelationEditorWidget::Button::DuplicateChildFeature, mRelationShowDuplicateChildFeatureCheckBox->isChecked() ); + buttons.setFlag( QgsRelationEditorWidget::Button::ZoomToChildFeature, mRelationShowZoomToFeatureCheckBox->isChecked() ); + buttons.setFlag( QgsRelationEditorWidget::Button::DeleteChildFeature, mRelationDeleteChildFeatureCheckBox->isChecked() ); + buttons.setFlag( QgsRelationEditorWidget::Button::SaveChildEdits, mRelationShowSaveChildEditsCheckBox->isChecked() ); - updateTitle(); + return QVariantMap( + { + {"buttons", qgsFlagValueToKeys( buttons )}, + } ); } -void QgsRelationEditorWidget::showContextMenu( QgsActionMenu *menu, const QgsFeatureId fid ) +void QgsRelationEditorConfigWidget::setConfig( const QVariantMap &config ) { - if ( mRelation.referencingLayer()->isEditable() ) - { - QAction *qAction = nullptr; + const QgsRelationEditorWidget::Buttons buttons = qgsFlagKeysToValue( config.value( QStringLiteral( "buttons" ) ).toString(), QgsRelationEditorWidget::Button::AllButtons ); - qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ), tr( "Delete Feature" ) ); - connect( qAction, &QAction::triggered, this, [this, fid]() { deleteFeature( fid ); } ); - - qAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUnlink.svg" ) ), tr( "Unlink Feature" ) ); - connect( qAction, &QAction::triggered, this, [this, fid]() { unlinkFeature( fid ); } ); - } + mRelationShowLinkCheckBox->setChecked( buttons.testFlag( QgsRelationEditorWidget::Button::Link ) ); + mRelationShowUnlinkCheckBox->setChecked( buttons.testFlag( QgsRelationEditorWidget::Button::Unlink ) ); + mRelationShowAddChildCheckBox->setChecked( buttons.testFlag( QgsRelationEditorWidget::Button::AddChildFeature ) ); + mRelationShowDuplicateChildFeatureCheckBox->setChecked( buttons.testFlag( QgsRelationEditorWidget::Button::DuplicateChildFeature ) ); + mRelationShowZoomToFeatureCheckBox->setChecked( buttons.testFlag( QgsRelationEditorWidget::Button::ZoomToChildFeature ) ); + mRelationDeleteChildFeatureCheckBox->setChecked( buttons.testFlag( QgsRelationEditorWidget::Button::DeleteChildFeature ) ); + mRelationShowSaveChildEditsCheckBox->setChecked( buttons.testFlag( QgsRelationEditorWidget::Button::SaveChildEdits ) ); } -void QgsRelationEditorWidget::setMapTool( QgsMapTool *mapTool ) -{ - QgsMapCanvas *mapCanvas = mEditorContext.mapCanvas(); - mapCanvas->setMapTool( mapTool ); - mapCanvas->window()->raise(); - mapCanvas->activateWindow(); - mapCanvas->setFocus(); - connect( mapTool, &QgsMapTool::deactivated, this, &QgsRelationEditorWidget::mapToolDeactivated ); -} +/////////////////////////////////////////////////////////////////////////////// -void QgsRelationEditorWidget::unsetMapTool() -{ - QgsMapCanvas *mapCanvas = mEditorContext.mapCanvas(); - // this will call mapToolDeactivated - mapCanvas->unsetMapTool( mMapToolDigitize ); +#ifndef SIP_RUN +QgsRelationEditorWidgetFactory::QgsRelationEditorWidgetFactory() +{ - disconnect( mapCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationEditorWidget::onKeyPressed ); - disconnect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationEditorWidget::onDigitizingCompleted ); } -void QgsRelationEditorWidget::updateTitle() +QString QgsRelationEditorWidgetFactory::type() const { - if ( mShowLabel && !mLabel.isEmpty() ) - { - setTitle( mLabel ); - } - else if ( mShowLabel && mRelation.isValid() ) - { - setTitle( mRelation.name() ); - } - else - { - setTitle( QString() ); - } + return QStringLiteral( "relation_editor" ); } -QgsFeature QgsRelationEditorWidget::feature() const +QString QgsRelationEditorWidgetFactory::name() const { - return mFeature; + return QStringLiteral( "Relation Editor" ); } -void QgsRelationEditorWidget::onKeyPressed( QKeyEvent *e ) +QgsAbstractRelationEditorWidget *QgsRelationEditorWidgetFactory::create( const QVariantMap &config, QWidget *parent ) const { - if ( e->key() == Qt::Key_Escape ) - { - unsetMapTool(); - } + return new QgsRelationEditorWidget( config, parent ); } -void QgsRelationEditorWidget::mapToolDeactivated() +QgsAbstractRelationEditorConfigWidget *QgsRelationEditorWidgetFactory::configWidget( const QgsRelation &relation, QWidget *parent ) const { - window()->setVisible( true ); - window()->raise(); - window()->activateWindow(); - - if ( mEditorContext.mainMessageBar() && mMessageBarItem ) - { - mEditorContext.mainMessageBar()->popWidget( mMessageBarItem ); - } - mMessageBarItem = nullptr; + return static_cast( new QgsRelationEditorConfigWidget( relation, parent ) ); } +#endif diff --git a/src/gui/qgsrelationeditorwidget.h b/src/gui/qgsrelationeditorwidget.h index c172d033b1bd..4bddc730c362 100644 --- a/src/gui/qgsrelationeditorwidget.h +++ b/src/gui/qgsrelationeditorwidget.h @@ -13,8 +13,8 @@ * * ***************************************************************************/ -#ifndef QGSRELATIONEDITOR_H -#define QGSRELATIONEDITOR_H +#ifndef QGSRELATIONEDITORWIDGET_H +#define QGSRELATIONEDITORWIDGET_H #include #include @@ -22,6 +22,9 @@ #include #include "qobjectuniqueptr.h" +#include "ui_qgsrelationeditorconfigwidgetbase.h" + +#include "qgsabstractrelationeditorwidget.h" #include "qobjectuniqueptr.h" #include "qgsattributeeditorcontext.h" #include "qgscollapsiblegroupbox.h" @@ -77,35 +80,44 @@ class QgsFilteredSelectionManager : public QgsVectorLayerSelectionManager /** + * The default relation widget in QGIS. Successor of the now deprecated {\see QgsRelationEditorWidget}. * \ingroup gui * \class QgsRelationEditorWidget + * \since QGIS 3.18 */ -class GUI_EXPORT QgsRelationEditorWidget : public QgsCollapsibleGroupBox +class GUI_EXPORT QgsRelationEditorWidget : public QgsAbstractRelationEditorWidget { -#ifdef SIP_RUN - SIP_CONVERT_TO_SUBCLASS_CODE - if ( qobject_cast( sipCpp ) ) - sipType = sipType_QgsRelationEditorWidget; - else - sipType = NULL; - SIP_END -#endif - - - Q_OBJECT Q_PROPERTY( QgsDualView::ViewMode viewMode READ viewMode WRITE setViewMode ) - Q_PROPERTY( bool showLabel READ showLabel WRITE setShowLabel ) - Q_PROPERTY( QgsAttributeEditorRelation::Buttons visibleButtons READ visibleButtons WRITE setVisibleButtons ) + Q_PROPERTY( Buttons visibleButtons READ visibleButtons WRITE setVisibleButtons ) public: + /** + * Possible buttons shown in the relation editor + */ + enum Button + { + Link = 1 << 1, //!< Link button + Unlink = 1 << 2, //!< Unlink button + SaveChildEdits = 1 << 3, //!< Save child edits button + AddChildFeature = 1 << 4, //!< Add child feature (as in some projects we only want to allow linking/unlinking existing features) + DuplicateChildFeature = 1 << 5, //!< Duplicate child feature + DeleteChildFeature = 1 << 6, //!< Delete child feature button + ZoomToChildFeature = 1 << 7, //!< Zoom to child feature + AllButtons = Link | Unlink | SaveChildEdits | AddChildFeature | DuplicateChildFeature | DeleteChildFeature | ZoomToChildFeature //!< All buttons + }; + Q_ENUM( Button ) + Q_DECLARE_FLAGS( Buttons, Button ) + Q_FLAG( Buttons ) /** + * Constructor + * \param config widget configuration * \param parent parent widget */ - QgsRelationEditorWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + QgsRelationEditorWidget( const QVariantMap &config, QWidget *parent SIP_TRANSFERTHIS = nullptr ); //! Define the view mode for the dual view void setViewMode( QgsDualView::ViewMode mode ); @@ -113,40 +125,6 @@ class GUI_EXPORT QgsRelationEditorWidget : public QgsCollapsibleGroupBox //! Gets the view mode for the dual view QgsDualView::ViewMode viewMode() {return mViewMode;} - /** - * Sets the \a relation and the \a feature - */ - void setRelationFeature( const QgsRelation &relation, const QgsFeature &feature ); - - /** - * Set the relation(s) for this widget - * If only one relation is set, it will act as a simple 1:N relation widget - * If both relations are set, it will act as an N:M relation widget - * inserting and deleting entries on the intermediate table as required. - * - * \param relation Relation referencing the edited table - * \param nmrelation Optional reference from the referencing table to a 3rd N:M table - */ - void setRelations( const QgsRelation &relation, const QgsRelation &nmrelation ); - - /** - * Sets the \a feature being edited and updates the UI unless \a update is set to FALSE - */ - void setFeature( const QgsFeature &feature, bool update = true ); - - /** - * Sets the editor \a context - * \note if context cadDockWidget is null, it won't be possible to digitize - * the geometry of a referencing feature from this widget - */ - void setEditorContext( const QgsAttributeEditorContext &context ); - - /** - * Returns the attribute editor context. - * \since QGIS 3.14 - */ - QgsAttributeEditorContext editorContext( ) const; - /** * The feature selection manager is responsible for the selected features * which are currently being edited. @@ -154,165 +132,89 @@ class GUI_EXPORT QgsRelationEditorWidget : public QgsCollapsibleGroupBox QgsIFeatureSelectionManager *featureSelectionManager(); /** - * Defines if a title label should be shown for this widget. - * - * \since QGIS 2.18 - */ - bool showLabel() const; - - /** - * Defines if a title label should be shown for this widget. - * - * \since QGIS 2.18 - */ - void setShowLabel( bool showLabel ); - - /** - * Determines if the "link feature" button should be shown - * \since QGIS 2.18 - * \deprecated since QGIS 3.16 use visibleButtons() instead - */ - Q_DECL_DEPRECATED bool showLinkButton() const SIP_DEPRECATED; - - /** - * Determines if the "link feature" button should be shown - * \since QGIS 2.18 - * \deprecated since QGIS 3.16 use setVisibleButtons() instead - */ - Q_DECL_DEPRECATED void setShowLinkButton( bool showLinkButton ) SIP_DEPRECATED; - - /** - * Determines if the "unlink feature" button should be shown - * \since QGIS 2.18 - * \deprecated since QGIS 3.16 use visibleButtons() instead + * Sets the editor \a context + * \note if context cadDockWidget is null, it won't be possible to digitize + * the geometry of a referencing feature from this widget */ - Q_DECL_DEPRECATED bool showUnlinkButton() const SIP_DEPRECATED; + void setEditorContext( const QgsAttributeEditorContext &context ) override; /** - * Determines if the "unlink feature" button should be shown - * \since QGIS 2.18 - * \deprecated since QGIS 3.16 use setVisibleButtons() instead + * Defines the buttons which are shown */ - Q_DECL_DEPRECATED void setShowUnlinkButton( bool showUnlinkButton ) SIP_DEPRECATED; + void setVisibleButtons( const Buttons &buttons ); /** - * Determines if the "Save child layer edits" button should be shown - * \since QGIS 3.14 - * \deprecated since QGIS 3.16 use setVisibleButtons() instead + * Returns the buttons which are shown */ - Q_DECL_DEPRECATED void setShowSaveChildEditsButton( bool showChildEdits ) SIP_DEPRECATED; + Buttons visibleButtons() const; /** - * Determines if the "Save child layer edits" button should be shown - * \since QGIS 3.14 - * \deprecated since QGIS 3.16 use visibleButtons() instead + * Duplicates a feature + * \deprecated since QGIS 3.18, use duplicateSelectedFeatures() instead */ - Q_DECL_DEPRECATED bool showSaveChildEditsButton() const SIP_DEPRECATED; + Q_DECL_DEPRECATED void duplicateFeature() SIP_DEPRECATED; /** - * Defines the buttons which are shown - * \since QGIS 3.16 + * Duplicates the selected features + * \since QGIS 3.18 */ - void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons ); + void duplicateSelectedFeatures(); /** - * Returns the buttons which are shown - * \since QGIS 3.16 + * Unlinks the selected features from the relation */ - QgsAttributeEditorRelation::Buttons visibleButtons() const; - - /** - * Determines the force suppress form popup status that is configured for this widget - * \since QGIS 3.16 - */ - bool forceSuppressFormPopup() const; + void unlinkSelectedFeatures(); /** - * Sets force suppress form popup status with \a forceSuppressFormPopup - * configured for this widget - * \since QGIS 3.16 + * Deletes the currently selected features */ - void setForceSuppressFormPopup( bool forceSuppressFormPopup ); - - /** - * Determines the relation id of the second relation involved in an N:M relation. - * \since QGIS 3.16 - */ - QVariant nmRelationId() const; + void deleteSelectedFeatures(); /** - * Sets \a nmRelationId for the relation id of the second relation involved in an N:M relation. - * If it's empty, then it's considered as a 1:M relationship. - * \since QGIS 3.16 + * Zooms to the selected features */ - void setNmRelationId( const QVariant &nmRelationId = QVariant() ); + void zoomToSelectedFeatures(); /** - * Determines the label of this element - * \since QGIS 3.16 + * Returns the current configuration */ - QString label() const; + QVariantMap config() const override; /** - * Sets \a label for this element - * If it's empty it takes the relation id as label - * \since QGIS 3.16 + * Defines the current configuration */ - void setLabel( const QString &label = QString() ); + void setConfig( const QVariantMap &config ) override; /** - * Returns the widget's current feature - * - * \since QGIS 3.14 - */ - QgsFeature feature() const; + * Sets the title of the root groupbox + */ + void setTitle( const QString &title ) override; public slots: - - /** - * Called when an \a attribute value in the parent widget has changed to \a newValue - * - * \since QGIS 3.14 - */ - void parentFormValueChanged( const QString &attribute, const QVariant &newValue ); + void parentFormValueChanged( const QString &attribute, const QVariant &newValue ) override; private slots: void setViewMode( int mode ) {setViewMode( static_cast( mode ) );} void updateButtons(); - void addFeature( const QgsGeometry &geometry = QgsGeometry() ); void addFeatureGeometry(); - void duplicateFeature(); - void linkFeature(); - void deleteFeature( QgsFeatureId featureid = QgsFeatureId() ); - void deleteSelectedFeatures(); - void unlinkFeature( QgsFeatureId featureid = QgsFeatureId() ); - void unlinkSelectedFeatures(); - void zoomToSelectedFeatures(); - void saveEdits(); void toggleEditing( bool state ); void onCollapsedStateChanged( bool collapsed ); void showContextMenu( QgsActionMenu *menu, QgsFeatureId fid ); void mapToolDeactivated(); void onKeyPressed( QKeyEvent *e ); void onDigitizingCompleted( const QgsFeature &feature ); - void onLinkFeatureDlgAccepted(); private: - void updateUi(); + void updateUi() override; void initDualView( QgsVectorLayer *layer, const QgsFeatureRequest &request ); void setMapTool( QgsMapTool *mapTool ); void unsetMapTool(); - void updateTitle(); + QgsCollapsibleGroupBox *mRootCollapsibleGroupBox = nullptr; QgsDualView *mDualView = nullptr; QPointer mMessageBarItem; QgsDualView::ViewMode mViewMode = QgsDualView::AttributeEditor; - QgsVectorLayerSelectionManager *mFeatureSelectionMgr = nullptr; - QgsAttributeEditorContext mEditorContext; - QgsRelation mRelation; - QgsRelation mNmRelation; - QgsFeature mFeature; QToolButton *mToggleEditingButton = nullptr; QToolButton *mSaveEditsButton = nullptr; @@ -328,30 +230,79 @@ class GUI_EXPORT QgsRelationEditorWidget : public QgsCollapsibleGroupBox QGridLayout *mRelationLayout = nullptr; QObjectUniquePtr mMapToolDigitize; QButtonGroup *mViewModeButtonGroup = nullptr; + QgsVectorLayerSelectionManager *mFeatureSelectionMgr = nullptr; + + Buttons mButtonsVisibility = Button::AllButtons; + bool mVisible = true; + + void beforeSetRelationFeature( const QgsRelation &newRelation, const QgsFeature &newFeature ) override; + void afterSetRelationFeature() override; + void beforeSetRelations( const QgsRelation &newRelation, const QgsRelation &newNmRelation ) override; + void afterSetRelations() override; +}; - QgsAttributeEditorRelation::Buttons mButtonsVisibility = QgsAttributeEditorRelation::Button::AllButtons; - bool mShowLabel = true; - bool mVisible = false; - bool mLayerInSameTransactionGroup = false; - bool mForceSuppressFormPopup = false; - QVariant mNmRelationId; - QString mLabel; +/** + * \ingroup gui + * \class QgsRelationEditorConfigWidget + * Creates a new configuration widget for the relation editor widget + * \since QGIS 3.18 + */ +class GUI_EXPORT QgsRelationEditorConfigWidget : public QgsAbstractRelationEditorConfigWidget, private Ui::QgsRelationEditorConfigWidgetBase +{ + Q_OBJECT + + public: + + /** + * Create a new configuration widget + * + * \param relation The relation for which the configuration dialog will be created + * \param parent A parent widget + */ + explicit QgsRelationEditorConfigWidget( const QgsRelation &relation, QWidget *parent SIP_TRANSFERTHIS ); /** - * Deletes the features - * \param featureids features to delete - * \since QGIS 3.00 + * \brief Create a configuration from the current GUI state + * + * \returns A widget configuration */ - void deleteFeatures( const QgsFeatureIds &featureids ); + QVariantMap config(); /** - * Unlinks the features - * \param featureids features to unlink - * \since QGIS 3.00 + * \brief Update the configuration widget to represent the given configuration. + * + * \param config The configuration which should be represented by this widget */ - void unlinkFeatures( const QgsFeatureIds &featureids ); + void setConfig( const QVariantMap &config ); + +}; + + +#ifndef SIP_RUN + +/** + * Factory class for creating a relation editor widget and the respective config widget. + * \ingroup gui + * \class QgsRelationEditorWidgetFactory + * \note not available in Python bindings + * \since QGIS 3.18 + */ +class GUI_EXPORT QgsRelationEditorWidgetFactory : public QgsAbstractRelationEditorWidgetFactory +{ + public: + QgsRelationEditorWidgetFactory(); + + QString type() const override; + + QString name() const override; + + QgsAbstractRelationEditorWidget *create( const QVariantMap &config, QWidget *parent = nullptr ) const override; + + QgsAbstractRelationEditorConfigWidget *configWidget( const QgsRelation &relation, QWidget *parent ) const override; + }; +#endif -#endif // QGSRELATIONEDITOR_H +#endif // QGSRELATIONEDITORWIDGET_H diff --git a/src/gui/qgsrelationwidgetregistry.cpp b/src/gui/qgsrelationwidgetregistry.cpp new file mode 100644 index 000000000000..79310ad29ff5 --- /dev/null +++ b/src/gui/qgsrelationwidgetregistry.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + qgsrelationwidgetregistry.h + ---------------------- + begin : October 2020 + copyright : (C) 2020 by Ivan Ivanov + email : ivan@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgsrelationwidgetregistry.h" +#include "qgsrelationeditorwidget.h" + +QgsRelationWidgetRegistry::QgsRelationWidgetRegistry() +{ + addRelationWidget( new QgsRelationEditorWidgetFactory() ); +} + +QgsRelationWidgetRegistry::~QgsRelationWidgetRegistry() +{ + qDeleteAll( mRelationWidgetFactories ); + mRelationWidgetFactories.clear(); +} + +void QgsRelationWidgetRegistry::addRelationWidget( QgsAbstractRelationEditorWidgetFactory *widgetFactory ) +{ + if ( !widgetFactory ) + return; + + if ( mRelationWidgetFactories.contains( widgetFactory->type() ) ) + return; + + mRelationWidgetFactories.insert( widgetFactory->type(), widgetFactory ); +} + +void QgsRelationWidgetRegistry::removeRelationWidget( const QString &widgetType ) +{ + // protect the relation editor widget from removing, so the user has at least one relation widget type + if ( dynamic_cast( mRelationWidgetFactories.value( widgetType ) ) ) + return; + + mRelationWidgetFactories.remove( widgetType ); +} + +QStringList QgsRelationWidgetRegistry::relationWidgetNames() +{ + return mRelationWidgetFactories.keys(); +} + +QMap QgsRelationWidgetRegistry::factories() const +{ + return mRelationWidgetFactories; +} + +QgsAbstractRelationEditorWidget *QgsRelationWidgetRegistry::create( const QString &widgetType, const QVariantMap &config, QWidget *parent ) const +{ + if ( ! mRelationWidgetFactories.contains( widgetType ) ) + return nullptr; + + return mRelationWidgetFactories.value( widgetType )->create( config, parent ); +} + +QgsAbstractRelationEditorConfigWidget *QgsRelationWidgetRegistry::createConfigWidget( const QString &widgetType, const QgsRelation &relation, QWidget *parent ) const +{ + if ( ! mRelationWidgetFactories.contains( widgetType ) ) + return nullptr; + + return mRelationWidgetFactories.value( widgetType )->configWidget( relation, parent ); +} diff --git a/src/gui/qgsrelationwidgetregistry.h b/src/gui/qgsrelationwidgetregistry.h new file mode 100644 index 000000000000..0ba25f807516 --- /dev/null +++ b/src/gui/qgsrelationwidgetregistry.h @@ -0,0 +1,87 @@ +/*************************************************************************** + qgsrelationwidgetregistry.h + ---------------------- + begin : October 2020 + copyright : (C) 2020 by Ivan Ivanov + email : ivan@opengis.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgsabstractrelationeditorwidget.h" +#include "qgis_gui.h" + + +#ifndef QGSRELATIONWIDGETREGISTRY_H +#define QGSRELATIONWIDGETREGISTRY_H + + +/** + * Keeps track of the registered relations widgets. New widgets can be registered, old ones deleted. + * The default {\see QgsRelationEditorWidget} is protected from removing. + * \ingroup gui + * \class QgsRelationWidgetRegistry + * \since QGIS 3.18 + */ +class GUI_EXPORT QgsRelationWidgetRegistry +{ + public: + + /** + * Constructor + */ + QgsRelationWidgetRegistry(); + + ~QgsRelationWidgetRegistry(); + + /** + * Adds a new registered relation \a widgetFactory + */ + void addRelationWidget( QgsAbstractRelationEditorWidgetFactory *widgetFactory SIP_TRANSFER ); + + /** + * Removes a registered relation widget with given \a widgetType + */ + void removeRelationWidget( const QString &widgetType ); + + /** + * Returns a list of names of registered relation widgets + */ + QStringList relationWidgetNames(); + + /** + * Gets access to all registered factories + */ + QMap factories() const; + + /** + * Create a relation widget of a given type for a given field. + * + * \param widgetType The widget type to create a relation editor for + * \param config The configuration of the widget + * \param parent + */ + QgsAbstractRelationEditorWidget *create( const QString &widgetType, const QVariantMap &config, QWidget *parent = nullptr ) const SIP_TRANSFERBACK; + + /** + * Creates a configuration widget + * + * \param widgetType The widget type to create a configuration widget for + * \param relation The relation for which this widget will be created + * \param parent The parent widget for the created widget + */ + QgsAbstractRelationEditorConfigWidget *createConfigWidget( const QString &widgetType, const QgsRelation &relation, QWidget *parent = nullptr ) const SIP_TRANSFERBACK; + + private: + + QMap mRelationWidgetFactories; +}; + +#endif // QGSRELATIONWIDGETREGISTRY_H diff --git a/src/gui/vector/qgsattributesformproperties.cpp b/src/gui/vector/qgsattributesformproperties.cpp index 2f264f8e3cf0..dda433cdfa8c 100644 --- a/src/gui/vector/qgsattributesformproperties.cpp +++ b/src/gui/vector/qgsattributesformproperties.cpp @@ -409,8 +409,11 @@ QTreeWidgetItem *QgsAttributesFormProperties::loadAttributeEditorTreeItem( QgsAt const QgsAttributeEditorRelation *relationEditor = static_cast( widgetDef ); DnDTreeItemData itemData = DnDTreeItemData( DnDTreeItemData::Relation, relationEditor->relation().id(), relationEditor->relation().name() ); itemData.setShowLabel( widgetDef->showLabel() ); + RelationEditorConfiguration relEdConfig; - relEdConfig.buttons = relationEditor->visibleButtons(); +// relEdConfig.buttons = relationEditor->visibleButtons(); + relEdConfig.mRelationWidgetType = relationEditor->relationWidgetTypeId(); + relEdConfig.mRelationWidgetConfig = relationEditor->config(); relEdConfig.nmRelationId = relationEditor->nmRelationId(); relEdConfig.forceSuppressFormPopup = relationEditor->forceSuppressFormPopup(); relEdConfig.label = relationEditor->label(); @@ -657,10 +660,12 @@ QgsAttributeEditorElement *QgsAttributesFormProperties::createAttributeEditorWid { QgsRelation relation = QgsProject::instance()->relationManager()->relation( itemData.name() ); QgsAttributeEditorRelation *relDef = new QgsAttributeEditorRelation( relation, parent ); - relDef->setVisibleButtons( itemData.relationEditorConfiguration().buttons ); - relDef->setNmRelationId( itemData.relationEditorConfiguration().nmRelationId ); - relDef->setForceSuppressFormPopup( itemData.relationEditorConfiguration().forceSuppressFormPopup ); - relDef->setLabel( itemData.relationEditorConfiguration().label ); + QgsAttributesFormProperties::RelationEditorConfiguration relationEditorConfig = itemData.relationEditorConfiguration(); + relDef->setRelationWidgetTypeId( relationEditorConfig.mRelationWidgetType ); + relDef->setConfig( relationEditorConfig.mRelationWidgetConfig ); + relDef->setNmRelationId( relationEditorConfig.nmRelationId ); + relDef->setForceSuppressFormPopup( relationEditorConfig.forceSuppressFormPopup ); + relDef->setLabel( relationEditorConfig.label ); widgetDef = relDef; break; } @@ -892,7 +897,6 @@ void QgsAttributesFormProperties::apply() /* * FieldConfig implementation */ - QgsAttributesFormProperties::FieldConfig::FieldConfig( QgsVectorLayer *layer, int idx ) { mAlias = layer->fields().at( idx ).alias(); @@ -913,6 +917,15 @@ QgsAttributesFormProperties::FieldConfig::operator QVariant() return QVariant::fromValue( *this ); } +/* + * RelationEditorConfiguration implementation + */ + +QgsAttributesFormProperties::RelationEditorConfiguration::operator QVariant() +{ + return QVariant::fromValue( *this ); +} + /* * DnDTree implementation */ diff --git a/src/gui/vector/qgsattributesformproperties.h b/src/gui/vector/qgsattributesformproperties.h index 428127a4b888..d7ba428d6833 100644 --- a/src/gui/vector/qgsattributesformproperties.h +++ b/src/gui/vector/qgsattributesformproperties.h @@ -62,12 +62,16 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress { DnDTreeRole = Qt::UserRole, FieldConfigRole, - FieldNameRole + FieldNameRole, }; struct RelationEditorConfiguration { + operator QVariant(); + QgsAttributeEditorRelation::Buttons buttons = QgsAttributeEditorRelation::Button::AllButtons; + QString mRelationWidgetType; + QVariantMap mRelationWidgetConfig; QVariant nmRelationId; bool forceSuppressFormPopup = false; QString label; @@ -310,6 +314,7 @@ class GUI_EXPORT QgsAttributesDnDTree : public QTreeWidget }; +Q_DECLARE_METATYPE( QgsAttributesFormProperties::RelationEditorConfiguration ) Q_DECLARE_METATYPE( QgsAttributesFormProperties::FieldConfig ) Q_DECLARE_METATYPE( QgsAttributesFormProperties::DnDTreeItemData ) diff --git a/src/ui/attributeformconfig/qgsattributewidgetrelationeditwidget.ui b/src/ui/attributeformconfig/qgsattributewidgetrelationeditwidget.ui index 9f427252d6e5..fa1bffe8a6aa 100644 --- a/src/ui/attributeformconfig/qgsattributewidgetrelationeditwidget.ui +++ b/src/ui/attributeformconfig/qgsattributewidgetrelationeditwidget.ui @@ -7,101 +7,41 @@ 0 0 340 - 361 + 316 Attribute Widget Relation Edit Widget - - - - - QLayout::SetDefaultConstraint - - - - - Label - - - - - - - - 0 - 0 - - - - - - - - - - Show link button - - - - - - - Show unlink button - - - - - + + + - Show save child layer edits button + Label - - - - Add child feature + + + + + 0 + 0 + - - + + - Duplicate child feature + Cardinality - - - - Delete child feature - - + + - - - - Zoom to child feature - - - - - - - - - Cardinality - - - - - - - - - + Do not open a new attribute form after digitizing a new feature, overrides all other options @@ -111,8 +51,20 @@ - - + + + + Widget Configuration + + + + + + + + + + Qt::Vertical @@ -124,8 +76,26 @@ + + + + + + + Widget Type + + + + + + QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
+
diff --git a/src/ui/qgsrelationeditorconfigwidgetbase.ui b/src/ui/qgsrelationeditorconfigwidgetbase.ui new file mode 100644 index 000000000000..84160718444c --- /dev/null +++ b/src/ui/qgsrelationeditorconfigwidgetbase.ui @@ -0,0 +1,70 @@ + + + QgsRelationEditorConfigWidgetBase + + + + 0 + 0 + 274 + 215 + + + + Attribute Widget Relation Edit Widget + + + + + + Show link button + + + + + + + Show unlink button + + + + + + + Show save child layer edits button + + + + + + + Add child feature + + + + + + + Duplicate child feature + + + + + + + Delete child feature + + + + + + + Zoom to child feature + + + + + + + + diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 46aac610666f..e95aee80b281 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -343,6 +343,7 @@ ADD_PYTHON_TEST(PyQgsFieldValidator test_qgsfieldvalidator.py) ADD_PYTHON_TEST(PyQgsPluginDependencies test_plugindependencies.py) ADD_PYTHON_TEST(PyQgsDBManagerSQLWindow test_db_manager_sql_window.py) ADD_PYTHON_TEST(PyQgsSelectiveMasking test_selective_masking.py) +ADD_PYTHON_TEST(PyQgsRelationEditorWidgetRegistry test_qgsrelationeditorwidgetregistry.py) if (NOT WIN32) ADD_PYTHON_TEST(PyQgsLogger test_qgslogger.py) diff --git a/tests/src/python/test_qgsrelationeditorwidgetregistry.py b/tests/src/python/test_qgsrelationeditorwidgetregistry.py new file mode 100644 index 000000000000..0f3f55f3d3a2 --- /dev/null +++ b/tests/src/python/test_qgsrelationeditorwidgetregistry.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for edit widgets. + +.. 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__ = 'Matthias Kuhn' +__date__ = '28/11/2015' +__copyright__ = 'Copyright 2015, The QGIS Project' + +import qgis # NOQA + +import os + +from qgis.core import ( + QgsRelation, + QgsVectorLayerTools, +) + +from qgis.gui import ( + QgsGui, + QgsAbstractRelationEditorWidget, + QgsAbstractRelationEditorConfigWidget, + QgsAbstractRelationEditorWidgetFactory, + QgsRelationEditorWidget, + QgsRelationEditorConfigWidget, + QgsAttributeEditorContext, + QgsAdvancedDigitizingDockWidget +) + +from qgis.PyQt.QtCore import QTimer +from qgis.PyQt.QtWidgets import ( + QToolButton, + QMessageBox, + QDialogButtonBox, + QTableView, + QDialog, + QLabel, + QGridLayout, + QCheckBox, +) +from qgis.testing import start_app, unittest + +start_app() + + +class TestQgsRelationEditorWidgetRegistry(unittest.TestCase): + + @classmethod + def setUpClass(cls): + """ + Setup the involved layers and relations for a n:m relation + :return: + """ + cls.registry = QgsGui.relationWidgetRegistry() + + def test_cannot_delete_relation_editor(self): + count_before = len(self.registry.relationWidgetNames()) + self.registry.removeRelationWidget('relation_editor') + count_after = len(self.registry.relationWidgetNames()) + + self.assertEqual(count_before, count_after) + self.assertIsInstance(self.registry.create('relation_editor', {}), QgsRelationEditorWidget) + self.assertIsInstance(self.registry.createConfigWidget('relation_editor', QgsRelation()), QgsRelationEditorConfigWidget) + + def test_returns_none_when_unknown_widget_id(self): + self.assertIsNone(self.registry.create('babinatatitrunkina', {})) + self.assertIsNone(self.registry.createConfigWidget('babinatatitrunkina', QgsRelation())) + + def test_creates_new_widget(self): + # define the widget + class QgsExampleRelationEditorWidget(QgsAbstractRelationEditorWidget): + + def __init__(self, config, parent): + super().__init__(config, parent) + + self.setLayout(QGridLayout()) + self.label = QLabel() + self.label.setText(f'According to the configuration, the checkboxin state was {str(config.get("checkboxin", "Unknown"))}') + self.layout().addWidget(self.label) + + def config(self): + return { + + } + + def setConfig(self, config): + self.label.setText(f'According to the configuration, the checkboxin state was {str(config.get("checkboxin", "Unknown"))}') + + # define the config widget + class QgsExampleRelationEditorConfigWidget(QgsAbstractRelationEditorConfigWidget): + + def __init__(self, relation, parent): + super().__init__(relation, parent) + + self.setLayout(QGridLayout()) + self.checkbox = QCheckBox('Is this checkbox checkboxin?') + self.layout().addWidget(self.checkbox) + + def config(self): + return { + "checkboxin": self.checkbox.isChecked() + } + + def setConfig(self, config): + self.checkbox.setChecked(config.get('checkboxin', False)) + + # define the widget factory + class QgsExampleRelationEditorWidgetFactory(QgsAbstractRelationEditorWidgetFactory): + def type(self): + return "example" + + def name(self): + return "Example Relation Widget" + + def create(self, config, parent): + return QgsExampleRelationEditorWidget(config, parent) + + def configWidget(self, relation, parent): + return QgsExampleRelationEditorConfigWidget(relation, parent) + + self.assertIsNone(self.registry.create('example', {})) + self.assertIsNone(self.registry.createConfigWidget('example', QgsRelation())) + + self.registry.addRelationWidget(QgsExampleRelationEditorWidgetFactory()) + + self.assertIsInstance(self.registry.create('example', {}), QgsExampleRelationEditorWidget) + self.assertIsInstance(self.registry.createConfigWidget('example', QgsRelation()), QgsExampleRelationEditorConfigWidget) + + +if __name__ == '__main__': + unittest.main()