Skip to content

Commit

Permalink
[feature][layouts] Allow cells in manual text tables to have expression
Browse files Browse the repository at this point in the history
based contents

Allows individual cells from a manual text table to take their contents
from a preset expression. Expressions have access to the full layout
item expression context, allowing cells to calculate and display
metadata style values or aggregate based calculations.

Sponsored by City of Canning
  • Loading branch information
nyalldawson committed Jul 14, 2020
1 parent 8e3151a commit 1f9b9c8
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 21 deletions.
Expand Up @@ -116,6 +116,14 @@ Returns the table header values.
Sets the table ``headers``.

.. seealso:: :py:func:`tableHeaders`
%End

void registerExpressionContextGenerator( QgsExpressionContextGenerator *generator );
%Docstring
Register an expression context generator class that will be used to retrieve
an expression context for the editor when required.

.. versionadded:: 3.16
%End

signals:
Expand Down
17 changes: 17 additions & 0 deletions python/gui/auto_generated/tableeditor/qgstableeditorwidget.sip.in
Expand Up @@ -118,6 +118,16 @@ If the returned value contains both horizontal and vertical alignment flags, the
the selected cells have a mix of different vertical alignments.

.. seealso:: :py:func:`selectionVerticalAlignment`
%End

QgsProperty selectionCellProperty();
%Docstring
Returns the QgsProperty used for the contents of the currently selected cells.

If the returned value is a default constructed :py:class:`QgsProperty`, then the selected
cells have a mix of different properties.

.. versionadded:: 3.16
%End

QgsTextFormat selectionTextFormat();
Expand Down Expand Up @@ -305,6 +315,13 @@ Sets the vertical alignment for the currently selected cells.

.. seealso:: :py:func:`setSelectionHorizontalAlignment`

.. versionadded:: 3.16
%End

void setSelectionCellProperty( const QgsProperty &property );
%Docstring
Sets the cell contents QgsProperty for the currently selected cells.

.. versionadded:: 3.16
%End

Expand Down
1 change: 1 addition & 0 deletions src/gui/layout/qgslayoutmanualtablewidget.cpp
Expand Up @@ -161,6 +161,7 @@ void QgsLayoutManualTableWidget::setTableContents()
else
{
mEditorDialog = new QgsTableEditorDialog( this );
mEditorDialog->registerExpressionContextGenerator( mTable );
connect( this, &QWidget::destroyed, mEditorDialog, &QMainWindow::close );

mEditorDialog->setIncludeTableHeader( mTable->includeTableHeader() );
Expand Down
9 changes: 8 additions & 1 deletion src/gui/tableeditor/qgstableeditordialog.cpp
Expand Up @@ -64,7 +64,7 @@ QgsTableEditorDialog::QgsTableEditorDialog( QWidget *parent )

int minDockWidth( fontMetrics().boundingRect( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ).width() );

mPropertiesDock = new QgsDockWidget( tr( "Formatting" ), this );
mPropertiesDock = new QgsDockWidget( tr( "Cell Contents" ), this );
mPropertiesDock->setObjectName( QStringLiteral( "FormattingDock" ) );
mPropertiesStack = new QgsPanelWidgetStack();
mPropertiesDock->setWidget( mPropertiesStack );
Expand All @@ -81,6 +81,7 @@ QgsTableEditorDialog::QgsTableEditorDialog( QWidget *parent )

connect( mFormattingWidget, &QgsTableEditorFormattingWidget::horizontalAlignmentChanged, mTableWidget, &QgsTableEditorWidget::setSelectionHorizontalAlignment );
connect( mFormattingWidget, &QgsTableEditorFormattingWidget::verticalAlignmentChanged, mTableWidget, &QgsTableEditorWidget::setSelectionVerticalAlignment );
connect( mFormattingWidget, &QgsTableEditorFormattingWidget::cellPropertyChanged, mTableWidget, &QgsTableEditorWidget::setSelectionCellProperty );

connect( mFormattingWidget, &QgsTableEditorFormattingWidget::textFormatChanged, this, [ = ]
{
Expand All @@ -104,6 +105,7 @@ QgsTableEditorDialog::QgsTableEditorDialog( QWidget *parent )
mFormattingWidget->setTextFormat( mTableWidget->selectionTextFormat() );
mFormattingWidget->setHorizontalAlignment( mTableWidget->selectionHorizontalAlignment() );
mFormattingWidget->setVerticalAlignment( mTableWidget->selectionVerticalAlignment() );
mFormattingWidget->setCellProperty( mTableWidget->selectionCellProperty() );

updateActionNamesFromSelection();

Expand Down Expand Up @@ -225,6 +227,11 @@ void QgsTableEditorDialog::setTableHeaders( const QVariantList &headers )
mTableWidget->setTableHeaders( headers );
}

void QgsTableEditorDialog::registerExpressionContextGenerator( QgsExpressionContextGenerator *generator )
{
mFormattingWidget->registerExpressionContextGenerator( generator );
}

void QgsTableEditorDialog::updateActionNamesFromSelection()
{
const int rowCount = mTableWidget->rowsAssociatedWithSelection().size();
Expand Down
8 changes: 8 additions & 0 deletions src/gui/tableeditor/qgstableeditordialog.h
Expand Up @@ -25,6 +25,7 @@ class QgsMessageBar;
class QgsDockWidget;
class QgsPanelWidgetStack;
class QgsTableEditorFormattingWidget;
class QgsExpressionContextGenerator;

/**
* \ingroup gui
Expand Down Expand Up @@ -134,6 +135,13 @@ class GUI_EXPORT QgsTableEditorDialog : public QMainWindow, private Ui::QgsTable
*/
void setTableHeaders( const QVariantList &headers );

/**
* Register an expression context generator class that will be used to retrieve
* an expression context for the editor when required.
* \since QGIS 3.16
*/
void registerExpressionContextGenerator( QgsExpressionContextGenerator *generator );

signals:

/**
Expand Down
47 changes: 46 additions & 1 deletion src/gui/tableeditor/qgstableeditorformattingwidget.cpp
Expand Up @@ -17,12 +17,13 @@
#include "qgsnumericformatselectorwidget.h"
#include "qgsnumericformat.h"
#include "qgis.h"
#include "qgsproperty.h"

QgsTableEditorFormattingWidget::QgsTableEditorFormattingWidget( QWidget *parent )
: QgsPanelWidget( parent )
{
setupUi( this );
setPanelTitle( tr( "Formatting" ) );
setPanelTitle( tr( "Cell Contents" ) );

mFormatNumbersCheckBox->setTristate( false );

Expand Down Expand Up @@ -132,6 +133,19 @@ QgsTableEditorFormattingWidget::QgsTableEditorFormattingWidget( QWidget *parent
emit verticalAlignmentChanged( mVerticalAlignComboBox->currentAlignment() );
}
} );

connect( mExpressionEdit, qgis::overload<const QString &>::of( &QgsFieldExpressionWidget::fieldChanged ), this, [ = ]( const QString & expression )
{
if ( !mBlockSignals )
{
emit cellPropertyChanged( expression.isEmpty() ? QgsProperty() : QgsProperty::fromExpression( expression ) );
}
} );

mExpressionEdit->setAllowEmptyFieldName( true );

mExpressionEdit->registerExpressionContextGenerator( this );
mFontButton->registerExpressionContextGenerator( this );
}

QgsNumericFormat *QgsTableEditorFormattingWidget::numericFormat()
Expand Down Expand Up @@ -219,4 +233,35 @@ void QgsTableEditorFormattingWidget::setVerticalAlignment( Qt::Alignment alignme
mBlockSignals--;
}

void QgsTableEditorFormattingWidget::setCellProperty( const QgsProperty &property )
{
mBlockSignals++;
if ( !property.isActive() )
mExpressionEdit->setExpression( QString() );
else
mExpressionEdit->setExpression( property.asExpression() );
mBlockSignals--;
}

void QgsTableEditorFormattingWidget::registerExpressionContextGenerator( QgsExpressionContextGenerator *generator )
{
mContextGenerator = generator;
}

QgsExpressionContext QgsTableEditorFormattingWidget::createExpressionContext() const
{
QgsExpressionContext context;
if ( mContextGenerator )
context = mContextGenerator->createExpressionContext();

QgsExpressionContextScope *cellScope = new QgsExpressionContextScope();
// TODO -- could set real row/column numbers here, in certain circumstances...
cellScope->setVariable( QStringLiteral( "row_number" ), 0 );
cellScope->setVariable( QStringLiteral( "column_number" ), 0 );
context.appendScope( cellScope );

context.setHighlightedVariables( QStringList() << QStringLiteral( "row_number" ) << QStringLiteral( "column_number" ) );
return context;
}

QgsTableEditorFormattingWidget::~QgsTableEditorFormattingWidget() = default;
28 changes: 27 additions & 1 deletion src/gui/tableeditor/qgstableeditorformattingwidget.h
Expand Up @@ -18,11 +18,13 @@
#include "qgis_gui.h"
#include "ui_qgstableeditorformattingwidgetbase.h"
#include "qgspanelwidget.h"
#include "qgsexpressioncontextgenerator.h"
#include <memory>

#define SIP_NO_FILE

class QgsNumericFormat;
class QgsProperty;

/**
* \ingroup gui
Expand All @@ -36,7 +38,7 @@ class QgsNumericFormat;
*
* \since QGIS 3.12
*/
class GUI_EXPORT QgsTableEditorFormattingWidget : public QgsPanelWidget, private Ui::QgsTableEditorFormattingWidgetBase
class GUI_EXPORT QgsTableEditorFormattingWidget : public QgsPanelWidget, public QgsExpressionContextGenerator, private Ui::QgsTableEditorFormattingWidgetBase
{
Q_OBJECT
public:
Expand Down Expand Up @@ -134,6 +136,22 @@ class GUI_EXPORT QgsTableEditorFormattingWidget : public QgsPanelWidget, private
*/
void setVerticalAlignment( Qt::Alignment alignment );

/**
* Sets the cell content's \a property to show in the widget.
*
* \since QGIS 3.16
*/
void setCellProperty( const QgsProperty &property );

/**
* Register an expression context generator class that will be used to retrieve
* an expression context for the widget when required.
* \since QGIS 3.16
*/
void registerExpressionContextGenerator( QgsExpressionContextGenerator *generator );

QgsExpressionContext createExpressionContext() const override;

signals:

/**
Expand Down Expand Up @@ -186,10 +204,18 @@ class GUI_EXPORT QgsTableEditorFormattingWidget : public QgsPanelWidget, private
*/
void verticalAlignmentChanged( Qt::Alignment alignment );

/**
* Emitted when the cell contents \a property shown in the widget is changed.
*
* \since QGIS 3.16
*/
void cellPropertyChanged( const QgsProperty &property );

private:

std::unique_ptr< QgsNumericFormat > mNumericFormat;
int mBlockSignals = 0;
QgsExpressionContextGenerator *mContextGenerator = nullptr;

};

Expand Down
85 changes: 82 additions & 3 deletions src/gui/tableeditor/qgstableeditorwidget.cpp
Expand Up @@ -320,14 +320,19 @@ void QgsTableEditorWidget::setTableContents( const QgsTableContents &contents )
int colNumber = 0;
for ( const QgsTableCell &col : row )
{
QTableWidgetItem *item = new QTableWidgetItem( col.content().toString() );
QTableWidgetItem *item = new QTableWidgetItem( col.content().value< QgsProperty >().isActive() ? col.content().value< QgsProperty >().asExpression() : col.content().toString() );
item->setData( CellContent, col.content() ); // can't use EditRole, because Qt. (https://bugreports.qt.io/browse/QTBUG-11549)
item->setData( Qt::BackgroundRole, col.backgroundColor().isValid() ? col.backgroundColor() : QColor( 255, 255, 255 ) );
item->setData( PresetBackgroundColorRole, col.backgroundColor().isValid() ? col.backgroundColor() : QVariant() );
item->setData( Qt::ForegroundRole, col.foregroundColor().isValid() ? col.foregroundColor() : QVariant() );
item->setData( TextFormat, QVariant::fromValue( col.textFormat() ) );
item->setData( HorizontalAlignment, static_cast< int >( col.horizontalAlignment() ) );
item->setData( VerticalAlignment, static_cast< int >( col.verticalAlignment() ) );
item->setData( CellProperty, QVariant::fromValue( col.content().value< QgsProperty >() ) );

if ( col.content().value< QgsProperty >().isActive() )
item->setFlags( item->flags() & ( ~Qt::ItemIsEditable ) );

if ( col.numericFormat() )
{
mNumericFormats.insert( item, col.numericFormat()->clone() );
Expand Down Expand Up @@ -365,7 +370,7 @@ QgsTableContents QgsTableEditorWidget::tableContents() const
QgsTableCell cell;
if ( QTableWidgetItem *i = item( r, c ) )
{
cell.setContent( i->data( CellContent ) );
cell.setContent( i->data( CellProperty ).value< QgsProperty >().isActive() ? i->data( CellProperty ) : i->data( CellContent ) );
cell.setBackgroundColor( i->data( PresetBackgroundColorRole ).value< QColor >() );
cell.setForegroundColor( i->data( Qt::ForegroundRole ).value< QColor >() );
cell.setTextFormat( i->data( TextFormat ).value< QgsTextFormat >() );
Expand Down Expand Up @@ -579,6 +584,29 @@ Qt::Alignment QgsTableEditorWidget::selectionVerticalAlignment()
return alignment;
}

QgsProperty QgsTableEditorWidget::selectionCellProperty()
{
QgsProperty property;
bool first = true;
const QModelIndexList selection = selectedIndexes();
for ( const QModelIndex &index : selection )
{
const QgsProperty cellProperty = model()->data( index, CellProperty ).value< QgsProperty >();
if ( first )
{
property = cellProperty;
first = false;
}
else if ( cellProperty == property )
continue;
else
{
return QgsProperty();
}
}
return property;
}

QgsTextFormat QgsTableEditorWidget::selectionTextFormat()
{
QgsTextFormat format;
Expand Down Expand Up @@ -1046,6 +1074,57 @@ void QgsTableEditorWidget::setSelectionVerticalAlignment( Qt::Alignment alignmen
emit tableChanged();
}

void QgsTableEditorWidget::setSelectionCellProperty( const QgsProperty &property )
{
const QModelIndexList selection = selectedIndexes();
bool changed = false;
mBlockSignals++;
for ( const QModelIndex &index : selection )
{
if ( index.row() == 0 && mIncludeHeader )
continue;

if ( QTableWidgetItem *i = item( index.row(), index.column() ) )
{
if ( i->data( CellProperty ).value< QgsProperty >() != property )
{
if ( property.isActive() )
{
i->setData( CellProperty, QVariant::fromValue( property ) );
i->setText( property.asExpression() );
i->setFlags( i->flags() & ( ~Qt::ItemIsEditable ) );
}
else
{
i->setData( CellProperty, QVariant() );
i->setText( QString() );
i->setFlags( i->flags() | Qt::ItemIsEditable );
}
changed = true;
}
}
else
{
QTableWidgetItem *newItem = new QTableWidgetItem( property.asExpression() );
if ( property.isActive() )
{
newItem->setData( CellProperty, QVariant::fromValue( property ) );
newItem->setFlags( newItem->flags() & ( ~Qt::ItemIsEditable ) );
}
else
{
newItem->setData( CellProperty, QVariant() );
newItem->setFlags( newItem->flags() | Qt::ItemIsEditable );
}
setItem( index.row(), index.column(), newItem );
changed = true;
}
}
mBlockSignals--;
if ( changed && !mBlockSignals )
emit tableChanged();
}

void QgsTableEditorWidget::setSelectionTextFormat( const QgsTextFormat &format )
{
const QModelIndexList selection = selectedIndexes();
Expand Down Expand Up @@ -1351,7 +1430,7 @@ void QgsTableEditorDelegate::setModelData( QWidget *editor, QAbstractItemModel *
if ( QgsTableEditorTextEdit *lineEdit = qobject_cast<QgsTableEditorTextEdit * >( editor ) )
{
const QString text = lineEdit->toPlainText();
if ( text != model->data( index, QgsTableEditorWidget::CellContent ).toString() )
if ( text != model->data( index, QgsTableEditorWidget::CellContent ).toString() && !model->data( index, QgsTableEditorWidget::CellProperty ).value< QgsProperty >().isActive() )
{
model->setData( index, text, QgsTableEditorWidget::CellContent );
model->setData( index, text, Qt::DisplayRole );
Expand Down

0 comments on commit 1f9b9c8

Please sign in to comment.