mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-11-04 00:04:25 -05:00 
			
		
		
		
	[FEATURE][layouts] Data defined table source for attribute table items
When an attribute table is set to a "Layer features" source, this allows the underlying vector layer from which to source features to be data defined. (All existing table attributes (column settings) are left intact, so setting a data defined table to a layer with different fields will result in empty columns in the table.) Sponsored by Kartoza/Inasafe
This commit is contained in:
		
							parent
							
								
									97324661c7
								
							
						
					
					
						commit
						0c1ceb3900
					
				@ -295,6 +295,9 @@ be replaced by a line break.
 | 
			
		||||
    virtual void finalizeRestoreFromXml();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  protected:
 | 
			
		||||
 | 
			
		||||
    virtual bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const;
 | 
			
		||||
 | 
			
		||||
@ -71,6 +71,8 @@ A base class for objects which belong to a layout.
 | 
			
		||||
      ScalebarFillColor2,
 | 
			
		||||
      ScalebarLineColor,
 | 
			
		||||
      ScalebarLineWidth,
 | 
			
		||||
      //table
 | 
			
		||||
      AttributeTableSourceLayer,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    enum PropertyValueType
 | 
			
		||||
 | 
			
		||||
@ -131,8 +131,12 @@ QgsLayoutAttributeTableWidget::QgsLayoutAttributeTableWidget( QgsLayoutFrame *fr
 | 
			
		||||
    {
 | 
			
		||||
      connect( atlas, &QgsLayoutAtlas::toggled, this, &QgsLayoutAttributeTableWidget::atlasToggled );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mLayerSourceDDBtn->registerExpressionContextGenerator( mTable );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  registerDataDefinedButton( mLayerSourceDDBtn, QgsLayoutObject::AttributeTableSourceLayer );
 | 
			
		||||
 | 
			
		||||
  //embed widget for general options
 | 
			
		||||
  if ( mFrame )
 | 
			
		||||
  {
 | 
			
		||||
@ -925,6 +929,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls()
 | 
			
		||||
    case QgsLayoutItemAttributeTable::LayerAttributes:
 | 
			
		||||
      mLayerComboBox->setEnabled( true );
 | 
			
		||||
      mLayerComboBox->setVisible( true );
 | 
			
		||||
      mLayerSourceDDBtn->setVisible( true );
 | 
			
		||||
      mLayerLabel->setVisible( true );
 | 
			
		||||
      mRelationsComboBox->setEnabled( false );
 | 
			
		||||
      mRelationsComboBox->setVisible( false );
 | 
			
		||||
@ -938,6 +943,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls()
 | 
			
		||||
    case QgsLayoutItemAttributeTable::AtlasFeature:
 | 
			
		||||
      mLayerComboBox->setEnabled( false );
 | 
			
		||||
      mLayerComboBox->setVisible( false );
 | 
			
		||||
      mLayerSourceDDBtn->setVisible( false );
 | 
			
		||||
      mLayerLabel->setVisible( false );
 | 
			
		||||
      mRelationsComboBox->setEnabled( false );
 | 
			
		||||
      mRelationsComboBox->setVisible( false );
 | 
			
		||||
@ -952,6 +958,7 @@ void QgsLayoutAttributeTableWidget::toggleSourceControls()
 | 
			
		||||
      mLayerComboBox->setEnabled( false );
 | 
			
		||||
      mLayerComboBox->setVisible( false );
 | 
			
		||||
      mLayerLabel->setVisible( false );
 | 
			
		||||
      mLayerSourceDDBtn->setVisible( false );
 | 
			
		||||
      mRelationsComboBox->setEnabled( true );
 | 
			
		||||
      mRelationsComboBox->setVisible( true );
 | 
			
		||||
      mRelationLabel->setVisible( true );
 | 
			
		||||
 | 
			
		||||
@ -552,6 +552,28 @@ void QgsLayoutItemAttributeTable::finalizeRestoreFromXml()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QgsLayoutItemAttributeTable::refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property )
 | 
			
		||||
{
 | 
			
		||||
  QgsExpressionContext context = createExpressionContext();
 | 
			
		||||
 | 
			
		||||
  if ( mSource == QgsLayoutItemAttributeTable::LayerAttributes &&
 | 
			
		||||
       ( property == QgsLayoutObject::AttributeTableSourceLayer || property == QgsLayoutObject::AllProperties ) )
 | 
			
		||||
  {
 | 
			
		||||
    mDataDefinedVectorLayer = nullptr;
 | 
			
		||||
 | 
			
		||||
    QString currentLayerIdentifier;
 | 
			
		||||
    if ( QgsVectorLayer *currentLayer = mVectorLayer.get() )
 | 
			
		||||
      currentLayerIdentifier = currentLayer->id();
 | 
			
		||||
 | 
			
		||||
    const QString layerIdentifier = mDataDefinedProperties.valueAsString( QgsLayoutObject::AttributeTableSourceLayer, context, currentLayerIdentifier );
 | 
			
		||||
    QgsVectorLayer *ddLayer = qobject_cast< QgsVectorLayer * >( QgsLayoutUtils::mapLayerFromString( layerIdentifier, mLayout->project() ) );
 | 
			
		||||
    if ( ddLayer )
 | 
			
		||||
      mDataDefinedVectorLayer = ddLayer;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  QgsLayoutMultiFrame::refreshDataDefinedProperty( property );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QVariant QgsLayoutItemAttributeTable::replaceWrapChar( const QVariant &variant ) const
 | 
			
		||||
{
 | 
			
		||||
  //avoid converting variants to string if not required (try to maintain original type for sorting)
 | 
			
		||||
@ -570,7 +592,12 @@ QgsVectorLayer *QgsLayoutItemAttributeTable::sourceLayer() const
 | 
			
		||||
    case QgsLayoutItemAttributeTable::AtlasFeature:
 | 
			
		||||
      return mLayout->reportContext().layer();
 | 
			
		||||
    case QgsLayoutItemAttributeTable::LayerAttributes:
 | 
			
		||||
      return mVectorLayer.get();
 | 
			
		||||
    {
 | 
			
		||||
      if ( mDataDefinedVectorLayer )
 | 
			
		||||
        return mDataDefinedVectorLayer;
 | 
			
		||||
      else
 | 
			
		||||
        return mVectorLayer.get();
 | 
			
		||||
    }
 | 
			
		||||
    case QgsLayoutItemAttributeTable::RelationChildren:
 | 
			
		||||
    {
 | 
			
		||||
      QgsRelation relation = mLayout->project()->relationManager()->relation( mRelationId );
 | 
			
		||||
 | 
			
		||||
@ -292,6 +292,8 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable
 | 
			
		||||
    QgsExpressionContext createExpressionContext() const override;
 | 
			
		||||
    void finalizeRestoreFromXml() override;
 | 
			
		||||
 | 
			
		||||
    void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ) override;
 | 
			
		||||
 | 
			
		||||
  protected:
 | 
			
		||||
 | 
			
		||||
    bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
 | 
			
		||||
@ -303,6 +305,10 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable
 | 
			
		||||
    ContentSource mSource = LayerAttributes;
 | 
			
		||||
    //! Associated vector layer
 | 
			
		||||
    QgsVectorLayerRef mVectorLayer;
 | 
			
		||||
 | 
			
		||||
    //! Data defined vector layer - only
 | 
			
		||||
    QPointer< QgsVectorLayer > mDataDefinedVectorLayer;
 | 
			
		||||
 | 
			
		||||
    //! Relation id, if in relation children mode
 | 
			
		||||
    QString mRelationId;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -76,6 +76,7 @@ void QgsLayoutObject::initPropertyDefinitions()
 | 
			
		||||
    { QgsLayoutObject::ScalebarFillColor2, QgsPropertyDefinition( "dataDefinedScalebarFill2", QObject::tr( "Secondary fill color" ), QgsPropertyDefinition::ColorWithAlpha ) },
 | 
			
		||||
    { QgsLayoutObject::ScalebarLineColor, QgsPropertyDefinition( "dataDefinedScalebarLineColor", QObject::tr( "Line color" ), QgsPropertyDefinition::ColorWithAlpha ) },
 | 
			
		||||
    { QgsLayoutObject::ScalebarLineWidth, QgsPropertyDefinition( "dataDefinedScalebarLineWidth", QObject::tr( "Line width" ), QgsPropertyDefinition::StrokeWidth ) },
 | 
			
		||||
    { QgsLayoutObject::AttributeTableSourceLayer, QgsPropertyDefinition( "dataDefinedAttributeTableSourceLayer", QObject::tr( "Table source layer" ), QgsPropertyDefinition::String ) },
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -92,7 +92,9 @@ class CORE_EXPORT QgsLayoutObject: public QObject, public QgsExpressionContextGe
 | 
			
		||||
      ScalebarFillColor, //!< Scalebar fill color
 | 
			
		||||
      ScalebarFillColor2, //!< Scalebar secondary fill color
 | 
			
		||||
      ScalebarLineColor, //!< Scalebar line color
 | 
			
		||||
      ScalebarLineWidth, //!< Scalebar line width
 | 
			
		||||
      ScalebarLineWidth, //!< Scalebar line width,
 | 
			
		||||
      //table item
 | 
			
		||||
      AttributeTableSourceLayer, //!< Attribute table source layer
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -55,8 +55,8 @@
 | 
			
		||||
       <rect>
 | 
			
		||||
        <x>0</x>
 | 
			
		||||
        <y>0</y>
 | 
			
		||||
        <width>689</width>
 | 
			
		||||
        <height>2574</height>
 | 
			
		||||
        <width>394</width>
 | 
			
		||||
        <height>1487</height>
 | 
			
		||||
       </rect>
 | 
			
		||||
      </property>
 | 
			
		||||
      <layout class="QVBoxLayout" name="mainLayout">
 | 
			
		||||
@ -85,6 +85,30 @@
 | 
			
		||||
          <item row="0" column="1">
 | 
			
		||||
           <widget class="QComboBox" name="mSourceComboBox"/>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="5" column="0">
 | 
			
		||||
           <widget class="QLabel" name="mRelationLabel">
 | 
			
		||||
            <property name="text">
 | 
			
		||||
             <string>Relation</string>
 | 
			
		||||
            </property>
 | 
			
		||||
           </widget>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="5" column="1">
 | 
			
		||||
           <widget class="QComboBox" name="mRelationsComboBox"/>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="6" column="0" colspan="2">
 | 
			
		||||
           <widget class="QPushButton" name="mRefreshPushButton">
 | 
			
		||||
            <property name="text">
 | 
			
		||||
             <string>Refresh table data</string>
 | 
			
		||||
            </property>
 | 
			
		||||
           </widget>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="7" column="0" colspan="2">
 | 
			
		||||
           <widget class="QPushButton" name="mAttributesPushButton">
 | 
			
		||||
            <property name="text">
 | 
			
		||||
             <string>Attributes...</string>
 | 
			
		||||
            </property>
 | 
			
		||||
           </widget>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="1" column="0">
 | 
			
		||||
           <widget class="QLabel" name="mLayerLabel">
 | 
			
		||||
            <property name="text">
 | 
			
		||||
@ -96,38 +120,25 @@
 | 
			
		||||
           </widget>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="1" column="1">
 | 
			
		||||
           <widget class="QgsMapLayerComboBox" name="mLayerComboBox">
 | 
			
		||||
            <property name="sizePolicy">
 | 
			
		||||
             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
 | 
			
		||||
              <horstretch>0</horstretch>
 | 
			
		||||
              <verstretch>0</verstretch>
 | 
			
		||||
             </sizepolicy>
 | 
			
		||||
            </property>
 | 
			
		||||
           </widget>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="2" column="0">
 | 
			
		||||
           <widget class="QLabel" name="mRelationLabel">
 | 
			
		||||
            <property name="text">
 | 
			
		||||
             <string>Relation</string>
 | 
			
		||||
            </property>
 | 
			
		||||
           </widget>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="2" column="1">
 | 
			
		||||
           <widget class="QComboBox" name="mRelationsComboBox"/>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="3" column="0" colspan="2">
 | 
			
		||||
           <widget class="QPushButton" name="mRefreshPushButton">
 | 
			
		||||
            <property name="text">
 | 
			
		||||
             <string>Refresh table data</string>
 | 
			
		||||
            </property>
 | 
			
		||||
           </widget>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="4" column="0" colspan="2">
 | 
			
		||||
           <widget class="QPushButton" name="mAttributesPushButton">
 | 
			
		||||
            <property name="text">
 | 
			
		||||
             <string>Attributes...</string>
 | 
			
		||||
            </property>
 | 
			
		||||
           </widget>
 | 
			
		||||
           <layout class="QHBoxLayout" name="horizontalLayout_6">
 | 
			
		||||
            <item>
 | 
			
		||||
             <widget class="QgsMapLayerComboBox" name="mLayerComboBox">
 | 
			
		||||
              <property name="sizePolicy">
 | 
			
		||||
               <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
 | 
			
		||||
                <horstretch>0</horstretch>
 | 
			
		||||
                <verstretch>0</verstretch>
 | 
			
		||||
               </sizepolicy>
 | 
			
		||||
              </property>
 | 
			
		||||
             </widget>
 | 
			
		||||
            </item>
 | 
			
		||||
            <item>
 | 
			
		||||
             <widget class="QgsPropertyOverrideButton" name="mLayerSourceDDBtn">
 | 
			
		||||
              <property name="text">
 | 
			
		||||
               <string>…</string>
 | 
			
		||||
              </property>
 | 
			
		||||
             </widget>
 | 
			
		||||
            </item>
 | 
			
		||||
           </layout>
 | 
			
		||||
          </item>
 | 
			
		||||
         </layout>
 | 
			
		||||
        </widget>
 | 
			
		||||
@ -832,12 +843,18 @@
 | 
			
		||||
   <extends>QComboBox</extends>
 | 
			
		||||
   <header>qgslayoutitemcombobox.h</header>
 | 
			
		||||
  </customwidget>
 | 
			
		||||
  <customwidget>
 | 
			
		||||
   <class>QgsPropertyOverrideButton</class>
 | 
			
		||||
   <extends>QToolButton</extends>
 | 
			
		||||
   <header>qgspropertyoverridebutton.h</header>
 | 
			
		||||
  </customwidget>
 | 
			
		||||
 </customwidgets>
 | 
			
		||||
 <tabstops>
 | 
			
		||||
  <tabstop>scrollArea</tabstop>
 | 
			
		||||
  <tabstop>groupBox</tabstop>
 | 
			
		||||
  <tabstop>mSourceComboBox</tabstop>
 | 
			
		||||
  <tabstop>mLayerComboBox</tabstop>
 | 
			
		||||
  <tabstop>mLayerSourceDDBtn</tabstop>
 | 
			
		||||
  <tabstop>mRelationsComboBox</tabstop>
 | 
			
		||||
  <tabstop>mRefreshPushButton</tabstop>
 | 
			
		||||
  <tabstop>mAttributesPushButton</tabstop>
 | 
			
		||||
@ -905,6 +922,8 @@
 | 
			
		||||
  <include location="../../../images/images.qrc"/>
 | 
			
		||||
  <include location="../../../images/images.qrc"/>
 | 
			
		||||
  <include location="../../../images/images.qrc"/>
 | 
			
		||||
  <include location="../../../images/images.qrc"/>
 | 
			
		||||
  <include location="../../../images/images.qrc"/>
 | 
			
		||||
 </resources>
 | 
			
		||||
 <connections/>
 | 
			
		||||
</ui>
 | 
			
		||||
 | 
			
		||||
@ -70,6 +70,7 @@ class TestQgsLayoutTable : public QObject
 | 
			
		||||
    void autoWrap(); //test auto word wrap
 | 
			
		||||
    void cellStyles(); //test cell styles
 | 
			
		||||
    void cellStylesRender(); //test rendering cell styles
 | 
			
		||||
    void dataDefinedSource();
 | 
			
		||||
 | 
			
		||||
  private:
 | 
			
		||||
    QgsVectorLayer *mVectorLayer = nullptr;
 | 
			
		||||
@ -1323,5 +1324,69 @@ void TestQgsLayoutTable::cellStylesRender()
 | 
			
		||||
  QVERIFY( checker.testLayout( mReport, 0 ) );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TestQgsLayoutTable::dataDefinedSource()
 | 
			
		||||
{
 | 
			
		||||
  // add a couple of layers
 | 
			
		||||
  QgsVectorLayer *layer1 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:integer&field=col3:integer" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
 | 
			
		||||
  QVERIFY( layer1->isValid() );
 | 
			
		||||
  QgsFeature f( layer1->fields() );
 | 
			
		||||
  f.setAttributes( QgsAttributes() << 1 << 2 << 3 );
 | 
			
		||||
  layer1->dataProvider()->addFeature( f );
 | 
			
		||||
 | 
			
		||||
  // different field order
 | 
			
		||||
  QgsVectorLayer *layer2 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col3:integer&field=col2:integer" ), QStringLiteral( "l2" ), QStringLiteral( "memory" ) );
 | 
			
		||||
  QVERIFY( layer2->isValid() );
 | 
			
		||||
  QgsFeature f2( layer2->fields() );
 | 
			
		||||
  f2.setAttributes( QgsAttributes() << 11 << 13 << 12 );
 | 
			
		||||
  layer2->dataProvider()->addFeature( f2 );
 | 
			
		||||
 | 
			
		||||
  // missing fields
 | 
			
		||||
  QgsVectorLayer *layer3 = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col3:integer" ), QStringLiteral( "l3" ), QStringLiteral( "memory" ) );
 | 
			
		||||
  QVERIFY( layer3->isValid() );
 | 
			
		||||
  QgsFeature f3( layer3->fields() );
 | 
			
		||||
  f3.setAttributes( QgsAttributes() << 21 << 23 );
 | 
			
		||||
  layer3->dataProvider()->addFeature( f3 );
 | 
			
		||||
 | 
			
		||||
  QgsProject p;
 | 
			
		||||
  p.addMapLayer( layer1 );
 | 
			
		||||
  p.addMapLayer( layer2 );
 | 
			
		||||
  p.addMapLayer( layer3 );
 | 
			
		||||
 | 
			
		||||
  QgsLayout l( &p );
 | 
			
		||||
  l.initializeDefaults();
 | 
			
		||||
  QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l );
 | 
			
		||||
  table->setSource( QgsLayoutItemAttributeTable::LayerAttributes );
 | 
			
		||||
  table->setVectorLayer( layer1 );
 | 
			
		||||
  table->setMaximumNumberOfFeatures( 50 );
 | 
			
		||||
  QCOMPARE( table->contents().length(), 1 );
 | 
			
		||||
  QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 1 << 2 << 3 );
 | 
			
		||||
 | 
			
		||||
  // data defined table name, by layer id
 | 
			
		||||
  table->dataDefinedProperties().setProperty( QgsLayoutObject::AttributeTableSourceLayer, layer1->id() );
 | 
			
		||||
  table->refresh();
 | 
			
		||||
  QCOMPARE( table->contents().length(), 1 );
 | 
			
		||||
  QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 1 << 2 << 3 );
 | 
			
		||||
 | 
			
		||||
  // by layer name
 | 
			
		||||
  table->dataDefinedProperties().setProperty( QgsLayoutObject::AttributeTableSourceLayer, QStringLiteral( "l2" ) );
 | 
			
		||||
  table->refresh();
 | 
			
		||||
  QCOMPARE( table->contents().length(), 1 );
 | 
			
		||||
  QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 11 << 12 << 13 );
 | 
			
		||||
 | 
			
		||||
  // by layer name (case insensitive)
 | 
			
		||||
  table->dataDefinedProperties().setProperty( QgsLayoutObject::AttributeTableSourceLayer, QStringLiteral( "L3" ) );
 | 
			
		||||
  table->refresh();
 | 
			
		||||
  QCOMPARE( table->contents().length(), 1 );
 | 
			
		||||
  QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 21 << QVariant() << 23 );
 | 
			
		||||
 | 
			
		||||
  // delete current data defined layer match
 | 
			
		||||
  p.removeMapLayer( layer3->id() );
 | 
			
		||||
  QApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
 | 
			
		||||
  // expect table to return to preset layer
 | 
			
		||||
  table->refreshAttributes();
 | 
			
		||||
  QCOMPARE( table->contents().length(), 1 );
 | 
			
		||||
  QCOMPARE( table->contents().at( 0 ), QVector< QVariant >() << 1 << 2 << 3 );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QGSTEST_MAIN( TestQgsLayoutTable )
 | 
			
		||||
#include "testqgslayouttable.moc"
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user