Merge pull request #3303 from nyalldawson/identify_url

Make links in identify results clickable
This commit is contained in:
Nyall Dawson 2016-07-14 07:17:01 +10:00 committed by GitHub
commit 258c8999ef
5 changed files with 133 additions and 11 deletions

View File

@ -46,4 +46,13 @@ class QgsStringUtils
* @returns 4 letter Soundex code
*/
static QString soundex( const QString &string );
/** Returns a string with any URL (eg http(s)/ftp) and mailto: text converted to valid HTML <a ...>
* links.
* @param string string to insert links into
* @param foundLinks if specified, will be set to true if any links were inserted into the string
* @returns string with inserted links
* @note added in QGIS 3.0
*/
static QString insertLinks( const QString& string, bool* foundLinks = nullptr );
};

View File

@ -37,6 +37,7 @@
#include "qgsvectordataprovider.h"
#include "qgswebview.h"
#include "qgswebframe.h"
#include "qgsstringutils.h"
#include <QCloseEvent>
#include <QLabel>
@ -53,6 +54,7 @@
#include <QDesktopServices>
#include <QMessageBox>
#include <QComboBox>
#include <QTextDocument>
//graph
#include <qwt_plot.h>
@ -61,7 +63,6 @@
#include <qwt_legend.h>
#include "qgsvectorcolorrampv2.h" // for random colors
QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWebView( parent )
{
setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
@ -471,12 +472,18 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat
if ( i >= fields.count() )
continue;
if ( vlayer->editFormConfig()->widgetType( i ) == "Hidden" )
{
continue;
}
QString defVal;
if ( fields.fieldOrigin( i ) == QgsFields::OriginProvider && vlayer->dataProvider() )
defVal = vlayer->dataProvider()->defaultValue( fields.fieldOriginIndex( i ) ).toString();
QString value = defVal == attrs.at( i ) ? defVal : fields.at( i ).displayString( attrs.at( i ) );
QTreeWidgetItem *attrItem = new QTreeWidgetItem( QStringList() << QString::number( i ) << value );
featItem->addChild( attrItem );
attrItem->setData( 0, Qt::DisplayRole, vlayer->attributeDisplayName( i ) );
attrItem->setData( 0, Qt::UserRole, fields[i].name() );
@ -484,15 +491,21 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat
attrItem->setData( 1, Qt::UserRole, value );
if ( vlayer->editFormConfig()->widgetType( i ) == "Hidden" )
value = representValue( vlayer, fields.at( i ).name(), attrs.at( i ) );
bool foundLinks = false;
QString links = QgsStringUtils::insertLinks( value, &foundLinks );
if ( foundLinks )
{
delete attrItem;
continue;
QLabel* valueLabel = new QLabel( links );
valueLabel->setOpenExternalLinks( true );
attrItem->treeWidget()->setItemWidget( attrItem, 1, valueLabel );
attrItem->setData( 1, Qt::DisplayRole, QString() );
}
else
{
attrItem->setData( 1, Qt::DisplayRole, value );
attrItem->treeWidget()->setItemWidget( attrItem, 1, nullptr );
}
value = representValue( vlayer, fields[i].name(), attrs.at( i ) );
attrItem->setData( 1, Qt::DisplayRole, value );
if ( fields[i].name() == vlayer->displayField() )
{
@ -500,8 +513,6 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeat
featItem->setText( 1, attrItem->text( 1 ) );
featureLabeled = true;
}
featItem->addChild( attrItem );
}
if ( !featureLabeled )
@ -1480,7 +1491,21 @@ void QgsIdentifyResultsDialog::attributeValueChanged( QgsFeatureId fid, int idx,
if ( item->data( 0, Qt::UserRole + 1 ).toInt() == idx )
{
value = representValue( vlayer, fld.name(), val );
item->setData( 1, Qt::DisplayRole, value );
bool foundLinks = false;
QString links = QgsStringUtils::insertLinks( value, &foundLinks );
if ( foundLinks )
{
QLabel* valueLabel = new QLabel( links );
valueLabel->setOpenExternalLinks( true );
item->treeWidget()->setItemWidget( item, 1, valueLabel );
item->setData( 1, Qt::DisplayRole, QString() );
}
else
{
item->treeWidget()->setItemWidget( item, 1, nullptr );
item->setData( 1, Qt::DisplayRole, value );
}
return;
}
}

View File

@ -15,6 +15,8 @@
#include "qgsstringutils.h"
#include <QVector>
#include <QRegExp>
#include <QTextDocument> // for Qt::escape
int QgsStringUtils::levenshteinDistance( const QString& string1, const QString& string2, bool caseSensitive )
{
@ -294,3 +296,44 @@ QString QgsStringUtils::soundex( const QString& string )
return tmp;
}
QString QgsStringUtils::insertLinks( const QString& string, bool *foundLinks )
{
QString converted = string;
// http://alanstorm.com/url_regex_explained
// note - there's more robust implementations available, but we need one which works within the limitation of QRegExp
static QRegExp urlRegEx( "(\\b(([\\w-]+://?|www[.])[^\\s()<>]+(?:\\([\\w\\d]+\\)|([^!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_`{|}~\\s]|/))))" );
static QRegExp protoRegEx( "^(?:f|ht)tps?://" );
static QRegExp emailRegEx( "([\\w._%+-]+@[\\w.-]+\\.[A-Za-z]+)" );
int offset = 0;
bool found = false;
while ( urlRegEx.indexIn( converted, offset ) != -1 )
{
found = true;
QString url = urlRegEx.cap( 1 );
QString protoUrl = url;
if ( protoRegEx.indexIn( protoUrl ) == -1 )
{
protoUrl.prepend( "http://" );
}
QString anchor = QString( "<a href=\"%1\">%2</a>" ).arg( Qt::escape( protoUrl ) ).arg( Qt::escape( url ) );
converted.replace( urlRegEx.pos( 1 ), url.length(), anchor );
offset = urlRegEx.pos( 1 ) + anchor.length();
}
offset = 0;
while ( emailRegEx.indexIn( converted, offset ) != -1 )
{
found = true;
QString email = emailRegEx.cap( 1 );
QString anchor = QString( "<a href=\"mailto:%1\">%1</a>" ).arg( Qt::escape( email ) ).arg( Qt::escape( email ) );
converted.replace( emailRegEx.pos( 1 ), email.length(), anchor );
offset = emailRegEx.pos( 1 ) + anchor.length();
}
if ( foundLinks )
*foundLinks = found;
return converted;
}

View File

@ -64,6 +64,15 @@ class CORE_EXPORT QgsStringUtils
* @returns 4 letter Soundex code
*/
static QString soundex( const QString &string );
/** Returns a string with any URL (eg http(s)/ftp) and mailto: text converted to valid HTML <a ...>
* links.
* @param string string to insert links into
* @param foundLinks if specified, will be set to true if any links were inserted into the string
* @returns string with inserted links
* @note added in QGIS 3.0
*/
static QString insertLinks( const QString& string, bool* foundLinks = nullptr );
};
#endif //QGSSTRINGUTILS_H

View File

@ -33,6 +33,7 @@ class TestQgsStringUtils : public QObject
void longestCommonSubstring();
void hammingDistance();
void soundex();
void insertLinks();
};
@ -118,6 +119,41 @@ void TestQgsStringUtils::soundex()
QCOMPARE( QgsStringUtils::soundex( "ashcroft" ), QString( "A261" ) );
}
void TestQgsStringUtils::insertLinks()
{
QCOMPARE( QgsStringUtils::insertLinks( QString() ), QString() );
QCOMPARE( QgsStringUtils::insertLinks( QString( "not a link!" ) ), QString( "not a link!" ) );
bool found = true;
QCOMPARE( QgsStringUtils::insertLinks( QString( "not a link!" ), &found ), QString( "not a link!" ) );
QVERIFY( !found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "this www.north-road.com is a link" ), &found ), QString( "this <a href=\"http://www.north-road.com\">www.north-road.com</a> is a link" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "this www.north-road.com.au is a link" ), &found ), QString( "this <a href=\"http://www.north-road.com.au\">www.north-road.com.au</a> is a link" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "this www.north-road.sucks is not a good link" ), &found ), QString( "this <a href=\"http://www.north-road.sucks\">www.north-road.sucks</a> is not a good link" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "this http://www.north-road.com is a link" ), &found ), QString( "this <a href=\"http://www.north-road.com\">http://www.north-road.com</a> is a link" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "this http://north-road.com is a link" ), &found ), QString( "this <a href=\"http://north-road.com\">http://north-road.com</a> is a link" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "this http://north-road.com is a link, so is http://qgis.org, ok?" ), &found ), QString( "this <a href=\"http://north-road.com\">http://north-road.com</a> is a link, so is <a href=\"http://qgis.org\">http://qgis.org</a>, ok?" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "this north-road.com might not be a link" ), &found ), QString( "this north-road.com might not be a link" ) );
QVERIFY( !found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "please ftp to ftp://droopbox.ru and submit stuff" ), &found ), QString( "please ftp to <a href=\"ftp://droopbox.ru\">ftp://droopbox.ru</a> and submit stuff" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "please visit https://fsociety.org" ), &found ), QString( "please visit <a href=\"https://fsociety.org\">https://fsociety.org</a>" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "send your credit card number to qgis@qgis.org today!" ), &found ), QString( "send your credit card number to <a href=\"mailto:qgis@qgis.org\">qgis@qgis.org</a> today!" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "send your credit card number to qgis@qgis.org.nz today!" ), &found ), QString( "send your credit card number to <a href=\"mailto:qgis@qgis.org.nz\">qgis@qgis.org.nz</a> today!" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "visit http://qgis.org or email qgis@qgis.org" ), &found ), QString( "visit <a href=\"http://qgis.org\">http://qgis.org</a> or email <a href=\"mailto:qgis@qgis.org\">qgis@qgis.org</a>" ) );
QVERIFY( found );
QCOMPARE( QgsStringUtils::insertLinks( QString( "is a@a an email?" ), &found ), QString( "is a@a an email?" ) );
QVERIFY( !found );
}
QTEST_MAIN( TestQgsStringUtils )
#include "testqgsstringutils.moc"