Add relation widget registry

This commit is contained in:
Ivan Ivanov 2020-12-18 02:26:59 +02:00
parent 112aaf06f8
commit 51cd712c76
40 changed files with 3240 additions and 161 deletions

View File

@ -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:
@ -411,18 +425,24 @@ 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 );
%Docstring
Defines the buttons which are shown
.. versionadded:: 3.16
.. deprecated:: QGIS 3.18
use setConfig() instead
%End
QgsAttributeEditorRelation::Buttons visibleButtons() const;
QgsAttributeEditorRelation::Buttons visibleButtons() const;
%Docstring
Returns the buttons which are shown
.. versionadded:: 3.16
.. deprecated:: QGIS 3.18
use setConfig() instead
%End
bool forceSuppressFormPopup() const;
@ -469,6 +489,16 @@ 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
%End
void setRelationWidgetTypeId( const QString &relationWidgetTypeId );
%Docstring
Sets the relation widget type
%End
};

View File

@ -0,0 +1,4 @@
# The following has been generated automatically from src/gui/qgsbasicrelationwidget.h
QgsBasicRelationWidget.Button.baseClass = QgsBasicRelationWidget
QgsBasicRelationWidget.Buttons.baseClass = QgsBasicRelationWidget
Buttons = QgsBasicRelationWidget # dirty hack since SIP seems to introduce the flags in module

View File

@ -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,35 @@ 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 );
%Docstring
Defines the buttons which are shown
.. versionadded:: 3.16
.. deprecated:: QGIS 3.18
%End
QgsAttributeEditorRelation::Buttons visibleButtons() const;
QgsAttributeEditorRelation::Buttons visibleButtons() const;
%Docstring
Returns the buttons which are shown
.. versionadded:: 3.16
.. deprecated:: QGIS 3.18
%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
%End
QVariantMap widgetConfig() const;
%Docstring
Returns the whole widget config
%End
bool forceSuppressFormPopup() const;

View File

@ -0,0 +1,110 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsbasicrelationwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsBasicRelationWidget : QgsRelationWidget
{
%Docstring
The default relation widget in QGIS. Successor of the now deprecated {:py:class:`QgsRelationEditorWidget`}.
.. versionadded:: 3.18
%End
%TypeHeaderCode
#include "qgsbasicrelationwidget.h"
%End
public:
enum Button
{
Link,
Unlink,
SaveChildEdits,
AddChildFeature,
DuplicateChildFeature,
DeleteChildFeature,
ZoomToChildFeature,
AllButtons
};
typedef QFlags<QgsBasicRelationWidget::Button> Buttons;
QgsBasicRelationWidget( const QVariantMap &config, QWidget *parent /TransferThis/ = 0 );
%Docstring
Constructor
:param config: widget configuration
:param parent: parent widget
%End
void setViewMode( QgsDualView::ViewMode mode );
%Docstring
Define the view mode for the dual view
%End
QgsDualView::ViewMode viewMode();
%Docstring
Gets the view mode for the dual view
%End
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
void setVisibleButtons( const Buttons &buttons );
%Docstring
Defines the buttons which are shown
%End
Buttons visibleButtons() const;
%Docstring
Returns the buttons which are shown
%End
virtual QVariantMap config() const;
%Docstring
Returns the current configuration
%End
virtual void setConfig( const QVariantMap &config );
%Docstring
Defines the current configuration
%End
virtual void setTitle( const QString &title );
%Docstring
Sets the title of the root groupbox
%End
public slots:
virtual void parentFormValueChanged( const QString &attribute, const QVariant &newValue );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsbasicrelationwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -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

View File

@ -0,0 +1,77 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsrelationconfigwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsRelationConfigWidget : 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:`QgsRelationWidgetFactory`}
.. versionadded:: 3.18
%End
%TypeHeaderCode
#include "qgsrelationconfigwidget.h"
%End
public:
explicit QgsRelationConfigWidget( 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
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsrelationconfigwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,226 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsrelationwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsRelationWidget : QWidget
{
%Docstring
Base class to build new relation widgets.
.. versionadded:: 3.18
%End
%TypeHeaderCode
#include "qgsrelationwidget.h"
%End
public:
QgsRelationWidget( 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
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
QgsIFeatureSelectionManager *featureSelectionManager();
%Docstring
The feature selection manager is responsible for the selected features
which are currently being edited.
%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 deleteSelectedFeatures();
%Docstring
Deletes the currently selected features
%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 unlinkSelectedFeatures();
%Docstring
Unlinks the selected features from the relation
%End
void duplicateFeature();
%Docstring
Duplicates a feature
%End
void zoomToSelectedFeatures();
%Docstring
Zooms to the selected 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
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsrelationwidget.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,75 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsrelationwidgetfactory.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsRelationWidgetFactory
{
%Docstring
Factory class for creating relation widgets and their corresponding config widgets
.. versionadded:: 3.18
%End
%TypeHeaderCode
#include "qgsrelationwidgetfactory.h"
%End
public:
QgsRelationWidgetFactory();
%Docstring
Creates a new relation widget factory with given ``name``
%End
virtual ~QgsRelationWidgetFactory();
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 QgsRelationWidget *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 QgsRelationConfigWidget *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/qgsrelationwidgetfactory.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -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:`QgsBasicRelationWidget`} is protected from removing.
.. versionadded:: 3.18
%End
%TypeHeaderCode
#include "qgsrelationwidgetregistry.h"
%End
public:
QgsRelationWidgetRegistry();
%Docstring
Constructor
%End
~QgsRelationWidgetRegistry();
void addRelationWidget( QgsRelationWidgetFactory *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<QString, QgsRelationWidgetFactory *> factories() const;
%Docstring
Gets access to all registered factories
%End
QgsRelationWidget *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
QgsRelationConfigWidget *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 *
************************************************************************/

View File

@ -181,6 +181,11 @@
%Include auto_generated/qgsrasterpyramidsoptionswidget.sip
%Include auto_generated/qgsratiolockbutton.sip
%Include auto_generated/qgsrelationeditorwidget.sip
%Include auto_generated/qgsrelationconfigwidget.sip
%Include auto_generated/qgsbasicrelationwidget.sip
%Include auto_generated/qgsrelationwidget.sip
%Include auto_generated/qgsrelationwidgetfactory.sip
%Include auto_generated/qgsrelationwidgetregistry.sip
%Include auto_generated/qgsrubberband.sip
%Include auto_generated/qgsscalecombobox.sip
%Include auto_generated/qgsscalerangewidget.sip

View File

@ -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
@ -211,6 +228,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 );

View File

@ -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:
@ -458,14 +473,16 @@ class CORE_EXPORT QgsAttributeEditorRelation : public QgsAttributeEditorElement
/**
* Defines the buttons which are shown
* \since QGIS 3.16
* \deprecated since QGIS 3.18 use setConfig() instead
*/
void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons );
Q_DECL_DEPRECATED void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons );
/**
* Returns the buttons which are shown
* \since QGIS 3.16
* \deprecated since QGIS 3.18 use setConfig() instead
*/
QgsAttributeEditorRelation::Buttons visibleButtons() const {return mButtons;}
Q_DECL_DEPRECATED QgsAttributeEditorRelation::Buttons visibleButtons() const {return mButtons;}
/**
* Determines the force suppress form popup status.
@ -507,6 +524,16 @@ class CORE_EXPORT QgsAttributeEditorRelation : public QgsAttributeEditorElement
*/
void setLabel( const QString &label = QString() );
/**
* Returns the current relation widget type id
*/
QString relationWidgetTypeId() const;
/**
* Sets the relation widget type
*/
void setRelationWidgetTypeId( const QString &relationWidgetTypeId );
private:
void saveConfiguration( QDomElement &elem ) const override;
QString typeIdentifier() const override;
@ -516,6 +543,7 @@ class CORE_EXPORT QgsAttributeEditorRelation : public QgsAttributeEditorElement
bool mForceSuppressFormPopup = false;
QVariant mNmRelationId;
QString mLabel;
QString mRelationWidgetTypeId;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsAttributeEditorRelation::Buttons )

View File

@ -24,6 +24,7 @@
#include "qgsapplication.h"
#include "qgsmessagelog.h"
QgsAttributeEditorContainer::~QgsAttributeEditorContainer()
{
qDeleteAll( mChildren );
@ -660,20 +661,36 @@ 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" ) )
QVariantMap config = QgsXmlUtils::readVariant( elem.firstChildElement( "config" ) ).toMap();
// load defaults
if ( config.isEmpty() )
config = relElement->config();
// pre QGIS 3.18 compatibility
Q_NOWARN_DEPRECATED_PUSH
if ( ! config.contains( QStringLiteral( "buttons" ) ) )
{
QString buttonString = elem.attribute( QStringLiteral( "buttons" ), qgsFlagValueToKeys( QgsAttributeEditorRelation::Button::AllButtons ) );
relElement->setVisibleButtons( 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() );
relElement->setVisibleButtons( buttons );
if ( elem.hasAttribute( "buttons" ) )
{
QString buttonString = elem.attribute( QStringLiteral( "buttons" ), qgsFlagValueToKeys( QgsAttributeEditorRelation::Button::AllButtons ) );
relElement->setVisibleButtons( qgsFlagKeysToValue( buttonString, QgsAttributeEditorRelation::Button::AllButtons ) );
config.insert( "buttons", qgsFlagValueToKeys( relElement->visibleButtons() ) );
}
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( relElement->visibleButtons() ) );
}
}
Q_NOWARN_DEPRECATED_POP
relElement->setConfig( config );
if ( elem.hasAttribute( QStringLiteral( "forceSuppressFormPopup" ) ) )
{
relElement->setForceSuppressFormPopup( elem.attribute( QStringLiteral( "forceSuppressFormPopup" ) ).toInt() );
@ -698,6 +715,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" ) )

View File

@ -566,6 +566,13 @@ set(QGIS_GUI_SRCS
qgsrasterlayersaveasdialog.cpp
qgsrasterpyramidsoptionswidget.cpp
qgsrelationeditorwidget.cpp
qgsrelationconfigwidget.cpp
qgsbasicrelationwidget.cpp
qgsbasicrelationconfigwidget.cpp
qgsbasicrelationwidgetfactory.cpp
qgsrelationwidget.cpp
qgsrelationwidgetfactory.cpp
qgsrelationwidgetregistry.cpp
qgsrubberband.cpp
qgsscalecombobox.cpp
qgsscalerangewidget.cpp
@ -812,6 +819,13 @@ set(QGIS_GUI_HDRS
qgsrasterpyramidsoptionswidget.h
qgsratiolockbutton.h
qgsrelationeditorwidget.h
qgsrelationconfigwidget.h
qgsbasicrelationwidget.h
qgsbasicrelationconfigwidget.h
qgsbasicrelationwidgetfactory.h
qgsrelationwidget.h
qgsrelationwidgetfactory.h
qgsrelationwidgetregistry.h
qgsrubberband.h
qgsscalecombobox.h
qgsscalerangewidget.h

View File

@ -15,6 +15,8 @@
#include "qgsattributewidgetedit.h"
#include "qgsattributesformproperties.h"
#include "qgsrelationwidgetregistry.h"
#include "qgsrelationconfigwidget.h"
QgsAttributeWidgetEdit::QgsAttributeWidgetEdit( QTreeWidgetItem *item, QWidget *parent )
@ -29,7 +31,6 @@ QgsAttributeWidgetEdit::QgsAttributeWidgetEdit( QTreeWidgetItem *item, QWidget *
// common configs
mShowLabelCheckBox->setChecked( itemData.showLabel() );
switch ( itemData.type() )
{
case QgsAttributesFormProperties::DnDTreeItemData::Relation:
@ -92,18 +93,18 @@ QgsAttributeWidgetRelationEditWidget::QgsAttributeWidgetRelationEditWidget( QWid
: QWidget( parent )
{
setupUi( this );
QMapIterator<QString, QgsRelationWidgetFactory *> 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,7 +116,30 @@ void QgsAttributeWidgetRelationEditWidget::setRelationEditorConfiguration( const
setCardinalityCombo( QStringLiteral( "%1 (%2)" ).arg( nmrel.referencedLayer()->name(), nmrel.fieldPairs().at( 0 ).referencedField() ), nmrel.id() );
}
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." ) );
int widgetTypeIdx = mWidgetTypeComboBox->findText( 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();
mConfigWidget->deleteLater();
mConfigWidget = QgsGui::relationWidgetRegistry()->createConfigWidget( widgetId, relation, this );
mConfigWidget->setConfig( config.mRelationWidgetConfig );
mWidgetTypePlaceholderLayout->addWidget( mConfigWidget );
update();
} );
mRelationCardinalityCombo->setToolTip( tr( "This is being changed" ) );
mRelationCardinalityCombo->addItem( "CHECKED" );
setNmRelationId( config.nmRelationId );
mRelationLabelEdit->setText( config.label );
@ -126,15 +150,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();

View File

@ -29,6 +29,7 @@
#include "qgis_gui.h"
class QTreeWidgetItem;
class QgsRelationConfigWidget;
/**
* 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;
QgsRelationConfigWidget *mConfigWidget = nullptr;
};
#endif // QGSATTRIBUTEWIDGETEDIT_H

View File

@ -19,12 +19,19 @@
#include "qgsattributeeditorcontext.h"
#include "qgsproject.h"
#include "qgsrelationmanager.h"
#include "qgsrelationwidgetregistry.h"
#include "qgsgui.h"
#include <QWidget>
QgsRelationWidgetWrapper::QgsRelationWidgetWrapper( QgsVectorLayer *vl, const QgsRelation &relation, QWidget *editor, QWidget *parent )
: QgsRelationWidgetWrapper( QStringLiteral( "basic" ), vl, relation, editor, parent )
{
}
QgsRelationWidgetWrapper::QgsRelationWidgetWrapper( const QString &relationEditorName, QgsVectorLayer *vl, const QgsRelation &relation, QWidget *editor, QWidget *parent )
: QgsWidgetWrapper( vl, editor, parent )
, mRelation( relation )
, mRelationEditorName( relationEditorName )
{
}
@ -34,7 +41,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( mRelationEditorName, widgetConfig(), parent );
if ( !widget )
{
QgsLogger::warning( QStringLiteral( "Failed to create relation widget \"%1\", fallback to \"basic\" relation widget" ).arg( mRelationEditorName ) );
widget = QgsGui::instance()->relationWidgetRegistry()->create( QStringLiteral( "basic" ), widgetConfig(), parent );
}
return widget;
}
void QgsRelationWidgetWrapper::setFeature( const QgsFeature &feature )
@ -101,25 +116,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 +146,12 @@ void QgsRelationWidgetWrapper::setShowLabel( bool showLabel )
void QgsRelationWidgetWrapper::initWidget( QWidget *editor )
{
QgsRelationEditorWidget *w = qobject_cast<QgsRelationEditorWidget *>( editor );
QgsRelationWidget *w = qobject_cast<QgsRelationWidget *>( 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( mRelationEditorName, widgetConfig(), editor );
editor->layout()->addWidget( 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();
}

View File

@ -20,7 +20,7 @@
#include "qgis_sip.h"
#include "qgis_gui.h"
class QgsRelationEditorWidget;
class QgsRelationWidget;
/**
* \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,29 @@ class GUI_EXPORT QgsRelationWidgetWrapper : public QgsWidgetWrapper
/**
* Defines the buttons which are shown
* \since QGIS 3.16
* \deprecated since QGIS 3.18
*/
void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons );
Q_DECL_DEPRECATED void setVisibleButtons( const QgsAttributeEditorRelation::Buttons &buttons );
/**
* Returns the buttons which are shown
* \since QGIS 3.16
* \deprecated since QGIS 3.18
*/
QgsAttributeEditorRelation::Buttons visibleButtons() const;
Q_DECL_DEPRECATED QgsAttributeEditorRelation::Buttons visibleButtons() const;
/**
* Will set the config of this widget wrapper to the specified config.
*
* \param config The config for this wrapper
*/
void setWidgetConfig( const QVariantMap &config );
/**
* Returns the whole widget config
*/
QVariantMap widgetConfig() const;
/**
* Determines the force suppress form popup status that is configured for this widget
@ -187,7 +216,8 @@ class GUI_EXPORT QgsRelationWidgetWrapper : public QgsWidgetWrapper
void aboutToSave() override;
QgsRelation mRelation;
QgsRelation mNmRelation;
QgsRelationEditorWidget *mWidget = nullptr;
QString mRelationEditorName;
QgsRelationWidget *mWidget = nullptr;
};
#endif // QGSRELATIONWIDGETWRAPPER_H

View File

@ -1254,7 +1254,12 @@ QList<QgsEditorWidgetWrapper *> 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 );
@ -1943,7 +1948,7 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt
{
const QgsAttributeEditorRelation *relDef = static_cast<const QgsAttributeEditorRelation *>( 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() );

View File

@ -415,6 +415,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
QList<QgsEditorWidgetWrapper *> constraintDependencies( QgsEditorWidgetWrapper *w );
QgsRelationWidgetWrapper *setupRelationWidgetWrapper( const QgsRelation &rel, const QgsAttributeEditorContext &context );
QgsRelationWidgetWrapper *setupRelationWidgetWrapper( const QString &relationWidgetTypeId, const QgsRelation &rel, const QgsAttributeEditorContext &context );
QgsVectorLayer *mLayer = nullptr;
QgsFeature mFeature;

View File

@ -0,0 +1,57 @@
/***************************************************************************
qgsbasicrelationconfigwidget.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 "qgsbasicrelationconfigwidget.h"
#include "qgsbasicrelationwidget.h"
QgsBasicRelationConfigWidget::QgsBasicRelationConfigWidget( const QgsRelation &relation, QWidget *parent )
: QgsRelationConfigWidget( relation, parent )
{
setupUi( this );
}
QVariantMap QgsBasicRelationConfigWidget::config()
{
QgsBasicRelationWidget::Buttons buttons;
buttons.setFlag( QgsBasicRelationWidget::Button::Link, mRelationShowLinkCheckBox->isChecked() );
buttons.setFlag( QgsBasicRelationWidget::Button::Unlink, mRelationShowUnlinkCheckBox->isChecked() );
buttons.setFlag( QgsBasicRelationWidget::Button::AddChildFeature, mRelationShowAddChildCheckBox->isChecked() );
buttons.setFlag( QgsBasicRelationWidget::Button::DuplicateChildFeature, mRelationShowDuplicateChildFeatureCheckBox->isChecked() );
buttons.setFlag( QgsBasicRelationWidget::Button::ZoomToChildFeature, mRelationShowZoomToFeatureCheckBox->isChecked() );
buttons.setFlag( QgsBasicRelationWidget::Button::DeleteChildFeature, mRelationDeleteChildFeatureCheckBox->isChecked() );
buttons.setFlag( QgsBasicRelationWidget::Button::SaveChildEdits, mRelationShowSaveChildEditsCheckBox->isChecked() );
return QVariantMap(
{
{"buttons", qgsFlagValueToKeys( buttons )},
} );
}
void QgsBasicRelationConfigWidget::setConfig( const QVariantMap &config )
{
const QgsBasicRelationWidget::Buttons buttons = qgsFlagKeysToValue( config.value( QStringLiteral( "buttons" ) ).toString(), QgsBasicRelationWidget::Button::AllButtons );
mRelationShowLinkCheckBox->setChecked( buttons.testFlag( QgsBasicRelationWidget::Button::Link ) );
mRelationShowUnlinkCheckBox->setChecked( buttons.testFlag( QgsBasicRelationWidget::Button::Unlink ) );
mRelationShowAddChildCheckBox->setChecked( buttons.testFlag( QgsBasicRelationWidget::Button::AddChildFeature ) );
mRelationShowDuplicateChildFeatureCheckBox->setChecked( buttons.testFlag( QgsBasicRelationWidget::Button::DuplicateChildFeature ) );
mRelationShowZoomToFeatureCheckBox->setChecked( buttons.testFlag( QgsBasicRelationWidget::Button::ZoomToChildFeature ) );
mRelationDeleteChildFeatureCheckBox->setChecked( buttons.testFlag( QgsBasicRelationWidget::Button::DeleteChildFeature ) );
mRelationShowSaveChildEditsCheckBox->setChecked( buttons.testFlag( QgsBasicRelationWidget::Button::SaveChildEdits ) );
}

View File

@ -0,0 +1,62 @@
/***************************************************************************
qgsbasicrelationconfigwidget.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 QGSBASICRELATIONCONFIGWIDGET_H
#define QGSBASICRELATIONCONFIGWIDGET_H
#include "ui_qgsrelationeditorconfigwidgetbase.h"
#include "qgsrelationconfigwidget.h"
#define SIP_NO_FILE
/**
* \ingroup gui
* \class QgsBasicRelationConfigWidget
* Creates a new configuration widget for the basic relation widget
* \since QGIS 3.18
*/
class QgsBasicRelationConfigWidget : public QgsRelationConfigWidget, private Ui::QgsRelationEditorConfigWidgetBase
{
public:
/**
* Create a new configuration widget
*
* \param relation The relation for which the configuration dialog will be created
* \param parent A parent widget
*/
explicit QgsBasicRelationConfigWidget( const QgsRelation &relation, QWidget *parent SIP_TRANSFERTHIS );
/**
* \brief Create a configuration from the current GUI state
*
* \returns A widget configuration
*/
QVariantMap config();
/**
* \brief Update the configuration widget to represent the given configuration.
*
* \param config The configuration which should be represented by this widget
*/
void setConfig( const QVariantMap &config );
};
#endif // QGSBASICRELATIONCONFIGWIDGET_H

View File

@ -0,0 +1,558 @@
/***************************************************************************
qgsbasicrelationwidget.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 "qgsbasicrelationwidget.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 <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
QgsBasicRelationWidget::QgsBasicRelationWidget( const QVariantMap &config, QWidget *parent )
: QgsRelationWidget( config, parent )
, mButtonsVisibility( qgsFlagKeysToValue( config.value( QStringLiteral( "buttons" ) ).toString(), QgsBasicRelationWidget::Button::AllButtons ) )
{
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 );
// buttons
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->setContentsMargins( 0, 0, 0, 0 );
// toggle editing
mToggleEditingButton = new QToolButton( this );
mToggleEditingButton->setObjectName( QStringLiteral( "mToggleEditingButton" ) );
mToggleEditingButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
mToggleEditingButton->setText( tr( "Toggle Editing" ) );
mToggleEditingButton->setEnabled( false );
mToggleEditingButton->setCheckable( true );
mToggleEditingButton->setToolTip( tr( "Toggle editing mode for child layer" ) );
buttonLayout->addWidget( mToggleEditingButton );
// save Edits
mSaveEditsButton = new QToolButton( this );
mSaveEditsButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) );
mSaveEditsButton->setText( tr( "Save Child Layer Edits" ) );
mSaveEditsButton->setToolTip( tr( "Save child layer edits" ) );
mSaveEditsButton->setEnabled( true );
buttonLayout->addWidget( mSaveEditsButton );
// add feature with geometry
mAddFeatureGeometryButton = new QToolButton( this );
mAddFeatureGeometryButton->setObjectName( QStringLiteral( "mAddFeatureGeometryButton" ) );
buttonLayout->addWidget( mAddFeatureGeometryButton );
// add feature
mAddFeatureButton = new QToolButton( this );
mAddFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewTableRow.svg" ) ) );
mAddFeatureButton->setText( tr( "Add Child Feature" ) );
mAddFeatureButton->setToolTip( tr( "Add child feature" ) );
mAddFeatureButton->setObjectName( QStringLiteral( "mAddFeatureButton" ) );
buttonLayout->addWidget( mAddFeatureButton );
// duplicate feature
mDuplicateFeatureButton = new QToolButton( this );
mDuplicateFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateFeature.svg" ) ) );
mDuplicateFeatureButton->setText( tr( "Duplicate Child Feature" ) );
mDuplicateFeatureButton->setToolTip( tr( "Duplicate child feature" ) );
mDuplicateFeatureButton->setObjectName( QStringLiteral( "mDuplicateFeatureButton" ) );
buttonLayout->addWidget( mDuplicateFeatureButton );
// delete feature
mDeleteFeatureButton = new QToolButton( this );
mDeleteFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ) );
mDeleteFeatureButton->setText( tr( "Delete Child Feature" ) );
mDeleteFeatureButton->setToolTip( tr( "Delete child feature" ) );
mDeleteFeatureButton->setObjectName( QStringLiteral( "mDeleteFeatureButton" ) );
buttonLayout->addWidget( mDeleteFeatureButton );
// link feature
mLinkFeatureButton = new QToolButton( this );
mLinkFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionLink.svg" ) ) );
mLinkFeatureButton->setText( tr( "Link Existing Features" ) );
mLinkFeatureButton->setToolTip( tr( "Link existing child features" ) );
mLinkFeatureButton->setObjectName( QStringLiteral( "mLinkFeatureButton" ) );
buttonLayout->addWidget( mLinkFeatureButton );
// unlink feature
mUnlinkFeatureButton = new QToolButton( this );
mUnlinkFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUnlink.svg" ) ) );
mUnlinkFeatureButton->setText( tr( "Unlink Feature" ) );
mUnlinkFeatureButton->setToolTip( tr( "Unlink child feature" ) );
mUnlinkFeatureButton->setObjectName( QStringLiteral( "mUnlinkFeatureButton" ) );
buttonLayout->addWidget( mUnlinkFeatureButton );
// zoom to linked feature
mZoomToFeatureButton = new QToolButton( this );
mZoomToFeatureButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomToSelected.svg" ) ) );
mZoomToFeatureButton->setText( tr( "Zoom To Feature" ) );
mZoomToFeatureButton->setToolTip( tr( "Zoom to child feature" ) );
mZoomToFeatureButton->setObjectName( QStringLiteral( "mZoomToFeatureButton" ) );
buttonLayout->addWidget( mZoomToFeatureButton );
// spacer
buttonLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding ) );
// form view
mFormViewButton = new QToolButton( this );
mFormViewButton->setText( tr( "Form View" ) );
mFormViewButton->setToolTip( tr( "Switch to form view" ) );
mFormViewButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
mFormViewButton->setCheckable( true );
mFormViewButton->setChecked( mViewMode == QgsDualView::AttributeEditor );
buttonLayout->addWidget( mFormViewButton );
// table view
mTableViewButton = new QToolButton( this );
mTableViewButton->setText( tr( "Table View" ) );
mTableViewButton->setToolTip( tr( "Switch to table view" ) );
mTableViewButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) ) );
mTableViewButton->setCheckable( true );
mTableViewButton->setChecked( mViewMode == QgsDualView::AttributeTable );
buttonLayout->addWidget( mTableViewButton );
// button group
mViewModeButtonGroup = new QButtonGroup( this );
mViewModeButtonGroup->addButton( mFormViewButton, QgsDualView::AttributeEditor );
mViewModeButtonGroup->addButton( mTableViewButton, QgsDualView::AttributeTable );
// add buttons layout
topLayout->addLayout( buttonLayout );
mRelationLayout = new QGridLayout();
mRelationLayout->setContentsMargins( 0, 0, 0, 0 );
topLayout->addLayout( mRelationLayout );
mDualView = new QgsDualView( this );
mDualView->setView( mViewMode );
mRelationLayout->addWidget( mDualView );
// connect( this, &QgsCollapsibleGroupBoxBasic::collapsedStateChanged, this, &QgsRelationEditorWidget2::onCollapsedStateChanged );
connect( mViewModeButtonGroup, static_cast<void ( QButtonGroup::* )( int )>( &QButtonGroup::buttonClicked ),
this, static_cast<void ( QgsBasicRelationWidget::* )( int )>( &QgsBasicRelationWidget::setViewMode ) );
connect( mToggleEditingButton, &QAbstractButton::clicked, this, &QgsBasicRelationWidget::toggleEditing );
connect( mSaveEditsButton, &QAbstractButton::clicked, this, &QgsBasicRelationWidget::saveEdits );
connect( mAddFeatureButton, &QAbstractButton::clicked, this, [this]() { addFeature(); } );
connect( mAddFeatureGeometryButton, &QAbstractButton::clicked, this, &QgsBasicRelationWidget::addFeatureGeometry );
connect( mDuplicateFeatureButton, &QAbstractButton::clicked, this, &QgsBasicRelationWidget::duplicateFeature );
connect( mDeleteFeatureButton, &QAbstractButton::clicked, this, &QgsBasicRelationWidget::deleteSelectedFeatures );
connect( mLinkFeatureButton, &QAbstractButton::clicked, this, &QgsBasicRelationWidget::linkFeature );
connect( mUnlinkFeatureButton, &QAbstractButton::clicked, this, &QgsBasicRelationWidget::unlinkSelectedFeatures );
connect( mZoomToFeatureButton, &QAbstractButton::clicked, this, &QgsBasicRelationWidget::zoomToSelectedFeatures );
connect( mDualView, &QgsDualView::showContextMenuExternally, this, &QgsBasicRelationWidget::showContextMenu );
// Set initial state for add/remove etc. buttons
updateButtons();
}
void QgsBasicRelationWidget::initDualView( QgsVectorLayer *layer, const QgsFeatureRequest &request )
{
QgsAttributeEditorContext ctx { mEditorContext };
ctx.setParentFormFeature( mFeature );
mDualView->init( layer, mEditorContext.mapCanvas(), request, ctx );
mFeatureSelectionMgr = new QgsFilteredSelectionManager( layer, request, mDualView );
mDualView->setFeatureSelectionManager( mFeatureSelectionMgr );
connect( mFeatureSelectionMgr, &QgsIFeatureSelectionManager::selectionChanged, this, &QgsBasicRelationWidget::updateButtons );
QIcon icon;
QString text;
if ( layer->geometryType() == QgsWkbTypes::PointGeometry )
{
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePoint.svg" ) );
text = tr( "Add Point child Feature" );
}
else if ( layer->geometryType() == QgsWkbTypes::LineGeometry )
{
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionCaptureLine.svg" ) );
text = tr( "Add Line child Feature" );
}
else if ( layer->geometryType() == QgsWkbTypes::PolygonGeometry )
{
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePolygon.svg" ) );
text = tr( "Add Polygon Feature" );
}
mAddFeatureGeometryButton->setIcon( icon );
mAddFeatureGeometryButton->setText( text );
mAddFeatureGeometryButton->setToolTip( text );
updateButtons();
}
void QgsBasicRelationWidget::setEditorContext( const QgsAttributeEditorContext &context )
{
mEditorContext = context;
if ( context.mapCanvas() && context.cadDockWidget() )
{
mMapToolDigitize.reset( new QgsMapToolDigitizeFeature( context.mapCanvas(), context.cadDockWidget() ) );
mMapToolDigitize->setButton( mAddFeatureGeometryButton );
}
updateButtons();
}
void QgsBasicRelationWidget::setViewMode( QgsDualView::ViewMode mode )
{
mDualView->setView( mode );
mViewMode = mode;
}
void QgsBasicRelationWidget::updateButtons()
{
bool editable = false;
bool linkable = false;
bool spatial = false;
bool selectionNotEmpty = mFeatureSelectionMgr ? mFeatureSelectionMgr->selectedFeatureCount() : false;
if ( mRelation.isValid() )
{
editable = mRelation.referencingLayer()->isEditable();
linkable = mRelation.referencingLayer()->isEditable();
spatial = mRelation.referencingLayer()->isSpatial();
}
if ( mNmRelation.isValid() )
{
editable = mNmRelation.referencedLayer()->isEditable();
spatial = mNmRelation.referencedLayer()->isSpatial();
}
mAddFeatureButton->setEnabled( editable );
mAddFeatureGeometryButton->setEnabled( editable );
mDuplicateFeatureButton->setEnabled( editable && selectionNotEmpty );
mLinkFeatureButton->setEnabled( linkable );
mDeleteFeatureButton->setEnabled( editable && selectionNotEmpty );
mUnlinkFeatureButton->setEnabled( linkable && selectionNotEmpty );
mZoomToFeatureButton->setEnabled( selectionNotEmpty );
mToggleEditingButton->setChecked( editable );
mSaveEditsButton->setEnabled( editable );
mToggleEditingButton->setVisible( !mLayerInSameTransactionGroup );
mLinkFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsBasicRelationWidget::Button::Link ) );
mUnlinkFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsBasicRelationWidget::Button::Unlink ) );
mSaveEditsButton->setVisible( mButtonsVisibility.testFlag( QgsBasicRelationWidget::Button::SaveChildEdits ) && !mLayerInSameTransactionGroup );
mAddFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsBasicRelationWidget::Button::AddChildFeature ) );
mAddFeatureGeometryButton->setVisible( mButtonsVisibility.testFlag( QgsBasicRelationWidget::Button::AddChildFeature ) && mEditorContext.mapCanvas() && mEditorContext.cadDockWidget() && spatial );
mDuplicateFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsBasicRelationWidget::Button::DuplicateChildFeature ) );
mDeleteFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsBasicRelationWidget::Button::DeleteChildFeature ) );
mZoomToFeatureButton->setVisible( mButtonsVisibility.testFlag( QgsBasicRelationWidget::Button::ZoomToChildFeature ) && mEditorContext.mapCanvas() && spatial );
}
void QgsBasicRelationWidget::addFeatureGeometry()
{
QgsVectorLayer *layer = nullptr;
if ( mNmRelation.isValid() )
layer = mNmRelation.referencedLayer();
else
layer = mRelation.referencingLayer();
mMapToolDigitize->setLayer( layer );
// window is always on top, so we hide it to digitize without seeing it
window()->setVisible( false );
setMapTool( mMapToolDigitize );
connect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsBasicRelationWidget::onDigitizingCompleted );
connect( mEditorContext.mapCanvas(), &QgsMapCanvas::keyPressed, this, &QgsBasicRelationWidget::onKeyPressed );
if ( auto *lMainMessageBar = mEditorContext.mainMessageBar() )
{
QString displayString = QgsVectorLayerUtils::getFeatureDisplayString( layer, mFeature );
QString title = tr( "Create child feature for parent %1 \"%2\"" ).arg( mRelation.referencedLayer()->name(), displayString );
QString msg = tr( "Digitize the geometry for the new feature on layer %1. Press &lt;ESC&gt; to cancel." )
.arg( layer->name() );
mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
lMainMessageBar->pushItem( mMessageBarItem );
}
}
void QgsBasicRelationWidget::onDigitizingCompleted( const QgsFeature &feature )
{
addFeature( feature.geometry() );
unsetMapTool();
}
void QgsBasicRelationWidget::toggleEditing( bool state )
{
QgsRelationWidget::toggleEditing( state );
updateButtons();
}
void QgsBasicRelationWidget::onCollapsedStateChanged( bool collapsed )
{
if ( !collapsed )
{
mVisible = true;
updateUi();
}
}
void QgsBasicRelationWidget::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
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 );
}
}
}
void QgsBasicRelationWidget::setVisibleButtons( const Buttons &buttons )
{
mButtonsVisibility = buttons;
updateButtons();
}
QgsBasicRelationWidget::Buttons QgsBasicRelationWidget::visibleButtons() const
{
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 QgsBasicRelationWidget::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
{
mDualView->parentFormValueChanged( attribute, newValue );
}
void QgsBasicRelationWidget::showContextMenu( QgsActionMenu *menu, const QgsFeatureId fid )
{
if ( mRelation.referencingLayer()->isEditable() )
{
QAction *qAction = nullptr;
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 ); } );
}
}
void QgsBasicRelationWidget::setMapTool( QgsMapTool *mapTool )
{
QgsMapCanvas *mapCanvas = mEditorContext.mapCanvas();
mapCanvas->setMapTool( mapTool );
mapCanvas->window()->raise();
mapCanvas->activateWindow();
mapCanvas->setFocus();
connect( mapTool, &QgsMapTool::deactivated, this, &QgsBasicRelationWidget::mapToolDeactivated );
}
void QgsBasicRelationWidget::unsetMapTool()
{
QgsMapCanvas *mapCanvas = mEditorContext.mapCanvas();
// this will call mapToolDeactivated
mapCanvas->unsetMapTool( mMapToolDigitize );
disconnect( mapCanvas, &QgsMapCanvas::keyPressed, this, &QgsBasicRelationWidget::onKeyPressed );
disconnect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsBasicRelationWidget::onDigitizingCompleted );
}
void QgsBasicRelationWidget::onKeyPressed( QKeyEvent *e )
{
if ( e->key() == Qt::Key_Escape )
{
unsetMapTool();
}
}
void QgsBasicRelationWidget::mapToolDeactivated()
{
window()->setVisible( true );
window()->raise();
window()->activateWindow();
if ( mEditorContext.mainMessageBar() && mMessageBarItem )
{
mEditorContext.mainMessageBar()->popWidget( mMessageBarItem );
}
mMessageBarItem = nullptr;
}
QVariantMap QgsBasicRelationWidget::config() const
{
return QVariantMap( {{"buttons", qgsFlagValueToKeys( visibleButtons() )}} );
}
void QgsBasicRelationWidget::setConfig( const QVariantMap &config )
{
mButtonsVisibility = qgsFlagKeysToValue( config.value( QStringLiteral( "buttons" ) ).toString(), QgsBasicRelationWidget::Button::AllButtons );
updateButtons();
}
void QgsBasicRelationWidget::setTitle( const QString &title )
{
mRootCollapsibleGroupBox->setTitle( title );
}
void QgsBasicRelationWidget::beforeSetRelationFeature( const QgsRelation &newRelation, const QgsFeature &newFeature )
{
Q_UNUSED( newRelation );
Q_UNUSED( newFeature );
if ( ! mRelation.isValid() )
return;
disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsBasicRelationWidget::updateButtons );
disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsBasicRelationWidget::updateButtons );
}
void QgsBasicRelationWidget::afterSetRelationFeature()
{
mToggleEditingButton->setEnabled( false );
if ( ! mRelation.isValid() )
return;
connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsBasicRelationWidget::updateButtons );
connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsBasicRelationWidget::updateButtons );
QgsVectorLayer *vl = mRelation.referencingLayer();
bool canChangeAttributes = vl->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues;
if ( canChangeAttributes && !vl->readOnly() )
{
mToggleEditingButton->setEnabled( true );
updateButtons();
}
else
{
mToggleEditingButton->setEnabled( false );
}
// 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 QgsBasicRelationWidget::beforeSetRelations( const QgsRelation &newRelation, const QgsRelation &newNmRelation )
{
Q_UNUSED( newRelation );
Q_UNUSED( newNmRelation );
if ( mRelation.isValid() )
{
disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsBasicRelationWidget::updateButtons );
disconnect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsBasicRelationWidget::updateButtons );
}
if ( mNmRelation.isValid() )
{
disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsBasicRelationWidget::updateButtons );
disconnect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsBasicRelationWidget::updateButtons );
}
}
void QgsBasicRelationWidget::afterSetRelations()
{
if ( !mRelation.isValid() )
return;
connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStarted, this, &QgsBasicRelationWidget::updateButtons );
connect( mRelation.referencingLayer(), &QgsVectorLayer::editingStopped, this, &QgsBasicRelationWidget::updateButtons );
if ( mNmRelation.isValid() )
{
connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStarted, this, &QgsBasicRelationWidget::updateButtons );
connect( mNmRelation.referencedLayer(), &QgsVectorLayer::editingStopped, this, &QgsBasicRelationWidget::updateButtons );
}
QgsVectorLayer *vl = mRelation.referencingLayer();
bool canChangeAttributes = vl->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues;
if ( canChangeAttributes && !vl->readOnly() )
{
mToggleEditingButton->setEnabled( true );
}
else
{
mToggleEditingButton->setEnabled( false );
}
updateButtons();
}

View File

@ -0,0 +1,172 @@
/***************************************************************************
qgsbasicrelationwidget.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 QGSBASICRELATIONWIDGET_H
#define QGSBASICRELATIONWIDGET_H
#include <QWidget>
#include <QToolButton>
#include <QButtonGroup>
#include <QGridLayout>
#include "qobjectuniqueptr.h"
#include "qgsrelationeditorwidget.h"
#include "qgsrelationwidget.h"
#include "qobjectuniqueptr.h"
#include "qgsattributeeditorcontext.h"
#include "qgscollapsiblegroupbox.h"
#include "qgsdualview.h"
#include "qgsrelation.h"
#include "qgsvectorlayerselectionmanager.h"
#include "qgis_gui.h"
class QgsFeature;
class QgsVectorLayer;
class QgsVectorLayerTools;
class QgsMapTool;
class QgsMapToolDigitizeFeature;
/**
* The default relation widget in QGIS. Successor of the now deprecated {\see QgsRelationEditorWidget}.
* \ingroup gui
* \class QgsBasicRelationWidget
* \since QGIS 3.18
*/
class GUI_EXPORT QgsBasicRelationWidget : public QgsRelationWidget
{
Q_OBJECT
Q_PROPERTY( QgsDualView::ViewMode viewMode READ viewMode WRITE setViewMode )
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
*/
QgsBasicRelationWidget( const QVariantMap &config, QWidget *parent SIP_TRANSFERTHIS = nullptr );
//! Define the view mode for the dual view
void setViewMode( QgsDualView::ViewMode mode );
//! Gets the view mode for the dual view
QgsDualView::ViewMode viewMode() {return mViewMode;}
/**
* 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 );
/**
* Defines the buttons which are shown
*/
void setVisibleButtons( const Buttons &buttons );
/**
* Returns the buttons which are shown
*/
Buttons visibleButtons() const;
/**
* Returns the current configuration
*/
QVariantMap config() const override;
/**
* Defines the current configuration
*/
void setConfig( const QVariantMap &config ) override;
/**
* Sets the title of the root groupbox
*/
void setTitle( const QString &title ) override;
public slots:
void parentFormValueChanged( const QString &attribute, const QVariant &newValue ) override;
private slots:
void setViewMode( int mode ) {setViewMode( static_cast<QgsDualView::ViewMode>( mode ) );}
void updateButtons();
void addFeatureGeometry();
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 );
private:
void updateUi() override;
void initDualView( QgsVectorLayer *layer, const QgsFeatureRequest &request );
void setMapTool( QgsMapTool *mapTool );
void unsetMapTool();
QgsCollapsibleGroupBox *mRootCollapsibleGroupBox = nullptr;
QgsDualView *mDualView = nullptr;
QPointer<QgsMessageBarItem> mMessageBarItem;
QgsDualView::ViewMode mViewMode = QgsDualView::AttributeEditor;
QToolButton *mToggleEditingButton = nullptr;
QToolButton *mSaveEditsButton = nullptr;
QToolButton *mAddFeatureButton = nullptr;
QToolButton *mDuplicateFeatureButton = nullptr;
QToolButton *mDeleteFeatureButton = nullptr;
QToolButton *mLinkFeatureButton = nullptr;
QToolButton *mUnlinkFeatureButton = nullptr;
QToolButton *mZoomToFeatureButton = nullptr;
QToolButton *mFormViewButton = nullptr;
QToolButton *mTableViewButton = nullptr;
QToolButton *mAddFeatureGeometryButton = nullptr;
QGridLayout *mRelationLayout = nullptr;
QObjectUniquePtr<QgsMapToolDigitizeFeature> mMapToolDigitize;
QButtonGroup *mViewModeButtonGroup = 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;
};
#endif // QGSBASICRELATIONWIDGET_H

View File

@ -0,0 +1,46 @@
/***************************************************************************
qgsbasicrelationwidgetfactory.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 "qgsbasicrelationwidgetfactory.h"
#include "qgsbasicrelationconfigwidget.h"
QgsBasicRelationWidgetFactory::QgsBasicRelationWidgetFactory()
{
}
QString QgsBasicRelationWidgetFactory::type() const
{
return QStringLiteral( "basic" );
}
QString QgsBasicRelationWidgetFactory::name() const
{
return QStringLiteral( "Relation Editor" );
}
QgsRelationWidget *QgsBasicRelationWidgetFactory::create( const QVariantMap &config, QWidget *parent ) const
{
return new QgsBasicRelationWidget( config, parent );
}
QgsRelationConfigWidget *QgsBasicRelationWidgetFactory::configWidget( const QgsRelation &relation, QWidget *parent ) const
{
return static_cast<QgsRelationConfigWidget *>( new QgsBasicRelationConfigWidget( relation, parent ) );
}

View File

@ -0,0 +1,48 @@
/***************************************************************************
qgsbasicrelationwidgetfactory.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 QGSBASICRELATIONWIDGETFACTORY_H
#define QGSBASICRELATIONWIDGETFACTORY_H
#include "qgsrelationwidgetfactory.h"
#include "qgsbasicrelationwidget.h"
#define SIP_NO_FILE
/**
* Factory class for creating a basic relation widget and the respective config widget.
* \ingroup gui
* \class QgsBasicRelationWidgetFactory
* \note not available in Python bindings
* \since QGIS 3.18
*/
class GUI_EXPORT QgsBasicRelationWidgetFactory : public QgsRelationWidgetFactory
{
public:
QgsBasicRelationWidgetFactory();
QString type() const override;
QString name() const override;
QgsRelationWidget *create( const QVariantMap &config, QWidget *parent = nullptr ) const override;
QgsRelationConfigWidget *configWidget( const QgsRelation &relation, QWidget *parent ) const override;
};
#endif // QGSBASICRELATIONWIDGETFACTORY_H

View File

@ -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();

View File

@ -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

View File

@ -0,0 +1,37 @@
/***************************************************************************
qgsrelationconfigbasewidget.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 "qgsrelationconfigwidget.h"
#include "qgsexpressioncontextutils.h"
QgsRelationConfigWidget::QgsRelationConfigWidget( const QgsRelation &relation, QWidget *parent )
: QWidget( parent )
, mRelation( relation )
{
}
QgsVectorLayer *QgsRelationConfigWidget::layer()
{
return mLayer;
}
QgsRelation QgsRelationConfigWidget::relation() const
{
return mRelation;
}

View File

@ -0,0 +1,87 @@
/***************************************************************************
qgsrelationconfigwidget.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 QGSRELATIONCONFIGBASEWIDGET_H
#define QGSRELATIONCONFIGBASEWIDGET_H
#include <QWidget>
#include "qgis_sip.h"
#include "qgis_gui.h"
#include "qgseditorwidgetwrapper.h"
class QgsRelation;
class QgsPropertyOverrideButton;
/**
* \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 QgsRelationWidgetFactory}
* \since QGIS 3.18
*/
class GUI_EXPORT QgsRelationConfigWidget : public QWidget
{
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 QgsRelationConfigWidget( 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;
};
#endif // QGSRELATIONCONFIGBASEWIDGET_H

View File

@ -0,0 +1,595 @@
/***************************************************************************
qgsrelationwidget.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 "qgsrelationwidget.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 <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
QgsRelationWidget::QgsRelationWidget( const QVariantMap &config, QWidget *parent )
: QWidget( parent )
{
Q_UNUSED( config );
}
void QgsRelationWidget::setRelationFeature( const QgsRelation &relation, const QgsFeature &feature )
{
beforeSetRelationFeature( relation, feature );
mRelation = relation;
mFeature = feature;
setObjectName( QStringLiteral( "referenced/" ) + mRelation.name() );
updateTitle();
afterSetRelationFeature();
updateUi();
}
void QgsRelationWidget::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 QgsRelationWidget::setEditorContext( const QgsAttributeEditorContext &context )
{
mEditorContext = context;
}
QgsAttributeEditorContext QgsRelationWidget::editorContext() const
{
return mEditorContext;
}
QgsIFeatureSelectionManager *QgsRelationWidget::featureSelectionManager()
{
return mFeatureSelectionMgr;
}
void QgsRelationWidget::setFeature( const QgsFeature &feature, bool update )
{
mFeature = feature;
mEditorContext.setFormFeature( feature );
if ( update )
updateUi();
}
void QgsRelationWidget::setNmRelationId( const QVariant &nmRelationId )
{
mNmRelationId = nmRelationId;
}
QVariant QgsRelationWidget::nmRelationId() const
{
return mNmRelationId;
}
QString QgsRelationWidget::label() const
{
return mLabel;
}
void QgsRelationWidget::setLabel( const QString &label )
{
mLabel = label;
updateTitle();
}
bool QgsRelationWidget::showLabel() const
{
return mShowLabel;
}
void QgsRelationWidget::setShowLabel( bool showLabel )
{
mShowLabel = showLabel;
updateTitle();
}
void QgsRelationWidget::setForceSuppressFormPopup( bool forceSuppressFormPopup )
{
mForceSuppressFormPopup = forceSuppressFormPopup;
}
bool QgsRelationWidget::forceSuppressFormPopup() const
{
return mForceSuppressFormPopup;
}
void QgsRelationWidget::updateTitle()
{
if ( mShowLabel && !mLabel.isEmpty() )
{
setTitle( mLabel );
}
else if ( mShowLabel && mRelation.isValid() )
{
setTitle( mRelation.name() );
}
else
{
setTitle( QString() );
}
}
QgsFeature QgsRelationWidget::feature() const
{
return mFeature;
}
void QgsRelationWidget::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 QgsRelationWidget::saveEdits()
{
mEditorContext.vectorLayerTools()->saveEdits( mRelation.referencingLayer() );
if ( mNmRelation.isValid() )
mEditorContext.vectorLayerTools()->saveEdits( mNmRelation.referencedLayer() );
}
void QgsRelationWidget::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() ) );
// TODO FINISH!!!!
// vlTools->addFeature( mDualView->masterModel()->layer(), keyAttrs, geometry );
}
}
}
void QgsRelationWidget::deleteFeature( const QgsFeatureId fid )
{
deleteFeatures( QgsFeatureIds() << fid );
}
void QgsRelationWidget::deleteSelectedFeatures()
{
QgsFeatureIds selectedFids = mFeatureSelectionMgr->selectedFeatureIds();
deleteFeatures( selectedFids );
}
void QgsRelationWidget::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 QgsRelationWidget::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, &QgsRelationWidget::onLinkFeatureDlgAccepted );
selectionDlg->show();
}
void QgsRelationWidget::onLinkFeatureDlgAccepted()
{
QgsFeatureSelectionDlg *selectionDlg = qobject_cast<QgsFeatureSelectionDlg *>( 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<int, QVariant> 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<int, QVariant> it( keys );
while ( it.hasNext() )
{
it.next();
mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), it.value() );
}
}
}
updateUi();
}
void QgsRelationWidget::unlinkFeature( const QgsFeatureId fid )
{
unlinkFeatures( QgsFeatureIds() << fid );
}
void QgsRelationWidget::unlinkSelectedFeatures()
{
unlinkFeatures( mFeatureSelectionMgr->selectedFeatureIds() );
}
void QgsRelationWidget::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<int, QgsField> 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<int, QgsField> it( keyFields );
while ( it.hasNext() )
{
it.next();
mRelation.referencingLayer()->changeAttributeValue( fid, it.key(), QVariant( it.value().type() ) );
}
}
}
}
void QgsRelationWidget::duplicateFeature()
{
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 );
}
}
void QgsRelationWidget::zoomToSelectedFeatures()
{
QgsMapCanvas *c = mEditorContext.mapCanvas();
if ( !c )
return;
c->zoomToFeatureIds(
mNmRelation.isValid() ? mNmRelation.referencedLayer() : mRelation.referencingLayer(),
mFeatureSelectionMgr->selectedFeatureIds()
);
}

260
src/gui/qgsrelationwidget.h Normal file
View File

@ -0,0 +1,260 @@
/***************************************************************************
qgsrelationwidget.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 QGSRELATIONWIDGET_H
#define QGSRELATIONWIDGET_H
#include <QWidget>
#include <QToolButton>
#include <QButtonGroup>
#include <QGridLayout>
#include "qobjectuniqueptr.h"
#include "qobjectuniqueptr.h"
#include "qgsattributeeditorcontext.h"
#include "qgscollapsiblegroupbox.h"
#include "qgsdualview.h"
#include "qgsrelation.h"
#include "qgsvectorlayerselectionmanager.h"
#include "qgis_gui.h"
class QgsFeature;
class QgsVectorLayer;
class QgsVectorLayerTools;
class QgsMapTool;
class QgsMapToolDigitizeFeature;
/**
* Base class to build new relation widgets.
* \ingroup gui
* \class QgsRelationWidget
* \since QGIS 3.18
*/
class GUI_EXPORT QgsRelationWidget : public QWidget
{
Q_OBJECT
public:
/**
* Constructor
*/
QgsRelationWidget( 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
*/
void setEditorContext( const QgsAttributeEditorContext &context );
/**
* Returns the attribute editor context.
*/
QgsAttributeEditorContext editorContext( ) const;
/**
* The feature selection manager is responsible for the selected features
* which are currently being edited.
*/
QgsIFeatureSelectionManager *featureSelectionManager();
/**
* 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() );
/**
* Deletes the currently selected features
*/
void deleteSelectedFeatures();
/**
* 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() );
/**
* Unlinks the selected features from the relation
*/
void unlinkSelectedFeatures();
/**
* Duplicates a feature
*/
void duplicateFeature();
/**
* Zooms to the selected features
*/
void zoomToSelectedFeatures();
protected:
QgsVectorLayerSelectionManager *mFeatureSelectionMgr = nullptr;
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() {};
};
#endif // QGSRELATIONWIDGET_H

View File

@ -0,0 +1,24 @@
/***************************************************************************
qgsrelationwidgetfactory.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 "qgsrelationwidgetfactory.h"
QgsRelationWidgetFactory::QgsRelationWidgetFactory()
{
}

View File

@ -0,0 +1,80 @@
/***************************************************************************
qgsrelationwidgetfactory.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 QGSRELATIONEDITORBASEWIDGETFACTORY_H
#define QGSRELATIONEDITORBASEWIDGETFACTORY_H
#include <QWidget>
#include "qgsrelationwidget.h"
#include "qgis_gui.h"
class QgsRelationConfigWidget;
/**
* Factory class for creating relation widgets and their corresponding config widgets
* \ingroup gui
* \class QgsRelationWidgetFactory
* \since QGIS 3.18
*/
class GUI_EXPORT QgsRelationWidgetFactory
{
public:
/**
* Creates a new relation widget factory with given \a name
*/
QgsRelationWidgetFactory();
virtual ~QgsRelationWidgetFactory() = 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 QgsRelationWidget *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 QgsRelationConfigWidget *configWidget( const QgsRelation &relation, QWidget *parent ) const = 0 SIP_FACTORY;
};
#endif // QGSRELATIONEDITORBASEWIDGETFACTORY_H

View File

@ -0,0 +1,78 @@
/***************************************************************************
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 "qgsrelationwidget.h"
#include "qgsrelationconfigwidget.h"
#include "qgsbasicrelationwidgetfactory.h"
QgsRelationWidgetRegistry::QgsRelationWidgetRegistry()
{
addRelationWidget( new QgsBasicRelationWidgetFactory() );
}
QgsRelationWidgetRegistry::~QgsRelationWidgetRegistry()
{
qDeleteAll( mRelationWidgetFactories );
mRelationWidgetFactories.clear();
}
void QgsRelationWidgetRegistry::addRelationWidget( QgsRelationWidgetFactory *widgetFactory )
{
if ( !widgetFactory )
return;
if ( mRelationWidgetFactories.contains( widgetFactory->type() ) )
return;
mRelationWidgetFactories.insert( widgetFactory->type(), widgetFactory );
}
void QgsRelationWidgetRegistry::removeRelationWidget( const QString &widgetType )
{
// protect the basic widget from removing, so the user has at least one relation widget type
if ( dynamic_cast<QgsBasicRelationWidgetFactory *>( mRelationWidgetFactories.value( widgetType ) ) )
return;
mRelationWidgetFactories.remove( widgetType );
}
QStringList QgsRelationWidgetRegistry::relationWidgetNames()
{
return mRelationWidgetFactories.keys();
}
QMap<QString, QgsRelationWidgetFactory *> QgsRelationWidgetRegistry::factories() const
{
return mRelationWidgetFactories;
}
QgsRelationWidget *QgsRelationWidgetRegistry::create( const QString &widgetType, const QVariantMap &config, QWidget *parent ) const
{
if ( ! mRelationWidgetFactories.contains( widgetType ) )
return nullptr;
return mRelationWidgetFactories.value( widgetType )->create( config, parent );
}
QgsRelationConfigWidget *QgsRelationWidgetRegistry::createConfigWidget( const QString &widgetType, const QgsRelation &relation, QWidget *parent ) const
{
if ( ! mRelationWidgetFactories.contains( widgetType ) )
return nullptr;
return mRelationWidgetFactories.value( widgetType )->configWidget( relation, parent );
}

View File

@ -0,0 +1,90 @@
/***************************************************************************
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 "qgseditorconfigwidget.h"
#include "qgsrelationwidget.h"
#include "qgsrelationwidgetfactory.h"
#include "qgis_gui.h"
#ifndef QGSRELATIONWIDGETREGISTRY_H
#define QGSRELATIONWIDGETREGISTRY_H
class QgsRelationConfigWidget;
/**
* Keeps track of the registered relations widgets. New widgets can be registered, old ones deleted.
* The default {\see QgsBasicRelationWidget} 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( QgsRelationWidgetFactory *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<QString, QgsRelationWidgetFactory *> 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
*/
QgsRelationWidget *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
*/
QgsRelationConfigWidget *createConfigWidget( const QString &widgetType, const QgsRelation &relation, QWidget *parent = nullptr ) const SIP_TRANSFERBACK;
private:
QMap<QString, QgsRelationWidgetFactory *> mRelationWidgetFactories;
};
#endif // QGSRELATIONWIDGETREGISTRY_H

View File

@ -409,8 +409,11 @@ QTreeWidgetItem *QgsAttributesFormProperties::loadAttributeEditorTreeItem( QgsAt
const QgsAttributeEditorRelation *relationEditor = static_cast<const QgsAttributeEditorRelation *>( 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<QgsAttributesFormProperties::FieldConfig>( *this );
}
/*
* RelationEditorConfiguration implementation
*/
QgsAttributesFormProperties::RelationEditorConfiguration::operator QVariant()
{
return QVariant::fromValue<QgsAttributesFormProperties::RelationEditorConfiguration>( *this );
}
/*
* DnDTree implementation
*/

View File

@ -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 )

View File

@ -7,101 +7,41 @@
<x>0</x>
<y>0</y>
<width>340</width>
<height>361</height>
<height>316</height>
</rect>
</property>
<property name="windowTitle">
<string>Attribute Widget Relation Edit Widget</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QLabel" name="labelLabel">
<property name="text">
<string>Label</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="mRelationLabelEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowLinkCheckBox">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="mLabelLabel">
<property name="text">
<string>Show link button</string>
<string>Label</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowUnlinkCheckBox">
<property name="text">
<string>Show unlink button</string>
<item row="0" column="1">
<widget class="QLineEdit" name="mRelationLabelEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowSaveChildEditsCheckBox">
<item row="1" column="0">
<widget class="QLabel" name="mCardinalityLabel">
<property name="text">
<string>Show save child layer edits button</string>
<string>Cardinality</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowAddChildCheckBox">
<property name="text">
<string>Add child feature</string>
</property>
</widget>
<item row="1" column="1">
<widget class="QComboBox" name="mRelationCardinalityCombo"/>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowDuplicateChildFeatureCheckBox">
<property name="text">
<string>Duplicate child feature</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationDeleteChildFeatureCheckBox">
<property name="text">
<string>Delete child feature</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowZoomToFeatureCheckBox">
<property name="text">
<string>Zoom to child feature</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="cardinalityLabel">
<property name="text">
<string>Cardinality</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="mRelationCardinalityCombo"/>
</item>
</layout>
</item>
<item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="mRelationForceSuppressFormPopupCheckBox">
<property name="statusTip">
<string>Do not open a new attribute form after digitizing a new feature, overrides all other options</string>
@ -111,8 +51,20 @@
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<item row="4" column="0" colspan="2">
<widget class="QgsCollapsibleGroupBox" name="mWidgetTypeGroupBox">
<property name="title">
<string>Widget Configuration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="mWidgetTypePlaceholderLayout"/>
</item>
</layout>
</widget>
</item>
<item row="5" column="0" colspan="2">
<spacer name="mVerticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@ -124,8 +76,26 @@
</property>
</spacer>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="mWidgetTypeComboBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="mCardinalityLabel_2">
<property name="text">
<string>Widget Type</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsCollapsibleGroupBox</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsRelationEditorConfigWidgetBase</class>
<widget class="QWidget" name="QgsRelationEditorConfigWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>274</width>
<height>215</height>
</rect>
</property>
<property name="windowTitle">
<string>Attribute Widget Relation Edit Widget</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="mRelationShowLinkCheckBox">
<property name="text">
<string>Show link button</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowUnlinkCheckBox">
<property name="text">
<string>Show unlink button</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowSaveChildEditsCheckBox">
<property name="text">
<string>Show save child layer edits button</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowAddChildCheckBox">
<property name="text">
<string>Add child feature</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowDuplicateChildFeatureCheckBox">
<property name="text">
<string>Duplicate child feature</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationDeleteChildFeatureCheckBox">
<property name="text">
<string>Delete child feature</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mRelationShowZoomToFeatureCheckBox">
<property name="text">
<string>Zoom to child feature</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>