From 050dfce89afd1fc6e8a6a89048ac201018b5dba3 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 19 Mar 2020 01:40:54 +0200 Subject: [PATCH] FIX #15144 add tooltip on fields in identify window / attribute window / feature form - added new `QString QgsField::displayType( const bool showConstraints = false )` to unify the display of field types whenever length or precision are present - added new argument `expression` to `QgsFieldModel::fieldToolTip( const QgsField &field, const QString &expression = QStringLiteral() )`. Now the tooltip shows " ()\n\n\n" with appropriate formatting - added meaningful field tooltips in the "Identify Results" dialog - field tooltips show the same content in "Feature Attributes" form, "Attribute Table" and "Identify Tool" Fixes #15144 --- python/core/auto_generated/qgsfield.sip.in | 12 +++++++++ .../core/auto_generated/qgsfieldmodel.sip.in | 2 +- src/app/qgsidentifyresultsdialog.cpp | 6 ++++- src/core/qgsfield.cpp | 23 ++++++++++++++++ src/core/qgsfield.h | 11 ++++++++ src/core/qgsfieldmodel.cpp | 26 +++++++++---------- src/core/qgsfieldmodel.h | 2 +- .../attributetable/qgsattributetablemodel.cpp | 5 +++- src/gui/qgsattributeform.cpp | 6 ++++- tests/src/core/testqgsfield.cpp | 19 ++++++++++++++ tests/src/python/test_qgsfieldmodel.py | 19 ++++++++++---- 11 files changed, 107 insertions(+), 24 deletions(-) diff --git a/python/core/auto_generated/qgsfield.sip.in b/python/core/auto_generated/qgsfield.sip.in index cfcdde9cf96..0160ebd092b 100644 --- a/python/core/auto_generated/qgsfield.sip.in +++ b/python/core/auto_generated/qgsfield.sip.in @@ -104,6 +104,18 @@ represents. .. versionadded:: 3.12 %End + + QString displayType( bool showConstraints = false ) const; +%Docstring +Returns the type to use when displaying this field, including the length and precision of the datatype if applicable. + +This will be used when the full datatype with details has to displayed to the user. + +.. seealso:: :py:func:`type` + +.. versionadded:: 3.14 +%End + QVariant::Type type() const; %Docstring Gets variant type of the field as it will be retrieved from data source diff --git a/python/core/auto_generated/qgsfieldmodel.sip.in b/python/core/auto_generated/qgsfieldmodel.sip.in index f2aff3f3619..497ae7bdeb4 100644 --- a/python/core/auto_generated/qgsfieldmodel.sip.in +++ b/python/core/auto_generated/qgsfieldmodel.sip.in @@ -128,7 +128,7 @@ Returns the layer associated with the model. virtual QVariant data( const QModelIndex &index, int role ) const; - static QString fieldToolTip( const QgsField &field ); + static QString fieldToolTip( const QgsField &field, const QString &expression = QString() ); %Docstring Returns a HTML formatted tooltip string for a ``field``, containing details like the field name, alias and type. diff --git a/src/app/qgsidentifyresultsdialog.cpp b/src/app/qgsidentifyresultsdialog.cpp index 906a3e4a28b..91a1e83ffdd 100644 --- a/src/app/qgsidentifyresultsdialog.cpp +++ b/src/app/qgsidentifyresultsdialog.cpp @@ -78,6 +78,7 @@ #include "qgsfiledownloaderdialog.h" #include "qgsfieldformatterregistry.h" #include "qgsfieldformatter.h" +#include "qgsfieldmodel.h" #include "qgssettings.h" #include "qgsgui.h" #include "qgsexpressioncontextutils.h" @@ -625,8 +626,11 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat QgsTreeWidgetItem *attrItem = new QgsTreeWidgetItem( QStringList() << QString::number( i ) << value ); featItem->addChild( attrItem ); + QString expressionString = fields.fieldOrigin( i ) == QgsFields::OriginExpression + ? vlayer->expressionField( i ) + : QStringLiteral(); attrItem->setData( 0, Qt::DisplayRole, vlayer->attributeDisplayName( i ) ); - attrItem->setToolTip( 0, vlayer->attributeDisplayName( i ) ); + attrItem->setToolTip( 0, QgsFieldModel::fieldToolTip( fields.at( i ), expressionString ) ); attrItem->setData( 0, Qt::UserRole, fields.at( i ).name() ); attrItem->setData( 0, Qt::UserRole + 1, i ); diff --git a/src/core/qgsfield.cpp b/src/core/qgsfield.cpp index 4dd97e48bfe..0c4756683cb 100644 --- a/src/core/qgsfield.cpp +++ b/src/core/qgsfield.cpp @@ -103,6 +103,29 @@ QString QgsField::displayNameWithAlias() const return QStringLiteral( "%1 (%2)" ).arg( name() ).arg( alias() ); } +QString QgsField::displayType( const bool showConstraints ) const +{ + QString typeStr = typeName(); + + if ( length() > 0 && precision() > 0 ) + typeStr += QStringLiteral( "(%1, %2)" ).arg( length() ).arg( precision() ); + else if ( length() > 0 ) + typeStr += QStringLiteral( "(%1)" ).arg( length() ); + + if ( showConstraints ) + { + typeStr += constraints().constraints() & QgsFieldConstraints::ConstraintNotNull + ? QStringLiteral( " NOT NULL" ) + : QStringLiteral( " NULL" ); + + typeStr += constraints().constraints() & QgsFieldConstraints::ConstraintUnique + ? QStringLiteral( " UNIQUE" ) + : QStringLiteral( "" ); + } + + return typeStr; +} + QVariant::Type QgsField::type() const { return d->type; diff --git a/src/core/qgsfield.h b/src/core/qgsfield.h index 5e7736b6684..8b01bcf1822 100644 --- a/src/core/qgsfield.h +++ b/src/core/qgsfield.h @@ -130,6 +130,17 @@ class CORE_EXPORT QgsField */ QString displayNameWithAlias() const; + + /** + * Returns the type to use when displaying this field, including the length and precision of the datatype if applicable. + * + * This will be used when the full datatype with details has to displayed to the user. + * + * \see type() + * \since QGIS 3.14 + */ + QString displayType( bool showConstraints = false ) const; + //! Gets variant type of the field as it will be retrieved from data source QVariant::Type type() const; diff --git a/src/core/qgsfieldmodel.cpp b/src/core/qgsfieldmodel.cpp index 551d17f7872..fb5e9de4506 100644 --- a/src/core/qgsfieldmodel.cpp +++ b/src/core/qgsfieldmodel.cpp @@ -464,7 +464,7 @@ QVariant QgsFieldModel::data( const QModelIndex &index, int role ) const } } -QString QgsFieldModel::fieldToolTip( const QgsField &field ) +QString QgsFieldModel::fieldToolTip( const QgsField &field, const QString &expression ) { QString toolTip; if ( !field.alias().isEmpty() ) @@ -475,23 +475,21 @@ QString QgsFieldModel::fieldToolTip( const QgsField &field ) { toolTip = QStringLiteral( "%1" ).arg( field.name() ); } - QString typeString; - if ( field.length() > 0 ) + + toolTip += QStringLiteral( "
%3" ).arg( field.displayType( true ) ); + + QString comment = field.comment(); + + if ( ! comment.isEmpty() ) { - if ( field.precision() > 0 ) - { - typeString = QStringLiteral( "%1 (%2, %3)" ).arg( field.typeName() ).arg( field.length() ).arg( field.precision() ); - } - else - { - typeString = QStringLiteral( "%1 (%2)" ).arg( field.typeName() ).arg( field.length() ); - } + toolTip += QStringLiteral( "
%1" ).arg( comment ); } - else + + if ( ! expression.isEmpty() ) { - typeString = field.typeName(); + toolTip += QStringLiteral( "
%3" ).arg( expression ); } - toolTip += QStringLiteral( "

%1

" ).arg( typeString ); + return toolTip; } diff --git a/src/core/qgsfieldmodel.h b/src/core/qgsfieldmodel.h index bee874bc19b..34e702b71b4 100644 --- a/src/core/qgsfieldmodel.h +++ b/src/core/qgsfieldmodel.h @@ -135,7 +135,7 @@ class CORE_EXPORT QgsFieldModel : public QAbstractItemModel * like the field name, alias and type. * \since QGIS 3.0 */ - static QString fieldToolTip( const QgsField &field ); + static QString fieldToolTip( const QgsField &field, const QString &expression = QString() ); /** * Manually sets the \a fields to use for the model. diff --git a/src/gui/attributetable/qgsattributetablemodel.cpp b/src/gui/attributetable/qgsattributetablemodel.cpp index 396bfb68b7c..e3fdb98d120 100644 --- a/src/gui/attributetable/qgsattributetablemodel.cpp +++ b/src/gui/attributetable/qgsattributetablemodel.cpp @@ -624,7 +624,10 @@ QVariant QgsAttributeTableModel::headerData( int section, Qt::Orientation orient else { const QgsField field = layer()->fields().at( mAttributes.at( section ) ); - return QgsFieldModel::fieldToolTip( field ); + QString expressionString = layer()->fields().fieldOrigin( mAttributes.at( section ) ) == QgsFields::OriginExpression + ? layer()->expressionField( mAttributes.at( section ) ) + : QStringLiteral(); + return QgsFieldModel::fieldToolTip( field, expressionString ); } } else diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 6d5ba905fb1..b32d74b0ea5 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -43,6 +43,7 @@ #include "qgsexpressioncontextutils.h" #include "qgsfeaturerequest.h" #include "qgstexteditwrapper.h" +#include "qgsfieldmodel.h" #include #include @@ -1505,8 +1506,11 @@ void QgsAttributeForm::init() bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx ); // This will also create the widget + QString expressionString = fields.fieldOrigin( idx ) == QgsFields::OriginExpression + ? mLayer->expressionField( idx ) + : QStringLiteral(); QLabel *l = new QLabel( labelText ); - l->setToolTip( QStringLiteral( "%1

%2

" ).arg( fieldName, field.comment() ) ); + l->setToolTip( QgsFieldModel::fieldToolTip( field, expressionString ) ); QSvgWidget *i = new QSvgWidget(); i->setFixedSize( 18, 18 ); diff --git a/tests/src/core/testqgsfield.cpp b/tests/src/core/testqgsfield.cpp index 2de70329c7e..5ea636e8539 100644 --- a/tests/src/core/testqgsfield.cpp +++ b/tests/src/core/testqgsfield.cpp @@ -48,6 +48,7 @@ class TestQgsField: public QObject void dataStream(); void displayName(); void displayNameWithAlias(); + void displayType(); void editorWidgetSetup(); void collection(); @@ -752,6 +753,24 @@ void TestQgsField::displayNameWithAlias() } +void TestQgsField::displayType() +{ + QgsField field; + field.setTypeName( QStringLiteral( "numeric" ) ); + QCOMPARE( field.displayType(), QString( "numeric" ) ); + field.setLength( 20 ); + QCOMPARE( field.displayType(), QString( "numeric(20)" ) ); + field.setPrecision( 10 ); + field.setPrecision( 10 ); + QCOMPARE( field.displayType(), QString( "numeric(20, 10)" ) ); + QCOMPARE( field.displayType( true ), QString( "numeric(20, 10) NULL" ) ); + QgsFieldConstraints constraints; + constraints.setConstraint( QgsFieldConstraints::ConstraintUnique ); + field.setConstraints( constraints ); + QCOMPARE( field.displayType( true ), QString( "numeric(20, 10) NULL UNIQUE" ) ); +} + + void TestQgsField::editorWidgetSetup() { QgsField field; diff --git a/tests/src/python/test_qgsfieldmodel.py b/tests/src/python/test_qgsfieldmodel.py index ea708568d2e..703c4468452 100644 --- a/tests/src/python/test_qgsfieldmodel.py +++ b/tests/src/python/test_qgsfieldmodel.py @@ -19,7 +19,8 @@ from qgis.core import (QgsField, QgsFieldProxyModel, QgsEditorWidgetSetup, QgsProject, - QgsVectorLayerJoinInfo) + QgsVectorLayerJoinInfo, + QgsFieldConstraints) from qgis.PyQt.QtCore import QVariant, Qt, QModelIndex from qgis.testing import start_app, unittest @@ -350,13 +351,21 @@ class TestQgsFieldModel(unittest.TestCase): def testFieldTooltip(self): f = QgsField('my_string', QVariant.String, 'string') - self.assertEqual(QgsFieldModel.fieldToolTip(f), 'my_string

string

') + self.assertEqual(QgsFieldModel.fieldToolTip(f), "my_string
string NULL") f.setAlias('my alias') - self.assertEqual(QgsFieldModel.fieldToolTip(f), 'my alias (my_string)

string

') + self.assertEqual(QgsFieldModel.fieldToolTip(f), "my alias (my_string)
string NULL") f.setLength(20) - self.assertEqual(QgsFieldModel.fieldToolTip(f), 'my alias (my_string)

string (20)

') + self.assertEqual(QgsFieldModel.fieldToolTip(f), "my alias (my_string)
string(20) NULL") f = QgsField('my_real', QVariant.Double, 'real', 8, 3) - self.assertEqual(QgsFieldModel.fieldToolTip(f), 'my_real

real (8, 3)

') + self.assertEqual(QgsFieldModel.fieldToolTip(f), "my_real
real(8, 3) NULL") + f.setComment('Comment text') + self.assertEqual(QgsFieldModel.fieldToolTip(f), "my_real
real(8, 3) NULL
Comment text") + self.assertEqual(QgsFieldModel.fieldToolTip(f, '1+1'), "my_real
real(8, 3) NULL
Comment text
1+1") + f.setAlias('my alias') + constraints = f.constraints() + constraints.setConstraint(QgsFieldConstraints.ConstraintUnique) + f.setConstraints(constraints) + self.assertEqual(QgsFieldModel.fieldToolTip(f, '1+1'), "my alias (my_real)
real(8, 3) NULL UNIQUE
Comment text
1+1") if __name__ == '__main__':