Skip to content

Commit

Permalink
Merge pull request #3303 from nyalldawson/identify_url
Browse files Browse the repository at this point in the history
Make links in identify results clickable
  • Loading branch information
nyalldawson committed Jul 13, 2016
2 parents 7f2bdcf + 05ced67 commit 258c899
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 11 deletions.
9 changes: 9 additions & 0 deletions python/core/qgsstringutils.sip
Expand Up @@ -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 );
};
47 changes: 36 additions & 11 deletions src/app/qgsidentifyresultsdialog.cpp
Expand Up @@ -37,6 +37,7 @@
#include "qgsvectordataprovider.h"
#include "qgswebview.h"
#include "qgswebframe.h"
#include "qgsstringutils.h"

#include <QCloseEvent>
#include <QLabel>
Expand All @@ -53,6 +54,7 @@
#include <QDesktopServices>
#include <QMessageBox>
#include <QComboBox>
#include <QTextDocument>

//graph
#include <qwt_plot.h>
Expand All @@ -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 );
Expand Down Expand Up @@ -471,37 +472,47 @@ 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() );
attrItem->setData( 0, Qt::UserRole + 1, i );

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() )
{
featItem->setText( 0, attrItem->text( 0 ) );
featItem->setText( 1, attrItem->text( 1 ) );
featureLabeled = true;
}

featItem->addChild( attrItem );
}

if ( !featureLabeled )
Expand Down Expand Up @@ -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;
}
}
Expand Down
43 changes: 43 additions & 0 deletions src/core/qgsstringutils.cpp
Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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;
}
9 changes: 9 additions & 0 deletions src/core/qgsstringutils.h
Expand Up @@ -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
36 changes: 36 additions & 0 deletions tests/src/core/testqgsstringutils.cpp
Expand Up @@ -33,6 +33,7 @@ class TestQgsStringUtils : public QObject
void longestCommonSubstring();
void hammingDistance();
void soundex();
void insertLinks();

};

Expand Down Expand Up @@ -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"

0 comments on commit 258c899

Please sign in to comment.