feat(#62838): asset context menu

This commit is contained in:
Tom Christian 2025-09-26 14:40:56 -07:00
parent 3cd64c8a6a
commit 8906518d27
No known key found for this signature in database
GPG Key ID: 0CC611E0CD80A370
11 changed files with 144 additions and 41 deletions

View File

@ -87,6 +87,17 @@ Returns a uri for the asset if it is a cloud optimized file like COG or
COPC COPC
.. versionadded:: 3.42 .. versionadded:: 3.42
%End
QString toHtml() const;
%Docstring
Returns an HTML representation of the STAC Asset without an ID
%End
QString toHtml( const QString &assetId ) const;
%Docstring
Returns an HTML representation of the STAC Asset including its ID within
its container
%End %End
}; };

View File

@ -87,6 +87,17 @@ Returns a uri for the asset if it is a cloud optimized file like COG or
COPC COPC
.. versionadded:: 3.42 .. versionadded:: 3.42
%End
QString toHtml() const;
%Docstring
Returns an HTML representation of the STAC Asset without an ID
%End
QString toHtml( const QString &assetId ) const;
%Docstring
Returns an HTML representation of the STAC Asset including its ID within
its container
%End %End
}; };

View File

@ -153,3 +153,23 @@ QgsMimeDataUtils::Uri QgsStacAsset::uri( QString authcfg ) const
return uri; return uri;
} }
QString QgsStacAsset::toHtml() const
{
return toHtml( QString() );
}
QString QgsStacAsset::toHtml( const QString &assetId ) const
{
QString html = QStringLiteral( "<h1>%1</h1>\n<hr>\n" ).arg( QLatin1String( "Asset" ) );
html += QStringLiteral( "<table class=\"list-view\">\n" );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "id" ), assetId );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "title" ), title() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "description" ), description() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td><a href=\"%2\">%2</a></td></tr>\n" ).arg( QStringLiteral( "url" ), href() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "type" ), mediaType() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "roles" ), roles().join( ',' ) );
html += QStringLiteral( "</table><br/>\n" );
return html;
}

View File

@ -84,6 +84,16 @@ class CORE_EXPORT QgsStacAsset
*/ */
QgsMimeDataUtils::Uri uri( QString authcfg ) const; QgsMimeDataUtils::Uri uri( QString authcfg ) const;
/**
* Returns an HTML representation of the STAC Asset without an ID
*/
QString toHtml() const;
/**
* Returns an HTML representation of the STAC Asset including its ID within its container
*/
QString toHtml( const QString &assetId ) const;
private: private:
QString mHref; QString mHref;
QString mTitle; QString mTitle;

View File

@ -69,7 +69,12 @@ bool QgsStacAssetItem::equal( const QgsDataItem * )
void QgsStacAssetItem::updateToolTip() void QgsStacAssetItem::updateToolTip()
{ {
mToolTip = QStringLiteral( "STAC Asset:\n%1\n%2" ).arg( mName, mStacAsset->title() ); QString title = mStacAsset->title();
if ( title.isNull() || title.isEmpty() )
{
title = mName;
}
mToolTip = QStringLiteral( "STAC Asset:\n%1\n%2" ).arg( title, mStacAsset->href() );
} }
// //

View File

@ -45,8 +45,8 @@ class CORE_EXPORT QgsStacAssetItem : public QgsDataItem
QgsMimeDataUtils::UriList mimeUris() const override; QgsMimeDataUtils::UriList mimeUris() const override;
bool equal( const QgsDataItem *other ) override; bool equal( const QgsDataItem *other ) override;
QVariant sortKey() const override { return QStringLiteral( "4 %1" ).arg( mName ); } QVariant sortKey() const override { return QStringLiteral( "4 %1" ).arg( mName ); }
void updateToolTip(); void updateToolTip();
const QgsStacAsset *stacAsset() { return mStacAsset; }
private: private:
const QgsStacAsset *mStacAsset; const QgsStacAsset *mStacAsset;

View File

@ -38,9 +38,7 @@ Qgis::StacObjectType QgsStacItem::type() const
QString QgsStacItem::toHtml() const QString QgsStacItem::toHtml() const
{ {
QString html = QStringLiteral( "<html><head></head>\n<body>\n" ); QString html = QStringLiteral( "<h1>%1</h1>\n<hr>\n" ).arg( QLatin1String( "Item" ) );
html += QStringLiteral( "<h1>%1</h1>\n<hr>\n" ).arg( QLatin1String( "Item" ) );
html += QLatin1String( "<table class=\"list-view\">\n" ); html += QLatin1String( "<table class=\"list-view\">\n" );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "id" ), id() ); html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "id" ), id() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "stac_version" ), stacVersion() ); html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "stac_version" ), stacVersion() );
@ -95,18 +93,9 @@ QString QgsStacItem::toHtml() const
html += QStringLiteral( "<h1>%1</h1>\n<hr>\n" ).arg( QLatin1String( "Assets" ) ); html += QStringLiteral( "<h1>%1</h1>\n<hr>\n" ).arg( QLatin1String( "Assets" ) );
for ( auto it = mAssets.constBegin(); it != mAssets.constEnd(); ++it ) for ( auto it = mAssets.constBegin(); it != mAssets.constEnd(); ++it )
{ {
html += QLatin1String( "<table class=\"list-view\">\n" ); html += it->toHtml( it.key() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "id" ), it.key() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "title" ), it->title() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "description" ), it->description() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td><a href=\"%2\">%2</a></td></tr>\n" ).arg( QStringLiteral( "url" ), it->href() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "type" ), it->mediaType() );
html += QStringLiteral( "<tr><td class=\"highlight\">%1</td><td>%2</td></tr>\n" ).arg( QStringLiteral( "roles" ), it->roles().join( ',' ) );
html += QLatin1String( "</table><br/>\n" );
} }
} }
html += QLatin1String( "\n</body>\n</html>\n" );
return html; return html;
} }

View File

@ -98,6 +98,17 @@ void QgsStacDataItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *
menu->addAction( actionDetails ); menu->addAction( actionDetails );
} }
} }
if ( QgsStacAssetItem *assetItem = qobject_cast<QgsStacAssetItem *>( item ) )
{
QAction *actionDownload = new QAction( tr( "Download Asset…" ), menu );
connect( actionDownload, &QAction::triggered, this, [assetItem, context] { downloadAssets( assetItem, context ); } );
menu->addAction( actionDownload );
QAction *actionDetails = new QAction( tr( "Details…" ), menu );
connect( actionDetails, &QAction::triggered, this, [assetItem] { showDetails( assetItem ); } );
menu->addAction( actionDetails );
}
} }
void QgsStacDataItemGuiProvider::editConnection( QgsDataItem *item ) void QgsStacDataItemGuiProvider::editConnection( QgsDataItem *item )
@ -162,25 +173,35 @@ void QgsStacDataItemGuiProvider::loadConnections( QgsDataItem *item )
void QgsStacDataItemGuiProvider::showDetails( QgsDataItem *item ) void QgsStacDataItemGuiProvider::showDetails( QgsDataItem *item )
{ {
QgsStacObject *obj = nullptr; QgsStacObject *stacObj = nullptr;
QString authcfg; QString authcfg;
if ( QgsStacItemItem *itemItem = qobject_cast<QgsStacItemItem *>( item ) ) if ( QgsStacItemItem *itemItem = qobject_cast<QgsStacItemItem *>( item ) )
{ {
obj = itemItem->stacItem(); stacObj = itemItem->stacItem();
authcfg = itemItem->stacController()->authCfg(); authcfg = itemItem->stacController()->authCfg();
} }
else if ( QgsStacCatalogItem *catalogItem = qobject_cast<QgsStacCatalogItem *>( item ) ) else if ( QgsStacCatalogItem *catalogItem = qobject_cast<QgsStacCatalogItem *>( item ) )
{ {
obj = catalogItem->stacCatalog(); stacObj = catalogItem->stacCatalog();
} }
if ( stacObj )
if ( obj )
{ {
QgsStacObjectDetailsDialog d; QgsStacObjectDetailsDialog d;
d.setAuthcfg( authcfg ); d.setAuthcfg( authcfg );
d.setStacObject( obj ); d.setContentFromStacObject( stacObj );
d.exec(); d.exec();
return;
}
if ( QgsStacAssetItem *assetItem = qobject_cast<QgsStacAssetItem *>( item ) )
{
QgsStacObjectDetailsDialog d;
QgsStacItemItem *itemItem = qobject_cast<QgsStacItemItem *>( assetItem->parent() );
d.setAuthcfg( itemItem->stacController()->authCfg() );
d.setContentFromStacAsset( assetItem->stacAsset() );
d.exec();
return;
} }
} }

View File

@ -31,7 +31,8 @@ QgsStacObjectDetailsDialog::QgsStacObjectDetailsDialog( QWidget *parent )
QgsGui::enableAutoGeometryRestore( this ); QgsGui::enableAutoGeometryRestore( this );
} }
void QgsStacObjectDetailsDialog::setStacObject( QgsStacObject *stacObject )
void QgsStacObjectDetailsDialog::setContentFromStacObject( QgsStacObject *stacObject )
{ {
if ( !stacObject ) if ( !stacObject )
return; return;
@ -42,28 +43,40 @@ void QgsStacObjectDetailsDialog::setStacObject( QgsStacObject *stacObject )
const QMap<QString, QgsStacAsset> assets = item->assets(); const QMap<QString, QgsStacAsset> assets = item->assets();
for ( auto it = assets.constBegin(); it != assets.constEnd(); ++it ) for ( auto it = assets.constBegin(); it != assets.constEnd(); ++it )
{ {
if ( it->roles().contains( QLatin1String( "thumbnail" ) ) ) if ( isThumbnailAsset( &it.value() ) )
{ {
QString uri = it->href(); thumbnails.append( thumbnailHtmlContent( &it.value() ) );
if ( !mAuthcfg.isEmpty() )
{
QStringList connectionItems;
connectionItems << uri;
QgsApplication::authManager()->updateDataSourceUriItems( connectionItems, mAuthcfg );
uri = connectionItems.first();
}
thumbnails.append( QStringLiteral( "<img src=\"%1\" border=1><br>" ).arg( uri ) );
} }
} }
} }
QString thumbnailHtml = thumbnails.join( QString() );
QString bodyHtml = stacObject->toHtml();
setContent( bodyHtml, thumbnailHtml );
}
void QgsStacObjectDetailsDialog::setContentFromStacAsset( const QgsStacAsset *stacAsset )
{
QString thumbnailHtml = QString( "" );
if ( isThumbnailAsset( stacAsset ) )
{
thumbnailHtml = thumbnailHtmlContent( stacAsset );
}
QString bodyHtml = stacAsset->toHtml();
setContent( bodyHtml, thumbnailHtml );
}
void QgsStacObjectDetailsDialog::setContent( QString bodyHtml, QString thumbnailHtml )
{
const QString myStyle = QgsApplication::reportStyleSheet( QgsApplication::StyleSheetType::WebBrowser ); const QString myStyle = QgsApplication::reportStyleSheet( QgsApplication::StyleSheetType::WebBrowser );
// inject thumbnails QString html = QStringLiteral( "<html>\n<head>\n" );
QString html = stacObject->toHtml().replace( QLatin1String( "<head>" ), QStringLiteral( "<head>\n%1" ).arg( thumbnails.join( QString() ) ) ); html += QStringLiteral( "<style type=\"text/css\">%1</style>\n" ).arg( myStyle );
// inject stylesheet html += QStringLiteral( "%1\n" ).arg( thumbnailHtml );
html = html.replace( QLatin1String( "<head>" ), QStringLiteral( R"raw(<head><style type="text/css">%1</style>)raw" ) ).arg( myStyle ); html += QStringLiteral( "</head>\n<body>\n" );
html += QStringLiteral( "%1\n" ).arg( bodyHtml );
html += QLatin1String( "</body>\n</html>\n" );
mWebView->page()->setLinkDelegationPolicy( QWebPage::LinkDelegationPolicy::DelegateAllLinks ); mWebView->page()->setLinkDelegationPolicy( QWebPage::LinkDelegationPolicy::DelegateAllLinks );
connect( mWebView, &QgsWebView::linkClicked, this, []( const QUrl &url ) { connect( mWebView, &QgsWebView::linkClicked, this, []( const QUrl &url ) {
QDesktopServices::openUrl( url ); QDesktopServices::openUrl( url );
@ -76,4 +89,22 @@ void QgsStacObjectDetailsDialog::setAuthcfg( const QString &authcfg )
mAuthcfg = authcfg; mAuthcfg = authcfg;
} }
bool QgsStacObjectDetailsDialog::isThumbnailAsset( const QgsStacAsset *stacAsset )
{
return stacAsset->roles().contains( QLatin1String( "thumbnail" ) );
}
QString QgsStacObjectDetailsDialog::thumbnailHtmlContent( const QgsStacAsset *stacAsset )
{
QString uri = stacAsset->href();
if ( !mAuthcfg.isEmpty() )
{
QStringList connectionItems;
connectionItems << uri;
QgsApplication::authManager()->updateDataSourceUriItems( connectionItems, mAuthcfg );
uri = connectionItems.first();
}
return QStringLiteral( "<img src=\"%1\" border=1><br>" ).arg( uri );
}
///@endcond ///@endcond

View File

@ -19,6 +19,7 @@
///@cond PRIVATE ///@cond PRIVATE
#define SIP_NO_FILE #define SIP_NO_FILE
#include "qgsstacasset.h"
#include "qgsstacobject.h" #include "qgsstacobject.h"
#include "ui_qgsstacobjectdetailsdialog.h" #include "ui_qgsstacobjectdetailsdialog.h"
@ -31,12 +32,16 @@ class QgsStacObjectDetailsDialog : public QDialog, private Ui::QgsStacObjectDeta
public: public:
explicit QgsStacObjectDetailsDialog( QWidget *parent = nullptr ); explicit QgsStacObjectDetailsDialog( QWidget *parent = nullptr );
void setStacObject( QgsStacObject *stacObject );
void setAuthcfg( const QString &authcfg ); void setAuthcfg( const QString &authcfg );
void setContentFromStacObject( QgsStacObject *stacObject );
void setContentFromStacAsset( const QgsStacAsset *stacAsset );
private: private:
QString mAuthcfg; QString mAuthcfg;
void setContent( QString bodyHtml, QString thumbnailHtml );
bool isThumbnailAsset( const QgsStacAsset *stacAsset );
QString thumbnailHtmlContent( const QgsStacAsset *stacAsset );
}; };
///@endcond ///@endcond

View File

@ -142,7 +142,7 @@ void QgsStacSourceSelect::showItemDetails( const QModelIndex &index )
{ {
QgsStacObjectDetailsDialog details( this ); QgsStacObjectDetailsDialog details( this );
details.setAuthcfg( mStac->authCfg() ); details.setAuthcfg( mStac->authCfg() );
details.setStacObject( index.data( QgsStacItemListModel::Role::StacObject ).value<QgsStacObject *>() ); details.setContentFromStacObject( index.data( QgsStacItemListModel::Role::StacObject ).value<QgsStacObject *>() );
details.exec(); details.exec();
} }