Merge pull request #7350 from PeterPetrik/quick_featurepanel

[quick] [feature] Feature attribute panel
This commit is contained in:
Matthias Kuhn 2018-07-13 16:31:56 +02:00 committed by GitHub
commit 12c8f394a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 3089 additions and 14 deletions

View File

@ -13,6 +13,9 @@ QGIS Quick consists of a Qt plugin that provides the QML components and of a sha
\subsection qgsquick_overview_widgets QML Classes
\subsubsection qgsquick_overview_widgets_mapcanvas MapCanvas
\subsubsection qgsquick_overview_widgets_featureform FeatureForm
A form listing attributes of a given feature. It supports basic edit field widgets for types such as edit text, map value,
checkbox, date/time picker or external resource (photo capture).
\subsubsection qgsquick_overview_widgets_positionmarker PositionMarker
The element refers to current position according gps location device connected to it. It holds information about longitude, latitude, altitude,
direction of the movement and accuracy of the signal. See also QgsQuickPositionKit.

View File

@ -1,6 +1,11 @@
############################################################
# sources
SET(QGIS_QUICK_GUI_MOC_HDRS
attributes/qgsquickattributeformmodel.h
attributes/qgsquickattributeformmodelbase.h
attributes/qgsquickattributemodel.h
attributes/qgsquicksubmodel.h
qgsquickfeaturelayerpair.h
qgsquickcoordinatetransformer.h
qgsquickfeaturehighlight.h
@ -20,6 +25,11 @@ SET(QGIS_QUICK_GUI_HDRS
)
SET(QGIS_QUICK_GUI_SRC
attributes/qgsquickattributeformmodel.cpp
attributes/qgsquickattributeformmodelbase.cpp
attributes/qgsquickattributemodel.cpp
attributes/qgsquicksubmodel.cpp
qgsquickfeaturelayerpair.cpp
qgsquickcoordinatetransformer.cpp
qgsquickfeaturehighlight.cpp
@ -37,6 +47,7 @@ SET(QGIS_QUICK_GUI_SRC
INCLUDE_DIRECTORIES(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/attributes
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}/src/core

View File

@ -0,0 +1,72 @@
/***************************************************************************
qgsquickattributeformmodel.cpp
--------------------------------------
Date : 22.9.2016
Copyright : (C) 2016 by Matthias Kuhn
Email : matthias@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 "qgsquickattributeformmodel.h"
#include "qgsquickattributeformmodelbase.h"
QgsQuickAttributeFormModel::QgsQuickAttributeFormModel( QObject *parent )
: QSortFilterProxyModel( parent )
, mSourceModel( new QgsQuickAttributeFormModelBase( this ) )
{
setSourceModel( mSourceModel );
connect( mSourceModel, &QgsQuickAttributeFormModelBase::hasTabsChanged, this, &QgsQuickAttributeFormModel::hasTabsChanged );
connect( mSourceModel, &QgsQuickAttributeFormModelBase::attributeModelChanged, this, &QgsQuickAttributeFormModel::attributeModelChanged );
connect( mSourceModel, &QgsQuickAttributeFormModelBase::constraintsValidChanged, this, &QgsQuickAttributeFormModel::constraintsValidChanged );
}
bool QgsQuickAttributeFormModel::hasTabs() const
{
return mSourceModel->hasTabs();
}
void QgsQuickAttributeFormModel::setHasTabs( bool hasTabs )
{
mSourceModel->setHasTabs( hasTabs );
}
QgsQuickAttributeModel *QgsQuickAttributeFormModel::attributeModel() const
{
return mSourceModel->attributeModel();
}
void QgsQuickAttributeFormModel::setAttributeModel( QgsQuickAttributeModel *attributeModel )
{
mSourceModel->setAttributeModel( attributeModel );
}
bool QgsQuickAttributeFormModel::constraintsValid() const
{
return mSourceModel->constraintsValid();
}
void QgsQuickAttributeFormModel::save()
{
mSourceModel->save();
}
void QgsQuickAttributeFormModel::create()
{
mSourceModel->create();
}
QVariant QgsQuickAttributeFormModel::attribute( const QString &name ) const
{
return mSourceModel->attribute( name );
}
bool QgsQuickAttributeFormModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
{
return mSourceModel->data( mSourceModel->index( source_row, 0, source_parent ), CurrentlyVisible ).toBool();
}

View File

@ -0,0 +1,122 @@
/***************************************************************************
qgsquickattributeformmodel.h
--------------------------------------
Date : 22.9.2016
Copyright : (C) 2016 by Matthias Kuhn
Email : matthias@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 QGSQUICKATTRIBUTEFORMMODEL_H
#define QGSQUICKATTRIBUTEFORMMODEL_H
#include <QSortFilterProxyModel>
#include "qgis_quick.h"
class QgsQuickAttributeFormModelBase;
class QgsQuickAttributeModel;
class QVariant;
/**
* \ingroup quick
* This is a model implementation for attribute form of a feature from a vector layer.
*
* The model is based on vector layer's edit form config (QgsEditFormConfig). It supports
* auto-generated editor layouts and "tab" layout (layout defined with groups and tabs).
* The form layout gets flattened into a list, each row has a bunch of roles with values
* extracted from the edit form config.
*
* It also adds filtering of attribute (attributes may be visible or hidden based on expressions).
*
* \note QML Type: AttributeFormModel
*
* \since QGIS 3.4
*/
class QUICK_EXPORT QgsQuickAttributeFormModel : public QSortFilterProxyModel
{
Q_OBJECT
//! Feature model with attributes
Q_PROPERTY( QgsQuickAttributeModel *attributeModel READ attributeModel WRITE setAttributeModel NOTIFY attributeModelChanged )
//! Whether use tabs layout
Q_PROPERTY( bool hasTabs READ hasTabs WRITE setHasTabs NOTIFY hasTabsChanged )
//! Returns true if all constraints defined on fields are satisfied with the current attribute values
Q_PROPERTY( bool constraintsValid READ constraintsValid NOTIFY constraintsValidChanged )
public:
//! Feature fields's roles
enum FeatureFieldRoles
{
ElementType = Qt::UserRole + 1, //!< User role used to identify either "field" or "container" type of item
Name, //!< Field Name
AttributeValue, //!< Field Value
AttributeEditable, //!< Whether is field editable
EditorWidget, //!< Widget type to represent the data (text field, value map, ...)
EditorWidgetConfig, //!< Widget configuration
RememberValue, //!< Remember value (default value for next feature)
Field, //!< Field
FieldIndex, //!< Index
Group, //!< Group
AttributeEditorElement, //!< Attribute editor element
CurrentlyVisible, //!< Field visible
ConstraintValid, //!< Contraint valid
ConstraintDescription //!< Contraint description
};
Q_ENUM( FeatureFieldRoles )
//! Create new attribute form model
QgsQuickAttributeFormModel( QObject *parent = nullptr );
//! \copydoc QgsQuickAttributeFormModel::hasTabs
bool hasTabs() const;
//! \copydoc QgsQuickAttributeFormModel::hasTabs
void setHasTabs( bool hasTabs );
//! \copydoc QgsQuickAttributeFormModel::attributeModel
QgsQuickAttributeModel *attributeModel() const;
//! \copydoc QgsQuickAttributeFormModel::attributeModel
void setAttributeModel( QgsQuickAttributeModel *attributeModel );
//! \copydoc QgsQuickAttributeFormModel::constraintsValid
bool constraintsValid() const;
//! Updates QgsFeature based on changes
Q_INVOKABLE void save();
//! Creates new QgsFeature
Q_INVOKABLE void create();
//! Returns attribute value with name
Q_INVOKABLE QVariant attribute( const QString &name ) const;
signals:
//! \copydoc QgsQuickAttributeFormModel::attributeModel
void attributeModelChanged();
//! \copydoc QgsQuickAttributeFormModel::hasTabs
void hasTabsChanged();
//! \copydoc QgsQuickAttributeFormModel::constraintsValid
void constraintsValidChanged();
protected:
virtual bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const override;
private:
QgsQuickAttributeFormModelBase *mSourceModel = nullptr; //not owned
};
#endif // QGSQUICKATTRIBUTEFORMMODEL_H

View File

@ -0,0 +1,383 @@
/***************************************************************************
qgsquickattributemodelbase.cpp
--------------------------------------
Date : 16.8.2016
Copyright : (C) 2016 by Matthias Kuhn
Email : matthias@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 "qgseditorwidgetsetup.h"
#include "qgsvectorlayer.h"
#include "qgsquickattributeformmodelbase.h"
#include "qgsquickattributeformmodel.h"
/// @cond PRIVATE
QgsQuickAttributeFormModelBase::QgsQuickAttributeFormModelBase( QObject *parent )
: QStandardItemModel( 0, 1, parent )
{
}
QHash<int, QByteArray> QgsQuickAttributeFormModelBase::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[QgsQuickAttributeFormModel::ElementType] = QByteArray( "Type" );
roles[QgsQuickAttributeFormModel::Name] = QByteArray( "Name" );
roles[QgsQuickAttributeFormModel::AttributeValue] = QByteArray( "AttributeValue" );
roles[QgsQuickAttributeFormModel::AttributeEditable] = QByteArray( "AttributeEditable" );
roles[QgsQuickAttributeFormModel::EditorWidget] = QByteArray( "EditorWidget" );
roles[QgsQuickAttributeFormModel::EditorWidgetConfig] = QByteArray( "EditorWidgetConfig" );
roles[QgsQuickAttributeFormModel::RememberValue] = QByteArray( "RememberValue" );
roles[QgsQuickAttributeFormModel::Field] = QByteArray( "Field" );
roles[QgsQuickAttributeFormModel::Group] = QByteArray( "Group" );
roles[QgsQuickAttributeFormModel::ConstraintValid] = QByteArray( "ConstraintValid" );
roles[QgsQuickAttributeFormModel::ConstraintDescription] = QByteArray( "ConstraintDescription" );
return roles;
}
bool QgsQuickAttributeFormModelBase::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( data( index, role ) != value )
{
switch ( role )
{
case QgsQuickAttributeFormModel::RememberValue:
{
QStandardItem *item = itemFromIndex( index );
int fieldIndex = item->data( QgsQuickAttributeFormModel::FieldIndex ).toInt();
mAttributeModel->setData( mAttributeModel->index( fieldIndex ), value, QgsQuickAttributeModel::RememberAttribute );
item->setData( value, QgsQuickAttributeFormModel::RememberValue );
break;
}
case QgsQuickAttributeFormModel::AttributeValue:
{
QStandardItem *item = itemFromIndex( index );
int fieldIndex = item->data( QgsQuickAttributeFormModel::FieldIndex ).toInt();
bool changed = mAttributeModel->setData( mAttributeModel->index( fieldIndex ), value, QgsQuickAttributeModel::AttributeValue );
if ( changed )
{
item->setData( value, QgsQuickAttributeFormModel::AttributeValue );
emit dataChanged( index, index, QVector<int>() << role );
}
updateVisibility( fieldIndex );
return changed;
break;
}
}
}
return false;
}
QgsQuickAttributeModel *QgsQuickAttributeFormModelBase::attributeModel() const
{
return mAttributeModel;
}
void QgsQuickAttributeFormModelBase::setAttributeModel( QgsQuickAttributeModel *attributeModel )
{
if ( mAttributeModel == attributeModel )
return;
if ( mAttributeModel )
{
disconnect( mAttributeModel, &QgsQuickAttributeModel::layerChanged, this, &QgsQuickAttributeFormModelBase::onLayerChanged );
disconnect( mAttributeModel, &QgsQuickAttributeModel::featureChanged, this, &QgsQuickAttributeFormModelBase::onFeatureChanged );
disconnect( mAttributeModel, &QgsQuickAttributeModel::modelReset, this, &QgsQuickAttributeFormModelBase::onFeatureChanged );
}
mAttributeModel = attributeModel;
if ( mAttributeModel )
{
connect( mAttributeModel, &QgsQuickAttributeModel::layerChanged, this, &QgsQuickAttributeFormModelBase::onLayerChanged );
connect( mAttributeModel, &QgsQuickAttributeModel::featureChanged, this, &QgsQuickAttributeFormModelBase::onFeatureChanged );
connect( mAttributeModel, &QgsQuickAttributeModel::modelReset, this, &QgsQuickAttributeFormModelBase::onFeatureChanged );
}
emit attributeModelChanged();
}
void QgsQuickAttributeFormModelBase::onLayerChanged()
{
clear();
mLayer = mAttributeModel->featureLayerPair().layer();
mVisibilityExpressions.clear();
mConstraints.clear();
if ( mLayer )
{
QgsAttributeEditorContainer *root = nullptr;
mTemporaryContainer = nullptr;
if ( mLayer->editFormConfig().layout() == QgsEditFormConfig::TabLayout )
{
root = mLayer->editFormConfig().invisibleRootContainer();
}
else
{
root = generateRootContainer(); //#spellok
mTemporaryContainer.reset( root );
}
setHasTabs( !root->children().isEmpty() && QgsAttributeEditorElement::AeTypeContainer == root->children().first()->type() );
invisibleRootItem()->setColumnCount( 1 );
if ( mHasTabs )
{
for ( QgsAttributeEditorElement *element : root->children() )
{
if ( element->type() == QgsAttributeEditorElement::AeTypeContainer )
{
QgsAttributeEditorContainer *container = static_cast<QgsAttributeEditorContainer *>( element );
QStandardItem *item = new QStandardItem();
item->setData( element->name(), QgsQuickAttributeFormModel::Name );
item->setData( QStringLiteral( "container" ), QgsQuickAttributeFormModel::ElementType );
item->setData( true, QgsQuickAttributeFormModel::CurrentlyVisible );
invisibleRootItem()->appendRow( item );
if ( container->visibilityExpression().enabled() )
{
mVisibilityExpressions.append( qMakePair( container->visibilityExpression().data(), QVector<QStandardItem *>() << item ) );
}
QVector<QStandardItem *> dummy;
flatten( container, item, QString(), dummy );
}
}
}
else
{
QVector<QStandardItem *> dummy;
flatten( invisibleRootContainer(), invisibleRootItem(), QString(), dummy );
}
mExpressionContext = mLayer->createExpressionContext();
}
}
void QgsQuickAttributeFormModelBase::onFeatureChanged()
{
for ( int i = 0 ; i < invisibleRootItem()->rowCount(); ++i )
{
updateAttributeValue( invisibleRootItem()->child( i ) );
}
updateVisibility();
}
QgsAttributeEditorContainer *QgsQuickAttributeFormModelBase::generateRootContainer() const //#spellok
{
QgsAttributeEditorContainer *root = new QgsAttributeEditorContainer( QString(), nullptr );
QgsFields fields = mLayer->fields();
for ( int i = 0; i < fields.size(); ++i )
{
if ( fields.at( i ).editorWidgetSetup().type() != QStringLiteral( "Hidden" ) )
{
QgsAttributeEditorField *field = new QgsAttributeEditorField( fields.at( i ).name(), i, root );
root->addChildElement( field );
}
}
return root;
}
QgsAttributeEditorContainer *QgsQuickAttributeFormModelBase::invisibleRootContainer() const
{
return mTemporaryContainer ? mTemporaryContainer.get() : mLayer->editFormConfig().invisibleRootContainer();
}
void QgsQuickAttributeFormModelBase::updateAttributeValue( QStandardItem *item )
{
if ( item->data( QgsQuickAttributeFormModel::ElementType ) == QStringLiteral( "field" ) )
{
item->setData( mAttributeModel->featureLayerPair().feature().attribute( item->data( QgsQuickAttributeFormModel::FieldIndex ).toInt() ), QgsQuickAttributeFormModel::AttributeValue );
}
else
{
for ( int i = 0; i < item->rowCount(); ++i )
{
updateAttributeValue( item->child( i ) );
}
}
}
void QgsQuickAttributeFormModelBase::flatten( QgsAttributeEditorContainer *container, QStandardItem *parent, const QString &parentVisibilityExpressions, QVector<QStandardItem *> &items )
{
for ( QgsAttributeEditorElement *element : container->children() )
{
switch ( element->type() )
{
case QgsAttributeEditorElement::AeTypeContainer:
{
QString visibilityExpression = parentVisibilityExpressions;
QgsAttributeEditorContainer *container = static_cast<QgsAttributeEditorContainer *>( element );
if ( container->visibilityExpression().enabled() )
{
if ( visibilityExpression.isNull() )
visibilityExpression = container->visibilityExpression().data().expression();
else
visibilityExpression += " AND " + container->visibilityExpression().data().expression();
}
QVector<QStandardItem *> newItems;
flatten( container, parent, visibilityExpression, newItems );
if ( !visibilityExpression.isEmpty() )
mVisibilityExpressions.append( qMakePair( QgsExpression( visibilityExpression ), newItems ) );
break;
}
case QgsAttributeEditorElement::AeTypeField:
{
QgsAttributeEditorField *editorField = static_cast<QgsAttributeEditorField *>( element );
int fieldIndex = editorField->idx();
if ( fieldIndex < 0 || fieldIndex >= mLayer->fields().size() )
continue;
QgsField field = mLayer->fields().at( fieldIndex );
QStandardItem *item = new QStandardItem();
item->setData( mLayer->attributeDisplayName( fieldIndex ), QgsQuickAttributeFormModel::Name );
item->setData( mAttributeModel->featureLayerPair().feature().attribute( fieldIndex ), QgsQuickAttributeFormModel::AttributeValue );
item->setData( !mLayer->editFormConfig().readOnly( fieldIndex ), QgsQuickAttributeFormModel::AttributeEditable );
QgsEditorWidgetSetup setup = mLayer->editorWidgetSetup( fieldIndex );
item->setData( setup.type(), QgsQuickAttributeFormModel::EditorWidget );
item->setData( setup.config(), QgsQuickAttributeFormModel::EditorWidgetConfig );
item->setData( mAttributeModel->rememberedAttributes().at( fieldIndex ) ? Qt::Checked : Qt::Unchecked, QgsQuickAttributeFormModel::RememberValue );
item->setData( mLayer->fields().at( fieldIndex ), QgsQuickAttributeFormModel::Field );
item->setData( QStringLiteral( "field" ), QgsQuickAttributeFormModel::ElementType );
item->setData( fieldIndex, QgsQuickAttributeFormModel::FieldIndex );
item->setData( container->isGroupBox() ? container->name() : QString(), QgsQuickAttributeFormModel::Group );
item->setData( true, QgsQuickAttributeFormModel::CurrentlyVisible );
item->setData( true, QgsQuickAttributeFormModel::ConstraintValid );
item->setData( field.constraints().constraintDescription(), QgsQuickAttributeFormModel::ConstraintDescription );
if ( !field.constraints().constraintExpression().isEmpty() )
{
mConstraints.insert( item, field.constraints().constraintExpression() );
}
items.append( item );
parent->appendRow( item );
break;
}
case QgsAttributeEditorElement::AeTypeRelation:
// todo
break;
case QgsAttributeEditorElement::AeTypeInvalid:
// todo
break;
}
}
}
void QgsQuickAttributeFormModelBase::updateVisibility( int fieldIndex )
{
QgsFields fields = mAttributeModel->featureLayerPair().feature().fields();
mExpressionContext.setFields( fields );
mExpressionContext.setFeature( mAttributeModel->featureLayerPair().feature() );
for ( const VisibilityExpression &it : mVisibilityExpressions )
{
if ( fieldIndex == -1 || it.first.referencedAttributeIndexes( fields ).contains( fieldIndex ) )
{
QgsExpression exp = it.first;
exp.prepare( &mExpressionContext );
bool visible = exp.evaluate( &mExpressionContext ).toInt();
for ( QStandardItem *item : it.second )
{
if ( item->data( QgsQuickAttributeFormModel::CurrentlyVisible ).toBool() != visible )
{
item->setData( visible, QgsQuickAttributeFormModel::CurrentlyVisible );
}
}
}
}
bool allConstraintsValid = true;
QMap<QStandardItem *, QgsExpression>::ConstIterator constraintIterator( mConstraints.constBegin() );
for ( ; constraintIterator != mConstraints.constEnd(); ++constraintIterator )
{
QStandardItem *item = constraintIterator.key();
QgsExpression exp = constraintIterator.value();
exp.prepare( &mExpressionContext );
bool constraintSatisfied = exp.evaluate( &mExpressionContext ).toBool();
if ( constraintSatisfied != item->data( QgsQuickAttributeFormModel::ConstraintValid ).toBool() )
{
item->setData( constraintSatisfied, QgsQuickAttributeFormModel::ConstraintValid );
}
if ( !item->data( QgsQuickAttributeFormModel::ConstraintValid ).toBool() )
{
allConstraintsValid = false;
}
}
setConstraintsValid( allConstraintsValid );
}
bool QgsQuickAttributeFormModelBase::constraintsValid() const
{
return mConstraintsValid;
}
QVariant QgsQuickAttributeFormModelBase::attribute( const QString &name ) const
{
if ( !mLayer )
return QVariant();
int idx = mLayer->fields().indexOf( name );
return mAttributeModel->featureLayerPair().feature().attribute( idx );
}
void QgsQuickAttributeFormModelBase::setConstraintsValid( bool constraintsValid )
{
if ( constraintsValid == mConstraintsValid )
return;
mConstraintsValid = constraintsValid;
emit constraintsValidChanged();
}
bool QgsQuickAttributeFormModelBase::hasTabs() const
{
return mHasTabs;
}
void QgsQuickAttributeFormModelBase::setHasTabs( bool hasTabs )
{
if ( hasTabs == mHasTabs )
return;
mHasTabs = hasTabs;
emit hasTabsChanged();
}
void QgsQuickAttributeFormModelBase::save()
{
mAttributeModel->save();
}
void QgsQuickAttributeFormModelBase::create()
{
mAttributeModel->create();
}
/// @endcond

View File

@ -0,0 +1,132 @@
/***************************************************************************
qgsquickattributemodelbase.h
--------------------------------------
Date : 16.8.2016
Copyright : (C) 2016 by Matthias Kuhn
Email : matthias@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 QGSQUICKATTRIBUTEFORMMODELBASE_H
#define QGSQUICKATTRIBUTEFORMMODELBASE_H
/// @cond PRIVATE
//
// W A R N I N G
// -------------
//
// This file is not part of the QGIS API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
#include <QStandardItemModel>
#include <QStack>
#include "qgseditformconfig.h"
#include "qgsexpressioncontext.h"
#include "qgis_quick.h"
#include "qgsquickattributemodel.h"
class QVariant;
/**
* \ingroup quick
* This is an internal (implementation) class used as the source model for QgsQuickAttributeFormModel.
*
* \sa QgsQuickAttributeFormModel
*
* \since QGIS 3.4
*/
class QgsQuickAttributeFormModelBase : public QStandardItemModel
{
Q_OBJECT
//! Feature model with attributes
Q_PROPERTY( QgsQuickAttributeModel *attributeModel READ attributeModel WRITE setAttributeModel NOTIFY attributeModelChanged )
//! Whether use tabs layout
Q_PROPERTY( bool hasTabs READ hasTabs WRITE setHasTabs NOTIFY hasTabsChanged )
//! Returns true if all constraints defined on fields are satisfied with the current attribute values
Q_PROPERTY( bool constraintsValid READ constraintsValid NOTIFY constraintsValidChanged )
public:
//! Constructor
explicit QgsQuickAttributeFormModelBase( QObject *parent = nullptr );
//! Destructor
~QgsQuickAttributeFormModelBase() = default;
QHash<int, QByteArray> roleNames() const override;
bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override;
//! \copydoc QgsQuickAttributeFormModelBase::attributeModel
QgsQuickAttributeModel *attributeModel() const;
//! \copydoc QgsQuickAttributeFormModelBase::attributeModel
void setAttributeModel( QgsQuickAttributeModel *attributeModel );
//! \copydoc QgsQuickAttributeFormModelBase::hasTabs
bool hasTabs() const;
//! \copydoc QgsQuickAttributeFormModelBase::hasTabs
void setHasTabs( bool hasTabs );
//! Saves changes
void save();
//! Creates a new feature
void create();
//! \copydoc QgsQuickAttributeFormModelBase::constraintsValid
bool constraintsValid() const;
/**
* Gets the value of attribute of the feature in the model
*
* \param name QString name of the wanted attribute
*/
QVariant attribute( const QString &name ) const;
signals:
//! \copydoc QgsQuickAttributeFormModelBase::attributeModel
void attributeModelChanged();
//! \copydoc QgsQuickAttributeFormModelBase::hasTabs
void hasTabsChanged();
//! \copydoc QgsQuickAttributeFormModelBase::constraintsValid
void constraintsValidChanged();
private slots:
void onFeatureChanged();
void onLayerChanged();
private:
QgsAttributeEditorContainer *generateRootContainer() const; //#spellok
QgsAttributeEditorContainer *invisibleRootContainer() const;
void updateAttributeValue( QStandardItem *item );
void flatten( QgsAttributeEditorContainer *container, QStandardItem *parent, const QString &parentVisibilityExpressions, QVector<QStandardItem *> &items );
void updateVisibility( int fieldIndex = -1 );
void setConstraintsValid( bool constraintsValid );
QgsQuickAttributeModel *mAttributeModel = nullptr; // not owned
QgsVectorLayer *mLayer = nullptr; // not owned
std::unique_ptr<QgsAttributeEditorContainer> mTemporaryContainer;
bool mHasTabs = false;
typedef QPair<QgsExpression, QVector<QStandardItem *> > VisibilityExpression;
QList<VisibilityExpression> mVisibilityExpressions;
QMap<QStandardItem *, QgsExpression> mConstraints;
QgsExpressionContext mExpressionContext;
bool mConstraintsValid = false;
};
/// @endcond
#endif // QGSQUICKATTRIBUTEFORMMODELBASE_H

View File

@ -0,0 +1,331 @@
/***************************************************************************
qgsquickattributemodel.cpp
--------------------------------------
Date : 10.12.2014
Copyright : (C) 2014 by Matthias Kuhn
Email : matthias@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 "qgis.h"
#include "qgsmessagelog.h"
#include "qgsvectorlayer.h"
#include "qgsquickattributemodel.h"
QgsQuickAttributeModel::QgsQuickAttributeModel( QObject *parent )
: QAbstractListModel( parent )
{
connect( this, &QgsQuickAttributeModel::modelReset, this, &QgsQuickAttributeModel::featureLayerPairChanged );
connect( this, &QgsQuickAttributeModel::featureChanged, this, &QgsQuickAttributeModel::featureLayerPairChanged );
connect( this, &QgsQuickAttributeModel::layerChanged, this, &QgsQuickAttributeModel::featureLayerPairChanged );
}
QgsQuickFeatureLayerPair QgsQuickAttributeModel::featureLayerPair() const
{
return mFeatureLayerPair;
}
void QgsQuickAttributeModel::setFeatureLayerPair( const QgsQuickFeatureLayerPair &pair )
{
setVectorLayer( pair.layer() );
setFeature( pair.feature() );
}
void QgsQuickAttributeModel::setVectorLayer( QgsVectorLayer *layer )
{
if ( mFeatureLayerPair.layer() == layer )
return;
beginResetModel();
mFeatureLayerPair = QgsQuickFeatureLayerPair( mFeatureLayerPair.feature(), layer );
if ( mFeatureLayerPair.layer() )
{
mRememberedAttributes.resize( mFeatureLayerPair.layer()->fields().size() );
mRememberedAttributes.fill( false );
}
else
{
mRememberedAttributes.clear();
}
endResetModel();
emit layerChanged();
}
void QgsQuickAttributeModel::setFeature( const QgsFeature &feature )
{
if ( mFeatureLayerPair.feature() == feature )
return;
beginResetModel();
mFeatureLayerPair = QgsQuickFeatureLayerPair( feature, mFeatureLayerPair.layer() );
endResetModel();
emit featureChanged();
}
QHash<int, QByteArray> QgsQuickAttributeModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
roles[AttributeName] = QByteArrayLiteral( "AttributeName" );
roles[AttributeValue] = QByteArrayLiteral( "AttributeValue" );
roles[Field] = QByteArrayLiteral( "Field" );
roles[RememberAttribute] = QByteArrayLiteral( "RememberAttribute" );
return roles;
}
int QgsQuickAttributeModel::rowCount( const QModelIndex &parent ) const
{
if ( parent.isValid() )
return 0;
else
return mFeatureLayerPair.feature().attributes().count();
}
QVariant QgsQuickAttributeModel::data( const QModelIndex &index, int role ) const
{
switch ( role )
{
case AttributeName:
return mFeatureLayerPair.layer()->attributeDisplayName( index.row() );
break;
case AttributeValue:
return mFeatureLayerPair.feature().attribute( index.row() );
break;
case Field:
return mFeatureLayerPair.layer()->fields().at( index.row() );
break;
case RememberAttribute:
return mRememberedAttributes.at( index.row() );
break;
}
return QVariant();
}
bool QgsQuickAttributeModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( data( index, role ) == value )
return true;
switch ( role )
{
case AttributeValue:
{
QVariant val( value );
QgsField fld = mFeatureLayerPair.feature().fields().at( index.row() );
if ( !fld.convertCompatible( val ) )
{
QgsMessageLog::logMessage( tr( "Value \"%1\" %4 could not be converted to a compatible value for field %2(%3)." ).arg( value.toString(), fld.name(), fld.typeName(), value.isNull() ? "NULL" : "NOT NULL" ) );
return false;
}
bool success = mFeatureLayerPair.featureRef().setAttribute( index.row(), val );
if ( success )
emit dataChanged( index, index, QVector<int>() << role );
return success;
break;
}
case RememberAttribute:
{
mRememberedAttributes[ index.row() ] = value.toBool();
emit dataChanged( index, index, QVector<int>() << role );
break;
}
}
return false;
}
bool QgsQuickAttributeModel::save()
{
if ( !mFeatureLayerPair.layer() )
return false;
bool rv = true;
if ( !startEditing() )
{
rv = false;
}
QgsFeature feat = mFeatureLayerPair.feature();
if ( !mFeatureLayerPair.layer()->updateFeature( feat ) )
QgsMessageLog::logMessage( tr( "Cannot update feature" ),
QStringLiteral( "QgsQuick" ),
Qgis::Warning );
// This calls lower-level I/O functions which shouldn't be used
// in a Q_INVOKABLE because they can make the UI unresponsive.
rv = commit();
if ( rv )
{
QgsFeature feat;
if ( mFeatureLayerPair.layer()->getFeatures( QgsFeatureRequest().setFilterFid( mFeatureLayerPair.feature().id() ) ).nextFeature( feat ) )
setFeature( feat );
else
QgsMessageLog::logMessage( tr( "Feature %1 could not be fetched after commit" ).arg( mFeatureLayerPair.feature().id() ),
QStringLiteral( "QgsQuick" ),
Qgis::Warning );
}
return rv;
}
bool QgsQuickAttributeModel::deleteFeature()
{
if ( !mFeatureLayerPair.layer() )
return false;
bool rv = true;
if ( !startEditing() )
{
rv = false;
}
if ( !mFeatureLayerPair.layer()->deleteFeature( mFeatureLayerPair.feature().id() ) )
QgsMessageLog::logMessage( tr( "Cannot delete feature" ),
QStringLiteral( "QgsQuick" ),
Qgis::Warning );
rv = commit();
return rv;
}
void QgsQuickAttributeModel::reset()
{
if ( !mFeatureLayerPair.layer() )
return;
mFeatureLayerPair.layer()->rollBack();
}
bool QgsQuickAttributeModel::suppressFeatureForm() const
{
if ( !mFeatureLayerPair.layer() )
return false;
return mFeatureLayerPair.layer()->editFormConfig().suppress();
}
void QgsQuickAttributeModel::resetAttributes()
{
if ( !mFeatureLayerPair.layer() )
return;
QgsExpressionContext expressionContext = mFeatureLayerPair.layer()->createExpressionContext();
expressionContext.setFeature( mFeatureLayerPair.feature() );
QgsFields fields = mFeatureLayerPair.layer()->fields();
beginResetModel();
for ( int i = 0; i < fields.count(); ++i )
{
if ( !mRememberedAttributes.at( i ) )
{
if ( !fields.at( i ).defaultValueDefinition().expression().isEmpty() )
{
QgsExpression exp( fields.at( i ).defaultValueDefinition().expression() );
exp.prepare( &expressionContext );
if ( exp.hasParserError() )
QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has parser error: %3" ).arg(
mFeatureLayerPair.layer()->name(),
fields.at( i ).name(),
exp.parserErrorString() ),
QStringLiteral( "QgsQuick" ),
Qgis::Warning );
QVariant value = exp.evaluate( &expressionContext );
if ( exp.hasEvalError() )
QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has evaluation error: %3" ).arg(
mFeatureLayerPair.layer()->name(),
fields.at( i ).name(),
exp.evalErrorString() ),
QStringLiteral( "QgsQuick" ),
Qgis::Warning );
mFeatureLayerPair.feature().setAttribute( i, value );
}
else
{
mFeatureLayerPair.feature().setAttribute( i, QVariant() );
}
}
}
endResetModel();
}
void QgsQuickAttributeModel::create()
{
if ( !mFeatureLayerPair.layer() )
return;
startEditing();
QgsFeature feat = mFeatureLayerPair.feature();
if ( !mFeatureLayerPair.layer()->addFeature( feat ) )
{
QgsMessageLog::logMessage( tr( "Feature could not be added" ),
QStringLiteral( "QgsQuick" ),
Qgis::Critical );
}
commit();
}
bool QgsQuickAttributeModel::commit()
{
if ( !mFeatureLayerPair.layer()->commitChanges() )
{
QgsMessageLog::logMessage( tr( "Could not save changes. Rolling back." ),
QStringLiteral( "QgsQuick" ),
Qgis::Critical );
mFeatureLayerPair.layer()->rollBack();
return false;
}
else
{
return true;
}
}
bool QgsQuickAttributeModel::startEditing()
{
// Already an edit session active
if ( mFeatureLayerPair.layer()->editBuffer() )
return true;
if ( !mFeatureLayerPair.layer()->startEditing() )
{
QgsMessageLog::logMessage( tr( "Cannot start editing" ),
QStringLiteral( "QgsQuick" ),
Qgis::Warning );
return false;
}
else
{
return true;
}
}
QVector<bool> QgsQuickAttributeModel::rememberedAttributes() const
{
return mRememberedAttributes;
}

View File

@ -0,0 +1,153 @@
/***************************************************************************
QgsQuickAttributeModel.h
--------------------------------------
Date : 10.12.2014
Copyright : (C) 2014 by Matthias Kuhn
Email : matthias@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 QGSQUICKATTRIBUTEMODEL_H
#define QGSQUICKATTRIBUTEMODEL_H
#include <QAbstractListModel>
#include <QVector>
#include "qgsfeature.h"
#include "qgsvectorlayer.h"
#include "qgis_quick.h"
#include "qgsquickfeaturelayerpair.h"
/**
* \ingroup quick
*
* Basic item model for attributes of QgsFeature associated
* from feature layer pair. Each attribute of the feature
* gets a row in the model. Also supports CRUD operations
* related to layer and feature pair.
*
* On top of the QgsFeature attributes, support for "remember"
* attribute is added. Remember attribute is used for
* quick addition of the multiple features to the same layer.
* A new feature can use "remembered" attribute values from
* the last feature added.
*
*
* \note QML Type: AttributeModel
*
* \since QGIS 3.4
*/
class QUICK_EXPORT QgsQuickAttributeModel : public QAbstractListModel
{
Q_OBJECT
/**
* QgsQuickFeatureLayerPair for the model. Input for attributes model.
*/
Q_PROPERTY( QgsQuickFeatureLayerPair featureLayerPair READ featureLayerPair WRITE setFeatureLayerPair NOTIFY featureLayerPairChanged )
/**
* Feature roles enum.
*/
Q_ENUMS( FeatureRoles )
public:
//! Feature roles
enum FeatureRoles
{
AttributeName = Qt::UserRole + 1, //!< Attribute's display name (the original field name or a custom alias)
AttributeValue, //!< Value of the feature's attribute
Field, //!< Field definition (QgsField)
RememberAttribute //!< Remember attribute value for next feature
};
//! Creates a new feature attribute model
explicit QgsQuickAttributeModel( QObject *parent = nullptr );
/**
* Creates a new feature attribute model
* \param feat Feature set to model
* \param parent Parent object.
*/
explicit QgsQuickAttributeModel( const QgsFeature &feat, QObject *parent = nullptr );
QHash<int, QByteArray> roleNames() const override;
int rowCount( const QModelIndex &parent ) const override;
QVariant data( const QModelIndex &index, int role ) const override;
bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override;
/**
* Commits the edit buffer of this layer.
* May change in the future to only commit the changes buffered in this model.
*
* @return Success of the operation.
*/
Q_INVOKABLE bool save();
/**
* Deletes the current feature from the layer and commit the changes.
* @return Success of the operation.
*/
Q_INVOKABLE bool deleteFeature();
/**
* Resets the feature to the original values and dismiss any buffered edits.
*/
Q_INVOKABLE void reset();
//! Adds feature from featureLayerPair to the layer
Q_INVOKABLE void create();
/**
* Suppress layer's QgsEditFormConfig
*
* \sa QgsEditFormConfig::suppress
*/
Q_INVOKABLE bool suppressFeatureForm() const;
//! Resets remembered attributes
Q_INVOKABLE virtual void resetAttributes();
//! Gets remembered attributes
QVector<bool> rememberedAttributes() const;
//!\copydoc QgsQuickAttributeModel::featureLayerPair
QgsQuickFeatureLayerPair featureLayerPair() const;
//!\copydoc QgsQuickAttributeModel::featureLayerPair
void setFeatureLayerPair( const QgsQuickFeatureLayerPair &pair );
public slots:
signals:
//! Feature or layer changed in feature layer pair
void featureLayerPairChanged();
//! Feature updated, layer is the same
void featureChanged();
//! Layer changed, feature is the same
void layerChanged();
protected:
//! Commits model changes
bool commit();
//! Starts editing model
bool startEditing();
QgsQuickFeatureLayerPair mFeatureLayerPair;
QVector<bool> mRememberedAttributes;
private:
void setFeature( const QgsFeature &feature );
void setVectorLayer( QgsVectorLayer *layer );
};
#endif // QGSQUICKATTRIBUTEMODEL_H

View File

@ -0,0 +1,183 @@
/***************************************************************************
qgsquicksubmodel.cpp
--------------------------------------
Date : 16.9.2016
Copyright : (C) 2016 by Matthias Kuhn
Email : matthias@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 "qgsquicksubmodel.h"
QgsQuickSubModel::QgsQuickSubModel( QObject *parent )
: QAbstractItemModel( parent )
{
}
QModelIndex QgsQuickSubModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( !mModel )
return mRootIndex;
QModelIndex sourceIndex = mModel->index( row, column, parent.isValid() ? mapToSource( parent ) : static_cast<QModelIndex>( mRootIndex ) );
return mapFromSource( sourceIndex );
}
QModelIndex QgsQuickSubModel::parent( const QModelIndex &child ) const
{
if ( !mModel )
return mRootIndex;
QModelIndex idx = mModel->parent( child );
if ( idx == mRootIndex )
return QModelIndex();
else
return mapFromSource( idx );
}
int QgsQuickSubModel::rowCount( const QModelIndex &parent ) const
{
if ( !mModel )
return 0;
return mModel->rowCount( parent.isValid() ? mapToSource( parent ) : static_cast<QModelIndex>( mRootIndex ) );
}
int QgsQuickSubModel::columnCount( const QModelIndex &parent ) const
{
if ( !mModel )
return 0;
return mModel->columnCount( parent.isValid() ? mapToSource( parent ) : static_cast<QModelIndex>( mRootIndex ) );
}
QVariant QgsQuickSubModel::data( const QModelIndex &index, int role ) const
{
if ( !mModel )
return QVariant();
return mModel->data( mapToSource( index ), role );
}
bool QgsQuickSubModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( !mModel )
return false;
return mModel->setData( mapToSource( index ), value, role );
}
QHash<int, QByteArray> QgsQuickSubModel::roleNames() const
{
if ( !mModel )
return QHash<int, QByteArray>();
return mModel->roleNames();
}
QModelIndex QgsQuickSubModel::rootIndex() const
{
return mRootIndex;
}
void QgsQuickSubModel::setRootIndex( const QModelIndex &rootIndex )
{
if ( rootIndex == mRootIndex )
return;
beginResetModel();
mRootIndex = rootIndex;
endResetModel();
emit rootIndexChanged();
}
QAbstractItemModel *QgsQuickSubModel::model() const
{
return mModel;
}
void QgsQuickSubModel::setModel( QAbstractItemModel *model )
{
if ( model == mModel )
return;
if ( model )
{
connect( model, &QAbstractItemModel::rowsAboutToBeInserted, this, &QgsQuickSubModel::onRowsAboutToBeInserted );
connect( model, &QAbstractItemModel::rowsInserted, this, &QgsQuickSubModel::onRowsInserted );
connect( model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &QgsQuickSubModel::onRowsAboutToBeRemoved );
connect( model, &QAbstractItemModel::rowsRemoved, this, &QgsQuickSubModel::onRowsRemoved );
connect( model, &QAbstractItemModel::modelAboutToBeReset, this, &QgsQuickSubModel::onModelAboutToBeReset );
connect( model, &QAbstractItemModel::modelReset, this, &QAbstractItemModel::modelReset );
connect( model, &QAbstractItemModel::dataChanged, this, &QgsQuickSubModel::onDataChanged );
}
mModel = model;
emit modelChanged();
}
void QgsQuickSubModel::onRowsAboutToBeInserted( const QModelIndex &parent, int first, int last )
{
beginInsertRows( mapFromSource( parent ), first, last );
}
void QgsQuickSubModel::onRowsInserted( const QModelIndex &parent, int first, int last )
{
Q_UNUSED( parent )
Q_UNUSED( first )
Q_UNUSED( last )
endInsertRows();
}
void QgsQuickSubModel::onRowsAboutToBeRemoved( const QModelIndex &parent, int first, int last )
{
beginRemoveRows( mapFromSource( parent ), first, last );
}
void QgsQuickSubModel::onRowsRemoved( const QModelIndex &parent, int first, int last )
{
Q_UNUSED( parent )
Q_UNUSED( first )
Q_UNUSED( last )
endRemoveRows();
}
void QgsQuickSubModel::onModelAboutToBeReset()
{
mMappings.clear();
}
void QgsQuickSubModel::onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles )
{
emit dataChanged( mapFromSource( topLeft ), mapFromSource( bottomRight ), roles );
}
QModelIndex QgsQuickSubModel::mapFromSource( const QModelIndex &sourceIndex ) const
{
if ( sourceIndex == mRootIndex || !sourceIndex.isValid() )
return QModelIndex();
if ( !mMappings.contains( sourceIndex.internalId() ) )
{
mMappings.insert( sourceIndex.internalId(), sourceIndex.parent() );
}
return createIndex( sourceIndex.row(), sourceIndex.column(), sourceIndex.internalId() );
}
QModelIndex QgsQuickSubModel::mapToSource( const QModelIndex &index ) const
{
if ( !index.isValid() )
return mRootIndex;
if ( !mModel )
return mRootIndex;
return mModel->index( index.row(), index.column(), mMappings.find( index.internalId() ).value() );
}

View File

@ -0,0 +1,93 @@
/***************************************************************************
qgsquicksubmodel.h
--------------------------------------
Date : 16.9.2016
Copyright : (C) 2016 by Matthias Kuhn
Email : matthias@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 QGSQUICKSUBMODEL_H
#define QGSQUICKSUBMODEL_H
#include "qgis_quick.h"
#include "qgsquickattributeformmodel.h"
#include <QAbstractItemModel>
/**
* \ingroup quick
*
* Helper class for submodels (e.g. tabs within feature model).
*
* It uses internal mapping from internal indexes to indexes in the parent model.
*
* \note QML Type: SubModel
*
* \since QGIS 3.4
*/
class QUICK_EXPORT QgsQuickSubModel : public QAbstractItemModel
{
Q_OBJECT
//! Parent model (e.g QgsQuickAttributeFormModel)
Q_PROPERTY( QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged )
//! Root index of parent model
Q_PROPERTY( QModelIndex rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged )
public:
//! Creates new sub model
QgsQuickSubModel( QObject *parent = nullptr );
QModelIndex index( int row, int column, const QModelIndex &parent ) const override;
QModelIndex parent( const QModelIndex &child ) const override;
int rowCount( const QModelIndex &parent ) const override;
int columnCount( const QModelIndex &parent ) const override;
QVariant data( const QModelIndex &index, int role ) const override;
bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override;
QHash<int, QByteArray> roleNames() const override;
//! \copydoc QgsQuickSubModel::model
QAbstractItemModel *model() const;
//! \copydoc QgsQuickSubModel::model
void setModel( QAbstractItemModel *model );
//! \copydoc QgsQuickSubModel::rootIndex
QModelIndex rootIndex() const;
//! \copydoc QgsQuickSubModel::rootIndex
void setRootIndex( const QModelIndex &rootIndex );
signals:
//! \copydoc QgsQuickSubModel::model
void modelChanged();
//! \copydoc QgsQuickSubModel::rootIndex
void rootIndexChanged();
private slots:
void onRowsAboutToBeInserted( const QModelIndex &parent, int first, int last );
void onRowsInserted( const QModelIndex &parent, int first, int last );
void onRowsAboutToBeRemoved( const QModelIndex &parent, int first, int last );
void onRowsRemoved( const QModelIndex &parent, int first, int last );
void onModelAboutToBeReset();
void onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>() );
private:
QModelIndex mapFromSource( const QModelIndex &sourceIndex ) const;
QModelIndex mapToSource( const QModelIndex &index ) const;
QAbstractItemModel *mModel = nullptr; // not owned
QPersistentModelIndex mRootIndex;
// Map internal id to parent index
mutable QHash<qintptr, QModelIndex> mMappings;
};
#endif // QGSQUICKSUBMODEL_H

View File

@ -0,0 +1,5 @@
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0zm0 0h24v24H0zm21 19c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2h14c1.1 0 2 .9 2 2" fill="none"/>
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M21 5v6.59l-3-3.01-4 4.01-4-4-4 4-3-3.01V5c0-1.1.9-2 2-2h14c1.1 0 2 .9 2 2zm-3 6.42l3 3.01V19c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2v-6.58l3 2.99 4-4 4 4 4-3.99z"/>
</svg>

After

Width:  |  Height:  |  Size: 447 B

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_camera_alt_border_24dp.svg">
<metadata
id="metadata14">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs12" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1016"
id="namedview10"
showgrid="true"
showguides="false"
inkscape:zoom="19.666667"
inkscape:cx="11.698998"
inkscape:cy="13.796445"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<path
inkscape:connector-curvature="0"
d="m 8.4,0 -2.196,2.4444444 -3.804,0 c -1.32,0 -2.4,1.1 -2.4,2.4444444 L 0,19.555556 C 0,20.9 1.08,22 2.4,22 l 19.2,0 C 22.92,22 24,20.9 24,19.555556 L 24,4.8888888 C 24,3.5444444 22.92,2.4444444 21.6,2.4444444 l -3.804,0 L 15.6,0 Z"
id="path6-7"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:nodetypes="ccssssssssccc" />
<circle
cx="12"
cy="12"
r="3.2"
id="circle4" />
<path
d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"
id="path6"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter-blend-mode:normal;filter-gaussianBlur-deviation:0;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
d="M0 0h24v24H0z"
fill="none"
id="path8" />
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,4 @@
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>

After

Width:  |  Height:  |  Size: 213 B

View File

@ -0,0 +1,4 @@
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 265 B

View File

@ -0,0 +1,4 @@
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 265 B

View File

@ -0,0 +1,5 @@
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zm2.46-7.12l1.41-1.41L12 12.59l2.12-2.12 1.41 1.41L13.41 14l2.12 2.12-1.41 1.41L12 15.41l-2.12 2.12-1.41-1.41L10.59 14l-2.13-2.12zM15.5 4l-1-1h-5l-1 1H5v2h14V4z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 411 B

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 48 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_photo_notavailable_white_48dp.svg">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1321"
inkscape:window-height="729"
id="namedview8"
showgrid="false"
inkscape:zoom="1.7383042"
inkscape:cx="34.371391"
inkscape:cy="-23.586747"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M0 0h48v48H0z"
fill="none"
id="path4" />
<path
d="M 36.449219 42 L 30.449219 36 L 10 36 L 17 27 L 22 33.009766 L 24.386719 29.9375 L 6 11.550781 L 6 38 C 6 40.21 7.79 42 10 42 L 36.449219 42 z "
id="path4149" />
<path
d="M 42 37.449219 L 42 10 C 42 7.79 40.21 6 38 6 L 10.550781 6 L 28.802734 24.251953 L 29 24 L 30.347656 25.796875 L 42 37.449219 z "
id="path4184" />
<path
inkscape:connector-curvature="0"
d="M 5,10.55 37.450002,43 l 2.55,-2.55 L 7.55,8.0000004 Z"
id="path6-3"
sodipodi:nodetypes="ccccc" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,4 @@
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/>
</svg>

After

Width:  |  Height:  |  Size: 306 B

View File

@ -1,5 +1,13 @@
<RCC>
<qresource prefix="/">
<file>ic_broken_image_black.svg</file>
<file>ic_camera_alt_border.svg</file>
<file>ic_check_black.svg</file>
<file>ic_clear_black.svg</file>
<file>ic_clear_white.svg</file>
<file>ic_delete_forever_white.svg</file>
<file>ic_navigation_black.svg</file>
<file>ic_photo_notavailable_white.svg</file>
<file>ic_save_white.svg</file>
</qresource>
</RCC>

View File

@ -10,8 +10,16 @@ SET(QGIS_QUICK_PLUGIN_SRC
)
SET(QGIS_QUICK_PLUGIN_RESOURCES
editor/qgsquickcheckbox.qml
editor/qgsquickdatetime.qml
editor/qgsquickexternalresource.qml
editor/qgsquicktextedit.qml
editor/qgsquickvaluemap.qml
qgsquickfeatureform.qml
qgsquickfeatureformstyling.qml
qgsquickmapcanvas.qml
qgsquickmessagelog.qml
qgsquickphotopanel.qml
qgsquickpositionmarker.qml
qgsquickscalebar.qml
qmldir

View File

@ -0,0 +1,49 @@
/***************************************************************************
qgsquickcheckbox.qml
--------------------------------------
Date : 2017
Copyright : (C) 2017 by Matthias Kuhn
Email : matthias@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. *
* *
***************************************************************************/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QgisQuick 0.1 as QgsQuick
/**
* Checkbox for QGIS Attribute Form
* Requires various global properties set to function, see qgsquickfeatureform Loader section
* Do not use directly from Application QML
*/
Item {
signal valueChanged( var value, bool isNull )
height: childrenRect.height
anchors {
right: parent.right
left: parent.left
}
CheckBox {
property var currentValue: value
checked: value == config['CheckedState']
onCheckedChanged: {
valueChanged( checked ? config['CheckedState'] : config['UncheckedState'], false )
forceActiveFocus()
}
// Workaround to get a signal when the value has changed
onCurrentValueChanged: {
checked = currentValue == config['CheckedState']
}
}
}

View File

@ -0,0 +1,125 @@
/***************************************************************************
qgsquickdatetime.qml
--------------------------------------
Date : 2017
Copyright : (C) 2017 by Matthias Kuhn
Email : matthias@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. *
* *
***************************************************************************/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4 as Controls1
import QgsQuick 0.1 as QgsQuick
/**
* Calendar for QGIS Attribute Form
* Requires various global properties set to function, see qgsquickfeatureform Loader section
* Do not use directly from Application QML
*/
Item {
signal valueChanged(var value, bool isNull)
height: childrenRect.height
anchors { right: parent.right; left: parent.left }
ColumnLayout {
id: main
property var currentValue: value
anchors { right: parent.right; left: parent.left }
Item {
anchors { right: parent.right; left: parent.left }
Layout.minimumHeight: 48 * QgsQuick.Utils.dp
Rectangle {
anchors.fill: parent
id: backgroundRect
border.color: "#17a81a"
border.width: 2
color: "#dddddd"
radius: 2
}
Label {
id: label
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
MouseArea {
anchors.fill: parent
onClicked: {
popup.open()
}
}
Image {
source: QgsQuick.Utils.getThemeIcon("ic_clear_black")
anchors.left: parent.right
visible: main.currentValue !== undefined && config['allow_null']
MouseArea {
anchors.fill: parent
onClicked: {
main.currentValue = undefined
}
}
}
}
}
Popup {
id: popup
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
parent: ApplicationWindow.overlay
ColumnLayout {
Controls1.Calendar {
id: calendar
selectedDate: main.currentValue
weekNumbersVisible: true
focus: false
onSelectedDateChanged: {
main.currentValue = selectedDate
}
}
RowLayout {
Button {
text: qsTr( "Ok" )
Layout.fillWidth: true
onClicked: popup.close()
}
}
}
}
onCurrentValueChanged: {
valueChanged(currentValue, main.currentValue === undefined)
if (main.currentValue === undefined)
{
label.text = qsTr('(no date)')
label.color = 'gray'
}
else
{
label.color = 'black'
label.text = new Date(value).toLocaleString(Qt.locale(), config['display_format'] )
}
}
}
}

View File

@ -0,0 +1,85 @@
/***************************************************************************
qgsquickexternalresource.qml
--------------------------------------
Date : 2017
Copyright : (C) 2017 by Matthias Kuhn
Email : matthias@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. *
* *
***************************************************************************/
import QtQuick 2.5
import QtQuick.Controls 2.0
import QgsQuick 0.1 as QgsQuick
/**
* External Resource (Photo capture) for QGIS Attribute Form
* Requires various global properties set to function, see qgsquickfeatureform Loader section
* Do not use directly from Application QML
*/
Item {
signal valueChanged(var value, bool isNull)
property var image: image
id: fieldItem
anchors.left: parent.left
anchors.right: parent.right
height: Math.max(image.height, button.height)
QgsQuick.PhotoCapture {
id: photoCapturePanel
visible: false
height: window.height
width: window.width
edge: Qt.RightEdge
}
Image {
property var currentValue: value
id: image
width: 200 * QgsQuick.Utils.dp
autoTransform: true
fillMode: Image.PreserveAspectFit
Component.onCompleted: image.source = getSource()
function getSource() {
if (image.status === Image.Error)
return QgsQuick.Utils.getThemeIcon("ic_broken_image_black")
else if (image.currentValue && QgsQuick.Utils.fileExists(homePath + "/" + image.currentValue))
return homePath + "/" + image.currentValue
else
return QgsQuick.Utils.getThemeIcon("ic_photo_notavailable_white")
}
}
Button {
id: button
visible: fieldItem.enabled
width: 45 * QgsQuick.Utils.dp
height: 45 * QgsQuick.Utils.dp
anchors.right: parent.right
anchors.bottom: parent.bottom
onClicked: {
photoCapturePanel.visible = true
photoCapturePanel.targetDir = homePath
photoCapturePanel.fieldItem = fieldItem
}
background: Image {
source: QgsQuick.Utils.getThemeIcon("ic_camera_alt_border")
width: button.width
height: button.height
}
}
}

View File

@ -0,0 +1,88 @@
/***************************************************************************
qgsquicktextedit.qml
--------------------------------------
Date : 2017
Copyright : (C) 2017 by Matthias Kuhn
Email : matthias@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. *
* *
***************************************************************************/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick 2.5
import QgsQuick 0.1 as QgsQuick
/**
* Text Edit for QGIS Attribute Form
* Requires various global properties set to function, see qgsquickfeatureform Loader section
* Do not use directly from Application QML
*/
Item {
signal valueChanged(var value, bool isNull)
height: childrenRect.height
TextField {
id: textField
height: textArea.height == 0 ? fontMetrics.height + 20 * QgsQuick.Utils.dp : 0
topPadding: 10 * QgsQuick.Utils.dp
bottomPadding: 10 * QgsQuick.Utils.dp
visible: height !== 0
anchors.left: parent.left
anchors.right: parent.right
font.pointSize: 14 * QgsQuick.Utils.dp
text: value || ''
inputMethodHints: field.isNumeric || widget == 'Range' ? field.precision === 0 ? Qt.ImhDigitsOnly : Qt.ImhFormattedNumbersOnly : Qt.ImhNone
// Make sure we do not input more characters than allowed for strings
states: [
State {
name: "limitedTextLengthState"
when: (!field.isNumeric) && (field.length > 0)
PropertyChanges {
target: textField
maximumLength: field.length
}
}
]
background: Rectangle {
y: textField.height - height - textField.bottomPadding / 2
implicitWidth: 120 * QgsQuick.Utils.dp
height: textField.activeFocus ? 2 * QgsQuick.Utils.dp : 1 * QgsQuick.Utils.dp
color: textField.activeFocus ? "#4CAF50" : "#C8E6C9"
}
onTextChanged: {
valueChanged( text, text == '' )
}
}
TextArea {
id: textArea
height: config['IsMultiline'] === true ? undefined : 0
visible: height !== 0
anchors.left: parent.left
anchors.right: parent.right
text: value || ''
textFormat: config['UseHtml'] ? TextEdit.RichText : TextEdit.PlainText
onEditingFinished: {
valueChanged( text, text == '' )
}
}
FontMetrics {
id: fontMetrics
font: textField.font
}
}

View File

@ -0,0 +1,109 @@
/***************************************************************************
qgsquickvaluemap.qml
--------------------------------------
Date : 2017
Copyright : (C) 2017 by Matthias Kuhn
Email : matthias@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. *
* *
***************************************************************************/
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtGraphicalEffects 1.0
import QgsQuick 0.1 as QgsQuick
/**
* Value Map for QGIS Attribute Form
* Requires various global properties set to function, see qgsquickfeatureform Loader section
* Do not use directly from Application QML
*/
Item {
signal valueChanged(var value, bool isNull)
anchors {
left: parent.left
right: parent.right
rightMargin: 10 * QgsQuick.Utils.dp
}
height: childrenRect.height + 10 * QgsQuick.Utils.dp
ComboBox {
id: comboBox
property var reverseConfig: ({})
property var currentValue: value
anchors { left: parent.left; right: parent.right }
currentIndex: find(reverseConfig[value])
Component.onCompleted: {
model = Object.keys(config['map']);
for(var key in config['map']) {
reverseConfig[config['map'][key]] = key;
}
currentIndex = find(reverseConfig[value])
}
onCurrentTextChanged: {
valueChanged(config['map'][currentText], false)
}
// Workaround to get a signal when the value has changed
onCurrentValueChanged: {
currentIndex = find(reverseConfig[value])
}
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: mouse.accepted = false
onPressed: { forceActiveFocus(); mouse.accepted = false; }
onReleased: mouse.accepted = false;
onDoubleClicked: mouse.accepted = false;
onPositionChanged: mouse.accepted = false;
onPressAndHold: mouse.accepted = false;
}
// [hidpi fixes]
delegate: ItemDelegate {
width: comboBox.width
height: 36 * QgsQuick.Utils.dp
text: modelData
font.weight: comboBox.currentIndex === index ? Font.DemiBold : Font.Normal
font.pointSize: 12 * QgsQuick.Utils.dp
highlighted: comboBox.highlightedIndex == index
}
contentItem: Text {
height: 36 * QgsQuick.Utils.dp
text: comboBox.displayText
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Item {
implicitWidth: 120 * QgsQuick.Utils.dp
implicitHeight: 36 * QgsQuick.Utils.dp
Rectangle {
anchors.fill: parent
id: backgroundRect
border.color: comboBox.pressed ? "#17a81a" : "#21be2b"
border.width: comboBox.visualFocus ? 2 : 1
color: "#dddddd"
radius: 2
}
}
// [/hidpi fixes]
}
}

View File

@ -0,0 +1,487 @@
/***************************************************************************
qgsquickfeatureform.qml
--------------------------------------
Date : Nov 2017
Copyright : (C) 2017 by Matthias Kuhn
Email : matthias@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. *
* *
***************************************************************************/
import QtQuick 2.6
import QtQuick.Controls 2.0
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQml.Models 2.2
import QtQml 2.2
// We use calendar in datetime widget that is not yet implemented in Controls 2.2
import QtQuick.Controls 1.4 as Controls1
import QgsQuick 0.1 as QgsQuick
Item {
/**
* When feature in the form is saved.
*/
signal saved
/**
* When the form is about to be closed by closeButton or deleting a feature.
*/
signal canceled
/**
* AttributeFormModel binded on a feature supporting auto-generated editor layouts and "tab" layout.
*/
property QgsQuick.AttributeFormModel model
/**
* Visibility of toolbar.
*/
property alias toolbarVisible: toolbar.visible
/**
* When adding a new feature, add checkbox to be able to save the same value for the next feature as default.
*/
property bool allowRememberAttribute: false
/**
* Active project.
*/
property QgsQuick.Project project
/**
* The function used for a component loader to find qml edit widget componets used in form.
*/
property var loadWidgetFn: QgsQuick.Utils.getEditorComponentSource
/**
* Icon path for save button.
*/
property string saveButtonIcon: QgsQuick.Utils.getThemeIcon( "ic_save_white" )
/**
* Icon path for delete button.
*/
property string deleteButtonIcon: QgsQuick.Utils.getThemeIcon( "ic_delete_forever_white" )
/**
* Icon path for close button
*/
property string closeButtonIcon: QgsQuick.Utils.getThemeIcon( "ic_clear_white" )
/**
* Predefined form styling
*/
property FeatureFormStyling style: FeatureFormStyling {}
id: form
states: [
State {
name: "ReadOnly"
},
State {
name: "Edit"
},
State {
name: "Add"
}
]
function reset() {
master.reset()
}
function save() {
parent.focus = true
if ( form.state === "Add" ) {
model.create()
state = "Edit"
}
else
{
model.save()
}
saved()
}
/**
* This is a relay to forward private signals to internal components.
*/
QtObject {
id: master
/**
* This signal is emitted whenever the state of Flickables and TabBars should
* be restored.
*/
signal reset
}
Item {
id: container
clip: true
anchors {
top: toolbar.bottom
bottom: parent.bottom
left: parent.left
right: parent.right
}
Flickable {
id: flickable
anchors {
left: parent.left
right: parent.right
}
height: tabRow.height
flickableDirection: Flickable.HorizontalFlick
contentWidth: tabRow.width
// Tabs
TabBar {
id: tabRow
visible: model.hasTabs
height: form.style.tabs.height
Connections {
target: master
onReset: tabRow.currentIndex = 0
}
Connections {
target: swipeView
onCurrentIndexChanged: tabRow.currentIndex = swipeView.currentIndex
}
Repeater {
model: form.model
TabButton {
id: tabButton
text: Name
leftPadding: 8 * QgsQuick.Utils.dp
rightPadding: 8 * QgsQuick.Utils.dp
width: contentItem.width + leftPadding + rightPadding
height: form.style.tabs.height
contentItem: Text {
// Make sure the width is derived from the text so we can get wider
// than the parent item and the Flickable is useful
width: paintedWidth
text: tabButton.text
color: !tabButton.enabled ? form.style.tabs.disabledColor : tabButton.down ||
tabButton.checked ? form.style.tabs.activeColor : form.style.tabs.normalColor
font.weight: tabButton.checked ? Font.DemiBold : Font.Normal
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
}
}
SwipeView {
id: swipeView
currentIndex: tabRow.currentIndex
anchors {
top: flickable.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
}
Repeater {
/**
* One page per tab in tabbed forms, 1 page in auto forms
*/
model: form.model.hasTabs ? form.model : 1
Item {
id: formPage
property int currentIndex: index
/**
* The main form content area
*/
Rectangle {
anchors.fill: parent
color: form.style.backgroundColor
opacity: form.style.backgroundOpacity
}
ListView {
id: content
anchors.fill: parent
clip: true
section.property: "Group"
section.labelPositioning: ViewSection.CurrentLabelAtStart | ViewSection.InlineLabels
section.delegate: Component {
// section header: group box name
Rectangle {
width: parent.width
height: section === "" ? 0 : form.style.group.height
color: form.style.group.backgroundColor
Text {
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
font.bold: true
text: section
}
}
}
Connections {
target: master
onReset: content.contentY = 0
}
model: QgsQuick.SubModel {
id: contentModel
model: form.model
rootIndex: form.model.hasTabs ? form.model.index(currentIndex, 0) : undefined
}
delegate: fieldItem
}
}
}
}
}
/**
* A field editor
*/
Component {
id: fieldItem
Item {
id: fieldContainer
visible: Type === 'field'
height: childrenRect.height
anchors {
left: parent.left
right: parent.right
leftMargin: 12 * QgsQuick.Utils.dp
}
Label {
id: fieldLabel
text: qsTr(Name) || ''
font.bold: true
color: ConstraintValid ? form.style.constraint.validColor : form.style.constraint.invalidColor
}
Label {
id: constraintDescriptionLabel
anchors {
left: parent.left
right: parent.right
top: fieldLabel.bottom
}
text: qsTr(ConstraintDescription)
height: ConstraintValid ? 0 : undefined
visible: !ConstraintValid
color: form.style.constraint.descriptionColor
}
Item {
id: placeholder
height: childrenRect.height
anchors { left: parent.left; right: rememberCheckbox.left; top: constraintDescriptionLabel.bottom }
Loader {
id: attributeEditorLoader
height: childrenRect.height
anchors { left: parent.left; right: parent.right }
enabled: form.state !== "ReadOnly" && !!AttributeEditable
property var value: AttributeValue
property var config: EditorWidgetConfig
property var widget: EditorWidget
property var field: Field
property var constraintValid: ConstraintValid
property var homePath: form.project ? form.project.homePath : ""
active: widget !== 'Hidden'
source: form.loadWidgetFn(widget.toLowerCase())
}
Connections {
target: attributeEditorLoader.item
onValueChanged: {
AttributeValue = isNull ? undefined : value
}
}
}
CheckBox {
id: rememberCheckbox
checked: RememberValue ? true : false
visible: form.allowRememberAttribute && form.state === "Add" && EditorWidget !== "Hidden"
width: visible ? undefined : 0
anchors { right: parent.right; top: fieldLabel.bottom }
onCheckedChanged: {
RememberValue = checked
}
}
}
}
Connections {
target: Qt.inputMethod
onVisibleChanged: {
Qt.inputMethod.commit()
}
}
/** The form toolbar **/
Item {
id: toolbar
height: visible ? 48 * QgsQuick.Utils.dp : 0
visible: form.state === 'Add'
anchors {
top: parent.top
left: parent.left
right: parent.right
}
RowLayout {
anchors.fill: parent
Layout.margins: 0
ToolButton {
id: saveButton
Layout.preferredWidth: form.style.toolbutton.size
Layout.preferredHeight: form.style.toolbutton.size
visible: form.state !== "ReadOnly"
contentItem: Image {
source: form.saveButtonIcon
sourceSize: Qt.size(width, height)
}
background: Rectangle {
color: model.constraintsValid ? form.style.toolbutton.backgroundColor : form.style.toolbutton.backgroundColorInvalid
}
enabled: model.constraintsValid
onClicked: {
form.save()
}
}
ToolButton {
id: deleteButton
Layout.preferredWidth: form.style.toolbutton.size
Layout.preferredHeight: form.style.toolbutton.size
visible: form.state === "Edit"
contentItem: Image {
source: form.deleteButtonIcon
sourceSize: Qt.size(width, height)
}
background: Rectangle {
color: form.style.toolbutton.backgroundColor
}
onClicked: deleteDialog.visible = true
}
Label {
id: titleLabel
text:
{
var currentLayer = model.attributeModel.featureLayerPair.layer
var layerName = 'N/A'
if (!!currentLayer)
layerName = currentLayer.name
if ( form.state === 'Add' )
qsTr( 'Add feature on <i>%1</i>' ).arg(layerName )
else if ( form.state === 'Edit' )
qsTr( 'Edit feature on <i>%1</i>' ).arg(layerName)
else
qsTr( 'View feature on <i>%1</i>' ).arg(layerName)
}
font.bold: true
font.pointSize: 16
elide: Label.ElideRight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
Layout.fillWidth: true
color: "white"
}
ToolButton {
id: closeButton
anchors.right: parent.right
Layout.preferredWidth: form.style.toolbutton.size
Layout.preferredHeight: form.style.toolbutton.size
contentItem: Image {
source: form.closeButtonIcon
sourceSize: Qt.size(width, height)
}
background: Rectangle {
color: form.style.toolbutton.backgroundColor
}
onClicked: {
Qt.inputMethod.hide()
form.canceled()
}
}
}
}
MessageDialog {
id: deleteDialog
visible: false
title: qsTr( "Delete feature" )
text: qsTr( "Really delete this feature?" )
icon: StandardIcon.Warning
standardButtons: StandardButton.Ok | StandardButton.Cancel
onAccepted: {
model.attributeModel.deleteFeature()
visible = false
form.canceled()
}
onRejected: {
visible = false
}
}
}

View File

@ -0,0 +1,47 @@
/***************************************************************************
qgsquickfeatureformstyling.qml
--------------------------------------
Date : January 2018
Copyright : (C) 2018 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
import QtQuick 2.0
import QgsQuick 0.1 as QgsQuick
QtObject {
property color backgroundColor: "white"
property real backgroundOpacity: 1
property QtObject group: QtObject {
property color backgroundColor: "lightGray"
property real height: 30 * QgsQuick.Utils.dp
}
property QtObject tabs: QtObject {
property color normalColor: "#4CAF50"
property color activeColor: "#1B5E20"
property color disabledColor: "#999999"
property real height: 48 * QgsQuick.Utils.dp
}
property QtObject constraint: QtObject {
property color validColor: "black"
property color invalidColor: "#c0392b"
property color descriptionColor: "#e67e22"
}
property QtObject toolbutton: QtObject {
property color backgroundColor: "transparent"
property color backgroundColorInvalid: "#bdc3c7"
property real size: 80 * QgsQuick.Utils.dp
}
}

View File

@ -0,0 +1,222 @@
/***************************************************************************
qgsquickphotopanel.qml
--------------------------------------
Date : Dec 2017
Copyright : (C) 2017 by Viktor Sklencar
Email : vsklencar at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
import QtQuick 2.3
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.2
import QtQml 2.2
import QtMultimedia 5.8
import QtGraphicalEffects 1.0
import QgsQuick 0.1 as QgsQuick
Drawer {
property var targetDir
property var lastPhotoName
property int iconSize: photoPanel.width/20
property var fieldItem
property color bgColor: "white"
property real bgOpacity: 0.8
property color borderColor: "black"
// icons:
property var captureButtonIcon: QgsQuick.Utils.getThemeIcon("ic_camera_alt_border")
property var okButtonIcon: QgsQuick.Utils.getThemeIcon("ic_check_black")
property var cancelButtonIcon: QgsQuick.Utils.getThemeIcon("ic_clear_black")
id: photoPanel
visible: false
modal: true
interactive: true
dragMargin: 0 // prevents opening the drawer by dragging.
background: Rectangle {
color: photoPanel.bgColor
opacity: photoPanel.bgOpacity
}
onVisibleChanged: {
if (visible) {
camera.setCameraState(Camera.ActiveState)
camera.start()
} else {
camera.stop()
photoPreview.visible = false
}
}
// PhotoCapture item
Item {
property bool saveImage: false
id: captureItem
width: window.width
height: window.height
Component.onDestruction: {
if (!captureItem && camera.imageCapture.capturedImagePath != ""){
captureItem.saveImage = false
QgsQuick.Utils.remove(camera.imageCapture.capturedImagePath)
}
captureItem.saveImage = false
}
Camera {
id: camera
cameraState: Camera.UnloadedState
imageCapture {
onImageCaptured: {
// Show the preview in an Image
photoPreview.source = preview
}
}
}
// Flipped VideoOutput on android - known ButtonGroup
// https://bugreports.qt.io/browse/QTBUG-64764
VideoOutput {
id: videoOutput
source: camera
focus : visible // to receive focus and capture key events when visible
anchors.fill: parent
autoOrientation: true
Rectangle {
id: captureButton
property int borderWidth: 10 * QgsQuick.Utils.dp
width: parent.width/20
height: parent.width/20
color: photoPanel.bgColor
border.color: photoPanel.borderColor
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
border.width: borderWidth
radius: width*0.5
antialiasing: true
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: {
if (targetDir !== "") {
camera.imageCapture.captureToLocation(photoPanel.targetDir);
} else {
// saved to default location - TODO handle this case
camera.imageCapture.capture();
}
photoPreview.visible = true;
}
}
Image {
id: captureButtonImage
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
sourceSize.height: captureButton.height/2
height: captureButton.height/2
source: photoPanel.captureButtonIcon
}
}
Image {
id: photoPreview
width: parent.width
height: parent.height
fillMode: Image.PreserveAspectFit
// Cancel button
Rectangle {
id: cancelButton
visible: camera.imageCapture.capturedImagePath != ""
property int borderWidth: 10 * QgsQuick.Utils.dp
width: parent.width/20
height: parent.width/20
color: photoPanel.bgColor
border.color: photoPanel.borderColor
anchors.right: parent.right
anchors.top: confirmButton.bottom
border.width: borderWidth
radius: width*0.5
antialiasing: true
MouseArea {
anchors.fill: parent
onClicked: {
captureItem.saveImage = false
photoPreview.visible = false
if (camera.imageCapture.capturedImagePath != "") {
QgsQuick.Utils.remove(camera.imageCapture.capturedImagePath)
}
}
}
Image {
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
sourceSize.height: captureButton.height/2
height: captureButton.height/2
source: photoPanel.cancelButtonIcon
}
}
// OK button
Rectangle {
id: confirmButton
visible: camera.imageCapture.capturedImagePath != ""
property int borderWidth: 10 * QgsQuick.Utils.dp
width: parent.width/20
height: parent.width/20
color: photoPanel.bgColor
border.color: photoPanel.borderColor
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
border.width: borderWidth
radius: width*0.5
antialiasing: true
MouseArea {
anchors.fill: parent
onClicked: {
captureItem.saveImage = true
photoPanel.visible = false
photoPanel.lastPhotoName = QgsQuick.Utils.getFileName(camera.imageCapture.capturedImagePath)
if (photoPanel.lastPhotoName !== "") {
fieldItem.image.source = photoPanel.targetDir + "/" + photoPanel.lastPhotoName
fieldItem.valueChanged(photoPanel.lastPhotoName, photoPanel.lastPhotoName === "" || photoPanel.lastPhotoName === null)
}
}
}
Image {
fillMode: Image.PreserveAspectFit
anchors.centerIn: parent
sourceSize.height: captureButton.height/2
height: captureButton.height/2
source: photoPanel.okButtonIcon
}
}
}
}
}
}

View File

@ -30,6 +30,9 @@
#include "qgsvectorlayer.h"
#include "qgsunittypes.h"
#include "qgsquickattributeformmodel.h"
#include "qgsquickattributeformmodelbase.h"
#include "qgsquickattributemodel.h"
#include "qgsquickfeaturehighlight.h"
#include "qgsquickcoordinatetransformer.h"
#include "qgsquickidentifykit.h"
@ -41,6 +44,7 @@
#include "qgsquickplugin.h"
#include "qgsquickpositionkit.h"
#include "qgsquickscalebarkit.h"
#include "qgsquicksubmodel.h"
#include "qgsquickutils.h"
static QObject *_utilsProvider( QQmlEngine *engine, QJSEngine *scriptEngine )
@ -69,6 +73,8 @@ void QgsQuickPlugin::registerTypes( const char *uri )
qmlRegisterUncreatableType< QgsUnitTypes >( uri, 0, 1, "QgsUnitTypes", "Only enums from QgsUnitTypes can be used" );
qmlRegisterType< QgsProject >( uri, 0, 1, "Project" );
qmlRegisterType< QgsQuickAttributeFormModel >( uri, 0, 1, "AttributeFormModel" );
qmlRegisterType< QgsQuickAttributeModel >( uri, 0, 1, "AttributeModel" );
qmlRegisterType< QgsQuickFeatureHighlight >( uri, 0, 1, "FeatureHighlight" );
qmlRegisterType< QgsQuickCoordinateTransformer >( uri, 0, 1, "CoordinateTransformer" );
qmlRegisterType< QgsQuickIdentifyKit >( uri, 0, 1, "IdentifyKit" );
@ -78,6 +84,7 @@ void QgsQuickPlugin::registerTypes( const char *uri )
qmlRegisterType< QgsQuickMessageLogModel >( uri, 0, 1, "MessageLogModel" );
qmlRegisterType< QgsQuickPositionKit >( uri, 0, 1, "PositionKit" );
qmlRegisterType< QgsQuickScaleBarKit >( uri, 0, 1, "ScaleBarKit" );
qmlRegisterType< QgsQuickSubModel >( uri, 0, 1, "SubModel" );
qmlRegisterType< QgsVectorLayer >( uri, 0, 1, "VectorLayer" );
qmlRegisterSingletonType< QgsQuickUtils >( uri, 0, 1, "Utils", _utilsProvider );

View File

@ -14,8 +14,11 @@ module QgsQuick
plugin qgis_quick_plugin
MapCanvas 0.1 qgsquickmapcanvas.qml
FeatureForm 0.1 qgsquickfeatureform.qml
FeatureFormStyling 0.1 qgsquickfeatureformstyling.qml
PositionMarker 0.1 qgsquickpositionmarker.qml
ScaleBar 0.1 qgsquickscalebar.qml
PhotoCapture 0.1 qgsquickphotopanel.qml
MessageLog 0.1 qgsquickmessagelog.qml
typeinfo qgsquick.qmltypes

View File

@ -30,7 +30,7 @@
* Helper class for transform of coordinates (QgsPoint) to a different coordinate reference system.
*
* It requires connection of transformation context from mapSettings, source position and source CRS to
* calculate projected position in desired destination CRS
* calculate projected position in desired destination CRS.
*
* \note QML Type: CoordinateTransformer
*

View File

@ -27,7 +27,7 @@ class QgsQuickMapSettings;
/**
* \ingroup quick
*
* Creates map highlights for a geometry provided by a FeatureModel.
* Creates map highlights for a geometry provided by a AttributeModel.
*
* The highlights are compatible with the QtQuick scene graph and
* can be direcly shown on map canvas

View File

@ -36,6 +36,11 @@ QgsFeature QgsQuickFeatureLayerPair::feature() const
return mFeature;
}
QgsFeature &QgsQuickFeatureLayerPair::featureRef()
{
return mFeature;
}
bool QgsQuickFeatureLayerPair::isValid() const
{
return ( mLayer && mFeature.isValid() && hasValidGeometry() );

View File

@ -82,6 +82,9 @@ class QUICK_EXPORT QgsQuickFeatureLayerPair
//! \copydoc QgsQuickFeatureLayerPair::feature
QgsFeature feature() const;
//! \copydoc QgsQuickFeatureLayerPair::feature
QgsFeature &featureRef();
//! \copydoc QgsQuickFeatureLayerPair::valid
bool isValid() const;

View File

@ -44,12 +44,12 @@ class QUICK_EXPORT QgsQuickMapTransform : public QQuickTransform
Q_PROPERTY( QgsQuickMapSettings *mapSettings READ mapSettings WRITE setMapSettings NOTIFY mapSettingsChanged )
public:
//! create new map transform
//! Creates a new map transform
QgsQuickMapTransform() = default;
~QgsQuickMapTransform() = default;
/**
* Apply transformation based on current map settings to a matrix.
* Applies transformation based on current map settings to a matrix.
*
* Also optimize resulting matrix after transformation
* \param matrix Matrix to be transformed
@ -70,7 +70,7 @@ class QUICK_EXPORT QgsQuickMapTransform : public QQuickTransform
void updateMatrix();
private:
QgsQuickMapSettings *mMapSettings = nullptr;
QgsQuickMapSettings *mMapSettings = nullptr; // not owned
QMatrix4x4 mMatrix;
};

View File

@ -21,6 +21,7 @@
#include "qgsdistancearea.h"
#include "qgslogger.h"
#include "qgsvectorlayer.h"
#include "qgsfeature.h"
#include "qgsquickfeaturelayerpair.h"
#include "qgsquickmapsettings.h"
@ -84,6 +85,20 @@ double QgsQuickUtils::screenUnitsToMeters( QgsQuickMapSettings *mapSettings, int
return mDistanceArea.measureLine( p1, p2 );
}
bool QgsQuickUtils::fileExists( const QString &path ) const
{
QFileInfo check_file( path );
// check if file exists and if yes: Is it really a file and no directory?
return ( check_file.exists() && check_file.isFile() );
}
QString QgsQuickUtils::getFileName( const QString &path ) const
{
QFileInfo fileInfo( path );
QString filename( fileInfo.fileName() );
return filename;
}
void QgsQuickUtils::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
{
QgsMessageLog::logMessage( message, tag, level );
@ -101,6 +116,25 @@ const QUrl QgsQuickUtils::getThemeIcon( const QString &name ) const
return QUrl( path );
}
const QUrl QgsQuickUtils::getEditorComponentSource( const QString &widgetName )
{
QString path( "qgsquick%1.qml" );
QStringList supportedWidgets = { QStringLiteral( "textedit" ),
QStringLiteral( "valuemap" ),
QStringLiteral( "checkbox" ),
QStringLiteral( "externalresource" ),
QStringLiteral( "datetime" )
};
if ( supportedWidgets.contains( widgetName ) )
{
return QUrl( path.arg( widgetName ) );
}
else
{
return QUrl( path.arg( QStringLiteral( "textedit" ) ) );
}
}
QString QgsQuickUtils::formatPoint(
const QgsPoint &point,
QgsCoordinateFormatter::Format format,

View File

@ -19,6 +19,7 @@
#include <QObject>
#include <QString>
#include <QUrl>
#include <QtPositioning/QGeoCoordinate>
#include <limits>
@ -31,9 +32,10 @@
#include "qgsquickmapsettings.h"
#include "qgsquickfeaturelayerpair.h"
#include "qgis_quick.h"
#include "qgsfeature.h"
#include "qgscoordinateformatter.h"
class QgsFeature;
class QgsVectorLayer;
class QgsCoordinateReferenceSystem;
@ -115,7 +117,21 @@ class QUICK_EXPORT QgsQuickUtils: public QObject
*/
Q_INVOKABLE static double screenUnitsToMeters( QgsQuickMapSettings *mapSettings, int baseLengthPixels );
//! Log message in QgsMessageLog
/**
* Returns whether file on path exists
* \since QGIS 3.4
*/
Q_INVOKABLE bool fileExists( const QString &path ) const;
/**
* Extracts filename from path
* \since QGIS 3.4
*/
Q_INVOKABLE QString getFileName( const QString &path ) const;
/**
* Log message in QgsMessageLog
*/
Q_INVOKABLE void logMessage( const QString &message,
const QString &tag = QString( "QgsQuick" ),
Qgis::MessageLevel level = Qgis::Warning );
@ -136,6 +152,15 @@ class QUICK_EXPORT QgsQuickUtils: public QObject
*/
Q_INVOKABLE const QUrl getThemeIcon( const QString &name ) const;
/**
* Returns url to field editor component for a feature form.
* If the widgetName does not match any supported widget, text edit is returned.
* \param widgetName name of the attribute field widget
*
* \since QGIS 3.4
*/
Q_INVOKABLE const QUrl getEditorComponentSource( const QString &widgetName );
/**
* \copydoc QgsCoordinateFormatter::format()
*

View File

@ -7,6 +7,7 @@ SET(QGIS_QUICK_APP_SRCS
SET(QGIS_QUICK_APP_QMLS
main.qml
FeaturePanel.qml
)
INCLUDE_DIRECTORIES(

View File

@ -0,0 +1,73 @@
/***************************************************************************
FeaturePanel.qml
----------------
Date : Nov 2017
Copyright : (C) 2017 by Peter Petrik
Email : zilolv at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QgsQuick 0.1 as QgsQuick
Drawer {
property var mapSettings
property var project
property alias state: featureForm.state
property alias feature: attributeModel.featureLayerPair
property alias currentAttributeModel: attributeModel
id: featurePanel
visible: false
modal: true
interactive: true
dragMargin: 0 // prevents opening the drawer by dragging.
background: Rectangle {
color: "white"
opacity: 0.5
}
function show_panel(feature, state) {
featurePanel.feature = feature
featurePanel.state = state
featurePanel.visible = true
}
QgsQuick.FeatureForm {
id: featureForm
// using anchors here is not working well as
width: featurePanel.width
height: featurePanel.height
model: QgsQuick.AttributeFormModel {
attributeModel: QgsQuick.AttributeModel {
id: attributeModel
}
}
project: featurePanel.project
toolbarVisible: true
onSaved: {
featurePanel.visible = false
}
onCanceled:
{
featurePanel.visible = false
}
}
}

View File

@ -1,6 +1,6 @@
/***************************************************************************
main.qml
--------------------------------------
--------
Date : Nov 2017
Copyright : (C) 2017 by Peter Petrik
Email : zilolv at gmail dot com
@ -21,7 +21,15 @@ ApplicationWindow {
id: window
visible: true
visibility: "Maximized"
title: qsTr("QGIS Quick Test App")
title: "QGIS Quick Test App"
// Some info
Button {
id: logbutton
text: "Log"
onClicked: logPanel.visible = true
z: 1
}
QgsQuick.MapCanvas {
id: mapCanvas
@ -38,9 +46,12 @@ ApplicationWindow {
}
onClicked: {
var screenPoint = Qt.point(mouse.x, mouse.y)
var screenPoint = Qt.point( mouse.x, mouse.y );
var res = identifyKit.identifyOne(screenPoint);
highlight.featureLayerPair = res
if (res.valid)
featurePanel.show_panel(res, "Edit" )
}
}
@ -55,11 +66,11 @@ ApplicationWindow {
/** Message Log */
Drawer {
id: logPanel
visible: true
visible: false
modal: true
interactive: true
height: window.height
width: QgsQuick.Utils.dp * 700
width: 700 * QgsQuick.Utils.dp
edge: Qt.RightEdge
z: 2 // make sure items from here are on top of the Z-order
@ -72,7 +83,6 @@ ApplicationWindow {
width: parent.width
height: parent.height
model: QgsQuick.MessageLogModel {}
visible: true
}
}
@ -108,6 +118,7 @@ ApplicationWindow {
QgsQuick.PositionMarker {
id: positionMarker
positionKit: positionKit
z: 2
}
Label {
@ -151,4 +162,14 @@ ApplicationWindow {
color: "steelblue"
z: 1
}
FeaturePanel {
id: featurePanel
height: window.height
width: 700 * QgsQuick.Utils.dp
edge: Qt.RightEdge
mapSettings: mapCanvas.mapSettings
project: __project
visible: false
}
}

View File

@ -1,5 +1,6 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>FeaturePanel.qml</file>
</qresource>
</RCC>

View File

@ -1,5 +1,5 @@
/***************************************************************************
testqgsquickidentifykit.cpp.cpp
testqgsquickidentifykit.cpp
--------------------------------------
Date : May 2018
Copyright : (C) 2018 by Viktor Sklencar

View File

@ -40,6 +40,9 @@ class TestQgsQuickUtils: public QObject
void transformedPoint();
void formatPoint();
void formatDistance();
void loadIcon();
void fileExists();
void loadQmlComponent();
private:
QgsQuickUtils utils;
@ -136,5 +139,30 @@ void TestQgsQuickUtils::formatDistance()
QVERIFY( dist2str == "1.2 NM" );
}
void TestQgsQuickUtils::loadIcon()
{
QUrl url = utils.getThemeIcon( "ic_save_white" );
Q_ASSERT( url.toString() == QStringLiteral( "qrc:/ic_save_white.svg" ) );
QString fileName = utils.getFileName( url.toString() );
Q_ASSERT( fileName == QStringLiteral( "ic_save_white.svg" ) );
}
void TestQgsQuickUtils::fileExists()
{
QString path = QStringLiteral( TEST_DATA_DIR ) + "/quickapp_project.qgs";
Q_ASSERT( utils.fileExists( path ) );
}
void TestQgsQuickUtils::loadQmlComponent()
{
QUrl dummy = utils.getEditorComponentSource( "dummy" );
Q_ASSERT( dummy.path() == QString( "qgsquicktextedit.qml" ) );
QUrl valuemap = utils.getEditorComponentSource( "valuemap" );
Q_ASSERT( valuemap.path() == QString( "qgsquickvaluemap.qml" ) );
}
QGSTEST_MAIN( TestQgsQuickUtils )
#include "testqgsquickutils.moc"