QGIS API Documentation 3.99.0-Master (09f76ad7019)
Loading...
Searching...
No Matches
qgsprocessingoutputdestinationwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprocessingmatrixparameterdialog.cpp
3 ------------------------------------
4 Date : February 2019
5 Copyright : (C) 2019 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
17
18#include "qgsapplication.h"
20#include "qgsdatasourceuri.h"
23#include "qgsfileutils.h"
28#include "qgssettings.h"
29
30#include <QCheckBox>
31#include <QFileDialog>
32#include <QInputDialog>
33#include <QLocale>
34#include <QMenu>
35#include <QString>
36#include <QTextCodec>
37#include <QUrl>
38
39#include "moc_qgsprocessingoutputdestinationwidget.cpp"
40
41using namespace Qt::StringLiterals;
42
44
45QgsProcessingLayerOutputDestinationWidget::QgsProcessingLayerOutputDestinationWidget( const QgsProcessingDestinationParameter *param, bool defaultSelection, QWidget *parent )
46 : QWidget( parent )
47 , mParameter( param )
48 , mDefaultSelection( defaultSelection )
49{
50 Q_ASSERT( mParameter );
51
52 setupUi( this );
53
54 mActionTemporaryOutputIcon = new QAction(
55 QgsApplication::getThemeIcon( u"/mActionCreateMemory.svg"_s ),
56 tr( "Temporary Output" ),
57 this
58 );
59
60 leText->setClearButtonEnabled( false );
61
62 if ( !( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() || mParameter->type() == QgsProcessingParameterFileDestination::typeName() ) )
63 {
64 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
65 }
66
67 connect( leText, &QLineEdit::textChanged, this, &QgsProcessingLayerOutputDestinationWidget::textChanged );
68
69 mMenu = new QMenu( this );
70 connect( mMenu, &QMenu::aboutToShow, this, &QgsProcessingLayerOutputDestinationWidget::menuAboutToShow );
71 mSelectButton->setMenu( mMenu );
72 mSelectButton->setPopupMode( QToolButton::InstantPopup );
73
74 QgsSettings settings;
75 mEncoding = QgsProcessingUtils::resolveDefaultEncoding( settings.value( u"/Processing/encoding"_s, u"System"_s ).toString() );
76 settings.setValue( u"/Processing/encoding"_s, mEncoding );
77
78 if ( !mParameter->defaultValueForGui().isValid() )
79 {
80 // no default value -- we default to either skipping the output or a temporary output, depending on the createByDefault value
81 if ( mParameter->flags() & Qgis::ProcessingParameterFlag::Optional && !mParameter->createByDefault() )
82 setValue( QVariant() );
83 else
85 }
86 else
87 {
88 setValue( mParameter->defaultValueForGui() );
89 }
90
91 setToolTip( mParameter->toolTip() );
92
93 setAcceptDrops( true );
94 leText->setAcceptDrops( false );
95}
96
97bool QgsProcessingLayerOutputDestinationWidget::outputIsSkipped() const
98{
99 return leText->text().isEmpty() && !mUseTemporary;
100}
101
102void QgsProcessingLayerOutputDestinationWidget::setValue( const QVariant &value )
103{
104 const bool prevSkip = outputIsSkipped();
105 mUseRemapping = false;
106 if ( !value.isValid() || ( value.userType() == QMetaType::Type::QString && couldBeTemporaryLayerName( value.toString() ) ) )
107 {
108 if ( mParameter->flags() & Qgis::ProcessingParameterFlag::Optional )
109 {
110 skipOutput();
111 emit destinationChanged();
112 }
113 else
114 {
115 saveToTemporary( value.toString() );
116 }
117 }
118 else
119 {
120 if ( value.toString() == "memory:"_L1 || value.toString() == QgsProcessing::TEMPORARY_OUTPUT )
121 {
122 saveToTemporary();
123 if ( !consideredEqualTemporaryOutputValues( mPreviousValueString, variantToString( value ) ) )
124 {
125 emit destinationChanged();
126 }
127 }
128 else if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
129 {
131 if ( def.sink.staticValue().toString() == "memory:"_L1 || def.sink.staticValue().toString() == QgsProcessing::TEMPORARY_OUTPUT || def.sink.staticValue().toString().isEmpty() )
132 {
133 saveToTemporary( def.destinationName );
134 }
135 else
136 {
137 leText->setText( def.sink.staticValue().toString() );
138 if ( prevSkip )
139 emit skipOutputChanged( false );
140 }
141 mUseRemapping = def.useRemapping();
142 mRemapDefinition = def.remappingDefinition();
143 mEncoding = def.createOptions.value( u"fileEncoding"_s ).toString();
144 }
145 else
146 {
147 leText->setText( value.toString() );
148 if ( prevSkip )
149 emit skipOutputChanged( false );
150 }
151 }
152
153 mPreviousValueString = variantToString( value );
154}
155
156QVariant QgsProcessingLayerOutputDestinationWidget::value() const
157{
158 QgsSettings settings;
159 QString key;
160 if ( mUseTemporary && mParameter->type() == QgsProcessingParameterFeatureSink::typeName() )
161 {
163 }
164 else if ( mUseTemporary && !mDefaultSelection )
165 {
167 }
168 else
169 {
170 key = leText->text();
171 }
172
173 if ( key.isEmpty() && mParameter->flags() & Qgis::ProcessingParameterFlag::Optional )
174 return QVariant();
175
176 QString provider;
177 QString uri;
178 if ( !key.isEmpty() && key != QgsProcessing::TEMPORARY_OUTPUT
179 && !key.startsWith( "memory:"_L1 )
180 && !key.startsWith( "ogr:"_L1 )
181 && !key.startsWith( "postgres:"_L1 )
182 && !key.startsWith( "postgis:"_L1 )
183 && !QgsProcessingUtils::decodeProviderKeyAndUri( key, provider, uri ) )
184 {
185 // output should be a file path
186 QString folder = QFileInfo( key ).path();
187 if ( folder == '.' )
188 {
189 // output name does not include a folder - use default
190 QString defaultFolder = settings.value( u"/Processing/Configuration/OUTPUTS_FOLDER"_s, u"%1/processing"_s.arg( QDir::homePath() ) ).toString();
191 QDir destDir( defaultFolder );
192 if ( !destDir.exists() && !QDir().mkpath( defaultFolder ) )
193 {
194 QgsDebugError( u"Can't create output folder '%1'"_s.arg( defaultFolder ) );
195 }
196 key = destDir.filePath( key );
197 }
198 }
199
200 if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() )
201 return key;
202 else if ( mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
203 return key;
204
206 value.createOptions.insert( u"fileEncoding"_s, mEncoding );
207 if ( mUseRemapping )
208 value.setRemappingDefinition( mRemapDefinition );
209
210 // this marks named temporary layer
211 if ( key == QgsProcessing::TEMPORARY_OUTPUT && couldBeTemporaryLayerName( leText->text() ) )
212 {
213 const QString memoryLayerName = memoryProviderLayerName( leText->text() );
214 value.destinationName = memoryLayerName.isEmpty() ? leText->text() : memoryLayerName;
215 }
216
217
218 if ( !mFormat.isEmpty() )
219 value.setFormat( mFormat );
220
221 return value;
222}
223
224void QgsProcessingLayerOutputDestinationWidget::setWidgetContext( const QgsProcessingParameterWidgetContext &context )
225{
226 mBrowserModel = context.browserModel();
227}
228
229void QgsProcessingLayerOutputDestinationWidget::setContext( QgsProcessingContext *context )
230{
231 mContext = context;
232}
233
234void QgsProcessingLayerOutputDestinationWidget::registerProcessingParametersGenerator( QgsProcessingParametersGenerator *generator )
235{
236 mParametersGenerator = generator;
237}
238
239void QgsProcessingLayerOutputDestinationWidget::addOpenAfterRunningOption()
240{
241 Q_ASSERT( mOpenAfterRunningCheck == nullptr );
242 mOpenAfterRunningCheck = new QCheckBox( tr( "Open output file after running algorithm" ) );
243 mOpenAfterRunningCheck->setChecked( !outputIsSkipped() );
244 mOpenAfterRunningCheck->setEnabled( !outputIsSkipped() );
245 gridLayout->addWidget( mOpenAfterRunningCheck, 1, 0, 1, 2 );
246
247 connect( this, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged, this, [this]( bool skipped ) {
248 bool enabled = !skipped;
249 mOpenAfterRunningCheck->setEnabled( enabled );
250 mOpenAfterRunningCheck->setChecked( enabled );
251 } );
252}
253
254bool QgsProcessingLayerOutputDestinationWidget::openAfterRunning() const
255{
256 return mOpenAfterRunningCheck && mOpenAfterRunningCheck->isChecked();
257}
258
259void QgsProcessingLayerOutputDestinationWidget::menuAboutToShow()
260{
261 mMenu->clear();
262
263 if ( !mDefaultSelection )
264 {
265 if ( mParameter->flags() & Qgis::ProcessingParameterFlag::Optional )
266 {
267 QAction *actionSkipOutput = new QAction( tr( "Skip Output" ), this );
268 connect( actionSkipOutput, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::skipOutput );
269 mMenu->addAction( actionSkipOutput );
270 }
271
272 QAction *actionSaveToTemp = nullptr;
273 if ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() && mParameter->supportsNonFileBasedOutput() )
274 {
275 // use memory layers for temporary layers if supported
276 actionSaveToTemp = new QAction( tr( "Create Temporary Layer" ), this );
277 }
278 else if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() )
279 {
280 actionSaveToTemp = new QAction( tr( "Save to a Temporary Directory" ), this );
281 }
282 else
283 {
284 actionSaveToTemp = new QAction( tr( "Save to a Temporary File" ), this );
285 }
286
287 connect( actionSaveToTemp, &QAction::triggered, this, [this]() { saveToTemporary(); } );
288 mMenu->addAction( actionSaveToTemp );
289 }
290
291 QAction *actionSaveToFile = nullptr;
292 if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() )
293 {
294 actionSaveToFile = new QAction( tr( "Save to Directory…" ), this );
295 connect( actionSaveToFile, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::selectDirectory );
296 }
297 else
298 {
299 actionSaveToFile = new QAction( tr( "Save to File…" ), this );
300 connect( actionSaveToFile, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::selectFile );
301 }
302 mMenu->addAction( actionSaveToFile );
303
304 if ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() && mParameter->supportsNonFileBasedOutput() )
305 {
306 QAction *actionSaveToGpkg = new QAction( tr( "Save to GeoPackage…" ), this );
307 connect( actionSaveToGpkg, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::saveToGeopackage );
308 mMenu->addAction( actionSaveToGpkg );
309
310 QAction *actionSaveToDatabase = new QAction( tr( "Save to Database Table…" ), this );
311 connect( actionSaveToDatabase, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::saveToDatabase );
312 mMenu->addAction( actionSaveToDatabase );
313
314 if ( mParameter->algorithm() && qgis::down_cast<const QgsProcessingParameterFeatureSink *>( mParameter )->supportsAppend() )
315 {
316 mMenu->addSeparator();
317 QAction *actionAppendToLayer = new QAction( tr( "Append to Layer…" ), this );
318 connect( actionAppendToLayer, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::appendToLayer );
319 mMenu->addAction( actionAppendToLayer );
320 if ( mUseRemapping )
321 {
322 QAction *editMappingAction = new QAction( tr( "Edit Field Mapping…" ), this );
323 connect( editMappingAction, &QAction::triggered, this, [this] {
324 setAppendDestination( value().value<QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), mRemapDefinition.destinationFields() );
325 } );
326 mMenu->addAction( editMappingAction );
327 }
328 }
329 }
330
331 if ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() )
332 {
333 mMenu->addSeparator();
334 QAction *actionSetEncoding = new QAction( tr( "Change File Encoding (%1)…" ).arg( mEncoding ), this );
335 connect( actionSetEncoding, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::selectEncoding );
336 mMenu->addAction( actionSetEncoding );
337 }
338}
339
340void QgsProcessingLayerOutputDestinationWidget::skipOutput()
341{
343 leText->setPlaceholderText( tr( "[Skip output]" ) );
344 leText->removeAction( mActionTemporaryOutputIcon );
345 leText->clear();
346 mPreviousValueString.clear();
347 mUseTemporary = false;
348 mUseRemapping = false;
349
350 emit skipOutputChanged( true );
351}
352
353void QgsProcessingLayerOutputDestinationWidget::setupPlaceholderText()
354{
355 if ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() && mParameter->supportsNonFileBasedOutput() )
356 {
357 leText->setPlaceholderText( tr( "[Create temporary layer]" ) );
358 }
359 else if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() )
360 {
361 leText->setPlaceholderText( tr( "[Save to temporary folder]" ) );
362 }
363 else
364 {
365 leText->setPlaceholderText( tr( "[Save to temporary file]" ) );
366 }
367}
368
369void QgsProcessingLayerOutputDestinationWidget::saveToTemporary( const QString &name )
370{
371 const bool prevSkip = outputIsSkipped();
372
373 // special case, destination has not changed (empty string to empty string) but we need to show the temporary icon
374 if ( prevSkip && name.isEmpty() )
375 {
376 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
377 }
378
379 setupPlaceholderText();
380
381 if ( name.isEmpty() )
382 leText->clear();
383
384 if ( mUseTemporary && leText->text() == name )
385 return;
386
387 leText->setText( name );
388
389 mUseTemporary = true;
390 mUseRemapping = false;
391 if ( prevSkip )
392 emit skipOutputChanged( false );
393}
394
395void QgsProcessingLayerOutputDestinationWidget::selectDirectory()
396{
397 QString lastDir = leText->text();
398 QgsSettings settings;
399 if ( lastDir.isEmpty() )
400 lastDir = settings.value( u"/Processing/LastOutputPath"_s, QDir::homePath() ).toString();
401
402 const QString dirName = QFileDialog::getExistingDirectory( this, tr( "Select Directory" ), lastDir, QFileDialog::Options() );
403 if ( !dirName.isEmpty() )
404 {
405 leText->setText( QDir::toNativeSeparators( dirName ) );
406 settings.setValue( u"/Processing/LastOutputPath"_s, dirName );
407 mUseTemporary = false;
408 mUseRemapping = false;
409 emit skipOutputChanged( false );
410 }
411}
412
413void QgsProcessingLayerOutputDestinationWidget::selectFile()
414{
415 const QString fileFilter = mParameter->createFileFilter();
416
417 QgsSettings settings;
418
419 QString lastExtPath;
420 QString lastExt;
421 QString lastFormatPath;
422 QString lastFormat;
423 if ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() || mParameter->type() == QgsProcessingParameterVectorDestination::typeName() )
424 {
425 lastExtPath = u"/Processing/LastVectorOutputExt"_s;
426 lastExt = settings.value( lastExtPath, u".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
427 }
428 else if ( mParameter->type() == QgsProcessingParameterRasterDestination::typeName() )
429 {
430 const QgsProcessingParameterRasterDestination *dest = dynamic_cast<const QgsProcessingParameterRasterDestination *>( mParameter );
431 Q_ASSERT( dest );
432 lastFormatPath = u"/Processing/LastRasterOutputFormat"_s;
433 lastFormat = settings.value( lastFormatPath, dest->defaultFileFormat() ).toString();
434 }
435 else if ( mParameter->type() == QgsProcessingParameterPointCloudDestination::typeName() )
436 {
437 lastExtPath = u"/Processing/LastPointCloudOutputExt"_s;
438 lastExt = settings.value( lastExtPath, u".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
439 }
440 else if ( mParameter->type() == QgsProcessingParameterVectorTileDestination::typeName() )
441 {
442 lastExtPath = u"/Processing/LastVectorTileOutputExt"_s;
443 lastExt = settings.value( lastExtPath, u".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
444 }
445
446 // get default filter
447 const QStringList filters = fileFilter.split( u";;"_s );
448 QString lastFilter;
449 for ( const QString &f : filters )
450 {
451 if ( !lastFormat.isEmpty() && f.contains( lastFormat, Qt::CaseInsensitive ) )
452 {
453 lastFilter = f;
454 break;
455 }
456 else if ( !lastExt.isEmpty() && f.contains( u"*.%1"_s.arg( lastExt ), Qt::CaseInsensitive ) )
457 {
458 lastFilter = f;
459 break;
460 }
461 }
462
463 QString path;
464 if ( settings.contains( u"/Processing/LastOutputPath"_s ) )
465 path = settings.value( u"/Processing/LastOutputPath"_s ).toString();
466 else
467 path = settings.value( u"/Processing/Configuration/OUTPUTS_FOLDER"_s ).toString();
468
469 const bool dontConfirmOverwrite = mParameter->metadata().value( u"widget_wrapper"_s ).toMap().value( u"dontconfirmoverwrite"_s, false ).toBool();
470
471 QString filename = QFileDialog::getSaveFileName( this, tr( "Save file" ), path, fileFilter, &lastFilter, dontConfirmOverwrite ? QFileDialog::Options( QFileDialog::DontConfirmOverwrite ) : QFileDialog::Options() );
472 if ( !filename.isEmpty() )
473 {
474 mUseTemporary = false;
475 mUseRemapping = false;
476 if ( mParameter->type() == QgsProcessingParameterRasterDestination::typeName() )
477 {
478 int spacePos = static_cast<int>( lastFilter.indexOf( ' ' ) );
479 if ( spacePos > 0 )
480 {
481 mFormat = lastFilter.left( spacePos );
482 }
483 }
484 filename = QgsFileUtils::addExtensionFromFilter( filename, lastFilter );
485
486 leText->setText( filename );
487
488 settings.setValue( u"/Processing/LastOutputPath"_s, QFileInfo( filename ).path() );
489 if ( !lastFormatPath.isEmpty() && !mFormat.isEmpty() )
490 settings.setValue( lastFormatPath, mFormat );
491 else if ( !lastExtPath.isEmpty() )
492 settings.setValue( lastExtPath, QFileInfo( filename ).suffix().toLower() );
493
494 emit skipOutputChanged( false );
495 }
496 // return dialog focus on Mac
497 activateWindow();
498 raise();
499}
500
501void QgsProcessingLayerOutputDestinationWidget::saveToGeopackage()
502{
503 QgsSettings settings;
504 QString lastPath = settings.value( u"/Processing/LastOutputPath"_s, QString() ).toString();
505 if ( lastPath.isEmpty() )
506 lastPath = settings.value( u"/Processing/Configuration/OUTPUTS_FOLDER"_s, QString() ).toString();
507
508 QString filename = QFileDialog::getSaveFileName( this, tr( "Save to GeoPackage" ), lastPath, tr( "GeoPackage files (*.gpkg);;All files (*.*)" ), nullptr, QFileDialog::DontConfirmOverwrite );
509 // return dialog focus on Mac
510 activateWindow();
511 raise();
512
513 if ( filename.isEmpty() )
514 return;
515
516 const QString layerName = QInputDialog::getText( this, tr( "Save to GeoPackage" ), tr( "Layer name" ), QLineEdit::Normal, mParameter->name().toLower() );
517 if ( layerName.isEmpty() )
518 return;
519
520 mUseTemporary = false;
521 mUseRemapping = false;
522
523 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"gpkg"_s );
524
525 settings.setValue( u"/Processing/LastOutputPath"_s, QFileInfo( filename ).path() );
526
528 uri.setTable( layerName );
529 uri.setDatabase( filename );
530
531 QString geomColumn;
532 if ( const QgsProcessingParameterFeatureSink *sink = dynamic_cast<const QgsProcessingParameterFeatureSink *>( mParameter ) )
533 {
534 if ( sink->hasGeometry() )
535 geomColumn = u"geom"_s;
536 }
537 uri.setGeometryColumn( geomColumn );
538
539 leText->setText( u"ogr:%1"_s.arg( uri.uri() ) );
540
541 emit skipOutputChanged( false );
542}
543
544void QgsProcessingLayerOutputDestinationWidget::saveToDatabase()
545{
546 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
547 {
548 QgsNewDatabaseTableNameWidget *widget = new QgsNewDatabaseTableNameWidget( mBrowserModel, QStringList() << u"postgres"_s << u"mssql"_s << u"ogr"_s << u"hana"_s << u"spatialite"_s << u"oracle"_s, this );
549 widget->setPanelTitle( tr( "Save “%1” to Database Table" ).arg( mParameter->description() ) );
550 widget->setAcceptButtonVisible( true );
551
552 panel->openPanel( widget );
553
554 auto changed = [this, widget] {
555 mUseTemporary = false;
556 mUseRemapping = false;
557
558 QString geomColumn;
559 if ( const QgsProcessingParameterFeatureSink *sink = dynamic_cast<const QgsProcessingParameterFeatureSink *>( mParameter ) )
560 {
561 if ( sink->hasGeometry() )
562 geomColumn = widget->dataProviderKey() == "oracle"_L1 ? u"GEOM"_s : u"geom"_s;
563 }
564
565 if ( widget->dataProviderKey() == "ogr"_L1 )
566 {
568 uri.setTable( widget->table() );
569 uri.setDatabase( widget->schema() );
570 uri.setGeometryColumn( geomColumn );
571 leText->setText( u"ogr:%1"_s.arg( uri.uri() ) );
572 }
573 else
574 {
575 QgsDataSourceUri uri( widget->uri() );
576 uri.setGeometryColumn( geomColumn );
577 leText->setText( QgsProcessingUtils::encodeProviderKeyAndUri( widget->dataProviderKey(), uri.uri() ) );
578 }
579
580 emit skipOutputChanged( false );
581 };
582
583 connect( widget, &QgsNewDatabaseTableNameWidget::tableNameChanged, this, [changed] { changed(); } );
584 connect( widget, &QgsNewDatabaseTableNameWidget::schemaNameChanged, this, [changed] { changed(); } );
585 connect( widget, &QgsNewDatabaseTableNameWidget::validationChanged, this, [changed] { changed(); } );
586 connect( widget, &QgsNewDatabaseTableNameWidget::providerKeyChanged, this, [changed] { changed(); } );
587 connect( widget, &QgsNewDatabaseTableNameWidget::accepted, this, [changed, widget] {
588 changed();
589 widget->acceptPanel();
590 } );
591 }
592}
593
594void QgsProcessingLayerOutputDestinationWidget::appendToLayer()
595{
596 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
597 {
599 widget->setPanelTitle( tr( "Append \"%1\" to Layer" ).arg( mParameter->description() ) );
600
601 panel->openPanel( widget );
602
603 connect( widget, &QgsDataSourceSelectWidget::itemTriggered, this, [widget]( const QgsMimeDataUtils::Uri & ) {
604 widget->acceptPanel();
605 } );
606 connect( widget, &QgsPanelWidget::panelAccepted, this, [this, widget]() {
607 if ( widget->uri().uri.isEmpty() )
608 setValue( QVariant() );
609 else
610 {
611 // get fields for destination
612 auto dest = std::make_unique<QgsVectorLayer>( widget->uri().uri, QString(), widget->uri().providerKey );
613 if ( widget->uri().providerKey == "ogr"_L1 )
614 setAppendDestination( widget->uri().uri, dest->fields() );
615 else
616 setAppendDestination( QgsProcessingUtils::encodeProviderKeyAndUri( widget->uri().providerKey, widget->uri().uri ), dest->fields() );
617 }
618 } );
619 }
620}
621
622
623void QgsProcessingLayerOutputDestinationWidget::setAppendDestination( const QString &uri, const QgsFields &destFields )
624{
625 const QgsProcessingAlgorithm *alg = mParameter->algorithm();
626 QVariantMap props;
627 if ( mParametersGenerator )
628 props = mParametersGenerator->createProcessingParameters();
629 props.insert( mParameter->name(), uri );
630
631 const QgsProcessingAlgorithm::VectorProperties outputProps = alg->sinkProperties( mParameter->name(), props, *mContext, QMap<QString, QgsProcessingAlgorithm::VectorProperties>() );
633 {
634 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
635 {
636 // get mapping from fields output by algorithm to destination fields
637 QgsFieldMappingWidget *widget = new QgsFieldMappingWidget( nullptr, outputProps.fields, destFields );
638 widget->setPanelTitle( tr( "Append \"%1\" to Layer" ).arg( mParameter->description() ) );
639 if ( !mRemapDefinition.fieldMap().isEmpty() )
640 widget->setFieldPropertyMap( mRemapDefinition.fieldMap() );
641
642 panel->openPanel( widget );
643
644 connect( widget, &QgsPanelWidget::panelAccepted, this, [this, outputProps, widget, destFields, uri]() {
647 remap.setSourceCrs( outputProps.crs );
648 remap.setFieldMap( widget->fieldPropertyMap() );
649 remap.setDestinationFields( destFields );
650 def.setRemappingDefinition( remap );
651 setValue( def );
652 } );
653 }
654 }
655}
656
657void QgsProcessingLayerOutputDestinationWidget::selectEncoding()
658{
659 QgsEncodingSelectionDialog dialog( this, tr( "File encoding" ), mEncoding );
660 if ( dialog.exec() )
661 {
662 mEncoding = QgsProcessingUtils::resolveDefaultEncoding( dialog.encoding() );
663
664 QgsSettings settings;
665 settings.setValue( u"/Processing/encoding"_s, mEncoding );
666
667 emit destinationChanged();
668 }
669}
670
671void QgsProcessingLayerOutputDestinationWidget::textChanged( const QString &text )
672{
673 // since the text changed we can't use outputIsSkipped() so we check against the previous value
674 const bool prevSkip = !mUseTemporary && mPreviousValueString.isEmpty();
675 if ( prevSkip )
676 {
677 setupPlaceholderText();
678 emit skipOutputChanged( false );
679 }
680
681 mUseRemapping = false;
682
683 if ( couldBeTemporaryLayerName( text ) || text == "memory:"_L1 )
684 {
685 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
686 mUseTemporary = true;
687 }
688 else
689 {
690 leText->removeAction( mActionTemporaryOutputIcon );
691 mUseTemporary = false;
692 }
693
694 // emit destinationChanged only if the value actually changed
695 if ( mPreviousValueString != text )
696 {
697 emit destinationChanged();
698 }
699
700 mPreviousValueString = text;
701}
702
703
704QString QgsProcessingLayerOutputDestinationWidget::mimeDataToPath( const QMimeData *data )
705{
707 for ( const QgsMimeDataUtils::Uri &u : uriList )
708 {
709 if ( ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName()
711 || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
712 && u.layerType == "vector"_L1 && u.providerKey == "ogr"_L1 )
713 {
714 return u.uri;
715 }
716 else if ( ( mParameter->type() == QgsProcessingParameterRasterDestination::typeName()
717 || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
718 && u.layerType == "raster"_L1 && u.providerKey == "gdal"_L1 )
719 {
720 return u.uri;
721 }
722 else if ( ( mParameter->type() == QgsProcessingParameterPointCloudDestination::typeName()
723 || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
724 && u.layerType == "pointcloud"_L1 && ( u.providerKey == "ept"_L1 || u.providerKey == "pdal"_L1 ) )
725 {
726 return u.uri;
727 }
728#if 0
729 else if ( ( mParameter->type() == QgsProcessingParameterMeshDestination::typeName()
730 || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
731 && u.layerType == "mesh"_L1 && u.providerKey == "mdal"_L1 )
732 return u.uri;
733
734#endif
735 else if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName()
736 && u.layerType == "directory"_L1 )
737 {
738 return u.uri;
739 }
740 }
741 if ( !uriList.isEmpty() )
742 return QString();
743
744 // files dragged from file explorer, outside of QGIS
745 QStringList rawPaths;
746 if ( data->hasUrls() )
747 {
748 const QList<QUrl> urls = data->urls();
749 rawPaths.reserve( urls.count() );
750 for ( const QUrl &url : urls )
751 {
752 const QString local = url.toLocalFile();
753 if ( !rawPaths.contains( local ) )
754 rawPaths.append( local );
755 }
756 }
757 if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
758 rawPaths.append( data->text() );
759
760 for ( const QString &path : std::as_const( rawPaths ) )
761 {
762 QFileInfo file( path );
763 if ( file.isFile() && ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() || mParameter->type() == QgsProcessingParameterVectorDestination::typeName() || mParameter->type() == QgsProcessingParameterRasterDestination::typeName() || mParameter->type() == QgsProcessingParameterVectorDestination::typeName() || mParameter->type() == QgsProcessingParameterFileDestination::typeName() || mParameter->type() == QgsProcessingParameterPointCloudDestination::typeName() ) )
764 {
765 // TODO - we should check to see if it's a valid extension for the parameter, but that's non-trivial
766 return path;
767 }
768 else if ( file.isDir() && ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() ) )
769 return path;
770 }
771
772 return QString();
773}
774
775void QgsProcessingLayerOutputDestinationWidget::dragEnterEvent( QDragEnterEvent *event )
776{
777 if ( !( event->possibleActions() & Qt::CopyAction ) )
778 return;
779
780 const QString path = mimeDataToPath( event->mimeData() );
781 if ( !path.isEmpty() )
782 {
783 // dragged an acceptable path, phew
784 event->setDropAction( Qt::CopyAction );
785 event->accept();
786 leText->setHighlighted( true );
787 }
788}
789
790void QgsProcessingLayerOutputDestinationWidget::dragLeaveEvent( QDragLeaveEvent *event )
791{
792 QWidget::dragLeaveEvent( event );
793 if ( leText->isHighlighted() )
794 {
795 event->accept();
796 leText->setHighlighted( false );
797 }
798}
799
800void QgsProcessingLayerOutputDestinationWidget::dropEvent( QDropEvent *event )
801{
802 if ( !( event->possibleActions() & Qt::CopyAction ) )
803 return;
804
805 const QString path = mimeDataToPath( event->mimeData() );
806 if ( !path.isEmpty() )
807 {
808 // dropped an acceptable path, phew
809 setFocus( Qt::MouseFocusReason );
810 event->setDropAction( Qt::CopyAction );
811 event->accept();
812 setValue( path );
813 }
814 leText->setHighlighted( false );
815}
816
817QString QgsProcessingLayerOutputDestinationWidget::memoryProviderLayerName( const QString &value ) const
818{
819 if ( value == "memory:"_L1 )
820 return QString();
821
822 QString provider;
823 QString uri;
824 bool hasProviderAndUri = QgsProcessingUtils::decodeProviderKeyAndUri( value, provider, uri );
825
826 if ( hasProviderAndUri && provider == "memory"_L1 )
827 {
828 return uri;
829 }
830
831 return QString();
832}
833
834bool QgsProcessingLayerOutputDestinationWidget::couldBeTemporaryLayerName( const QString &value ) const
835{
836 if ( value == QgsProcessing::TEMPORARY_OUTPUT || mParameter->type() != QgsProcessingParameterFeatureSink::typeName() || !mParameter->supportsNonFileBasedOutput() )
837 return false;
838
839 if ( value.isEmpty() )
840 return true;
841
842 if ( value == "memory:"_L1 )
843 return false;
844
845 QString provider;
846 QString uri;
847 const bool hasProviderAndUri = QgsProcessingUtils::decodeProviderKeyAndUri( value, provider, uri );
848
849 if ( provider == "memory"_L1 )
850 return true;
851
852 if ( hasProviderAndUri )
853 return false;
854
855 if ( QFileInfo( value ).isAbsolute() || !QFileInfo( value ).suffix().isEmpty() )
856 return false;
857
858 return true;
859}
860
861bool QgsProcessingLayerOutputDestinationWidget::consideredEqualTemporaryOutputValues( const QString &val1, const QString &val2 ) const
862{
863 if ( ( val1 == "memory:"_L1 && val2 == QgsProcessing::TEMPORARY_OUTPUT ) || ( val1 == QgsProcessing::TEMPORARY_OUTPUT && val2 == "memory:"_L1 ) || val1 == val2 )
864 return true;
865
866 return false;
867}
868
869QString QgsProcessingLayerOutputDestinationWidget::variantToString( const QVariant &value ) const
870{
871 if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
872 {
873 return value.value<QgsProcessingOutputLayerDefinition>().sink.staticValue().toString();
874 }
875 else
876 {
877 return value.toString();
878 }
879}
880
@ Available
Properties are available.
Definition qgis.h:3715
@ Vector
Vector layer.
Definition qgis.h:194
@ Optional
Parameter is optional.
Definition qgis.h:3836
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Embeds the browser view to select an existing data source.
void itemTriggered(const QgsMimeDataUtils::Uri &uri)
Emitted when an item is triggered, e.g.
QgsMimeDataUtils::Uri uri() const
Returns the (possibly invalid) uri of the selected data source.
Stores the component parts of a data source URI (e.g.
void setTable(const QString &table)
Sets table to table.
void setGeometryColumn(const QString &geometryColumn)
Sets geometry column name to geometryColumn.
QString uri(bool expandAuthConfig=true) const
Returns the complete URI as a string.
void setDatabase(const QString &database)
Sets the URI database name.
A dialog which presents the user with a choice of file encodings.
Creates a mapping from one set of QgsFields to another, for each set of "destination" fields an expre...
QMap< QString, QgsProperty > fieldPropertyMap() const
Returns a map of destination field name to QgsProperty definition for field value,...
void setFieldPropertyMap(const QMap< QString, QgsProperty > &map)
Sets a map of destination field name to QgsProperty definition for field value.
Container of fields for a vector layer.
Definition qgsfields.h:46
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
static QString addExtensionFromFilter(const QString &fileName, const QString &filter)
Ensures that a fileName ends with an extension from the specified filter string.
QList< QgsMimeDataUtils::Uri > UriList
static UriList decodeUriList(const QMimeData *data)
A widget which embeds the browser view to select a DB schema and a new table name.
QString table() const
Returns the current name of the new table.
QString dataProviderKey() const
Returns the currently selected data item provider key.
void setAcceptButtonVisible(bool visible)
Sets whether the optional "Ok"/accept button should be visible.
void providerKeyChanged(const QString &providerKey)
This signal is emitted when the selects a data provider or a schema name that has a different data pr...
QString schema() const
Returns the currently selected schema or file path (in case of filesystem-based DBs like spatialite o...
void tableNameChanged(const QString &tableName)
This signal is emitted when the user enters a table name.
void accepted()
Emitted when the OK/accept button is clicked.
void validationChanged(bool isValid)
This signal is emitted whenever the validation status of the widget changes.
QString uri() const
Returns the (possibly blank) string representation of the new table data source URI.
void schemaNameChanged(const QString &schemaName)
This signal is emitted when the user selects a schema (or file path for filesystem-based DBs like spa...
Base class for any widget that can be shown as an inline panel.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void acceptPanel()
Accept the panel.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
Abstract base class for processing algorithms.
virtual QgsProcessingAlgorithm::VectorProperties sinkProperties(const QString &sink, const QVariantMap &parameters, QgsProcessingContext &context, const QMap< QString, QgsProcessingAlgorithm::VectorProperties > &sourceProperties) const
Returns the vector properties which will be used for the sink with matching name.
Contains information about the context in which a processing algorithm is executed.
Base class for all parameter definitions which represent file or layer destinations,...
Encapsulates settings relating to a feature sink or output raster layer for a processing algorithm.
QgsProperty sink
Sink/layer definition.
bool useRemapping() const
Returns true if the output uses a remapping definition.
QgsRemappingSinkDefinition remappingDefinition() const
Returns the output remapping definition, if useRemapping() is true.
QString destinationName
Name to use for sink if it's to be loaded into a destination project.
QVariantMap createOptions
Map of optional sink/layer creation options, which are passed to the underlying provider when creatin...
void setRemappingDefinition(const QgsRemappingSinkDefinition &definition)
Sets the remapping definition to use when adding features to the output layer.
A feature sink output for processing algorithms.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
A raster layer destination parameter, for specifying the destination path for a raster layer created ...
QString defaultFileFormat() const
Returns the default file format for destination file paths associated with this parameter.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
Contains settings which reflect the context in which a Processing parameter widget is shown.
QgsBrowserGuiModel * browserModel() const
Returns the browser model associated with the widget.
An interface for objects which can create sets of parameter values for processing algorithms.
static QString encodeProviderKeyAndUri(const QString &providerKey, const QString &uri)
Encodes a provider key and layer uri to a single string, for use with decodeProviderKeyAndUri().
static QString resolveDefaultEncoding(const QString &defaultEncoding="System")
Returns the default encoding.
static bool decodeProviderKeyAndUri(const QString &string, QString &providerKey, QString &uri)
Decodes a provider key and layer uri from an encoded string, for use with encodeProviderKeyAndUri().
static const QString TEMPORARY_OUTPUT
Constant used to indicate that a Processing algorithm output should be a temporary layer/file.
QVariant staticValue() const
Returns the current static value for the property.
Defines the parameters used to remap features when creating a QgsRemappingProxyFeatureSink.
void setFieldMap(const QMap< QString, QgsProperty > &map)
Sets the field mapping, which defines how to map the values from incoming features to destination fie...
void setDestinationFields(const QgsFields &fields)
Sets the fields for the destination sink.
void setSourceCrs(const QgsCoordinateReferenceSystem &source)
Sets the source crs used for reprojecting incoming features to the sink's destination CRS.
Stores settings for use within QGIS.
Definition qgssettings.h:68
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
bool contains(const QString &key, QgsSettings::Section section=QgsSettings::NoSection) const
Returns true if there exists a setting called key; returns false otherwise.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
RAII signal blocking class.
Definition qgis.h:6799
#define QgsDebugError(str)
Definition qgslogger.h:59
QString uri
Identifier of the data source recognized by its providerKey.
QString providerKey
For "vector" / "raster" type: provider id.
Properties of a vector source or sink used in an algorithm.
QgsCoordinateReferenceSystem crs
Coordinate Reference System.
Qgis::ProcessingPropertyAvailability availability
Availability of the properties. By default properties are not available.