Skip to content

Commit

Permalink
[FEATURE] Use OGR to parse clipboard text so that GeoJSON (and others…
Browse files Browse the repository at this point in the history
…) can be

directly pasted into QGIS (fix #9727)
  • Loading branch information
nyalldawson committed Mar 1, 2016
1 parent 3f62cd4 commit 7f6446a
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 36 deletions.
8 changes: 5 additions & 3 deletions src/app/qgisapp.cpp
Expand Up @@ -6837,11 +6837,11 @@ void QgisApp::editPaste( QgsMapLayer *destinationLayer )
int nTotalFeatures = features.count();

QHash<int, int> remap;
const QgsFields &fields = clipboard()->fields();
QgsFields fields = clipboard()->fields();
QgsAttributeList pkAttrList = pasteVectorLayer->pkAttributeList();
for ( int idx = 0; idx < fields.count(); ++idx )
{
int dst = pasteVectorLayer->fieldNameIndex( fields[idx].name() );
int dst = pasteVectorLayer->fieldNameIndex( fields.at( idx ).name() );
if ( dst < 0 )
continue;

Expand Down Expand Up @@ -6984,9 +6984,11 @@ QgsVectorLayer *QgisApp::pasteAsNewMemoryVector( const QString & theLayerName )

QgsVectorLayer *QgisApp::pasteToNewMemoryVector()
{
QgsFields fields = clipboard()->fields();

// Decide geometry type from features, switch to multi type if at least one multi is found
QMap<QGis::WkbType, int> typeCounts;
QgsFeatureList features = clipboard()->copyOf();
QgsFeatureList features = clipboard()->copyOf( fields );
for ( int i = 0; i < features.size(); i++ )
{
QgsFeature &feature = features[i];
Expand Down
63 changes: 48 additions & 15 deletions src/app/qgsclipboard.cpp
Expand Up @@ -24,6 +24,7 @@
#include <QClipboard>
#include <QSettings>
#include <QMimeData>
#include <QTextCodec>

#include "qgsclipboard.h"
#include "qgsfeature.h"
Expand All @@ -32,6 +33,7 @@
#include "qgscoordinatereferencesystem.h"
#include "qgslogger.h"
#include "qgsvectorlayer.h"
#include "qgsogrutils.h"

QgsClipboard::QgsClipboard()
: QObject()
Expand Down Expand Up @@ -143,25 +145,19 @@ void QgsClipboard::setSystemClipboard()
QgsDebugMsg( QString( "replaced system clipboard with: %1." ).arg( textCopy ) );
}

QgsFeatureList QgsClipboard::copyOf( const QgsFields &fields )
QgsFeatureList QgsClipboard::stringToFeatureList( const QString& string, const QgsFields& fields ) const
{
QgsDebugMsg( "returning clipboard." );
if ( !mUseSystemClipboard )
return mFeatureClipboard;
//first try using OGR to read string
QgsFeatureList features = QgsOgrUtils::stringToFeatureList( string, fields, QTextCodec::codecForName( "System" ) );

QClipboard *cb = QApplication::clipboard();
if ( !features.isEmpty() )
return features;

#ifndef Q_OS_WIN
QString text = cb->text( QClipboard::Selection );
#else
QString text = cb->text( QClipboard::Clipboard );
#endif
// otherwise try to read in as WKT
QStringList values = string.split( '\n' );
if ( values.isEmpty() || string.isEmpty() )
return features;

QStringList values = text.split( '\n' );
if ( values.isEmpty() || text.isEmpty() )
return mFeatureClipboard;

QgsFeatureList features;
Q_FOREACH ( const QString& row, values )
{
// Assume that it's just WKT for now.
Expand All @@ -177,6 +173,38 @@ QgsFeatureList QgsClipboard::copyOf( const QgsFields &fields )
features.append( feature );
}

return features;
}

QgsFields QgsClipboard::retrieveFields() const
{
QClipboard *cb = QApplication::clipboard();

#ifndef Q_OS_WIN
QString string = cb->text( QClipboard::Selection );
#else
QString string = cb->text( QClipboard::Clipboard );
#endif

return QgsOgrUtils::stringToFields( string, QTextCodec::codecForName( "System" ) );
}

QgsFeatureList QgsClipboard::copyOf( const QgsFields &fields )
{
QgsDebugMsg( "returning clipboard." );
if ( !mUseSystemClipboard )
return mFeatureClipboard;

QClipboard *cb = QApplication::clipboard();

#ifndef Q_OS_WIN
QString text = cb->text( QClipboard::Selection );
#else
QString text = cb->text( QClipboard::Clipboard );
#endif

QgsFeatureList features = stringToFeatureList( text, fields );

if ( features.isEmpty() )
return mFeatureClipboard;

Expand Down Expand Up @@ -258,6 +286,11 @@ void QgsClipboard::setData( const QString& mimeType, const QByteArray& data )
setData( mimeType, data, nullptr );
}

void QgsClipboard::setText( const QString& text )
{
setData( "text/plain", text.toLocal8Bit(), nullptr );
}

bool QgsClipboard::hasFormat( const QString& mimeType )
{
return QApplication::clipboard()->mimeData()->hasFormat( mimeType );
Expand Down
55 changes: 37 additions & 18 deletions src/app/qgsclipboard.h
Expand Up @@ -62,84 +62,89 @@ class APP_EXPORT QgsClipboard : public QObject
//! Destructor
virtual ~QgsClipboard();

/*
/**
* Place a copy of features on the internal clipboard,
* destroying the previous contents.
*/
void replaceWithCopyOf( QgsVectorLayer *src );

/*
/**
* Place a copy of features on the internal clipboard,
* destroying the previous contents.
*/
void replaceWithCopyOf( QgsFeatureStore & featureStore );

/*
/**
* Returns a copy of features on the internal clipboard,
* the caller assumes responsibility for destroying the contents
* when it's done with it.
*/
QgsFeatureList copyOf( const QgsFields &fields = QgsFields() );

/*
/**
* Clears the internal clipboard.
*/
void clear();

/*
/**
* Inserts a copy of the feature on the internal clipboard.
*/
void insert( QgsFeature& feature );

/*
/**
* Returns true if the internal clipboard is empty, else false.
*/
bool empty();

/*
/**
* Returns a copy of features on the internal clipboard, transformed
* from the clipboard CRS to the destCRS.
* The caller assumes responsibility for destroying the contents
* when it's done with it.
*/
QgsFeatureList transformedCopyOf( const QgsCoordinateReferenceSystem& destCRS, const QgsFields &fields = QgsFields() );

/*
/**
* Get the clipboard CRS
*/
QgsCoordinateReferenceSystem crs();

/*
/**
* Stores a MimeData together with a text into the system clipboard
*/
void setData( const QString& mimeType, const QByteArray& data, const QString* text = nullptr );
/*

/**
* Stores a MimeData together with a text into the system clipboard
*/
void setData( const QString& mimeType, const QByteArray& data, const QString& text );
/*

/**
* Stores a MimeData into the system clipboard
*/
void setData( const QString& mimeType, const QByteArray& data );
/*

/**
* Stores a text into the system clipboard
*/
void setText( const QString& text );
/*

/**
* Proxy to QMimeData::hasFormat
* Tests whether the system clipboard contains data of a given MIME type
*/
bool hasFormat( const QString& mimeType );
/*

/**
* Retrieve data from the system clipboard.
* No copy is involved, since the return QByteArray is implicitly shared
*/
QByteArray data( const QString& mimeType );

/*
* source fields
/**
* Source fields
*/
const QgsFields &fields() { return mFeatureFields; }
QgsFields fields() { return !mUseSystemClipboard ? mFeatureFields : retrieveFields(); }

private slots:

Expand All @@ -150,11 +155,25 @@ class APP_EXPORT QgsClipboard : public QObject
void changed();

private:
/*

/**
* Set system clipboard from previously set features.
*/
void setSystemClipboard();

/** Attempts to convert a string to a list of features, by parsing the string as WKT and GeoJSON
* @param string string to convert
* @param fields fields for resultant features
* @returns list of features if conversion was successful
*/
QgsFeatureList stringToFeatureList( const QString& string, const QgsFields& fields ) const;

/** Attempts to parse the clipboard contents and return a QgsFields object representing the fields
* present in the clipboard.
* @note Only valid for text based clipboard contents
*/
QgsFields retrieveFields() const;

/** QGIS-internal vector feature clipboard.
Stored as values not pointers as each clipboard operation
involves a deep copy anyway.
Expand Down
66 changes: 66 additions & 0 deletions tests/src/app/testqgisappclipboard.cpp
Expand Up @@ -26,6 +26,8 @@
#include <qgsclipboard.h>
#include <qgsmaplayerregistry.h>
#include <qgsvectorlayer.h>
#include "qgsgeometry.h"
#include "qgspointv2.h"

/** \ingroup UnitTests
* This is a unit test for the QgisApp clipboard.
Expand All @@ -44,6 +46,9 @@ class TestQgisAppClipboard : public QObject
void cleanup() {} // will be called after every testfunction.

void copyPaste();
void pasteWkt();
void pasteGeoJson();
void retrieveFields();

private:
QgisApp * mQgisApp;
Expand Down Expand Up @@ -107,5 +112,66 @@ void TestQgisAppClipboard::copyPaste()
}
}

void TestQgisAppClipboard::pasteWkt()
{
mQgisApp->clipboard()->clear();
mQgisApp->clipboard()->setText( "POINT (125 10)\nPOINT (111 30)" );

QgsFeatureList features = mQgisApp->clipboard()->copyOf();
QCOMPARE( features.length(), 2 );
QVERIFY( features.at( 0 ).constGeometry() && !features.at( 0 ).constGeometry()->isEmpty() );
QCOMPARE( features.at( 0 ).constGeometry()->geometry()->wkbType(), QgsWKBTypes::Point );
const QgsPointV2* point = dynamic_cast< QgsPointV2* >( features.at( 0 ).constGeometry()->geometry() );
QCOMPARE( point->x(), 125.0 );
QCOMPARE( point->y(), 10.0 );
QVERIFY( features.at( 1 ).constGeometry() && !features.at( 1 ).constGeometry()->isEmpty() );
QCOMPARE( features.at( 1 ).constGeometry()->geometry()->wkbType(), QgsWKBTypes::Point );
point = dynamic_cast< QgsPointV2* >( features.at( 1 ).constGeometry()->geometry() );
QCOMPARE( point->x(), 111.0 );
QCOMPARE( point->y(), 30.0 );
}

void TestQgisAppClipboard::pasteGeoJson()
{
mQgisApp->clipboard()->clear();
QgsFields fields;
fields.append( QgsField( "name", QVariant::String ) );
mQgisApp->clipboard()->setText( "{\n\"type\": \"Feature\",\"geometry\": {\"type\": \"Point\",\"coordinates\": [125, 10]},\"properties\": {\"name\": \"Dinagat Islands\"}}" );

QgsFeatureList features = mQgisApp->clipboard()->copyOf( fields );
QCOMPARE( features.length(), 1 );
QVERIFY( features.at( 0 ).constGeometry() && !features.at( 0 ).constGeometry()->isEmpty() );
QCOMPARE( features.at( 0 ).constGeometry()->geometry()->wkbType(), QgsWKBTypes::Point );
const QgsPointV2* point = dynamic_cast< QgsPointV2* >( features.at( 0 ).constGeometry()->geometry() );
QCOMPARE( point->x(), 125.0 );
QCOMPARE( point->y(), 10.0 );
QCOMPARE( features.at( 0 ).attribute( "name" ).toString(), QString( "Dinagat Islands" ) );
}

void TestQgisAppClipboard::retrieveFields()
{
mQgisApp->clipboard()->clear();

//empty string
mQgisApp->clipboard()->setText( "" );

QgsFields fields = mQgisApp->clipboard()->fields();
QCOMPARE( fields.count(), 0 );

// bad string
mQgisApp->clipboard()->setText( "asdasdas" );
fields = mQgisApp->clipboard()->fields();
QCOMPARE( fields.count(), 0 );

// geojson string
mQgisApp->clipboard()->setText( "{\n\"type\": \"Feature\",\"geometry\": {\"type\": \"Point\",\"coordinates\": [125, 10]},\"properties\": {\"name\": \"Dinagat Islands\",\"height\":5.5}}" );
fields = mQgisApp->clipboard()->fields();
QCOMPARE( fields.count(), 2 );
QCOMPARE( fields.at( 0 ).name(), QString( "name" ) );
QCOMPARE( fields.at( 0 ).type(), QVariant::String );
QCOMPARE( fields.at( 1 ).name(), QString( "height" ) );
QCOMPARE( fields.at( 1 ).type(), QVariant::Double );
}

QTEST_MAIN( TestQgisAppClipboard )
#include "testqgisappclipboard.moc"

0 comments on commit 7f6446a

Please sign in to comment.