QGIS/src/app/qgsidentifyresultsdialog.cpp

1993 lines
65 KiB
C++

/***************************************************************************
qgsidentifyresults.cpp - description
-------------------
begin : Fri Oct 25 2002
copyright : (C) 2002 by Gary E.Sherman
email : sherman at mrcc dot com
Romans 3:23=>Romans 6:23=>Romans 5:8=>Romans 10:9,10=>Romans 12
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgisapp.h"
#include "qgsapplication.h"
#include "qgsactionmanager.h"
#include "qgsattributedialog.h"
#include "qgsdockwidget.h"
#include "qgseditorwidgetregistry.h"
#include "qgsfeatureaction.h"
#include "qgsfeatureiterator.h"
#include "qgsfeaturestore.h"
#include "qgsgeometry.h"
#include "qgshighlight.h"
#include "qgsidentifyresultsdialog.h"
#include "qgslogger.h"
#include "qgsmapcanvas.h"
#include "qgsmaplayeractionregistry.h"
#include "qgsmaplayer.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsproject.h"
#include "qgsrasterdataprovider.h"
#include "qgsrasterlayer.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"
#include "qgswebview.h"
#include "qgswebframe.h"
#include "qgsstringutils.h"
#include "qgstreewidgetitem.h"
#include "qgsfiledownloaderdialog.h"
#include "qgsfieldformatterregistry.h"
#include "qgsfieldformatter.h"
#include "qgssettings.h"
#include "qgsgui.h"
#include <QCloseEvent>
#include <QLabel>
#include <QAction>
#include <QTreeWidgetItem>
#include <QPixmap>
#include <QMenu>
#include <QClipboard>
#include <QMenuBar>
#include <QPushButton>
#include <QPrinter>
#include <QPrintDialog>
#include <QDesktopServices>
#include <QMessageBox>
#include <QComboBox>
#include <QTextDocument>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QFileDialog>
#include <QFileInfo>
#include <QRegExp>
//graph
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_symbol.h>
#include <qwt_legend.h>
#include "qgscolorramp.h" // for random colors
QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWebView( parent )
{
setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
page()->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
#ifdef WITH_QTWEBKIT
page()->setForwardUnsupportedContent( true );
#endif
page()->setLinkDelegationPolicy( QWebPage::DontDelegateLinks );
settings()->setAttribute( QWebSettings::LocalContentCanAccessRemoteUrls, true );
settings()->setAttribute( QWebSettings::JavascriptCanOpenWindows, true );
settings()->setAttribute( QWebSettings::PluginsEnabled, true );
#ifdef QGISDEBUG
settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
#endif
connect( page(), &QWebPage::downloadRequested, this, &QgsIdentifyResultsWebView::downloadRequested );
connect( page(), &QWebPage::unsupportedContent, this, &QgsIdentifyResultsWebView::unsupportedContent );
}
void QgsIdentifyResultsWebView::downloadRequested( const QNetworkRequest &request )
{
handleDownload( request.url() );
}
void QgsIdentifyResultsWebView::unsupportedContent( QNetworkReply *reply )
{
handleDownload( reply->url() );
}
void QgsIdentifyResultsWebView::handleDownload( QUrl url )
{
if ( ! url.isValid() )
{
QMessageBox::warning( this, tr( "Invalid URL" ), tr( "The download URL is not valid: %1" ).arg( url.toString() ) );
}
else
{
const QString DOWNLOADER_LAST_DIR_KEY( QStringLiteral( "Qgis/fileDownloaderLastDir" ) );
QgsSettings settings;
// Try to get some information from the URL
QFileInfo info( url.toString() );
QString savePath = settings.value( DOWNLOADER_LAST_DIR_KEY ).toString();
QString fileName = info.fileName().replace( QRegExp( "[^A-z0-9\\-_\\.]" ), QStringLiteral( "_" ) );
if ( ! savePath.isEmpty() && ! fileName.isEmpty() )
{
savePath = QDir::cleanPath( savePath + QDir::separator() + fileName );
}
QString targetFile = QFileDialog::getSaveFileName( this,
tr( "Save as" ),
savePath,
info.suffix().isEmpty() ? QString() : "*." + info.suffix()
);
if ( ! targetFile.isEmpty() )
{
settings.setValue( DOWNLOADER_LAST_DIR_KEY, QFileInfo( targetFile ).dir().absolutePath() );
// Start the download
new QgsFileDownloaderDialog( url, targetFile );
}
}
}
void QgsIdentifyResultsWebView::print()
{
QPrinter printer;
QPrintDialog *dialog = new QPrintDialog( &printer );
if ( dialog->exec() == QDialog::Accepted )
{
QgsWebView::print( &printer );
}
}
void QgsIdentifyResultsWebView::contextMenuEvent( QContextMenuEvent *e )
{
QMenu *menu = page()->createStandardContextMenu();
if ( !menu )
return;
QAction *action = new QAction( tr( "Print" ), this );
connect( action, &QAction::triggered, this, &QgsIdentifyResultsWebView::print );
menu->addAction( action );
menu->exec( e->globalPos() );
delete menu;
}
QgsWebView *QgsIdentifyResultsWebView::createWindow( QWebPage::WebWindowType type )
{
QDialog *d = new QDialog( this );
QLayout *l = new QVBoxLayout( d );
QgsWebView *wv = new QgsWebView( d );
l->addWidget( wv );
wv->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
wv->page()->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
wv->settings()->setAttribute( QWebSettings::LocalContentCanAccessRemoteUrls, true );
wv->settings()->setAttribute( QWebSettings::JavascriptCanOpenWindows, true );
wv->settings()->setAttribute( QWebSettings::PluginsEnabled, true );
#ifdef QGISDEBUG
wv->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
#endif
d->setModal( type != QWebPage::WebBrowserWindow );
d->show();
return wv;
}
// QgsIdentifyResultsWebView size:
// ---------------
//
// 1) QTreeWidget is not able to scroll continuously through the content of large widgets
// inserted into items via setItemWidget, it always jumps to the top of next
// item and it is not able to scroll to the bottom of an inserted large
// widget (until the QTreeWidget itself is large enough to show the whole
// inserted widget). => We have to keep the height of QgsIdentifyResultsWebView smaller
// than the height of QTreeWidget so that a user can see it entire, even if
// this height is smaller than QgsIdentifyResultsWebView content (i.e. QgsIdentifyResultsWebView scroolbar
// is added). We make it even a bit smaller so that a user can see a bit of
// context (items above/below) when scrolling which is more pleasant.
//
// 2) contentsSize() is 0,0 until a page is loaded. If there are no external
// resources (like images) used, contentsSize() is available immediately
// after setHtml(), otherwise the contentSize() is 0,0 until the page is
// loaded and contentsSizeChanged () is emitted.
//
// 3) If QgsIdentifyResultsWebView is resized (on page load) after it was inserted into
// QTreeWidget, the row does not reflect that change automatically and
// consecutive resize of QTreeWidget will cause to shrink QgsIdentifyResultsWebView to the
// original row height. That is expected, Qt: "setItemWidget() should only
// be used to display static content... => we must not change QgsIdentifyResultsWebView
// size after it was inserted to QTreeWidget
// TODO(?): Sometimes it may happen that if multiple QgsIdentifyResultsWebView are inserted to
// QTreeWidget for the first time, and both share the same external source
// (image) the layout gets somehow confused - wrong positions, overlapped (Qt
// bug?) until next QTreeWidget resize.
// TODO(?): if the results dialog is resized to smaller height, existing
// QgsIdentifyResultsWebView are not (and must not be!) resized and scrolling becomes a bit
// unpleasant until next identify. AFAIK it could only be solved using
// QItemDelegate.
// size hint according to content
QSize QgsIdentifyResultsWebView::sizeHint() const
{
QSize s = page()->mainFrame()->contentsSize();
QgsDebugMsg( QString( "content size: %1 x %2" ).arg( s.width() ).arg( s.height() ) );
int height = s.height();
// parent is qt_scrollarea_viewport
// parent is not available the first time - before results dialog was shown
QWidget *widget = qobject_cast<QWidget *>( parent() );
if ( widget )
{
// It can probably happen that parent is available but it does not have yet
// correct size, see #9377.
int max = widget->size().height() * 0.9;
QgsDebugMsg( QString( "parent widget height = %1 max height = %2" ).arg( widget->size().height() ).arg( max ) );
height = std::min( height, max );
}
else
{
QgsDebugMsg( "parent not available" );
}
// Always keep some minimum size, e.g. if page is not yet loaded
// or parent has wrong size
height = std::max( height, 100 );
s = QSize( size().width(), height );
QgsDebugMsg( QString( "size: %1 x %2" ).arg( s.width() ).arg( s.height() ) );
return s;
}
QgsIdentifyResultsFeatureItem::QgsIdentifyResultsFeatureItem( const QgsFields &fields, const QgsFeature &feature, const QgsCoordinateReferenceSystem &crs, const QStringList &strings )
: QTreeWidgetItem( strings )
, mFields( fields )
, mFeature( feature )
, mCrs( crs )
{
}
void QgsIdentifyResultsWebViewItem::setHtml( const QString &html )
{
mWebView->setHtml( html );
}
void QgsIdentifyResultsWebViewItem::setContent( const QByteArray &data, const QString &mimeType, const QUrl &baseUrl )
{
mWebView->setContent( data, mimeType, baseUrl );
}
QgsIdentifyResultsWebViewItem::QgsIdentifyResultsWebViewItem( QTreeWidget *treeWidget )
{
mWebView = new QgsIdentifyResultsWebView( treeWidget );
mWebView->hide();
setText( 0, tr( "Loading..." ) );
connect( mWebView->page(), &QWebPage::loadFinished, this, &QgsIdentifyResultsWebViewItem::loadFinished );
}
void QgsIdentifyResultsWebViewItem::loadFinished( bool ok )
{
Q_UNUSED( ok );
mWebView->show();
treeWidget()->setItemWidget( this, 0, mWebView );
// Span columns to save some space, must be after setItemWidget() to take effect.
setFirstColumnSpanned( true );
disconnect( mWebView->page(), &QWebPage::loadFinished, this, &QgsIdentifyResultsWebViewItem::loadFinished );
}
// Tree hierarchy
//
// layer [userrole: QgsMapLayer]
// feature: displayfield|displayvalue [userrole: fid, index in feature list]
// derived attributes (if any) [userrole: "derived"]
// name value
// actions (if any) [userrole: "actions"]
// edit [userrole: "edit"]
// action [userrole: "action", idx]
// action [userrole: "map_layer_action", QgsMapLayerAction]
// displayname [userroles: fieldIdx, original name] displayvalue [userrole: original value]
// displayname [userroles: fieldIdx, original name] displayvalue [userrole: original value]
// displayname [userroles: fieldIdx, original name] displayvalue [userrole: original value]
// feature
// derived attributes (if any)
// name value
// actions (if any)
// action
// name value
QgsIdentifyResultsDialog::QgsIdentifyResultsDialog( QgsMapCanvas *canvas, QWidget *parent, Qt::WindowFlags f )
: QDialog( parent, f )
, mCanvas( canvas )
{
setupUi( this );
connect( cmbIdentifyMode, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsIdentifyResultsDialog::cmbIdentifyMode_currentIndexChanged );
connect( cmbViewMode, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsIdentifyResultsDialog::cmbViewMode_currentIndexChanged );
connect( mExpandNewAction, &QAction::triggered, this, &QgsIdentifyResultsDialog::mExpandNewAction_triggered );
connect( cbxAutoFeatureForm, &QCheckBox::toggled, this, &QgsIdentifyResultsDialog::cbxAutoFeatureForm_toggled );
connect( mExpandAction, &QAction::triggered, this, &QgsIdentifyResultsDialog::mExpandAction_triggered );
connect( mCollapseAction, &QAction::triggered, this, &QgsIdentifyResultsDialog::mCollapseAction_triggered );
connect( mActionCopy, &QAction::triggered, this, &QgsIdentifyResultsDialog::mActionCopy_triggered );
mOpenFormAction->setDisabled( true );
QgsSettings mySettings;
mDock = new QgsDockWidget( tr( "Identify Results" ), QgisApp::instance() );
mDock->setObjectName( QStringLiteral( "IdentifyResultsDock" ) );
mDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
mDock->setWidget( this );
if ( !QgisApp::instance()->restoreDockWidget( mDock ) )
QgisApp::instance()->addDockWidget( Qt::RightDockWidgetArea, mDock );
else
QgisApp::instance()->panelMenu()->addAction( mDock->toggleViewAction() );
int size = mySettings.value( QStringLiteral( "IconSize" ), 16 ).toInt();
if ( size > 32 )
{
size -= 16;
}
else if ( size == 32 )
{
size = 24;
}
else
{
size = 16;
}
mIdentifyToolbar->setIconSize( QSize( size, size ) );
mExpandNewAction->setChecked( mySettings.value( QStringLiteral( "Map/identifyExpand" ), false ).toBool() );
mActionCopy->setEnabled( false );
lstResults->setColumnCount( 2 );
lstResults->sortByColumn( -1 );
setColumnText( 0, tr( "Feature" ) );
setColumnText( 1, tr( "Value" ) );
int width = mySettings.value( QStringLiteral( "Windows/Identify/columnWidth" ), "0" ).toInt();
if ( width > 0 )
{
lstResults->setColumnWidth( 0, width );
}
width = mySettings.value( QStringLiteral( "Windows/Identify/columnWidthTable" ), "0" ).toInt();
if ( width > 0 )
{
tblResults->setColumnWidth( 0, width );
}
// retrieve mode before on_cmbIdentifyMode_currentIndexChanged resets it on addItem
int identifyMode = mySettings.value( QStringLiteral( "Map/identifyMode" ), 0 ).toInt();
cmbIdentifyMode->addItem( tr( "Current layer" ), 0 );
cmbIdentifyMode->addItem( tr( "Top down, stop at first" ), 1 );
cmbIdentifyMode->addItem( tr( "Top down" ), 2 );
cmbIdentifyMode->addItem( tr( "Layer selection" ), 3 );
cmbIdentifyMode->setCurrentIndex( cmbIdentifyMode->findData( identifyMode ) );
cbxAutoFeatureForm->setChecked( mySettings.value( QStringLiteral( "Map/identifyAutoFeatureForm" ), false ).toBool() );
// view modes
cmbViewMode->addItem( tr( "Tree" ), 0 );
cmbViewMode->addItem( tr( "Table" ), 0 );
cmbViewMode->addItem( tr( "Graph" ), 0 );
// graph
mPlot->setVisible( false );
mPlot->setAutoFillBackground( false );
mPlot->setAutoDelete( true );
mPlot->insertLegend( new QwtLegend(), QwtPlot::TopLegend );
QSizePolicy sizePolicy = QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
sizePolicy.setHorizontalStretch( 0 );
sizePolicy.setVerticalStretch( 0 );
sizePolicy.setHeightForWidth( mPlot->sizePolicy().hasHeightForWidth() );
mPlot->setSizePolicy( sizePolicy );
mPlot->updateGeometry();
connect( lstResults, &QTreeWidget::itemExpanded,
this, &QgsIdentifyResultsDialog::itemExpanded );
connect( lstResults, &QTreeWidget::currentItemChanged,
this, &QgsIdentifyResultsDialog::handleCurrentItemChanged );
connect( lstResults, &QTreeWidget::itemClicked,
this, &QgsIdentifyResultsDialog::itemClicked );
connect( mActionPrint, &QAction::triggered, this, &QgsIdentifyResultsDialog::printCurrentItem );
connect( mOpenFormAction, &QAction::triggered, this, &QgsIdentifyResultsDialog::featureForm );
connect( mClearResultsAction, &QAction::triggered, this, &QgsIdentifyResultsDialog::clear );
connect( mHelpToolButton, &QAbstractButton::clicked, this, &QgsIdentifyResultsDialog::showHelp );
}
QgsIdentifyResultsDialog::~QgsIdentifyResultsDialog()
{
clearHighlights();
QgsSettings settings;
settings.setValue( QStringLiteral( "Windows/Identify/columnWidth" ), lstResults->columnWidth( 0 ) );
if ( mActionPopup )
delete mActionPopup;
Q_FOREACH ( QgsIdentifyPlotCurve *curve, mPlotCurves )
delete curve;
mPlotCurves.clear();
}
QTreeWidgetItem *QgsIdentifyResultsDialog::layerItem( QObject *object )
{
for ( int i = 0; i < lstResults->topLevelItemCount(); i++ )
{
QTreeWidgetItem *item = lstResults->topLevelItem( i );
if ( item->data( 0, Qt::UserRole ).value<QObject *>() == object )
return item;
}
return nullptr;
}
void QgsIdentifyResultsDialog::addFeature( const QgsMapToolIdentify::IdentifyResult &result )
{
if ( result.mLayer->type() == QgsMapLayer::VectorLayer )
{
addFeature( qobject_cast<QgsVectorLayer *>( result.mLayer ), result.mFeature, result.mDerivedAttributes );
}
else if ( result.mLayer->type() == QgsMapLayer::RasterLayer )
{
addFeature( qobject_cast<QgsRasterLayer *>( result.mLayer ), result.mLabel, result.mAttributes, result.mDerivedAttributes, result.mFields, result.mFeature, result.mParams );
}
}
void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer, const QgsFeature &f, const QMap<QString, QString> &derivedAttributes )
{
QTreeWidgetItem *layItem = layerItem( vlayer );
lstResults->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
lstResults->header()->setStretchLastSection( false );
if ( !layItem )
{
layItem = new QTreeWidgetItem( QStringList() << vlayer->name() );
layItem->setData( 0, Qt::UserRole, QVariant::fromValue( qobject_cast<QObject *>( vlayer ) ) );
lstResults->addTopLevelItem( layItem );
connect( vlayer, &QObject::destroyed, this, &QgsIdentifyResultsDialog::layerDestroyed );
connect( vlayer, &QgsMapLayer::crsChanged, this, &QgsIdentifyResultsDialog::layerDestroyed );
connect( vlayer, &QgsVectorLayer::featureDeleted, this, &QgsIdentifyResultsDialog::featureDeleted );
connect( vlayer, &QgsVectorLayer::attributeValueChanged,
this, &QgsIdentifyResultsDialog::attributeValueChanged );
connect( vlayer, &QgsVectorLayer::editingStarted, this, &QgsIdentifyResultsDialog::editingToggled );
connect( vlayer, &QgsVectorLayer::editingStopped, this, &QgsIdentifyResultsDialog::editingToggled );
}
QgsIdentifyResultsFeatureItem *featItem = new QgsIdentifyResultsFeatureItem( vlayer->fields(), f, vlayer->crs() );
featItem->setData( 0, Qt::UserRole, FID_TO_STRING( f.id() ) );
featItem->setData( 0, Qt::UserRole + 1, mFeatures.size() );
mFeatures << f;
layItem->addChild( featItem );
if ( derivedAttributes.size() >= 0 )
{
QgsTreeWidgetItem *derivedItem = new QgsTreeWidgetItem( QStringList() << tr( "(Derived)" ) );
derivedItem->setData( 0, Qt::UserRole, "derived" );
derivedItem->setAlwaysOnTopPriority( 0 );
featItem->addChild( derivedItem );
for ( QMap< QString, QString>::const_iterator it = derivedAttributes.begin(); it != derivedAttributes.end(); ++it )
{
derivedItem->addChild( new QTreeWidgetItem( QStringList() << it.key() << it.value() ) );
}
}
//get valid QgsMapLayerActions for this layer
QList< QgsMapLayerAction * > registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( vlayer );
QList<QgsAction> actions = vlayer->actions()->actions( QStringLiteral( "Feature" ) );
if ( !vlayer->fields().isEmpty() || !actions.isEmpty() || !registeredActions.isEmpty() )
{
QgsTreeWidgetItem *actionItem = new QgsTreeWidgetItem( QStringList() << tr( "(Actions)" ) );
actionItem->setData( 0, Qt::UserRole, "actions" );
actionItem->setAlwaysOnTopPriority( 1 );
featItem->addChild( actionItem );
if ( vlayer->fields().size() > 0 )
{
QTreeWidgetItem *editItem = new QTreeWidgetItem( QStringList() << QLatin1String( "" ) << ( vlayer->isEditable() ? tr( "Edit feature form" ) : tr( "View feature form" ) ) );
editItem->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mActionFormView.svg" ) ) );
editItem->setData( 0, Qt::UserRole, "edit" );
actionItem->addChild( editItem );
}
Q_FOREACH ( const QgsAction &action, actions )
{
if ( !action.runable() )
continue;
QTreeWidgetItem *twi = new QTreeWidgetItem( QStringList() << QLatin1String( "" ) << action.name() );
twi->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mAction.svg" ) ) );
twi->setData( 0, Qt::UserRole, "action" );
twi->setData( 0, Qt::UserRole + 1, action.id() );
actionItem->addChild( twi );
}
//add actions from QgsMapLayerActionRegistry
for ( int i = 0; i < registeredActions.size(); i++ )
{
QgsMapLayerAction *action = registeredActions.at( i );
QTreeWidgetItem *twi = new QTreeWidgetItem( QStringList() << QLatin1String( "" ) << action->text() );
twi->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mAction.svg" ) ) );
twi->setData( 0, Qt::UserRole, "map_layer_action" );
twi->setData( 0, Qt::UserRole + 1, qVariantFromValue( qobject_cast<QObject *>( action ) ) );
actionItem->addChild( twi );
connect( action, &QObject::destroyed, this, &QgsIdentifyResultsDialog::mapLayerActionDestroyed );
}
}
const QgsFields &fields = vlayer->fields();
QgsAttributes attrs = f.attributes();
bool featureLabeled = false;
for ( int i = 0; i < attrs.count(); ++i )
{
if ( i >= fields.count() )
break;
const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( vlayer, fields[i].name() );
if ( setup.type() == QLatin1String( "Hidden" ) )
{
continue;
}
QString defVal;
if ( fields.fieldOrigin( i ) == QgsFields::OriginProvider && vlayer->dataProvider() )
defVal = vlayer->dataProvider()->defaultValueClause( fields.fieldOriginIndex( i ) );
QString value = defVal == attrs.at( i ) ? defVal : fields.at( i ).displayString( attrs.at( i ) );
QgsTreeWidgetItem *attrItem = new QgsTreeWidgetItem( QStringList() << QString::number( i ) << value );
featItem->addChild( attrItem );
attrItem->setData( 0, Qt::DisplayRole, vlayer->attributeDisplayName( i ) );
attrItem->setToolTip( 0, vlayer->attributeDisplayName( i ) );
attrItem->setData( 0, Qt::UserRole, fields.at( i ).name() );
attrItem->setData( 0, Qt::UserRole + 1, i );
attrItem->setData( 1, Qt::UserRole, value );
value = representValue( vlayer, setup, fields.at( i ).name(), attrs.at( i ) );
attrItem->setSortData( 1, value );
attrItem->setToolTip( 1, value );
bool foundLinks = false;
QString links = QgsStringUtils::insertLinks( value, &foundLinks );
if ( foundLinks )
{
QLabel *valueLabel = new QLabel( links );
valueLabel->setOpenExternalLinks( true );
attrItem->setData( 1, Qt::DisplayRole, QString() );
attrItem->treeWidget()->setItemWidget( attrItem, 1, valueLabel );
}
else
{
attrItem->setData( 1, Qt::DisplayRole, value );
attrItem->treeWidget()->setItemWidget( attrItem, 1, nullptr );
}
if ( fields.at( i ).name() == vlayer->displayField() )
{
featItem->setText( 0, attrItem->text( 0 ) );
featItem->setToolTip( 0, attrItem->text( 0 ) );
featItem->setText( 1, attrItem->text( 1 ) );
featItem->setToolTip( 1, attrItem->text( 1 ) );
featureLabeled = true;
}
}
if ( !featureLabeled )
{
featItem->setText( 0, tr( "Title" ) );
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( vlayer ) );
context.setFeature( f );
QString value = QgsExpression( vlayer->displayExpression() ).evaluate( &context ).toString();
featItem->setText( 1, value );
featItem->setToolTip( 1, value );
}
// table
int j = tblResults->rowCount();
for ( int i = 0; i < attrs.count(); ++i )
{
if ( i >= fields.count() )
continue;
QString value = fields.at( i ).displayString( attrs.at( i ) );
const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( vlayer, fields.at( i ).name() );
QString value2 = representValue( vlayer, setup, fields.at( i ).name(), value );
tblResults->setRowCount( j + 1 );
QgsDebugMsgLevel( QStringLiteral( "adding item #%1 / %2 / %3 / %4" ).arg( j ).arg( vlayer->name(), vlayer->attributeDisplayName( i ), value2 ), 4 );
QTableWidgetItem *item = new QTableWidgetItem( vlayer->name() );
item->setData( Qt::UserRole, QVariant::fromValue( qobject_cast<QObject *>( vlayer ) ) );
item->setData( Qt::UserRole + 1, vlayer->id() );
tblResults->setItem( j, 0, item );
item = new QTableWidgetItem( FID_TO_STRING( f.id() ) );
item->setData( Qt::UserRole, FID_TO_STRING( f.id() ) );
item->setData( Qt::UserRole + 1, mFeatures.size() );
tblResults->setItem( j, 1, item );
item = new QTableWidgetItem( QString::number( i ) );
if ( fields.at( i ).name() == vlayer->displayField() )
item->setData( Qt::DisplayRole, vlayer->attributeDisplayName( i ) + " *" );
else
item->setData( Qt::DisplayRole, vlayer->attributeDisplayName( i ) );
item->setData( Qt::UserRole, fields.at( i ).name() );
item->setData( Qt::UserRole + 1, i );
tblResults->setItem( j, 2, item );
item = new QTableWidgetItem( value );
item->setData( Qt::UserRole, value );
item->setData( Qt::DisplayRole, value2 );
tblResults->setItem( j, 3, item );
// highlight first item
// if ( i==0 )
// {
// QBrush b = tblResults->palette().brush( QPalette::AlternateBase );
// for ( int k = 0; k <= 3; k++)
// tblResults->item( j, k )->setBackground( b );
// }
tblResults->resizeRowToContents( j );
j++;
}
//tblResults->resizeColumnToContents( 1 );
highlightFeature( featItem );
}
void QgsIdentifyResultsDialog::mapLayerActionDestroyed()
{
QTreeWidgetItemIterator it( lstResults );
while ( *it )
{
if ( ( *it )->data( 0, Qt::UserRole ) == "map_layer_action" &&
( *it )->data( 0, Qt::UserRole + 1 ).value< QObject *>() == sender() )
delete *it;
else
++it;
}
}
QgsIdentifyPlotCurve::QgsIdentifyPlotCurve( const QMap<QString, QString> &attributes,
QwtPlot *plot, const QString &title, QColor color )
{
mPlotCurve = new QwtPlotCurve( title );
if ( color == QColor() )
{
color = QgsLimitedRandomColorRamp::randomColors( 1 ).at( 0 );
}
mPlotCurve->setSymbol( new QwtSymbol( QwtSymbol::Ellipse, QBrush( Qt::white ),
QPen( color, 2 ), QSize( 9, 9 ) ) );
mPlotCurve->setPen( QPen( color, 2 ) ); // needed for legend
QVector<QPointF> myData;
int i = 1;
for ( QMap<QString, QString>::const_iterator it = attributes.begin();
it != attributes.end(); ++it )
{
myData << QPointF( double( i++ ), it.value().toDouble() );
}
mPlotCurve->setSamples( myData );
mPlotCurve->attach( plot );
plot->setAxisMaxMinor( QwtPlot::xBottom, 0 );
//mPlot->setAxisScale( QwtPlot::xBottom, 1, mPlotCurve->dataSize());
//mPlot->setAxisScale( QwtPlot::yLeft, ymin, ymax );
plot->replot();
plot->setVisible( true );
}
QgsIdentifyPlotCurve::~QgsIdentifyPlotCurve()
{
if ( mPlotCurve )
{
mPlotCurve->detach();
delete mPlotCurve;
}
}
QString QgsIdentifyResultsDialog::representValue( QgsVectorLayer *vlayer, const QgsEditorWidgetSetup &setup, const QString &fieldName, const QVariant &value )
{
QVariant cache;
QMap<QString, QVariant> &layerCaches = mWidgetCaches[vlayer->id()];
QgsEditorWidgetFactory *factory = QgsGui::editorWidgetRegistry()->factory( setup.type() );
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
int idx = vlayer->fields().lookupField( fieldName );
if ( !factory )
return value.toString();
if ( layerCaches.contains( fieldName ) )
{
cache = layerCaches[ fieldName ];
}
else
{
cache = fieldFormatter->createCache( vlayer, idx, setup.config() );
layerCaches.insert( fieldName, cache );
}
return fieldFormatter->representValue( vlayer, idx, setup.config(), cache, value );
}
void QgsIdentifyResultsDialog::addFeature( QgsRasterLayer *layer,
const QString &label,
const QMap<QString, QString> &attributes,
const QMap<QString, QString> &derivedAttributes,
const QgsFields &fields,
const QgsFeature &feature,
const QMap<QString, QVariant> &params )
{
QgsDebugMsg( QString( "feature.isValid() = %1" ).arg( feature.isValid() ) );
QTreeWidgetItem *layItem = layerItem( layer );
QgsRaster::IdentifyFormat currentFormat = QgsRasterDataProvider::identifyFormatFromName( layer->customProperty( QStringLiteral( "identify/format" ) ).toString() );
if ( !layItem )
{
layItem = new QTreeWidgetItem( QStringList() << QString::number( lstResults->topLevelItemCount() ) << layer->name() );
layItem->setData( 0, Qt::UserRole, QVariant::fromValue( qobject_cast<QObject *>( layer ) ) );
lstResults->addTopLevelItem( layItem );
QComboBox *formatCombo = new QComboBox();
// Add all supported formats, best first. HTML is considered the best because
// it usually holds most information.
int capabilities = layer->dataProvider()->capabilities();
QList<QgsRaster::IdentifyFormat> formats;
formats << QgsRaster::IdentifyFormatHtml
<< QgsRaster::IdentifyFormatFeature
<< QgsRaster::IdentifyFormatText
<< QgsRaster::IdentifyFormatValue;
Q_FOREACH ( QgsRaster::IdentifyFormat f, formats )
{
if ( !( QgsRasterDataProvider::identifyFormatToCapability( f ) & capabilities ) )
continue;
formatCombo->addItem( QgsRasterDataProvider::identifyFormatLabel( f ), f );
formatCombo->setItemData( formatCombo->count() - 1, qVariantFromValue( qobject_cast<QObject *>( layer ) ), Qt::UserRole + 1 );
if ( currentFormat == f )
formatCombo->setCurrentIndex( formatCombo->count() - 1 );
}
if ( formatCombo->count() > 1 )
{
// Add format combo box item
// Space added before format to keep it first in ordered list: TODO better (user data)
QTreeWidgetItem *formatItem = new QTreeWidgetItem( QStringList() << ' ' + tr( "Format" ) );
layItem->addChild( formatItem );
lstResults->setItemWidget( formatItem, 1, formatCombo );
connect( formatCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
this, static_cast<void ( QgsIdentifyResultsDialog::* )( int )>( &QgsIdentifyResultsDialog::formatChanged ) );
}
else
{
delete formatCombo;
}
connect( layer, &QObject::destroyed, this, &QgsIdentifyResultsDialog::layerDestroyed );
connect( layer, &QgsMapLayer::crsChanged, this, &QgsIdentifyResultsDialog::layerDestroyed );
}
// Set/reset getFeatureInfoUrl (currently only available for Feature, so it may change if format changes)
layItem->setData( 0, GetFeatureInfoUrlRole, params.value( QStringLiteral( "getFeatureInfoUrl" ) ) );
QgsIdentifyResultsFeatureItem *featItem = new QgsIdentifyResultsFeatureItem( fields, feature, layer->crs(), QStringList() << label << QLatin1String( "" ) );
layItem->addChild( featItem );
// add feature attributes
if ( feature.isValid() )
{
QgsDebugMsg( QString( "fields size = %1 attributes size = %2" ).arg( fields.size() ).arg( feature.attributes().size() ) );
QgsAttributes attrs = feature.attributes();
for ( int i = 0; i < attrs.count(); ++i )
{
if ( i >= fields.count() )
continue;
QTreeWidgetItem *attrItem = new QTreeWidgetItem( QStringList() << QString::number( i ) << attrs.at( i ).toString() );
attrItem->setData( 0, Qt::DisplayRole, fields.at( i ).name() );
QVariant value = attrs.at( i );
attrItem->setData( 1, Qt::DisplayRole, value );
featItem->addChild( attrItem );
}
}
if ( currentFormat == QgsRaster::IdentifyFormatHtml || currentFormat == QgsRaster::IdentifyFormatText )
{
QgsIdentifyResultsWebViewItem *attrItem = new QgsIdentifyResultsWebViewItem( lstResults );
featItem->addChild( attrItem ); // before setHtml()!
if ( !attributes.isEmpty() )
{
attrItem->setContent( attributes.begin().value().toUtf8(), currentFormat == QgsRaster::IdentifyFormatHtml ? "text/html" : "text/plain; charset=utf-8" );
}
else
{
attrItem->setContent( tr( "No attributes." ).toUtf8(), QStringLiteral( "text/plain; charset=utf-8" ) );
}
}
else
{
for ( QMap<QString, QString>::const_iterator it = attributes.begin(); it != attributes.end(); ++it )
{
featItem->addChild( new QTreeWidgetItem( QStringList() << it.key() << it.value() ) );
}
}
if ( derivedAttributes.size() >= 0 )
{
QgsTreeWidgetItem *derivedItem = new QgsTreeWidgetItem( QStringList() << tr( "(Derived)" ) );
derivedItem->setData( 0, Qt::UserRole, "derived" );
derivedItem->setAlwaysOnTopPriority( 0 );
featItem->addChild( derivedItem );
for ( QMap< QString, QString>::const_iterator it = derivedAttributes.begin(); it != derivedAttributes.end(); ++it )
{
derivedItem->addChild( new QTreeWidgetItem( QStringList() << it.key() << it.value() ) );
}
}
// table
int i = 0;
int j = tblResults->rowCount();
tblResults->setRowCount( j + attributes.count() );
for ( QMap<QString, QString>::const_iterator it = attributes.begin(); it != attributes.end(); ++it )
{
QgsDebugMsg( QString( "adding item #%1 / %2 / %3 / %4" ).arg( j ).arg( layer->name(), it.key(), it.value() ) );
QTableWidgetItem *item = new QTableWidgetItem( layer->name() );
item->setData( Qt::UserRole, QVariant::fromValue( qobject_cast<QObject *>( layer ) ) );
item->setData( Qt::UserRole + 1, layer->id() );
tblResults->setItem( j, 0, item );
tblResults->setItem( j, 1, new QTableWidgetItem( QString::number( i + 1 ) ) );
tblResults->setItem( j, 2, new QTableWidgetItem( it.key() ) );
tblResults->setItem( j, 3, new QTableWidgetItem( it.value() ) );
tblResults->resizeRowToContents( j );
j++;
i++;
}
//tblResults->resizeColumnToContents( 1 );
// graph
if ( !attributes.isEmpty() )
{
mPlotCurves.append( new QgsIdentifyPlotCurve( attributes, mPlot, layer->name() ) );
}
}
void QgsIdentifyResultsDialog::editingToggled()
{
QTreeWidgetItem *layItem = layerItem( sender() );
QgsVectorLayer *vlayer = vectorLayer( layItem );
if ( !layItem || !vlayer )
return;
// iterate features
int i;
for ( i = 0; i < layItem->childCount(); i++ )
{
QTreeWidgetItem *featItem = layItem->child( i );
int j;
for ( j = 0; j < featItem->childCount() && featItem->child( j )->data( 0, Qt::UserRole ).toString() != QLatin1String( "actions" ); j++ )
QgsDebugMsg( QString( "%1: skipped %2" ).arg( featItem->child( j )->data( 0, Qt::UserRole ).toString() ) );
if ( j == featItem->childCount() || featItem->child( j )->childCount() < 1 )
continue;
QTreeWidgetItem *actions = featItem->child( j );
for ( j = 0; i < actions->childCount() && actions->child( j )->data( 0, Qt::UserRole ).toString() != QLatin1String( "edit" ); j++ )
;
if ( j == actions->childCount() )
continue;
QTreeWidgetItem *editItem = actions->child( j );
mOpenFormAction->setToolTip( vlayer->isEditable() ? tr( "Edit feature form" ) : tr( "View feature form" ) );
editItem->setText( 1, mOpenFormAction->toolTip() );
}
}
// Call to show the dialog box.
void QgsIdentifyResultsDialog::show()
{
bool showFeatureForm = false;
if ( lstResults->topLevelItemCount() > 0 )
{
QTreeWidgetItem *layItem = lstResults->topLevelItem( 0 );
QTreeWidgetItem *featItem = layItem->child( 0 );
if ( lstResults->topLevelItemCount() == 1 && layItem->childCount() == 1 )
{
lstResults->setCurrentItem( featItem );
if ( QgsSettings().value( QStringLiteral( "/Map/identifyAutoFeatureForm" ), false ).toBool() )
{
QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( layItem->data( 0, Qt::UserRole ).value<QObject *>() );
if ( layer )
{
// if this is the only feature and it's on a vector layer
// show the form dialog instead of the results window
showFeatureForm = true;
}
}
}
// expand first layer and feature
featItem->setExpanded( true );
layItem->setExpanded( true );
}
// expand all if enabled
if ( mExpandNewAction->isChecked() )
{
lstResults->expandAll();
}
QDialog::show();
// when the feature form is opened don't show and raise the identify result.
// If it's not docked, the results would open after or possibly on top of the
// feature form and stay open (on top the canvas) after the feature form is
// closed.
if ( showFeatureForm )
{
featureForm();
}
else
{
mDock->show();
mDock->raise();
}
}
void QgsIdentifyResultsDialog::itemClicked( QTreeWidgetItem *item, int column )
{
Q_UNUSED( column );
if ( item->data( 0, Qt::UserRole ).toString() == QLatin1String( "edit" ) )
{
lstResults->setCurrentItem( item );
featureForm();
}
else if ( item->data( 0, Qt::UserRole ).toString() == QLatin1String( "action" ) )
{
doAction( item, item->data( 0, Qt::UserRole + 1 ).toString() );
}
else if ( item->data( 0, Qt::UserRole ).toString() == QLatin1String( "map_layer_action" ) )
{
QgsMapLayerAction *action = item->data( 0, Qt::UserRole + 1 ).value<QgsMapLayerAction *>();
doMapLayerAction( item, action );
}
}
// Popup (create if necessary) a context menu that contains a list of
// actions that can be applied to the data in the identify results
// dialog box.
void QgsIdentifyResultsDialog::contextMenuEvent( QContextMenuEvent *event )
{
// only handle context menu event if showing tree widget
if ( stackedWidget->currentIndex() != 0 )
return;
QTreeWidgetItem *item = lstResults->itemAt( lstResults->viewport()->mapFrom( this, event->pos() ) );
// if the user clicked below the end of the attribute list, just return
if ( !item )
return;
QgsMapLayer *layer = vectorLayer( item );
QgsVectorLayer *vlayer = vectorLayer( item );
QgsRasterLayer *rlayer = rasterLayer( item );
if ( !vlayer && !rlayer )
{
QgsDebugMsg( "Item does not belong to a layer." );
return;
}
if ( mActionPopup )
delete mActionPopup;
mActionPopup = new QMenu();
int idx = -1;
// QTreeWidgetItem *featItem = featureItem( item );
QgsIdentifyResultsFeatureItem *featItem = dynamic_cast<QgsIdentifyResultsFeatureItem *>( featureItem( item ) );
if ( featItem )
{
if ( vlayer )
{
mActionPopup->addAction(
QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ),
vlayer->isEditable() ? tr( "Edit feature form" ) : tr( "View feature form" ),
this, SLOT( featureForm() ) );
}
if ( featItem->feature().isValid() )
{
mActionPopup->addAction( tr( "Zoom to feature" ), this, SLOT( zoomToFeature() ) );
mActionPopup->addAction( tr( "Copy feature" ), this, SLOT( copyFeature() ) );
mActionPopup->addAction( tr( "Toggle feature selection" ), this, SLOT( toggleFeatureSelection() ) );
}
mActionPopup->addAction( tr( "Copy attribute value" ), this, SLOT( copyAttributeValue() ) );
mActionPopup->addAction( tr( "Copy feature attributes" ), this, SLOT( copyFeatureAttributes() ) );
if ( item->parent() == featItem && item->childCount() == 0 )
{
idx = item->data( 0, Qt::UserRole + 1 ).toInt();
}
}
if ( rlayer )
{
QTreeWidgetItem *layItem = layerItem( item );
if ( layItem && !layItem->data( 0, GetFeatureInfoUrlRole ).toString().isEmpty() )
{
mActionPopup->addAction( tr( "Copy GetFeatureInfo request URL" ), this, SLOT( copyGetFeatureInfoUrl() ) );
}
}
if ( !mActionPopup->children().isEmpty() )
{
mActionPopup->addSeparator();
}
mActionPopup->addAction( tr( "Clear results" ), this, SLOT( clear() ) );
mActionPopup->addAction( tr( "Clear highlights" ), this, SLOT( clearHighlights() ) );
mActionPopup->addAction( tr( "Highlight all" ), this, SLOT( highlightAll() ) );
mActionPopup->addAction( tr( "Highlight layer" ), this, SLOT( highlightLayer() ) );
if ( layer && QgsProject::instance()->layerIsEmbedded( layer->id() ).isEmpty() )
{
mActionPopup->addAction( tr( "Activate layer" ), this, SLOT( activateLayer() ) );
mActionPopup->addAction( tr( "Layer properties..." ), this, SLOT( layerProperties() ) );
}
mActionPopup->addSeparator();
mActionPopup->addAction( tr( "Expand all" ), this, SLOT( expandAll() ) );
mActionPopup->addAction( tr( "Collapse all" ), this, SLOT( collapseAll() ) );
mActionPopup->addSeparator();
if ( featItem && vlayer )
{
QList<QgsAction> actions = vlayer->actions()->actions( QStringLiteral( "Field" ) );
if ( !actions.isEmpty() )
{
mActionPopup->addSeparator();
int featIdx = featItem->data( 0, Qt::UserRole + 1 ).toInt();
Q_FOREACH ( const QgsAction &action, actions )
{
if ( !action.runable() )
continue;
QgsFeatureAction *a = new QgsFeatureAction( action.name(), mFeatures[ featIdx ], vlayer, action.id(), idx, this );
mActionPopup->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mAction.svg" ) ), action.name(), a, SLOT( execute() ) );
}
}
}
if ( featItem && vlayer )
{
//get valid QgsMapLayerActions for this layer
QList< QgsMapLayerAction * > registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( vlayer );
if ( !registeredActions.isEmpty() )
{
//add a separator between user defined and standard actions
mActionPopup->addSeparator();
int featIdx = featItem->data( 0, Qt::UserRole + 1 ).toInt();
QList<QgsMapLayerAction *>::iterator actionIt;
for ( actionIt = registeredActions.begin(); actionIt != registeredActions.end(); ++actionIt )
{
QgsIdentifyResultsDialogMapLayerAction *a = new QgsIdentifyResultsDialogMapLayerAction( ( *actionIt )->text(), this, ( *actionIt ), vlayer, &( mFeatures[ featIdx ] ) );
mActionPopup->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mAction.svg" ) ), ( *actionIt )->text(), a, SLOT( execute() ) );
}
}
}
mActionPopup->popup( event->globalPos() );
}
// Save the current window location (store in ~/.qt/qgisrc)
void QgsIdentifyResultsDialog::saveWindowLocation()
{
QgsSettings settings;
// first column width
settings.setValue( QStringLiteral( "Windows/Identify/columnWidth" ), lstResults->columnWidth( 0 ) );
settings.setValue( QStringLiteral( "Windows/Identify/columnWidthTable" ), tblResults->columnWidth( 0 ) );
}
void QgsIdentifyResultsDialog::setColumnText( int column, const QString &label )
{
QTreeWidgetItem *header = lstResults->headerItem();
header->setText( column, label );
}
void QgsIdentifyResultsDialog::expandColumnsToFit()
{
lstResults->resizeColumnToContents( 0 );
lstResults->resizeColumnToContents( 1 );
}
void QgsIdentifyResultsDialog::clear()
{
for ( int i = 0; i < lstResults->topLevelItemCount(); i++ )
{
disconnectLayer( lstResults->topLevelItem( i )->data( 0, Qt::UserRole ).value<QObject *>() );
}
lstResults->clear();
lstResults->sortByColumn( -1 );
clearHighlights();
tblResults->clearContents();
tblResults->setRowCount( 0 );
mPlot->setVisible( false );
Q_FOREACH ( QgsIdentifyPlotCurve *curve, mPlotCurves )
delete curve;
mPlotCurves.clear();
mExpressionContextScope = QgsExpressionContextScope();
// keep it visible but disabled, it can switch from disabled/enabled
// after raster format change
mActionPrint->setDisabled( true );
}
void QgsIdentifyResultsDialog::updateViewModes()
{
// get # of identified vector and raster layers - there must be a better way involving caching
int vectorCount = 0, rasterCount = 0;
for ( int i = 0; i < lstResults->topLevelItemCount(); i++ )
{
QTreeWidgetItem *item = lstResults->topLevelItem( i );
if ( vectorLayer( item ) ) vectorCount++;
else if ( rasterLayer( item ) ) rasterCount++;
}
lblViewMode->setEnabled( rasterCount > 0 );
cmbViewMode->setEnabled( rasterCount > 0 );
if ( rasterCount == 0 )
cmbViewMode->setCurrentIndex( 0 );
}
void QgsIdentifyResultsDialog::clearHighlights()
{
Q_FOREACH ( QgsHighlight *h, mHighlights )
{
delete h;
}
mHighlights.clear();
}
void QgsIdentifyResultsDialog::activate()
{
Q_FOREACH ( QgsHighlight *h, mHighlights )
{
h->show();
}
if ( lstResults->topLevelItemCount() > 0 )
{
raise();
}
}
void QgsIdentifyResultsDialog::deactivate()
{
Q_FOREACH ( QgsHighlight *h, mHighlights )
{
h->hide();
}
}
void QgsIdentifyResultsDialog::doAction( QTreeWidgetItem *item, const QString &action )
{
QTreeWidgetItem *featItem = featureItem( item );
if ( !featItem )
return;
QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( featItem->parent()->data( 0, Qt::UserRole ).value<QObject *>() );
if ( !layer )
return;
int idx = -1;
if ( item->parent() == featItem )
{
QString fieldName = item->data( 0, Qt::DisplayRole ).toString();
const QgsFields &fields = layer->fields();
for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx )
{
if ( fields.at( fldIdx ).name() == fieldName )
{
idx = fldIdx;
break;
}
}
}
int featIdx = featItem->data( 0, Qt::UserRole + 1 ).toInt();
layer->actions()->doAction( action, mFeatures[ featIdx ], idx, mExpressionContextScope );
}
void QgsIdentifyResultsDialog::doMapLayerAction( QTreeWidgetItem *item, QgsMapLayerAction *action )
{
QTreeWidgetItem *featItem = featureItem( item );
if ( !featItem )
return;
QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( featItem->parent()->data( 0, Qt::UserRole ).value<QObject *>() );
if ( !layer )
return;
if ( !action )
return;
int featIdx = featItem->data( 0, Qt::UserRole + 1 ).toInt();
action->triggerForFeature( layer, &mFeatures[ featIdx ] );
}
QTreeWidgetItem *QgsIdentifyResultsDialog::featureItem( QTreeWidgetItem *item )
{
if ( !item )
return nullptr;
QTreeWidgetItem *featItem = nullptr;
if ( item->parent() )
{
if ( item->parent()->parent() )
{
if ( item->parent()->parent()->parent() )
{
// derived or action attribute item
featItem = item->parent()->parent();
}
else
{
// attribute item
featItem = item->parent();
}
}
else
{
// feature item
featItem = item;
}
}
else
{
// top level layer item, return feature item if only one
#if 0
if ( item->childCount() > 1 )
return 0;
featItem = item->child( 0 );
#endif
int count = 0;
for ( int i = 0; i < item->childCount(); i++ )
{
QgsIdentifyResultsFeatureItem *fi = dynamic_cast<QgsIdentifyResultsFeatureItem *>( item->child( i ) );
if ( fi )
{
count++;
if ( !featItem )
featItem = fi;
}
}
if ( count != 1 )
return nullptr;
}
return featItem;
}
QTreeWidgetItem *QgsIdentifyResultsDialog::layerItem( QTreeWidgetItem *item )
{
if ( item && item->parent() )
{
item = featureItem( item )->parent();
}
return item;
}
QgsMapLayer *QgsIdentifyResultsDialog::layer( QTreeWidgetItem *item )
{
item = layerItem( item );
if ( !item )
return nullptr;
return qobject_cast<QgsMapLayer *>( item->data( 0, Qt::UserRole ).value<QObject *>() );
}
QgsVectorLayer *QgsIdentifyResultsDialog::vectorLayer( QTreeWidgetItem *item )
{
item = layerItem( item );
if ( !item )
return nullptr;
return qobject_cast<QgsVectorLayer *>( item->data( 0, Qt::UserRole ).value<QObject *>() );
}
QgsRasterLayer *QgsIdentifyResultsDialog::rasterLayer( QTreeWidgetItem *item )
{
item = layerItem( item );
if ( !item )
return nullptr;
return qobject_cast<QgsRasterLayer *>( item->data( 0, Qt::UserRole ).value<QObject *>() );
}
QTreeWidgetItem *QgsIdentifyResultsDialog::retrieveAttributes( QTreeWidgetItem *item, QgsAttributeMap &attributes, int &idx )
{
QTreeWidgetItem *featItem = featureItem( item );
if ( !featItem )
return nullptr;
idx = -1;
attributes.clear();
for ( int i = 0; i < featItem->childCount(); i++ )
{
QTreeWidgetItem *item = featItem->child( i );
if ( item->childCount() > 0 )
continue;
if ( item == lstResults->currentItem() )
idx = item->data( 0, Qt::UserRole + 1 ).toInt();
attributes.insert( item->data( 0, Qt::UserRole + 1 ).toInt(), item->data( 1, Qt::DisplayRole ) );
}
return featItem;
}
void QgsIdentifyResultsDialog::itemExpanded( QTreeWidgetItem *item )
{
Q_UNUSED( item );
// column width is now stored in settings
//expandColumnsToFit();
}
void QgsIdentifyResultsDialog::handleCurrentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
{
Q_UNUSED( previous );
mActionPrint->setEnabled( false );
QgsIdentifyResultsFeatureItem *featItem = dynamic_cast<QgsIdentifyResultsFeatureItem *>( featureItem( current ) );
mActionCopy->setEnabled( featItem && featItem->feature().isValid() );
mOpenFormAction->setEnabled( featItem && featItem->feature().isValid() );
QgsVectorLayer *vlayer = vectorLayer( current );
if ( vlayer )
{
mOpenFormAction->setToolTip( vlayer->isEditable() ? tr( "Edit feature form" ) : tr( "View feature form" ) );
}
if ( !current )
{
emit selectedFeatureChanged( nullptr, 0 );
return;
}
// An item may be printed if a child is QgsIdentifyResultsWebViewItem
for ( int i = 0; i < current->childCount(); i++ )
{
QgsIdentifyResultsWebViewItem *wv = dynamic_cast<QgsIdentifyResultsWebViewItem *>( current->child( i ) );
if ( wv )
{
mActionPrint->setEnabled( true );
break;
}
}
QTreeWidgetItem *layItem = layerItem( current );
if ( current == layItem )
{
highlightLayer( layItem );
}
else
{
clearHighlights();
highlightFeature( current );
}
}
void QgsIdentifyResultsDialog::layerDestroyed()
{
QObject *senderObject = sender();
for ( int i = 0; i < lstResults->topLevelItemCount(); i++ )
{
QTreeWidgetItem *layItem = lstResults->topLevelItem( i );
if ( layItem->data( 0, Qt::UserRole ).value<QObject *>() == senderObject )
{
for ( int j = 0; j < layItem->childCount(); j++ )
{
delete mHighlights.take( layItem->child( j ) );
}
}
}
disconnectLayer( senderObject );
delete layerItem( senderObject );
// remove items, starting from last
for ( int i = tblResults->rowCount() - 1; i >= 0; i-- )
{
QgsDebugMsg( QString( "item %1 / %2" ).arg( i ).arg( tblResults->rowCount() ) );
QTableWidgetItem *layItem = tblResults->item( i, 0 );
if ( layItem && layItem->data( Qt::UserRole ).value<QObject *>() == senderObject )
{
QgsDebugMsg( QString( "removing row %1" ).arg( i ) );
tblResults->removeRow( i );
}
}
}
void QgsIdentifyResultsDialog::disconnectLayer( QObject *layer )
{
if ( !layer )
return;
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
if ( vlayer )
{
disconnect( vlayer, &QgsVectorLayer::featureDeleted, this, &QgsIdentifyResultsDialog::featureDeleted );
disconnect( vlayer, &QgsVectorLayer::attributeValueChanged,
this, &QgsIdentifyResultsDialog::attributeValueChanged );
disconnect( vlayer, &QgsVectorLayer::editingStarted, this, &QgsIdentifyResultsDialog::editingToggled );
disconnect( vlayer, &QgsVectorLayer::editingStopped, this, &QgsIdentifyResultsDialog::editingToggled );
}
disconnect( layer, &QObject::destroyed, this, &QgsIdentifyResultsDialog::layerDestroyed );
}
void QgsIdentifyResultsDialog::featureDeleted( QgsFeatureId fid )
{
QTreeWidgetItem *layItem = layerItem( sender() );
if ( !layItem )
return;
for ( int i = 0; i < layItem->childCount(); i++ )
{
QTreeWidgetItem *featItem = layItem->child( i );
if ( featItem && STRING_TO_FID( featItem->data( 0, Qt::UserRole ) ) == fid )
{
delete mHighlights.take( featItem );
delete featItem;
break;
}
}
if ( layItem->childCount() == 0 )
{
delete layItem;
}
for ( int i = tblResults->rowCount() - 1; i >= 0; i-- )
{
QgsDebugMsg( QString( "item %1 / %2" ).arg( i ).arg( tblResults->rowCount() ) );
QTableWidgetItem *layItem = tblResults->item( i, 0 );
QTableWidgetItem *featItem = tblResults->item( i, 1 );
if ( layItem && layItem->data( Qt::UserRole ).value<QObject *>() == sender() &&
featItem && STRING_TO_FID( featItem->data( Qt::UserRole ) ) == fid )
{
QgsDebugMsg( QString( "removing row %1" ).arg( i ) );
tblResults->removeRow( i );
}
}
}
void QgsIdentifyResultsDialog::attributeValueChanged( QgsFeatureId fid, int idx, const QVariant &val )
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( sender() );
QTreeWidgetItem *layItem = layerItem( sender() );
if ( !layItem )
return;
if ( idx >= vlayer->fields().size() )
return;
QgsField fld = vlayer->fields().at( idx );
for ( int i = 0; i < layItem->childCount(); i++ )
{
QTreeWidgetItem *featItem = layItem->child( i );
if ( featItem && STRING_TO_FID( featItem->data( 0, Qt::UserRole ) ) == fid )
{
QString value( fld.displayString( val ) );
if ( fld.name() == vlayer->displayField() )
featItem->setData( 1, Qt::DisplayRole, value );
for ( int j = 0; j < featItem->childCount(); j++ )
{
QTreeWidgetItem *item = featItem->child( j );
if ( item->childCount() > 0 )
continue;
if ( item->data( 0, Qt::UserRole + 1 ).toInt() == idx )
{
const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( vlayer, fld.name() );
value = representValue( vlayer, setup, fld.name(), val );
QgsTreeWidgetItem *treeItem = static_cast< QgsTreeWidgetItem * >( item );
treeItem->setSortData( 1, value );
treeItem->setToolTip( 1, value );
bool foundLinks = false;
QString links = QgsStringUtils::insertLinks( value, &foundLinks );
if ( foundLinks )
{
QLabel *valueLabel = new QLabel( links );
valueLabel->setOpenExternalLinks( true );
treeItem->setData( 1, Qt::DisplayRole, QString() );
treeItem->treeWidget()->setItemWidget( item, 1, valueLabel );
}
else
{
treeItem->setData( 1, Qt::DisplayRole, value );
treeItem->treeWidget()->setItemWidget( item, 1, nullptr );
}
return;
}
}
}
}
}
void QgsIdentifyResultsDialog::highlightFeature( QTreeWidgetItem *item )
{
QgsMapLayer *layer = nullptr;
QgsVectorLayer *vlayer = vectorLayer( item );
QgsRasterLayer *rlayer = rasterLayer( item );
layer = vlayer ? static_cast<QgsMapLayer *>( vlayer ) : static_cast<QgsMapLayer *>( rlayer );
if ( !layer ) return;
QgsIdentifyResultsFeatureItem *featItem = dynamic_cast<QgsIdentifyResultsFeatureItem *>( featureItem( item ) );
if ( !featItem )
return;
if ( mHighlights.contains( featItem ) )
return;
if ( !featItem->feature().geometry() || featItem->feature().geometry().wkbType() == QgsWkbTypes::Unknown )
return;
QgsHighlight *highlight = nullptr;
if ( vlayer )
{
highlight = new QgsHighlight( mCanvas, featItem->feature(), vlayer );
}
else
{
highlight = new QgsHighlight( mCanvas, featItem->feature().geometry(), layer );
highlight->setWidth( 2 );
}
QgsSettings settings;
QColor color = QColor( settings.value( QStringLiteral( "Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
int alpha = settings.value( QStringLiteral( "Map/highlight/colorAlpha" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
double buffer = settings.value( QStringLiteral( "Map/highlight/buffer" ), Qgis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
double minWidth = settings.value( QStringLiteral( "Map/highlight/minWidth" ), Qgis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
highlight->setColor( color ); // sets also fill with default alpha
color.setAlpha( alpha );
highlight->setFillColor( color ); // sets fill with alpha
highlight->setBuffer( buffer );
highlight->setMinWidth( minWidth );
highlight->show();
mHighlights.insert( featItem, highlight );
}
void QgsIdentifyResultsDialog::zoomToFeature()
{
QTreeWidgetItem *item = lstResults->currentItem();
QgsVectorLayer *vlayer = vectorLayer( item );
QgsRasterLayer *rlayer = rasterLayer( item );
if ( !vlayer && !rlayer )
return;
QgsMapLayer *layer = nullptr;
if ( vlayer )
layer = vlayer;
else
layer = rlayer;
QgsIdentifyResultsFeatureItem *featItem = dynamic_cast<QgsIdentifyResultsFeatureItem *>( featureItem( item ) );
if ( !featItem )
return;
QgsFeature feat = featItem->feature();
if ( !feat.hasGeometry() )
return;
// TODO: verify CRS for raster WMS features
QgsRectangle rect = mCanvas->mapSettings().layerExtentToOutputExtent( layer, feat.geometry().boundingBox() );
if ( rect.isEmpty() )
{
QgsPointXY c = rect.center();
rect = mCanvas->extent();
rect.scale( 0.5, &c );
}
mCanvas->setExtent( rect );
mCanvas->refresh();
}
void QgsIdentifyResultsDialog::featureForm()
{
QTreeWidgetItem *item = lstResults->currentItem();
QgsVectorLayer *vlayer = vectorLayer( item );
if ( !vlayer )
return;
QTreeWidgetItem *featItem = featureItem( item );
if ( !featItem )
return;
QgsFeatureId fid = STRING_TO_FID( featItem->data( 0, Qt::UserRole ) );
QgsFeature f;
if ( !vlayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( f ) )
return;
QString actionId = featItem->data( 0, Qt::UserRole + 1 ).toString();
QgsFeatureAction action( tr( "Attributes changed" ), f, vlayer, actionId, -1, this );
if ( vlayer->isEditable() )
{
action.editFeature( false );
}
else
{
action.viewFeatureForm( mHighlights.take( featItem ) );
}
}
void QgsIdentifyResultsDialog::highlightAll()
{
for ( int i = 0; i < lstResults->topLevelItemCount(); i++ )
{
QTreeWidgetItem *layItem = lstResults->topLevelItem( i );
for ( int j = 0; j < layItem->childCount(); j++ )
{
highlightFeature( layItem->child( j ) );
}
}
}
void QgsIdentifyResultsDialog::highlightLayer()
{
highlightLayer( lstResults->currentItem() );
}
void QgsIdentifyResultsDialog::highlightLayer( QTreeWidgetItem *item )
{
QTreeWidgetItem *layItem = layerItem( item );
if ( !layItem )
return;
clearHighlights();
for ( int i = 0; i < layItem->childCount(); i++ )
{
highlightFeature( layItem->child( i ) );
}
}
void QgsIdentifyResultsDialog::layerProperties()
{
layerProperties( lstResults->currentItem() );
}
void QgsIdentifyResultsDialog::activateLayer()
{
connect( this, static_cast<void ( QgsIdentifyResultsDialog::* )( QgsMapLayer * )>( &QgsIdentifyResultsDialog::activateLayer )
, QgisApp::instance(), &QgisApp::setActiveLayer );
emit activateLayer( layer( lstResults->currentItem() ) );
disconnect( this, static_cast<void ( QgsIdentifyResultsDialog::* )( QgsMapLayer * )>( &QgsIdentifyResultsDialog::activateLayer ),
QgisApp::instance(), &QgisApp::setActiveLayer );
}
void QgsIdentifyResultsDialog::layerProperties( QTreeWidgetItem *item )
{
QgsVectorLayer *vlayer = vectorLayer( item );
if ( !vlayer )
return;
QgisApp::instance()->showLayerProperties( vlayer );
}
void QgsIdentifyResultsDialog::expandAll()
{
lstResults->expandAll();
}
void QgsIdentifyResultsDialog::collapseAll()
{
lstResults->collapseAll();
}
void QgsIdentifyResultsDialog::copyAttributeValue()
{
QClipboard *clipboard = QApplication::clipboard();
QString text = lstResults->currentItem()->data( 1, Qt::DisplayRole ).toString();
QgsDebugMsg( QString( "set clipboard: %1" ).arg( text ) );
clipboard->setText( text );
}
void QgsIdentifyResultsDialog::copyFeatureAttributes()
{
QClipboard *clipboard = QApplication::clipboard();
QString text;
QgsVectorLayer *vlayer = vectorLayer( lstResults->currentItem() );
QgsRasterLayer *rlayer = rasterLayer( lstResults->currentItem() );
if ( !vlayer && !rlayer )
{
return;
}
if ( vlayer )
{
int idx;
QgsAttributeMap attributes;
retrieveAttributes( lstResults->currentItem(), attributes, idx );
const QgsFields &fields = vlayer->fields();
for ( QgsAttributeMap::const_iterator it = attributes.constBegin(); it != attributes.constEnd(); ++it )
{
int attrIdx = it.key();
if ( attrIdx < 0 || attrIdx >= fields.count() )
continue;
text += QStringLiteral( "%1: %2\n" ).arg( fields.at( attrIdx ).name(), it.value().toString() );
}
}
else if ( rlayer )
{
QTreeWidgetItem *featItem = featureItem( lstResults->currentItem() );
if ( !featItem )
return;
for ( int i = 0; i < featItem->childCount(); i++ )
{
QTreeWidgetItem *item = featItem->child( i );
if ( item->childCount() > 0 )
continue;
text += QStringLiteral( "%1: %2\n" ).arg( item->data( 0, Qt::DisplayRole ).toString(), item->data( 1, Qt::DisplayRole ).toString() );
}
}
QgsDebugMsg( QString( "set clipboard: %1" ).arg( text ) );
clipboard->setText( text );
}
void QgsIdentifyResultsDialog::copyGetFeatureInfoUrl()
{
QClipboard *clipboard = QApplication::clipboard();
QTreeWidgetItem *item = lstResults->currentItem();
QTreeWidgetItem *layItem = layerItem( item );
if ( !layItem )
return;
clipboard->setText( layItem->data( 0, GetFeatureInfoUrlRole ).toString() );
}
void QgsIdentifyResultsDialog::printCurrentItem()
{
QTreeWidgetItem *item = lstResults->currentItem();
if ( !item )
return;
// There should only be one HTML item / result
QgsIdentifyResultsWebViewItem *wv = nullptr;
for ( int i = 0; i < item->childCount() && !wv; i++ )
{
wv = dynamic_cast<QgsIdentifyResultsWebViewItem *>( item->child( i ) );
}
if ( !wv )
{
QMessageBox::warning( this, tr( "Cannot print" ), tr( "Cannot print this item" ) );
return;
}
wv->webView()->print();
}
void QgsIdentifyResultsDialog::cmbIdentifyMode_currentIndexChanged( int index )
{
QgsSettings settings;
settings.setValue( QStringLiteral( "Map/identifyMode" ), cmbIdentifyMode->itemData( index ).toInt() );
}
void QgsIdentifyResultsDialog::cmbViewMode_currentIndexChanged( int index )
{
stackedWidget->setCurrentIndex( index );
}
void QgsIdentifyResultsDialog::cbxAutoFeatureForm_toggled( bool checked )
{
QgsSettings settings;
settings.setValue( QStringLiteral( "Map/identifyAutoFeatureForm" ), checked );
}
void QgsIdentifyResultsDialog::mExpandNewAction_triggered( bool checked )
{
QgsSettings settings;
settings.setValue( QStringLiteral( "Map/identifyExpand" ), checked );
}
void QgsIdentifyResultsDialog::mActionCopy_triggered( bool checked )
{
Q_UNUSED( checked );
copyFeature();
}
void QgsIdentifyResultsDialog::copyFeature()
{
QgsIdentifyResultsFeatureItem *item = dynamic_cast<QgsIdentifyResultsFeatureItem *>( featureItem( lstResults->selectedItems().value( 0 ) ) );
if ( !item ) // should not happen
{
QgsDebugMsg( "Selected item is not feature" );
return;
}
QgsFeatureStore featureStore( item->fields(), item->crs() );
QgsFeature f( item->feature() );
featureStore.addFeature( f );
emit copyToClipboard( featureStore );
}
void QgsIdentifyResultsDialog::toggleFeatureSelection()
{
QgsIdentifyResultsFeatureItem *item = dynamic_cast<QgsIdentifyResultsFeatureItem *>( featureItem( lstResults->selectedItems().value( 0 ) ) );
if ( !item ) // should not happen
{
QgsDebugMsg( "Selected item is not feature" );
return;
}
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer( item ) );
if ( !vl )
return;
if ( vl->selectedFeatureIds().contains( item->feature().id() ) )
vl->deselect( item->feature().id() );
else
vl->select( item->feature().id() );
}
void QgsIdentifyResultsDialog::formatChanged( int index )
{
QComboBox *combo = qobject_cast<QComboBox *>( sender() );
if ( !combo )
{
QgsDebugMsg( "sender is not QComboBox" );
return;
}
QgsRaster::IdentifyFormat format = ( QgsRaster::IdentifyFormat ) combo->itemData( index, Qt::UserRole ).toInt();
QgsDebugMsg( QString( "format = %1" ).arg( format ) );
QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( combo->itemData( index, Qt::UserRole + 1 ).value<QObject *>() );
if ( !layer )
{
QgsDebugMsg( "cannot get raster layer" );
return;
}
// Store selected identify format in layer
layer->setCustomProperty( QStringLiteral( "identify/format" ), QgsRasterDataProvider::identifyFormatName( format ) );
// remove all children of that layer from results, except the first (format)
QTreeWidgetItem *layItem = layerItem( layer );
if ( !layItem )
{
QgsDebugMsg( "cannot get layer item" );
return;
}
for ( int i = layItem->childCount() - 1; i > 0; i-- )
{
QTreeWidgetItem *child = layItem->child( i );
QgsDebugMsg( QString( "remove %1:0x%2" ).arg( i ).arg( ( qint64 ) child, 0, 16 ) );
layItem->removeChild( child );
QgsDebugMsg( QString( "removed %1:0x%2" ).arg( i ).arg( ( qint64 ) child, 0, 16 ) );
}
// let know QgsMapToolIdentify that format changed
emit formatChanged( layer );
// Expand
layItem->setExpanded( true );
for ( int i = 1; i < layItem->childCount(); i++ )
{
QTreeWidgetItem *subItem = layItem->child( i );
subItem->setExpanded( true );
for ( int j = 0; j < subItem->childCount(); j++ )
{
subItem->child( j )->setExpanded( true );
}
}
}
/*
* QgsIdentifyResultsDialogMapLayerAction
*/
void QgsIdentifyResultsDialogMapLayerAction::execute()
{
mAction->triggerForFeature( mLayer, mFeature );
}
void QgsIdentifyResultsDialog::showHelp()
{
QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#identify" ) );
}
void QgsIdentifyResultsDialog::setExpressionContextScope( const QgsExpressionContextScope &scope )
{
mExpressionContextScope = scope;
}
QgsExpressionContextScope QgsIdentifyResultsDialog::expressionContextScope() const
{
return mExpressionContextScope;
}