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

View File

@ -15,6 +15,8 @@
#include "qgsstringutils.h" #include "qgsstringutils.h"
#include <QVector> #include <QVector>
#include <QRegExp>
#include <QTextDocument> // for Qt::escape
int QgsStringUtils::levenshteinDistance( const QString& string1, const QString& string2, bool caseSensitive ) int QgsStringUtils::levenshteinDistance( const QString& string1, const QString& string2, bool caseSensitive )
{ {
@ -294,3 +296,44 @@ QString QgsStringUtils::soundex( const QString& string )
return tmp; 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 * @returns 4 letter Soundex code
*/ */
static QString soundex( const QString &string ); 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 #endif //QGSSTRINGUTILS_H

View File

@ -33,6 +33,7 @@ class TestQgsStringUtils : public QObject
void longestCommonSubstring(); void longestCommonSubstring();
void hammingDistance(); void hammingDistance();
void soundex(); void soundex();
void insertLinks();
}; };
@ -118,6 +119,41 @@ void TestQgsStringUtils::soundex()
QCOMPARE( QgsStringUtils::soundex( "ashcroft" ), QString( "A261" ) ); 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 ) QTEST_MAIN( TestQgsStringUtils )
#include "testqgsstringutils.moc" #include "testqgsstringutils.moc"