Skip to content
Permalink
Browse files

FIX #15144 add tooltip on fields in identify window / attribute windo…

…w / 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 "<alias> (<field>)\n<type>\n<comment>\n<expression>" 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
  • Loading branch information
suricactus authored and nyalldawson committed Mar 18, 2020
1 parent 0e1eb51 commit 050dfce89afd1fc6e8a6a89048ac201018b5dba3
@@ -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
@@ -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.
@@ -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 );

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

@@ -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( "<b>%1</b>" ).arg( field.name() );
}
QString typeString;
if ( field.length() > 0 )

toolTip += QStringLiteral( "<br><font style='font-family:monospace; white-space: nowrap;'>%3</font>" ).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( "<br><em>%1</em>" ).arg( comment );
}
else

if ( ! expression.isEmpty() )
{
typeString = field.typeName();
toolTip += QStringLiteral( "<br><font style='font-family:monospace;'>%3</font>" ).arg( expression );
}
toolTip += QStringLiteral( "<p>%1</p>" ).arg( typeString );

return toolTip;
}

@@ -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.
@@ -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
@@ -43,6 +43,7 @@
#include "qgsexpressioncontextutils.h"
#include "qgsfeaturerequest.h"
#include "qgstexteditwrapper.h"
#include "qgsfieldmodel.h"

#include <QDir>
#include <QTextStream>
@@ -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( "<b>%1</b><p>%2</p>" ).arg( fieldName, field.comment() ) );
l->setToolTip( QgsFieldModel::fieldToolTip( field, expressionString ) );
QSvgWidget *i = new QSvgWidget();
i->setFixedSize( 18, 18 );

@@ -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;
@@ -19,7 +19,8 @@
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 @@ def testJoinedFieldIsEditableRole(self):

def testFieldTooltip(self):
f = QgsField('my_string', QVariant.String, 'string')
self.assertEqual(QgsFieldModel.fieldToolTip(f), '<b>my_string</b><p>string</p>')
self.assertEqual(QgsFieldModel.fieldToolTip(f), "<b>my_string</b><br><font style='font-family:monospace; white-space: nowrap;'>string NULL</font>")
f.setAlias('my alias')
self.assertEqual(QgsFieldModel.fieldToolTip(f), '<b>my alias</b> (my_string)<p>string</p>')
self.assertEqual(QgsFieldModel.fieldToolTip(f), "<b>my alias</b> (my_string)<br><font style='font-family:monospace; white-space: nowrap;'>string NULL</font>")
f.setLength(20)
self.assertEqual(QgsFieldModel.fieldToolTip(f), '<b>my alias</b> (my_string)<p>string (20)</p>')
self.assertEqual(QgsFieldModel.fieldToolTip(f), "<b>my alias</b> (my_string)<br><font style='font-family:monospace; white-space: nowrap;'>string(20) NULL</font>")
f = QgsField('my_real', QVariant.Double, 'real', 8, 3)
self.assertEqual(QgsFieldModel.fieldToolTip(f), '<b>my_real</b><p>real (8, 3)</p>')
self.assertEqual(QgsFieldModel.fieldToolTip(f), "<b>my_real</b><br><font style='font-family:monospace; white-space: nowrap;'>real(8, 3) NULL</font>")
f.setComment('Comment text')
self.assertEqual(QgsFieldModel.fieldToolTip(f), "<b>my_real</b><br><font style='font-family:monospace; white-space: nowrap;'>real(8, 3) NULL</font><br><em>Comment text</em>")
self.assertEqual(QgsFieldModel.fieldToolTip(f, '1+1'), "<b>my_real</b><br><font style='font-family:monospace; white-space: nowrap;'>real(8, 3) NULL</font><br><em>Comment text</em><br><font style='font-family:monospace;'>1+1</font>")
f.setAlias('my alias')
constraints = f.constraints()
constraints.setConstraint(QgsFieldConstraints.ConstraintUnique)
f.setConstraints(constraints)
self.assertEqual(QgsFieldModel.fieldToolTip(f, '1+1'), "<b>my alias</b> (my_real)<br><font style='font-family:monospace; white-space: nowrap;'>real(8, 3) NULL UNIQUE</font><br><em>Comment text</em><br><font style='font-family:monospace;'>1+1</font>")


if __name__ == '__main__':

0 comments on commit 050dfce

Please sign in to comment.
You can’t perform that action at this time.