diff --git a/python/core/auto_generated/qgsstringutils.sip.in b/python/core/auto_generated/qgsstringutils.sip.in index aba9cb257ef..30dc879e87f 100644 --- a/python/core/auto_generated/qgsstringutils.sip.in +++ b/python/core/auto_generated/qgsstringutils.sip.in @@ -279,6 +279,17 @@ links. :return: string with inserted links .. versionadded:: 3.0 +%End + + static bool isUrl( const QString &string ); +%Docstring +Returns whether the string is a URL (http,https,ftp,file) + +:param string: the string to check + +:return: whether the string is an URL + +.. versionadded:: 3.22 %End static QString wordWrap( const QString &string, int length, bool useMaxLineLength = true, const QString &customDelimiter = QString() ); diff --git a/src/core/qgsstringutils.cpp b/src/core/qgsstringutils.cpp index 50b7db9a1e7..1b0eb5dbdc0 100644 --- a/src/core/qgsstringutils.cpp +++ b/src/core/qgsstringutils.cpp @@ -563,6 +563,12 @@ QString QgsStringUtils::insertLinks( const QString &string, bool *foundLinks ) return converted; } +bool QgsStringUtils::isUrl( const QString &string ) +{ + const thread_local QRegularExpression rxUrl( "^(http|https|ftp|file)://\\S+$" ); + return rxUrl.match( string ).hasMatch(); +} + QString QgsStringUtils::htmlToMarkdown( const QString &html ) { // Any changes in this function must be copied to qgscrashreport.cpp too diff --git a/src/core/qgsstringutils.h b/src/core/qgsstringutils.h index 06a0a6e6148..5b4b65f23e5 100644 --- a/src/core/qgsstringutils.h +++ b/src/core/qgsstringutils.h @@ -277,6 +277,14 @@ class CORE_EXPORT QgsStringUtils */ static QString insertLinks( const QString &string, bool *foundLinks = nullptr ); + /** + * Returns whether the string is a URL (http,https,ftp,file) + * \param string the string to check + * \returns whether the string is an URL + * \since QGIS 3.22 + */ + static bool isUrl( const QString &string ); + /** * Automatically wraps a \a string by inserting new line characters at appropriate locations in the string. * diff --git a/src/gui/attributetable/qgsattributetablemodel.cpp b/src/gui/attributetable/qgsattributetablemodel.cpp index c4602ac30d8..228675aa001 100644 --- a/src/gui/attributetable/qgsattributetablemodel.cpp +++ b/src/gui/attributetable/qgsattributetablemodel.cpp @@ -40,6 +40,7 @@ #include "qgsfieldmodel.h" #include "qgstexteditwidgetfactory.h" #include "qgsexpressioncontextutils.h" +#include "qgsstringutils.h" #include "qgsvectorlayerutils.h" #include "qgsvectorlayercache.h" @@ -702,13 +703,24 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons switch ( role ) { case Qt::DisplayRole: - case Qt::ToolTipRole: return mFieldFormatters.at( index.column() )->representValue( mLayer, fieldId, mWidgetConfigs.at( index.column() ), mAttributeWidgetCaches.at( index.column() ), val ); - + case Qt::ToolTipRole: + { + QString tooltip = mFieldFormatters.at( index.column() )->representValue( mLayer, + fieldId, + mWidgetConfigs.at( index.column() ), + mAttributeWidgetCaches.at( index.column() ), + val ); + if ( val.type() == QVariant::String && QgsStringUtils::isUrl( val.toString() ) ) + { + tooltip = tr( "%1 (Ctrl+click to open)" ).arg( tooltip ); + } + return tooltip; + } case Qt::EditRole: return val; @@ -746,7 +758,7 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) if ( role == Qt::TextColorRole && style.validTextColor() ) #else - if ( role == Qt::ForegroundRole && style.validTextColor() ) + if ( role == Qt::ForegroundRole ) #endif return style.textColor(); if ( role == Qt::DecorationRole ) @@ -754,6 +766,19 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons if ( role == Qt::FontRole ) return style.font(); } + else if ( val.type() == QVariant::String && QgsStringUtils::isUrl( val.toString() ) ) + { + if ( role == Qt::ForegroundRole ) + { + return QColor( Qt::blue ); + } + else if ( role == Qt::FontRole ) + { + QFont font; + font.setUnderline( true ); + return font; + } + } return QVariant(); } diff --git a/src/gui/attributetable/qgsattributetableview.cpp b/src/gui/attributetable/qgsattributetableview.cpp index 730f2d6068f..aa1524630c3 100644 --- a/src/gui/attributetable/qgsattributetableview.cpp +++ b/src/gui/attributetable/qgsattributetableview.cpp @@ -13,6 +13,7 @@ * * ***************************************************************************/ +#include #include #include #include @@ -34,6 +35,7 @@ #include "qgsfeatureselectionmodel.h" #include "qgsmaplayeractionregistry.h" #include "qgsfeatureiterator.h" +#include "qgsstringutils.h" #include "qgsgui.h" QgsAttributeTableView::QgsAttributeTableView( QWidget *parent ) @@ -304,6 +306,19 @@ void QgsAttributeTableView::mouseReleaseEvent( QMouseEvent *event ) setSelectionMode( QAbstractItemView::NoSelection ); QTableView::mouseReleaseEvent( event ); setSelectionMode( QAbstractItemView::ExtendedSelection ); + if ( event->modifiers() == Qt::ControlModifier ) + { + QModelIndex index = indexAt( event->pos() ); + QVariant data = model()->data( index, Qt::DisplayRole ); + if ( data.type() == QVariant::String ) + { + QString textVal = data.toString(); + if ( QgsStringUtils::isUrl( textVal ) ) + { + QDesktopServices::openUrl( QUrl( textVal ) ); + } + } + } } void QgsAttributeTableView::mouseMoveEvent( QMouseEvent *event ) diff --git a/tests/src/core/testqgsstringutils.cpp b/tests/src/core/testqgsstringutils.cpp index c26b9b880cc..5f045cee101 100644 --- a/tests/src/core/testqgsstringutils.cpp +++ b/tests/src/core/testqgsstringutils.cpp @@ -42,6 +42,7 @@ class TestQgsStringUtils : public QObject void htmlToMarkdown(); void wordWrap_data(); void wordWrap(); + void testIsUrl(); }; @@ -258,6 +259,18 @@ void TestQgsStringUtils::wordWrap() QCOMPARE( QgsStringUtils::wordWrap( input, length, isMax, delimiter ), expected ); } +void TestQgsStringUtils::testIsUrl() +{ + QVERIFY( QgsStringUtils::isUrl( QStringLiteral( "http://example.com" ) ) ); + QVERIFY( QgsStringUtils::isUrl( QStringLiteral( "https://example.com" ) ) ); + QVERIFY( QgsStringUtils::isUrl( QStringLiteral( "ftp://example.com" ) ) ); + QVERIFY( QgsStringUtils::isUrl( QStringLiteral( "file:///path/to/file" ) ) ); + QVERIFY( QgsStringUtils::isUrl( QStringLiteral( "file://C:\\path\\to\\file" ) ) ); + QVERIFY( !QgsStringUtils::isUrl( QStringLiteral( "" ) ) ); + QVERIFY( !QgsStringUtils::isUrl( QStringLiteral( "some:random/string" ) ) ); + QVERIFY( !QgsStringUtils::isUrl( QStringLiteral( "bla" ) ) ); +} + QGSTEST_MAIN( TestQgsStringUtils ) #include "testqgsstringutils.moc"