Skip to content

Commit fbd243b

Browse files
committedJun 15, 2019
[processing] Port map layer selection combobox widget to c++
And: - fix enable state of selected features only after changing between map layers with/without selections - fix state of selected features only when running an algorithm from the history list, e.g. respect original setting for selected features only (or not) - ensure no duplicate changed signals are sent, and correctly emit changed signals in all applicable circumstances - handle drag and dropped layers from browser panel (UX fix) - soak with unit tests
1 parent 9d82273 commit fbd243b

File tree

10 files changed

+1110
-234
lines changed

10 files changed

+1110
-234
lines changed
 
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/processing/qgsprocessingmaplayercombobox.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
13+
14+
class QgsProcessingMapLayerComboBox : QWidget
15+
{
16+
%Docstring
17+
Processing map layer combo box.
18+
19+
.. warning::
20+
21+
Not part of stable API and may change in future QGIS releases.
22+
23+
.. versionadded:: 3.8
24+
%End
25+
26+
%TypeHeaderCode
27+
#include "qgsprocessingmaplayercombobox.h"
28+
%End
29+
public:
30+
31+
QgsProcessingMapLayerComboBox( QgsProcessingParameterDefinition *parameter, QWidget *parent = 0 );
32+
%Docstring
33+
Constructor for QgsProcessingMapLayerComboBox, with the specified ``parameter`` definition.
34+
%End
35+
36+
void setLayer( QgsMapLayer *layer );
37+
%Docstring
38+
Sets the combo box to the specified ``layer``, if ``layer`` is compatible with the
39+
widget's parameter definition.
40+
%End
41+
42+
QgsMapLayer *currentLayer();
43+
%Docstring
44+
Returns the current layer selected in the combobox, or ``None`` if the selection cannot
45+
be represented as a map layer.
46+
47+
.. warning::
48+
49+
Prefer calling value() instead, as it correctly encapsulates all valid
50+
values which can be represented by the widget.
51+
52+
.. seealso:: :py:func:`currentText`
53+
%End
54+
55+
QString currentText();
56+
%Docstring
57+
Returns the current text of the selected item in the combobox.
58+
59+
.. warning::
60+
61+
Prefer calling value() instead, as it correctly encapsulates all valid
62+
values which can be represented by the widget.
63+
64+
.. seealso:: :py:func:`currentLayer`
65+
%End
66+
67+
void setValue( const QVariant &value, QgsProcessingContext &context );
68+
%Docstring
69+
Sets the ``value`` shown in the widget.
70+
71+
.. seealso:: :py:func:`value`
72+
%End
73+
74+
QVariant value() const;
75+
%Docstring
76+
Returns the current value of the widget.
77+
78+
.. seealso:: :py:func:`setValue`
79+
%End
80+
81+
signals:
82+
83+
void valueChanged();
84+
%Docstring
85+
Emitted whenever the value is changed in the widget.
86+
%End
87+
88+
void triggerFileSelection();
89+
%Docstring
90+
Emitted when the widget has triggered a file selection operation (to be
91+
handled in Python for now).
92+
%End
93+
94+
protected:
95+
96+
virtual void dragEnterEvent( QDragEnterEvent *event );
97+
98+
virtual void dragLeaveEvent( QDragLeaveEvent *event );
99+
100+
virtual void dropEvent( QDropEvent *event );
101+
102+
103+
};
104+
105+
/************************************************************************
106+
* This file has been generated automatically from *
107+
* *
108+
* src/gui/processing/qgsprocessingmaplayercombobox.h *
109+
* *
110+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
111+
************************************************************************/

‎python/gui/gui_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@
330330
%Include auto_generated/locator/qgslocatorwidget.sip
331331
%Include auto_generated/processing/qgsprocessingalgorithmconfigurationwidget.sip
332332
%Include auto_generated/processing/qgsprocessingalgorithmdialogbase.sip
333+
%Include auto_generated/processing/qgsprocessingmaplayercombobox.sip
333334
%Include auto_generated/processing/qgsprocessingmodelerparameterwidget.sip
334335
%Include auto_generated/processing/qgsprocessingmultipleselectiondialog.sip
335336
%Include auto_generated/processing/qgsprocessingrecentalgorithmlog.sip

‎python/plugins/processing/gui/wrappers.py

Lines changed: 42 additions & 226 deletions
Large diffs are not rendered by default.

‎python/plugins/processing/tests/GuiTest.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
QgsProcessingParameterVectorDestination,
3333
QgsProcessingParameterRasterDestination,
3434
QgsProcessingParameterRange,
35+
QgsFeature,
3536
QgsVectorLayer,
3637
QgsProject)
3738
from qgis.analysis import QgsNativeAlgorithms
@@ -134,6 +135,10 @@ def testSource(self):
134135

135136
# dummy layer
136137
layer = QgsVectorLayer('Point', 'test', 'memory')
138+
# need at least one feature in order to have a selection
139+
layer.dataProvider().addFeature(QgsFeature())
140+
layer.selectAll()
141+
137142
self.assertTrue(layer.isValid())
138143
QgsProject.instance().addMapLayer(layer)
139144

@@ -148,20 +153,15 @@ def testSource(self):
148153
wrapper.setValue(layer.id())
149154
self.assertEqual(wrapper.value(), layer.id())
150155

151-
# check not set
152-
wrapper.setValue('')
153-
self.assertFalse(wrapper.value())
154-
155156
# check selected only - expect a QgsProcessingFeatureSourceDefinition
156-
wrapper.setValue(layer.id())
157-
wrapper.use_selection_checkbox.setChecked(True)
157+
wrapper.setValue(QgsProcessingFeatureSourceDefinition(layer.id(), True))
158158
value = wrapper.value()
159159
self.assertIsInstance(value, QgsProcessingFeatureSourceDefinition)
160160
self.assertTrue(value.selectedFeaturesOnly)
161161
self.assertEqual(value.source.staticValue(), layer.id())
162162

163163
# NOT selected only, expect a direct layer id or source value
164-
wrapper.use_selection_checkbox.setChecked(False)
164+
wrapper.setValue(QgsProcessingFeatureSourceDefinition(layer.id(), False))
165165
value = wrapper.value()
166166
self.assertEqual(value, layer.id())
167167

‎src/gui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ SET(QGIS_GUI_SRCS
201201
processing/qgsprocessingalgorithmdialogbase.cpp
202202
processing/qgsprocessingconfigurationwidgets.cpp
203203
processing/qgsprocessingguiregistry.cpp
204+
processing/qgsprocessingmaplayercombobox.cpp
204205
processing/qgsprocessingmatrixparameterdialog.cpp
205206
processing/qgsprocessingmodelerparameterwidget.cpp
206207
processing/qgsprocessingmultipleselectiondialog.cpp
@@ -752,6 +753,7 @@ SET(QGIS_GUI_MOC_HDRS
752753
processing/qgsprocessingalgorithmconfigurationwidget.h
753754
processing/qgsprocessingalgorithmdialogbase.h
754755
processing/qgsprocessingconfigurationwidgets.h
756+
processing/qgsprocessingmaplayercombobox.h
755757
processing/qgsprocessingmatrixparameterdialog.h
756758
processing/qgsprocessingmodelerparameterwidget.h
757759
processing/qgsprocessingmultipleselectiondialog.h
Lines changed: 399 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,399 @@
1+
/***************************************************************************
2+
qgsprocessingmaplayercombobox.cpp
3+
-------------------------------
4+
begin : June 2019
5+
copyright : (C) 2019 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsprocessingmaplayercombobox.h"
17+
#include "qgsmaplayercombobox.h"
18+
#include "qgsmimedatautils.h"
19+
#include "qgsprocessingparameters.h"
20+
#include "qgssettings.h"
21+
#include "qgsvectorlayer.h"
22+
#include "qgsfeatureid.h"
23+
#include <QHBoxLayout>
24+
#include <QVBoxLayout>
25+
#include <QToolButton>
26+
#include <QCheckBox>
27+
#include <QDragEnterEvent>
28+
29+
///@cond PRIVATE
30+
31+
QgsProcessingMapLayerComboBox::QgsProcessingMapLayerComboBox( QgsProcessingParameterDefinition *parameter, QWidget *parent )
32+
: QWidget( parent )
33+
, mParameter( parameter )
34+
{
35+
QHBoxLayout *layout = new QHBoxLayout();
36+
layout->setMargin( 0 );
37+
layout->setContentsMargins( 0, 0, 0, 0 );
38+
layout->setSpacing( 6 );
39+
40+
mCombo = new QgsMapLayerComboBox();
41+
layout->addWidget( mCombo );
42+
layout->setAlignment( mCombo, Qt::AlignTop );
43+
44+
mSelectButton = new QToolButton();
45+
mSelectButton->setText( QStringLiteral( "" ) );
46+
mSelectButton->setToolTip( tr( "Select file" ) );
47+
connect( mSelectButton, &QToolButton::clicked, this, &QgsProcessingMapLayerComboBox::triggerFileSelection );
48+
layout->addWidget( mSelectButton );
49+
layout->setAlignment( mSelectButton, Qt::AlignTop );
50+
51+
QVBoxLayout *vl = new QVBoxLayout();
52+
vl->setMargin( 0 );
53+
vl->setContentsMargins( 0, 0, 0, 0 );
54+
vl->setSpacing( 6 );
55+
vl->addLayout( layout );
56+
57+
QgsMapLayerProxyModel::Filters filters = nullptr;
58+
59+
if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
60+
{
61+
mUseSelectionCheckBox = new QCheckBox( tr( "Selected features only" ) );
62+
mUseSelectionCheckBox->setChecked( false );
63+
mUseSelectionCheckBox->setEnabled( false );
64+
vl->addWidget( mUseSelectionCheckBox );
65+
}
66+
67+
if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() || mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
68+
{
69+
QList<int> dataTypes;
70+
if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
71+
dataTypes = static_cast< QgsProcessingParameterFeatureSource *>( mParameter )->dataTypes();
72+
else if ( mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
73+
dataTypes = static_cast< QgsProcessingParameterVectorLayer *>( mParameter )->dataTypes();
74+
75+
if ( dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.isEmpty() )
76+
filters = QgsMapLayerProxyModel::HasGeometry;
77+
if ( dataTypes.contains( QgsProcessing::TypeVectorPoint ) )
78+
filters |= QgsMapLayerProxyModel::PointLayer;
79+
if ( dataTypes.contains( QgsProcessing::TypeVectorLine ) )
80+
filters |= QgsMapLayerProxyModel::LineLayer;
81+
if ( dataTypes.contains( QgsProcessing::TypeVectorPolygon ) )
82+
filters |= QgsMapLayerProxyModel::PolygonLayer;
83+
if ( !filters )
84+
filters = QgsMapLayerProxyModel::VectorLayer;
85+
}
86+
else if ( mParameter->type() == QgsProcessingParameterRasterLayer::typeName() )
87+
{
88+
filters = QgsMapLayerProxyModel::RasterLayer;
89+
}
90+
else if ( mParameter->type() == QgsProcessingParameterMeshLayer::typeName() )
91+
{
92+
filters = QgsMapLayerProxyModel::MeshLayer;
93+
}
94+
95+
QgsSettings settings;
96+
if ( settings.value( QStringLiteral( "Processing/Configuration/SHOW_CRS_DEF" ), true ).toBool() )
97+
mCombo->setShowCrs( true );
98+
99+
if ( filters )
100+
mCombo->setFilters( filters );
101+
mCombo->setExcludedProviders( QStringList() << QStringLiteral( "grass" ) ); // not sure if this is still required...
102+
103+
if ( mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
104+
{
105+
mCombo->setAllowEmptyLayer( true );
106+
mCombo->setLayer( nullptr );
107+
}
108+
109+
connect( mCombo, &QgsMapLayerComboBox::layerChanged, this, &QgsProcessingMapLayerComboBox::onLayerChanged );
110+
if ( mUseSelectionCheckBox )
111+
connect( mUseSelectionCheckBox, &QCheckBox::toggled, this, [ = ]
112+
{
113+
if ( !mBlockChangedSignal )
114+
emit valueChanged();
115+
} );
116+
117+
setLayout( vl );
118+
119+
setAcceptDrops( true );
120+
}
121+
122+
void QgsProcessingMapLayerComboBox::setLayer( QgsMapLayer *layer )
123+
{
124+
if ( layer || mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
125+
mCombo->setLayer( layer );
126+
}
127+
128+
QgsMapLayer *QgsProcessingMapLayerComboBox::currentLayer()
129+
{
130+
return mCombo->currentLayer();
131+
}
132+
133+
QString QgsProcessingMapLayerComboBox::currentText()
134+
{
135+
return mCombo->currentText();
136+
}
137+
138+
void QgsProcessingMapLayerComboBox::setValue( const QVariant &value, QgsProcessingContext &context )
139+
{
140+
QVariant val = value;
141+
bool found = false;
142+
bool selectedOnly = false;
143+
if ( val.canConvert<QgsProcessingFeatureSourceDefinition>() )
144+
{
145+
QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( val );
146+
val = fromVar.source;
147+
selectedOnly = fromVar.selectedFeaturesOnly;
148+
}
149+
150+
if ( val.canConvert<QgsProperty>() )
151+
{
152+
if ( val.value< QgsProperty >().propertyType() == QgsProperty::StaticProperty )
153+
{
154+
val = val.value< QgsProperty >().staticValue();
155+
}
156+
else
157+
{
158+
val = val.value< QgsProperty >().valueAsString( context.expressionContext(), mParameter->defaultValue().toString() );
159+
}
160+
}
161+
162+
QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( val.value< QObject * >() );
163+
if ( !layer && val.type() == QVariant::String )
164+
{
165+
layer = QgsProcessingUtils::mapLayerFromString( val.toString(), context, false );
166+
}
167+
168+
if ( layer )
169+
{
170+
mBlockChangedSignal++;
171+
QgsMapLayer *prevLayer = currentLayer();
172+
setLayer( layer );
173+
found = static_cast< bool >( currentLayer() );
174+
bool changed = found && ( currentLayer() != prevLayer );
175+
if ( found && mUseSelectionCheckBox )
176+
{
177+
const bool hasSelection = qobject_cast< QgsVectorLayer * >( layer ) && qobject_cast< QgsVectorLayer * >( layer )->selectedFeatureCount() > 0;
178+
changed = changed | ( ( hasSelection && selectedOnly ) != mUseSelectionCheckBox->isChecked() );
179+
if ( hasSelection )
180+
{
181+
mUseSelectionCheckBox->setEnabled( true );
182+
mUseSelectionCheckBox->setChecked( selectedOnly );
183+
}
184+
else
185+
{
186+
mUseSelectionCheckBox->setChecked( false );
187+
mUseSelectionCheckBox->setEnabled( false );
188+
}
189+
}
190+
mBlockChangedSignal--;
191+
if ( changed )
192+
emit valueChanged(); // and ensure we only ever raise one
193+
}
194+
195+
if ( !found )
196+
{
197+
const QString string = val.toString();
198+
if ( !string.isEmpty() )
199+
{
200+
mBlockChangedSignal++;
201+
if ( mCombo->findText( string ) < 0 )
202+
{
203+
QStringList additional = mCombo->additionalItems();
204+
additional.append( string );
205+
mCombo->setAdditionalItems( additional );
206+
}
207+
mCombo->setCurrentIndex( mCombo->findText( string ) ); // this may or may not throw a signal, so let's block it..
208+
mBlockChangedSignal--;
209+
if ( !mBlockChangedSignal )
210+
emit valueChanged(); // and ensure we only ever raise one
211+
}
212+
else if ( mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
213+
{
214+
mCombo->setLayer( nullptr );
215+
}
216+
}
217+
}
218+
219+
QVariant QgsProcessingMapLayerComboBox::value() const
220+
{
221+
if ( QgsMapLayer *layer = mCombo->currentLayer() )
222+
{
223+
if ( mUseSelectionCheckBox && mUseSelectionCheckBox->isChecked() )
224+
return QgsProcessingFeatureSourceDefinition( layer->id(), true );
225+
else
226+
return layer->id();
227+
}
228+
else
229+
{
230+
if ( !mCombo->currentText().isEmpty() )
231+
{
232+
if ( mUseSelectionCheckBox && mUseSelectionCheckBox->isChecked() )
233+
return QgsProcessingFeatureSourceDefinition( mCombo->currentText(), true );
234+
else
235+
return mCombo->currentText();
236+
}
237+
}
238+
return QVariant();
239+
}
240+
241+
242+
QgsMapLayer *QgsProcessingMapLayerComboBox::compatibleMapLayerFromMimeData( const QMimeData *data, bool &incompatibleLayerSelected ) const
243+
{
244+
incompatibleLayerSelected = false;
245+
const QgsMimeDataUtils::UriList uriList = QgsMimeDataUtils::decodeUriList( data );
246+
for ( const QgsMimeDataUtils::Uri &u : uriList )
247+
{
248+
// is this uri from the current project?
249+
if ( QgsMapLayer *layer = u.mapLayer() )
250+
{
251+
if ( mCombo->mProxyModel->acceptsLayer( layer ) )
252+
return layer;
253+
else
254+
{
255+
incompatibleLayerSelected = true;
256+
return nullptr;
257+
}
258+
}
259+
}
260+
return nullptr;
261+
}
262+
263+
264+
QString QgsProcessingMapLayerComboBox::compatibleUriFromMimeData( const QMimeData *data ) const
265+
{
266+
const QgsMimeDataUtils::UriList uriList = QgsMimeDataUtils::decodeUriList( data );
267+
for ( const QgsMimeDataUtils::Uri &u : uriList )
268+
{
269+
if ( ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName()
270+
|| mParameter->type() == QgsProcessingParameterVectorLayer::typeName()
271+
|| mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
272+
&& u.layerType == QLatin1String( "vector" ) && u.providerKey == QLatin1String( "ogr" ) )
273+
return u.uri;
274+
else if ( ( mParameter->type() == QgsProcessingParameterRasterLayer::typeName()
275+
|| mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
276+
&& u.layerType == QLatin1String( "raster" ) && u.providerKey == QLatin1String( "gdal" ) )
277+
return u.uri;
278+
else if ( ( mParameter->type() == QgsProcessingParameterMeshLayer::typeName()
279+
|| mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
280+
&& u.layerType == QLatin1String( "mesh" ) && u.providerKey == QLatin1String( "mdal" ) )
281+
return u.uri;
282+
}
283+
if ( !uriList.isEmpty() )
284+
return QString();
285+
286+
// second chance -- files dragged from file explorer, outside of QGIS
287+
QStringList rawPaths;
288+
if ( data->hasUrls() )
289+
{
290+
const QList< QUrl > urls = data->urls();
291+
rawPaths.reserve( urls.count() );
292+
for ( const QUrl &url : urls )
293+
{
294+
const QString local = url.toLocalFile();
295+
if ( !rawPaths.contains( local ) )
296+
rawPaths.append( local );
297+
}
298+
}
299+
if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
300+
rawPaths.append( data->text() );
301+
302+
for ( const QString &path : qgis::as_const( rawPaths ) )
303+
{
304+
QFileInfo file( path );
305+
if ( file.isFile() )
306+
{
307+
// TODO - we should check to see if it's a valid extension for the parameter, but that's non-trivial
308+
return path;
309+
}
310+
}
311+
312+
return QString();
313+
}
314+
315+
void QgsProcessingMapLayerComboBox::dragEnterEvent( QDragEnterEvent *event )
316+
{
317+
if ( !( event->possibleActions() & Qt::CopyAction ) )
318+
return;
319+
320+
bool incompatibleLayerSelected = false;
321+
QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData(), incompatibleLayerSelected );
322+
const QString uri = compatibleUriFromMimeData( event->mimeData() );
323+
if ( layer || ( !incompatibleLayerSelected && !uri.isEmpty() ) )
324+
{
325+
// dragged an acceptable layer, phew
326+
event->setDropAction( Qt::CopyAction );
327+
event->accept();
328+
mDragActive = true;
329+
mCombo->mHighlight = true;
330+
update();
331+
}
332+
}
333+
334+
void QgsProcessingMapLayerComboBox::dragLeaveEvent( QDragLeaveEvent *event )
335+
{
336+
QWidget::dragLeaveEvent( event );
337+
if ( mDragActive )
338+
{
339+
event->accept();
340+
mDragActive = false;
341+
mCombo->mHighlight = false;
342+
update();
343+
}
344+
}
345+
346+
void QgsProcessingMapLayerComboBox::dropEvent( QDropEvent *event )
347+
{
348+
if ( !( event->possibleActions() & Qt::CopyAction ) )
349+
return;
350+
351+
bool incompatibleLayerSelected = false;
352+
QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData(), incompatibleLayerSelected );
353+
const QString uri = compatibleUriFromMimeData( event->mimeData() );
354+
if ( layer || ( !incompatibleLayerSelected && !uri.isEmpty() ) )
355+
{
356+
// dropped an acceptable layer, phew
357+
setFocus( Qt::MouseFocusReason );
358+
event->setDropAction( Qt::CopyAction );
359+
event->accept();
360+
QgsProcessingContext context;
361+
setValue( layer ? QVariant::fromValue( layer ) : QVariant::fromValue( uri ), context );
362+
}
363+
mDragActive = false;
364+
mCombo->mHighlight = false;
365+
update();
366+
}
367+
368+
void QgsProcessingMapLayerComboBox::onLayerChanged( QgsMapLayer *layer )
369+
{
370+
if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
371+
{
372+
if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
373+
{
374+
if ( QgsVectorLayer *prevLayer = qobject_cast< QgsVectorLayer * >( mPrevLayer ) )
375+
{
376+
disconnect( prevLayer, &QgsVectorLayer::selectionChanged, this, &QgsProcessingMapLayerComboBox::selectionChanged );
377+
}
378+
if ( vl->selectedFeatureCount() == 0 )
379+
mUseSelectionCheckBox->setChecked( false );
380+
mUseSelectionCheckBox->setEnabled( vl->selectedFeatureCount() > 0 );
381+
connect( vl, &QgsVectorLayer::selectionChanged, this, &QgsProcessingMapLayerComboBox::selectionChanged );
382+
}
383+
}
384+
385+
mPrevLayer = layer;
386+
if ( !mBlockChangedSignal )
387+
emit valueChanged();
388+
}
389+
390+
void QgsProcessingMapLayerComboBox::selectionChanged( const QgsFeatureIds &selected, const QgsFeatureIds &, bool )
391+
{
392+
if ( selected.isEmpty() )
393+
mUseSelectionCheckBox->setChecked( false );
394+
mUseSelectionCheckBox->setEnabled( !selected.isEmpty() );
395+
}
396+
397+
398+
399+
///@endcond
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/***************************************************************************
2+
qgsprocessingmaplayercombobox.h
3+
-----------------------------
4+
begin : June 2019
5+
copyright : (C) 2019 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSPROCESSINGMAPLAYERCOMBOBOX_H
17+
#define QGSPROCESSINGMAPLAYERCOMBOBOX_H
18+
19+
#include "qgis.h"
20+
#include "qgis_gui.h"
21+
#include <QTreeView>
22+
#include "qgsprocessingtoolboxmodel.h"
23+
#include "qgsfeatureid.h"
24+
#include "qgsmimedatautils.h"
25+
#include "qgsprocessingcontext.h"
26+
27+
28+
class QgsMapLayerComboBox;
29+
class QToolButton;
30+
class QCheckBox;
31+
class QgsProcessingParameterDefinition;
32+
33+
///@cond PRIVATE
34+
35+
/**
36+
* Processing map layer combo box.
37+
* \ingroup gui
38+
* \warning Not part of stable API and may change in future QGIS releases.
39+
* \since QGIS 3.8
40+
*/
41+
class GUI_EXPORT QgsProcessingMapLayerComboBox : public QWidget
42+
{
43+
Q_OBJECT
44+
45+
public:
46+
47+
/**
48+
* Constructor for QgsProcessingMapLayerComboBox, with the specified \a parameter definition.
49+
*/
50+
QgsProcessingMapLayerComboBox( QgsProcessingParameterDefinition *parameter, QWidget *parent = nullptr );
51+
52+
/**
53+
* Sets the combo box to the specified \a layer, if \a layer is compatible with the
54+
* widget's parameter definition.
55+
*/
56+
void setLayer( QgsMapLayer *layer );
57+
58+
/**
59+
* Returns the current layer selected in the combobox, or NULLPTR if the selection cannot
60+
* be represented as a map layer.
61+
*
62+
* \warning Prefer calling value() instead, as it correctly encapsulates all valid
63+
* values which can be represented by the widget.
64+
*
65+
* \see currentText()
66+
*/
67+
QgsMapLayer *currentLayer();
68+
69+
/**
70+
* Returns the current text of the selected item in the combobox.
71+
*
72+
* \warning Prefer calling value() instead, as it correctly encapsulates all valid
73+
* values which can be represented by the widget.
74+
*
75+
* \see currentLayer()
76+
*/
77+
QString currentText();
78+
79+
/**
80+
* Sets the \a value shown in the widget.
81+
*
82+
* \see value()
83+
*/
84+
void setValue( const QVariant &value, QgsProcessingContext &context );
85+
86+
/**
87+
* Returns the current value of the widget.
88+
*
89+
* \see setValue()
90+
*/
91+
QVariant value() const;
92+
93+
signals:
94+
95+
/**
96+
* Emitted whenever the value is changed in the widget.
97+
*/
98+
void valueChanged();
99+
100+
/**
101+
* Emitted when the widget has triggered a file selection operation (to be
102+
* handled in Python for now).
103+
*/
104+
void triggerFileSelection();
105+
106+
protected:
107+
108+
void dragEnterEvent( QDragEnterEvent *event ) override;
109+
void dragLeaveEvent( QDragLeaveEvent *event ) override;
110+
void dropEvent( QDropEvent *event ) override;
111+
112+
private slots:
113+
114+
void onLayerChanged( QgsMapLayer *layer );
115+
void selectionChanged( const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect );
116+
117+
private:
118+
QgsProcessingParameterDefinition *mParameter = nullptr;
119+
QgsMapLayerComboBox *mCombo = nullptr;
120+
QToolButton *mSelectButton = nullptr;
121+
QCheckBox *mUseSelectionCheckBox = nullptr;
122+
bool mDragActive = false;
123+
QPointer< QgsMapLayer> mPrevLayer;
124+
int mBlockChangedSignal = 0;
125+
QgsMapLayer *compatibleMapLayerFromMimeData( const QMimeData *data, bool &incompatibleLayerSelected ) const;
126+
QString compatibleUriFromMimeData( const QMimeData *data ) const;
127+
};
128+
129+
///@endcond
130+
#endif // QGSPROCESSINGMAPLAYERCOMBOBOX_H

‎src/gui/qgsmaplayercombobox.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ void QgsMapLayerComboBox::dropEvent( QDropEvent *event )
208208
void QgsMapLayerComboBox::paintEvent( QPaintEvent *e )
209209
{
210210
QComboBox::paintEvent( e );
211-
if ( mDragActive )
211+
if ( mDragActive || mHighlight )
212212
{
213213
QPainter p( this );
214214
int width = 2; // width of highlight rectangle inside frame

‎src/gui/qgsmaplayercombobox.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,15 @@ class GUI_EXPORT QgsMapLayerComboBox : public QComboBox
153153
private:
154154
QgsMapLayerProxyModel *mProxyModel = nullptr;
155155
bool mDragActive = false;
156+
bool mHighlight = false;
156157

157158
/**
158159
* Returns a map layer, compatible with the filters set for the combo box, from
159160
* the specified mime \a data (if possible!).
160161
*/
161162
QgsMapLayer *compatibleMapLayerFromMimeData( const QMimeData *data ) const;
163+
164+
friend class QgsProcessingMapLayerComboBox;
162165
};
163166

164167
#endif // QGSMAPLAYERCOMBOBOX_H

‎tests/src/gui/testprocessinggui.cpp

Lines changed: 414 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.