Skip to content

Commit b1a6c79

Browse files
committedAug 6, 2017
Remove QgsFileDropEdit widget and integrate functionality into QgsFileWidget
It makes no sense to have two classes covering this use case, with partial functionality in each. Smash the two together so we can safely use QgsFileWidget for all use cases in future.
1 parent 10968ae commit b1a6c79

File tree

11 files changed

+254
-242
lines changed

11 files changed

+254
-242
lines changed
 

‎doc/api_break.dox

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ should now call QgsCoordinateReferenceSystem::invalidateCache() and QgsCoordinat
267267
- QgsDataDefinedSymbolDialog was removed. Code using this dialog should be reworked to use QgsPropertyOverrideButton
268268
- QgsDefaultPluginLayerLegend was removed. Use QgsMapLayer::setLegend() to provide legend nodes for plugin layers.
269269
- QgsFileNameWidgetWrapper was removed. Use QgsExternalResourceWidgetWrapper instead.
270+
- QgsFileDropEdit was removed. Use QgsFileWidget instead.
270271
- QgsFormAnnotationItem. Use QgsFormAnnotation instead.
271272
- QgsHtmlAnnotationItem. Use QgsHtmlAnnotation instead.
272273
- QgsHttpTransaction. This class was outdated and code should be ported to native Qt or Python implementations.

‎python/gui/gui_auto.sip

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
%Include qgscustomdrophandler.sip
55
%Include qgsdetaileditemdata.sip
66
%Include qgsexpressionbuilderdialog.sip
7-
%Include qgsfiledropedit.sip
87
%Include qgsgeometryrubberband.sip
98
%Include qgsgui.sip
109
%Include qgshelp.sip

‎python/gui/qgsfilewidget.sip

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010

1111

12-
13-
1412
class QgsFileWidget : QWidget
1513
{
1614
%Docstring
@@ -149,6 +147,14 @@ returns if the relative path is with respect to the project path or the default
149147
determines if the relative path is with respect to the project path or the default path
150148
%End
151149

150+
QLineEdit *lineEdit();
151+
%Docstring
152+
Returns a pointer to the widget's line edit, which can be used to customise
153+
the appearance and behavior of the line edit portion of the widget.
154+
.. versionadded:: 3.0
155+
:rtype: QLineEdit
156+
%End
157+
152158
signals:
153159
void fileChanged( const QString & );
154160
%Docstring
@@ -157,6 +163,10 @@ emitted as soon as the current file or directory is changed
157163

158164
};
159165

166+
167+
168+
169+
160170
/************************************************************************
161171
* This file has been generated automatically from *
162172
* *

‎src/gui/CMakeLists.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,6 @@ SET(QGIS_GUI_SRCS
243243
qgsfieldexpressionwidget.cpp
244244
qgsfieldvalidator.cpp
245245
qgsfieldvalueslineedit.cpp
246-
qgsfiledropedit.cpp
247246
qgsfilewidget.cpp
248247
qgsfilterlineedit.cpp
249248
qgsfloatingwidget.cpp
@@ -408,7 +407,6 @@ SET(QGIS_GUI_MOC_HDRS
408407
qgsfieldexpressionwidget.h
409408
qgsfieldvalidator.h
410409
qgsfieldvalueslineedit.h
411-
qgsfiledropedit.h
412410
qgsfilewidget.h
413411
qgsfilterlineedit.h
414412
qgsfloatingwidget.h
@@ -691,7 +689,6 @@ SET(QGIS_GUI_HDRS
691689
qgscustomdrophandler.h
692690
qgsdetaileditemdata.h
693691
qgsexpressionbuilderdialog.h
694-
qgsfiledropedit.h
695692
qgsgeometryrubberband.h
696693
qgsgui.h
697694
qgsguiutils.h

‎src/gui/qgsfiledropedit.cpp

Lines changed: 0 additions & 121 deletions
This file was deleted.

‎src/gui/qgsfiledropedit.h

Lines changed: 0 additions & 78 deletions
This file was deleted.

‎src/gui/qgsfilewidget.cpp

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <QFileDialog>
2323
#include <QGridLayout>
2424
#include <QUrl>
25+
#include <QDropEvent>
2526

2627
#include "qgssettings.h"
2728
#include "qgsfilterlineedit.h"
@@ -58,8 +59,8 @@ QgsFileWidget::QgsFileWidget( QWidget *parent )
5859
mLinkLabel->setTextFormat( Qt::RichText );
5960
mLinkLabel->hide(); // do not show by default
6061

61-
// otherwise, use the traditional QLineEdit
62-
mLineEdit = new QgsFilterLineEdit( this );
62+
// otherwise, use the traditional QLineEdit subclass
63+
mLineEdit = new QgsFileDropEdit( this );
6364
connect( mLineEdit, &QLineEdit::textChanged, this, &QgsFileWidget::textEdited );
6465
mLayout->addWidget( mLineEdit );
6566

@@ -111,6 +112,7 @@ QString QgsFileWidget::filter() const
111112
void QgsFileWidget::setFilter( const QString &filters )
112113
{
113114
mFilter = filters;
115+
mLineEdit->setFilters( filters );
114116
}
115117

116118
bool QgsFileWidget::fileWidgetButtonVisible() const
@@ -181,6 +183,7 @@ QgsFileWidget::StorageMode QgsFileWidget::storageMode() const
181183
void QgsFileWidget::setStorageMode( QgsFileWidget::StorageMode storageMode )
182184
{
183185
mStorageMode = storageMode;
186+
mLineEdit->setStorageMode( storageMode );
184187
}
185188

186189
QgsFileWidget::RelativeStorage QgsFileWidget::relativeStorage() const
@@ -193,6 +196,11 @@ void QgsFileWidget::setRelativeStorage( QgsFileWidget::RelativeStorage relativeS
193196
mRelativeStorage = relativeStorage;
194197
}
195198

199+
QLineEdit *QgsFileWidget::lineEdit()
200+
{
201+
return mLineEdit;
202+
}
203+
196204
void QgsFileWidget::openFileDialog()
197205
{
198206
QgsSettings settings;
@@ -316,3 +324,101 @@ QString QgsFileWidget::toUrl( const QString &path ) const
316324

317325
return rep;
318326
}
327+
328+
329+
330+
331+
///@cond PRIVATE
332+
333+
334+
QgsFileDropEdit::QgsFileDropEdit( QWidget *parent )
335+
: QgsFilterLineEdit( parent )
336+
{
337+
mDragActive = false;
338+
setAcceptDrops( true );
339+
}
340+
341+
void QgsFileDropEdit::setFilters( const QString &filters )
342+
{
343+
mAcceptableExtensions.clear();
344+
345+
if ( filters.contains( QStringLiteral( "*.*" ) ) )
346+
return; // everything is allowed!
347+
348+
QRegularExpression rx( "\\*\\.(\\w+)" );
349+
QRegularExpressionMatchIterator i = rx.globalMatch( filters );
350+
while ( i.hasNext() )
351+
{
352+
QRegularExpressionMatch match = i.next();
353+
if ( match.hasMatch() )
354+
{
355+
mAcceptableExtensions << match.captured( 1 ).toLower();
356+
}
357+
}
358+
}
359+
360+
QString QgsFileDropEdit::acceptableFilePath( QDropEvent *event ) const
361+
{
362+
QString path;
363+
if ( event->mimeData()->hasUrls() )
364+
{
365+
QFileInfo file( event->mimeData()->urls().first().toLocalFile() );
366+
if ( ( mStorageMode == QgsFileWidget::GetFile && file.isFile() &&
367+
( mAcceptableExtensions.isEmpty() || mAcceptableExtensions.contains( file.suffix(), Qt::CaseInsensitive ) ) )
368+
|| ( mStorageMode == QgsFileWidget::GetDirectory && file.isDir() ) )
369+
path = file.filePath();
370+
}
371+
return path;
372+
}
373+
374+
void QgsFileDropEdit::dragEnterEvent( QDragEnterEvent *event )
375+
{
376+
QString filePath = acceptableFilePath( event );
377+
if ( !filePath.isEmpty() )
378+
{
379+
event->acceptProposedAction();
380+
mDragActive = true;
381+
update();
382+
}
383+
else
384+
{
385+
event->ignore();
386+
}
387+
}
388+
389+
void QgsFileDropEdit::dragLeaveEvent( QDragLeaveEvent *event )
390+
{
391+
QgsFilterLineEdit::dragLeaveEvent( event );
392+
event->accept();
393+
mDragActive = false;
394+
update();
395+
}
396+
397+
void QgsFileDropEdit::dropEvent( QDropEvent *event )
398+
{
399+
QString filePath = acceptableFilePath( event );
400+
if ( !filePath.isEmpty() )
401+
{
402+
setText( filePath );
403+
selectAll();
404+
setFocus( Qt::MouseFocusReason );
405+
event->acceptProposedAction();
406+
mDragActive = false;
407+
update();
408+
}
409+
}
410+
411+
void QgsFileDropEdit::paintEvent( QPaintEvent *e )
412+
{
413+
QgsFilterLineEdit::paintEvent( e );
414+
if ( mDragActive )
415+
{
416+
QPainter p( this );
417+
int width = 2; // width of highlight rectangle inside frame
418+
p.setPen( QPen( palette().highlight(), width ) );
419+
QRect r = rect().adjusted( width, width, -width, -width );
420+
p.drawRect( r );
421+
}
422+
}
423+
424+
///@endcond

‎src/gui/qgsfilewidget.h

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
class QLabel;
2121
class QToolButton;
2222
class QVariant;
23-
24-
class QgsFilterLineEdit;
25-
23+
class QgsFileDropEdit;
24+
class QHBoxLayout;
2625
#include <QWidget>
2726

2827
#include "qgis_gui.h"
2928
#include "qgis.h"
29+
#include "qgsfilterlineedit.h"
3030

3131
/** \ingroup gui
3232
* \brief The QgsFileWidget class creates a widget for selecting a file or a folder.
@@ -137,6 +137,13 @@ class GUI_EXPORT QgsFileWidget : public QWidget
137137
//! determines if the relative path is with respect to the project path or the default path
138138
void setRelativeStorage( QgsFileWidget::RelativeStorage relativeStorage );
139139

140+
/**
141+
* Returns a pointer to the widget's line edit, which can be used to customise
142+
* the appearance and behavior of the line edit portion of the widget.
143+
* \since QGIS 3.0
144+
*/
145+
QLineEdit *lineEdit();
146+
140147
signals:
141148
//! emitted as soon as the current file or directory is changed
142149
void fileChanged( const QString & );
@@ -157,7 +164,7 @@ class GUI_EXPORT QgsFileWidget : public QWidget
157164
RelativeStorage mRelativeStorage;
158165

159166
QLabel *mLinkLabel = nullptr;
160-
QgsFilterLineEdit *mLineEdit = nullptr;
167+
QgsFileDropEdit *mLineEdit = nullptr;
161168
QToolButton *mFileWidgetButton = nullptr;
162169
QHBoxLayout *mLayout = nullptr;
163170

@@ -170,4 +177,52 @@ class GUI_EXPORT QgsFileWidget : public QWidget
170177
friend class TestQgsFileWidget;
171178
};
172179

180+
181+
182+
///@cond PRIVATE
183+
184+
#ifndef SIP_RUN
185+
186+
/** \ingroup gui
187+
* A line edit for capturing file names that can have files dropped onto
188+
* it via drag & drop.
189+
*
190+
* Dropping can be limited to files only, files with a specific extension
191+
* or directories only. By default, dropping is limited to files only.
192+
* \note not available in Python bindings
193+
*/
194+
class GUI_EXPORT QgsFileDropEdit: public QgsFilterLineEdit
195+
{
196+
Q_OBJECT
197+
198+
public:
199+
QgsFileDropEdit( QWidget *parent SIP_TRANSFERTHIS = 0 );
200+
201+
void setStorageMode( QgsFileWidget::StorageMode storageMode ) { mStorageMode = storageMode; }
202+
203+
void setFilters( const QString &filters );
204+
205+
protected:
206+
207+
virtual void dragEnterEvent( QDragEnterEvent *event ) override;
208+
virtual void dragLeaveEvent( QDragLeaveEvent *event ) override;
209+
virtual void dropEvent( QDropEvent *event ) override;
210+
virtual void paintEvent( QPaintEvent *e ) override;
211+
212+
private:
213+
214+
/**
215+
Return file name if object meets drop criteria.
216+
*/
217+
QString acceptableFilePath( QDropEvent *event ) const;
218+
219+
QStringList mAcceptableExtensions;
220+
QgsFileWidget::StorageMode mStorageMode = QgsFileWidget::GetFile;
221+
bool mDragActive;
222+
friend class TestQgsFileWidget;
223+
};
224+
225+
#endif
226+
///@endcond
227+
173228
#endif // QGSFILEWIDGET_H

‎src/plugins/gps_importer/qgsgpsplugingui.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ QgsGPSPluginGui::QgsGPSPluginGui( const BabelMap &importers,
5151
// click it
5252
pbnOK = buttonBox->button( QDialogButtonBox::Ok );
5353
pbnOK->setEnabled( false );
54-
connect( leGPXFile, &QLineEdit::textChanged,
54+
connect( mFileWidget, &QgsFileWidget::fileChanged,
5555
this, &QgsGPSPluginGui::enableRelevantControls );
5656
connect( leIMPInput, &QLineEdit::textChanged,
5757
this, &QgsGPSPluginGui::enableRelevantControls );
@@ -75,7 +75,7 @@ QgsGPSPluginGui::QgsGPSPluginGui( const BabelMap &importers,
7575
this, &QgsGPSPluginGui::enableRelevantControls );
7676

7777
// drag and drop filter
78-
leGPXFile->setSuffixFilter( QStringLiteral( "gpx" ) );
78+
mFileWidget->setFilter( tr( "GPX files (*.gpx)" ) );
7979
}
8080

8181
QgsGPSPluginGui::~QgsGPSPluginGui()
@@ -92,7 +92,7 @@ void QgsGPSPluginGui::on_buttonBox_accepted()
9292
{
9393
case 0:
9494
// add a GPX layer?
95-
emit loadGPXFile( leGPXFile->text(), cbGPXWaypoints->isChecked(),
95+
emit loadGPXFile( mFileWidget->filePath(), cbGPXWaypoints->isChecked(),
9696
cbGPXRoutes->isChecked(), cbGPXTracks->isChecked() );
9797
break;
9898

@@ -180,7 +180,7 @@ void QgsGPSPluginGui::enableRelevantControls()
180180
// load GPX
181181
if ( tabWidget->currentIndex() == 0 )
182182
{
183-
if ( ( leGPXFile->text() == QLatin1String( "" ) ) )
183+
if ( !mFileWidget->filePath().isEmpty() )
184184
{
185185
pbnOK->setEnabled( false );
186186
cbGPXWaypoints->setEnabled( false );
@@ -259,7 +259,7 @@ void QgsGPSPluginGui::on_pbnGPXSelectFile_clicked()
259259
tr( "GPS eXchange format" ) + " (*.gpx)" );
260260
if ( !myFileNameQString.isEmpty() )
261261
{
262-
leGPXFile->setText( myFileNameQString );
262+
mFileWidget->setFilePath( myFileNameQString );
263263
settings.setValue( QStringLiteral( "Plugin-GPS/gpxdirectory" ), QFileInfo( myFileNameQString ).absolutePath() );
264264
}
265265
}

‎src/plugins/gps_importer/qgsgpspluginguibase.ui

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,16 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>647</width>
10-
<height>277</height>
9+
<width>1217</width>
10+
<height>504</height>
1111
</rect>
1212
</property>
1313
<property name="windowTitle">
1414
<string>GPS Tools</string>
1515
</property>
1616
<property name="windowIcon">
1717
<iconset>
18-
<normaloff/>
19-
</iconset>
18+
<normaloff>.</normaloff>.</iconset>
2019
</property>
2120
<layout class="QVBoxLayout" name="verticalLayout_2">
2221
<item>
@@ -30,25 +29,15 @@
3029
</attribute>
3130
<layout class="QVBoxLayout" name="verticalLayout">
3231
<item>
33-
<layout class="QGridLayout" name="gridLayout_2">
32+
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,1">
33+
<item row="0" column="1">
34+
<widget class="QgsFileWidget" name="mFileWidget" native="true"/>
35+
</item>
3436
<item row="0" column="0">
3537
<widget class="QLabel" name="lblGPXFile">
3638
<property name="text">
3739
<string>File</string>
3840
</property>
39-
<property name="buddy">
40-
<cstring>pbnGPXSelectFile</cstring>
41-
</property>
42-
</widget>
43-
</item>
44-
<item row="0" column="1">
45-
<widget class="QgsFileDropEdit" name="leGPXFile"/>
46-
</item>
47-
<item row="0" column="2">
48-
<widget class="QPushButton" name="pbnGPXSelectFile">
49-
<property name="text">
50-
<string>Browse...</string>
51-
</property>
5241
</widget>
5342
</item>
5443
</layout>
@@ -655,15 +644,14 @@
655644
<layoutdefault spacing="6" margin="11"/>
656645
<customwidgets>
657646
<customwidget>
658-
<class>QgsFileDropEdit</class>
659-
<extends>QLineEdit</extends>
660-
<header>qgsfiledropedit.h</header>
647+
<class>QgsFileWidget</class>
648+
<extends>QWidget</extends>
649+
<header>qgsfilewidget.h</header>
650+
<container>1</container>
661651
</customwidget>
662652
</customwidgets>
663653
<tabstops>
664654
<tabstop>tabWidget</tabstop>
665-
<tabstop>leGPXFile</tabstop>
666-
<tabstop>pbnGPXSelectFile</tabstop>
667655
<tabstop>cbGPXWaypoints</tabstop>
668656
<tabstop>cbGPXRoutes</tabstop>
669657
<tabstop>cbGPXTracks</tabstop>
@@ -693,8 +681,6 @@
693681
<tabstop>leCONVLayer</tabstop>
694682
<tabstop>buttonBox</tabstop>
695683
</tabstops>
696-
<resources>
697-
<include location="qgsgps_plugin.qrc"/>
698-
</resources>
684+
<resources/>
699685
<connections/>
700686
</ui>

‎tests/src/gui/testqgsfilewidget.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "qgstest.h"
1818

1919
#include "qgsfilewidget.h"
20+
#include <memory>
2021

2122
class TestQgsFileWidget: public QObject
2223
{
@@ -29,6 +30,7 @@ class TestQgsFileWidget: public QObject
2930

3031
void relativePath();
3132
void toUrl();
33+
void testDroppedFiles();
3234

3335
};
3436

@@ -81,7 +83,62 @@ void TestQgsFileWidget::toUrl()
8183
QCOMPARE( w->toUrl( "../test2/file6.ext" ), QString( "<a href=\"file:///home/test2/file6.ext\">file6.ext</a>" ) );
8284
}
8385

86+
void TestQgsFileWidget::testDroppedFiles()
87+
{
88+
QgsFileWidget *w = new QgsFileWidget();
89+
w->setStorageMode( QgsFileWidget::GetFile );
90+
91+
// should not accept dropped folders
92+
std::unique_ptr< QMimeData > mime( new QMimeData() );
93+
mime->setUrls( QList<QUrl>() << QUrl::fromLocalFile( TEST_DATA_DIR ) );
94+
std::unique_ptr< QDropEvent > event( new QDropEvent( QPointF( 1, 1 ), Qt::CopyAction, mime.get(), Qt::LeftButton, Qt::NoModifier ) );
95+
96+
qobject_cast< QgsFileDropEdit * >( w->lineEdit() )->dropEvent( event.get() );
97+
QVERIFY( w->lineEdit()->text().isEmpty() );
98+
99+
// but dropped files should be fine
100+
mime->setUrls( QList<QUrl>() << QUrl::fromLocalFile( TEST_DATA_DIR + QStringLiteral( "/bug5598.shp" ) ) );
101+
event.reset( new QDropEvent( QPointF( 1, 1 ), Qt::CopyAction, mime.get(), Qt::LeftButton, Qt::NoModifier ) );
102+
qobject_cast< QgsFileDropEdit * >( w->lineEdit() )->dropEvent( event.get() );
103+
QCOMPARE( w->lineEdit()->text(), TEST_DATA_DIR + QStringLiteral( "/bug5598.shp" ) );
104+
105+
// with file filter
106+
w->setFilter( QStringLiteral( "Data (*.shp)" ) );
107+
w->setFilePath( QString() );
108+
qobject_cast< QgsFileDropEdit * >( w->lineEdit() )->dropEvent( event.get() );
109+
QCOMPARE( w->lineEdit()->text(), TEST_DATA_DIR + QStringLiteral( "/bug5598.shp" ) );
110+
w->setFilePath( QString() );
111+
// should be rejected, not compatible with filter
112+
mime->setUrls( QList<QUrl>() << QUrl::fromLocalFile( TEST_DATA_DIR + QStringLiteral( "/encoded_html.html" ) ) );
113+
event.reset( new QDropEvent( QPointF( 1, 1 ), Qt::CopyAction, mime.get(), Qt::LeftButton, Qt::NoModifier ) );
114+
qobject_cast< QgsFileDropEdit * >( w->lineEdit() )->dropEvent( event.get() );
115+
QVERIFY( w->lineEdit()->text().isEmpty() );
116+
// new filter, should be allowed now
117+
w->setFilter( QStringLiteral( "Data (*.shp);;HTML (*.HTML)" ) );
118+
qobject_cast< QgsFileDropEdit * >( w->lineEdit() )->dropEvent( event.get() );
119+
QCOMPARE( w->lineEdit()->text(), TEST_DATA_DIR + QStringLiteral( "/encoded_html.html" ) );
120+
121+
//try with wildcard filter
122+
w->setFilter( QStringLiteral( "All files (*.*);;Data (*.shp);;HTML (*.HTML)" ) );
123+
mime->setUrls( QList<QUrl>() << QUrl::fromLocalFile( TEST_DATA_DIR + QStringLiteral( "/bug5598.prj" ) ) );
124+
event.reset( new QDropEvent( QPointF( 1, 1 ), Qt::CopyAction, mime.get(), Qt::LeftButton, Qt::NoModifier ) );
125+
qobject_cast< QgsFileDropEdit * >( w->lineEdit() )->dropEvent( event.get() );
126+
QCOMPARE( w->lineEdit()->text(), TEST_DATA_DIR + QStringLiteral( "/bug5598.prj" ) );
127+
128+
// try with folders
129+
w->setStorageMode( QgsFileWidget::GetDirectory );
130+
w->setFilePath( QString() );
131+
// should be rejected
132+
qobject_cast< QgsFileDropEdit * >( w->lineEdit() )->dropEvent( event.get() );
133+
QVERIFY( w->lineEdit()->text().isEmpty() );
134+
135+
// but dropping a folder should work
136+
mime->setUrls( QList<QUrl>() << QUrl::fromLocalFile( TEST_DATA_DIR ) );
137+
event.reset( new QDropEvent( QPointF( 1, 1 ), Qt::CopyAction, mime.get(), Qt::LeftButton, Qt::NoModifier ) );
138+
qobject_cast< QgsFileDropEdit * >( w->lineEdit() )->dropEvent( event.get() );
139+
QCOMPARE( w->lineEdit()->text(), QString( TEST_DATA_DIR ) );
84140

141+
}
85142

86143
QGSTEST_MAIN( TestQgsFileWidget )
87144
#include "testqgsfilewidget.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.