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 "<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
This commit is contained in:
Ivan Ivanov 2020-03-19 01:40:54 +02:00 committed by Nyall Dawson
parent 0e1eb51c2c
commit 050dfce89a
11 changed files with 107 additions and 24 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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), '<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__':