Skip to content

Commit 7f6446a

Browse files
committed
[FEATURE] Use OGR to parse clipboard text so that GeoJSON (and others) can be
directly pasted into QGIS (fix #9727)
1 parent 3f62cd4 commit 7f6446a

File tree

4 files changed

+156
-36
lines changed

4 files changed

+156
-36
lines changed

src/app/qgisapp.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6837,11 +6837,11 @@ void QgisApp::editPaste( QgsMapLayer *destinationLayer )
68376837
int nTotalFeatures = features.count();
68386838

68396839
QHash<int, int> remap;
6840-
const QgsFields &fields = clipboard()->fields();
6840+
QgsFields fields = clipboard()->fields();
68416841
QgsAttributeList pkAttrList = pasteVectorLayer->pkAttributeList();
68426842
for ( int idx = 0; idx < fields.count(); ++idx )
68436843
{
6844-
int dst = pasteVectorLayer->fieldNameIndex( fields[idx].name() );
6844+
int dst = pasteVectorLayer->fieldNameIndex( fields.at( idx ).name() );
68456845
if ( dst < 0 )
68466846
continue;
68476847

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

69856985
QgsVectorLayer *QgisApp::pasteToNewMemoryVector()
69866986
{
6987+
QgsFields fields = clipboard()->fields();
6988+
69876989
// Decide geometry type from features, switch to multi type if at least one multi is found
69886990
QMap<QGis::WkbType, int> typeCounts;
6989-
QgsFeatureList features = clipboard()->copyOf();
6991+
QgsFeatureList features = clipboard()->copyOf( fields );
69906992
for ( int i = 0; i < features.size(); i++ )
69916993
{
69926994
QgsFeature &feature = features[i];

src/app/qgsclipboard.cpp

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <QClipboard>
2525
#include <QSettings>
2626
#include <QMimeData>
27+
#include <QTextCodec>
2728

2829
#include "qgsclipboard.h"
2930
#include "qgsfeature.h"
@@ -32,6 +33,7 @@
3233
#include "qgscoordinatereferencesystem.h"
3334
#include "qgslogger.h"
3435
#include "qgsvectorlayer.h"
36+
#include "qgsogrutils.h"
3537

3638
QgsClipboard::QgsClipboard()
3739
: QObject()
@@ -143,25 +145,19 @@ void QgsClipboard::setSystemClipboard()
143145
QgsDebugMsg( QString( "replaced system clipboard with: %1." ).arg( textCopy ) );
144146
}
145147

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

152-
QClipboard *cb = QApplication::clipboard();
153+
if ( !features.isEmpty() )
154+
return features;
153155

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

160-
QStringList values = text.split( '\n' );
161-
if ( values.isEmpty() || text.isEmpty() )
162-
return mFeatureClipboard;
163-
164-
QgsFeatureList features;
165161
Q_FOREACH ( const QString& row, values )
166162
{
167163
// Assume that it's just WKT for now.
@@ -177,6 +173,38 @@ QgsFeatureList QgsClipboard::copyOf( const QgsFields &fields )
177173
features.append( feature );
178174
}
179175

176+
return features;
177+
}
178+
179+
QgsFields QgsClipboard::retrieveFields() const
180+
{
181+
QClipboard *cb = QApplication::clipboard();
182+
183+
#ifndef Q_OS_WIN
184+
QString string = cb->text( QClipboard::Selection );
185+
#else
186+
QString string = cb->text( QClipboard::Clipboard );
187+
#endif
188+
189+
return QgsOgrUtils::stringToFields( string, QTextCodec::codecForName( "System" ) );
190+
}
191+
192+
QgsFeatureList QgsClipboard::copyOf( const QgsFields &fields )
193+
{
194+
QgsDebugMsg( "returning clipboard." );
195+
if ( !mUseSystemClipboard )
196+
return mFeatureClipboard;
197+
198+
QClipboard *cb = QApplication::clipboard();
199+
200+
#ifndef Q_OS_WIN
201+
QString text = cb->text( QClipboard::Selection );
202+
#else
203+
QString text = cb->text( QClipboard::Clipboard );
204+
#endif
205+
206+
QgsFeatureList features = stringToFeatureList( text, fields );
207+
180208
if ( features.isEmpty() )
181209
return mFeatureClipboard;
182210

@@ -258,6 +286,11 @@ void QgsClipboard::setData( const QString& mimeType, const QByteArray& data )
258286
setData( mimeType, data, nullptr );
259287
}
260288

289+
void QgsClipboard::setText( const QString& text )
290+
{
291+
setData( "text/plain", text.toLocal8Bit(), nullptr );
292+
}
293+
261294
bool QgsClipboard::hasFormat( const QString& mimeType )
262295
{
263296
return QApplication::clipboard()->mimeData()->hasFormat( mimeType );

src/app/qgsclipboard.h

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,84 +62,89 @@ class APP_EXPORT QgsClipboard : public QObject
6262
//! Destructor
6363
virtual ~QgsClipboard();
6464

65-
/*
65+
/**
6666
* Place a copy of features on the internal clipboard,
6767
* destroying the previous contents.
6868
*/
6969
void replaceWithCopyOf( QgsVectorLayer *src );
7070

71-
/*
71+
/**
7272
* Place a copy of features on the internal clipboard,
7373
* destroying the previous contents.
7474
*/
7575
void replaceWithCopyOf( QgsFeatureStore & featureStore );
7676

77-
/*
77+
/**
7878
* Returns a copy of features on the internal clipboard,
7979
* the caller assumes responsibility for destroying the contents
8080
* when it's done with it.
8181
*/
8282
QgsFeatureList copyOf( const QgsFields &fields = QgsFields() );
8383

84-
/*
84+
/**
8585
* Clears the internal clipboard.
8686
*/
8787
void clear();
8888

89-
/*
89+
/**
9090
* Inserts a copy of the feature on the internal clipboard.
9191
*/
9292
void insert( QgsFeature& feature );
9393

94-
/*
94+
/**
9595
* Returns true if the internal clipboard is empty, else false.
9696
*/
9797
bool empty();
9898

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

107-
/*
107+
/**
108108
* Get the clipboard CRS
109109
*/
110110
QgsCoordinateReferenceSystem crs();
111111

112-
/*
112+
/**
113113
* Stores a MimeData together with a text into the system clipboard
114114
*/
115115
void setData( const QString& mimeType, const QByteArray& data, const QString* text = nullptr );
116-
/*
116+
117+
/**
117118
* Stores a MimeData together with a text into the system clipboard
118119
*/
119120
void setData( const QString& mimeType, const QByteArray& data, const QString& text );
120-
/*
121+
122+
/**
121123
* Stores a MimeData into the system clipboard
122124
*/
123125
void setData( const QString& mimeType, const QByteArray& data );
124-
/*
126+
127+
/**
125128
* Stores a text into the system clipboard
126129
*/
127130
void setText( const QString& text );
128-
/*
131+
132+
/**
129133
* Proxy to QMimeData::hasFormat
130134
* Tests whether the system clipboard contains data of a given MIME type
131135
*/
132136
bool hasFormat( const QString& mimeType );
133-
/*
137+
138+
/**
134139
* Retrieve data from the system clipboard.
135140
* No copy is involved, since the return QByteArray is implicitly shared
136141
*/
137142
QByteArray data( const QString& mimeType );
138143

139-
/*
140-
* source fields
144+
/**
145+
* Source fields
141146
*/
142-
const QgsFields &fields() { return mFeatureFields; }
147+
QgsFields fields() { return !mUseSystemClipboard ? mFeatureFields : retrieveFields(); }
143148

144149
private slots:
145150

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

152157
private:
153-
/*
158+
159+
/**
154160
* Set system clipboard from previously set features.
155161
*/
156162
void setSystemClipboard();
157163

164+
/** Attempts to convert a string to a list of features, by parsing the string as WKT and GeoJSON
165+
* @param string string to convert
166+
* @param fields fields for resultant features
167+
* @returns list of features if conversion was successful
168+
*/
169+
QgsFeatureList stringToFeatureList( const QString& string, const QgsFields& fields ) const;
170+
171+
/** Attempts to parse the clipboard contents and return a QgsFields object representing the fields
172+
* present in the clipboard.
173+
* @note Only valid for text based clipboard contents
174+
*/
175+
QgsFields retrieveFields() const;
176+
158177
/** QGIS-internal vector feature clipboard.
159178
Stored as values not pointers as each clipboard operation
160179
involves a deep copy anyway.

tests/src/app/testqgisappclipboard.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include <qgsclipboard.h>
2727
#include <qgsmaplayerregistry.h>
2828
#include <qgsvectorlayer.h>
29+
#include "qgsgeometry.h"
30+
#include "qgspointv2.h"
2931

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

4648
void copyPaste();
49+
void pasteWkt();
50+
void pasteGeoJson();
51+
void retrieveFields();
4752

4853
private:
4954
QgisApp * mQgisApp;
@@ -107,5 +112,66 @@ void TestQgisAppClipboard::copyPaste()
107112
}
108113
}
109114

115+
void TestQgisAppClipboard::pasteWkt()
116+
{
117+
mQgisApp->clipboard()->clear();
118+
mQgisApp->clipboard()->setText( "POINT (125 10)\nPOINT (111 30)" );
119+
120+
QgsFeatureList features = mQgisApp->clipboard()->copyOf();
121+
QCOMPARE( features.length(), 2 );
122+
QVERIFY( features.at( 0 ).constGeometry() && !features.at( 0 ).constGeometry()->isEmpty() );
123+
QCOMPARE( features.at( 0 ).constGeometry()->geometry()->wkbType(), QgsWKBTypes::Point );
124+
const QgsPointV2* point = dynamic_cast< QgsPointV2* >( features.at( 0 ).constGeometry()->geometry() );
125+
QCOMPARE( point->x(), 125.0 );
126+
QCOMPARE( point->y(), 10.0 );
127+
QVERIFY( features.at( 1 ).constGeometry() && !features.at( 1 ).constGeometry()->isEmpty() );
128+
QCOMPARE( features.at( 1 ).constGeometry()->geometry()->wkbType(), QgsWKBTypes::Point );
129+
point = dynamic_cast< QgsPointV2* >( features.at( 1 ).constGeometry()->geometry() );
130+
QCOMPARE( point->x(), 111.0 );
131+
QCOMPARE( point->y(), 30.0 );
132+
}
133+
134+
void TestQgisAppClipboard::pasteGeoJson()
135+
{
136+
mQgisApp->clipboard()->clear();
137+
QgsFields fields;
138+
fields.append( QgsField( "name", QVariant::String ) );
139+
mQgisApp->clipboard()->setText( "{\n\"type\": \"Feature\",\"geometry\": {\"type\": \"Point\",\"coordinates\": [125, 10]},\"properties\": {\"name\": \"Dinagat Islands\"}}" );
140+
141+
QgsFeatureList features = mQgisApp->clipboard()->copyOf( fields );
142+
QCOMPARE( features.length(), 1 );
143+
QVERIFY( features.at( 0 ).constGeometry() && !features.at( 0 ).constGeometry()->isEmpty() );
144+
QCOMPARE( features.at( 0 ).constGeometry()->geometry()->wkbType(), QgsWKBTypes::Point );
145+
const QgsPointV2* point = dynamic_cast< QgsPointV2* >( features.at( 0 ).constGeometry()->geometry() );
146+
QCOMPARE( point->x(), 125.0 );
147+
QCOMPARE( point->y(), 10.0 );
148+
QCOMPARE( features.at( 0 ).attribute( "name" ).toString(), QString( "Dinagat Islands" ) );
149+
}
150+
151+
void TestQgisAppClipboard::retrieveFields()
152+
{
153+
mQgisApp->clipboard()->clear();
154+
155+
//empty string
156+
mQgisApp->clipboard()->setText( "" );
157+
158+
QgsFields fields = mQgisApp->clipboard()->fields();
159+
QCOMPARE( fields.count(), 0 );
160+
161+
// bad string
162+
mQgisApp->clipboard()->setText( "asdasdas" );
163+
fields = mQgisApp->clipboard()->fields();
164+
QCOMPARE( fields.count(), 0 );
165+
166+
// geojson string
167+
mQgisApp->clipboard()->setText( "{\n\"type\": \"Feature\",\"geometry\": {\"type\": \"Point\",\"coordinates\": [125, 10]},\"properties\": {\"name\": \"Dinagat Islands\",\"height\":5.5}}" );
168+
fields = mQgisApp->clipboard()->fields();
169+
QCOMPARE( fields.count(), 2 );
170+
QCOMPARE( fields.at( 0 ).name(), QString( "name" ) );
171+
QCOMPARE( fields.at( 0 ).type(), QVariant::String );
172+
QCOMPARE( fields.at( 1 ).name(), QString( "height" ) );
173+
QCOMPARE( fields.at( 1 ).type(), QVariant::Double );
174+
}
175+
110176
QTEST_MAIN( TestQgisAppClipboard )
111177
#include "testqgisappclipboard.moc"

0 commit comments

Comments
 (0)