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