QGIS API Documentation 3.99.0-Master (357b655ed83)
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 const QgsProcessingParameterRasterDestination *rasterParam = static_cast<const QgsProcessingParameterRasterDestination *>( mParameter );
479 const QList<QPair<QString, QString>> formatAndExtensions = rasterParam->supportedOutputRasterLayerFormatAndExtensions();
480 Q_ASSERT( formatAndExtensions.size() + 1 == filters.size() ); // filters have one more entry for all files
481 int idxFilter = 0;
482 for ( const QString &f : filters )
483 {
484 if ( f == lastFilter )
485 {
486 mFormat = formatAndExtensions[idxFilter].first;
487 break;
488 }
489 ++idxFilter;
490 }
491 }
492 filename = QgsFileUtils::addExtensionFromFilter( filename, lastFilter );
493
494 leText->setText( filename );
495
496 settings.setValue( u"/Processing/LastOutputPath"_s, QFileInfo( filename ).path() );
497 if ( !lastFormatPath.isEmpty() && !mFormat.isEmpty() )
498 settings.setValue( lastFormatPath, mFormat );
499 else if ( !lastExtPath.isEmpty() )
500 settings.setValue( lastExtPath, QFileInfo( filename ).suffix().toLower() );
501
502 emit skipOutputChanged( false );
503 }
504 // return dialog focus on Mac
505 activateWindow();
506 raise();
507}
508
509void QgsProcessingLayerOutputDestinationWidget::saveToGeopackage()
510{
511 QgsSettings settings;
512 QString lastPath = settings.value( u"/Processing/LastOutputPath"_s, QString() ).toString();
513 if ( lastPath.isEmpty() )
514 lastPath = settings.value( u"/Processing/Configuration/OUTPUTS_FOLDER"_s, QString() ).toString();
515
516 QString filename = QFileDialog::getSaveFileName( this, tr( "Save to GeoPackage" ), lastPath, tr( "GeoPackage files (*.gpkg);;All files (*.*)" ), nullptr, QFileDialog::DontConfirmOverwrite );
517 // return dialog focus on Mac
518 activateWindow();
519 raise();
520
521 if ( filename.isEmpty() )
522 return;
523
524 const QString layerName = QInputDialog::getText( this, tr( "Save to GeoPackage" ), tr( "Layer name" ), QLineEdit::Normal, mParameter->name().toLower() );
525 if ( layerName.isEmpty() )
526 return;
527
528 mUseTemporary = false;
529 mUseRemapping = false;
530
531 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"gpkg"_s );
532
533 settings.setValue( u"/Processing/LastOutputPath"_s, QFileInfo( filename ).path() );
534
536 uri.setTable( layerName );
537 uri.setDatabase( filename );
538
539 QString geomColumn;
540 if ( const QgsProcessingParameterFeatureSink *sink = dynamic_cast<const QgsProcessingParameterFeatureSink *>( mParameter ) )
541 {
542 if ( sink->hasGeometry() )
543 geomColumn = u"geom"_s;
544 }
545 uri.setGeometryColumn( geomColumn );
546
547 leText->setText( u"ogr:%1"_s.arg( uri.uri() ) );
548
549 emit skipOutputChanged( false );
550}
551
552void QgsProcessingLayerOutputDestinationWidget::saveToDatabase()
553{
554 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
555 {
556 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 );
557 widget->setPanelTitle( tr( "Save “%1” to Database Table" ).arg( mParameter->description() ) );
558 widget->setAcceptButtonVisible( true );
559
560 panel->openPanel( widget );
561
562 auto changed = [this, widget] {
563 mUseTemporary = false;
564 mUseRemapping = false;
565
566 QString geomColumn;
567 if ( const QgsProcessingParameterFeatureSink *sink = dynamic_cast<const QgsProcessingParameterFeatureSink *>( mParameter ) )
568 {
569 if ( sink->hasGeometry() )
570 geomColumn = widget->dataProviderKey() == "oracle"_L1 ? u"GEOM"_s : u"geom"_s;
571 }
572
573 if ( widget->dataProviderKey() == "ogr"_L1 )
574 {
576 uri.setTable( widget->table() );
577 uri.setDatabase( widget->schema() );
578 uri.setGeometryColumn( geomColumn );
579 leText->setText( u"ogr:%1"_s.arg( uri.uri() ) );
580 }
581 else
582 {
583 QgsDataSourceUri uri( widget->uri() );
584 uri.setGeometryColumn( geomColumn );
585 leText->setText( QgsProcessingUtils::encodeProviderKeyAndUri( widget->dataProviderKey(), uri.uri() ) );
586 }
587
588 emit skipOutputChanged( false );
589 };
590
591 connect( widget, &QgsNewDatabaseTableNameWidget::tableNameChanged, this, [changed] { changed(); } );
592 connect( widget, &QgsNewDatabaseTableNameWidget::schemaNameChanged, this, [changed] { changed(); } );
593 connect( widget, &QgsNewDatabaseTableNameWidget::validationChanged, this, [changed] { changed(); } );
594 connect( widget, &QgsNewDatabaseTableNameWidget::providerKeyChanged, this, [changed] { changed(); } );
595 connect( widget, &QgsNewDatabaseTableNameWidget::accepted, this, [changed, widget] {
596 changed();
597 widget->acceptPanel();
598 } );
599 }
600}
601
602void QgsProcessingLayerOutputDestinationWidget::appendToLayer()
603{
604 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
605 {
607 widget->setPanelTitle( tr( "Append \"%1\" to Layer" ).arg( mParameter->description() ) );
608
609 panel->openPanel( widget );
610
611 connect( widget, &QgsDataSourceSelectWidget::itemTriggered, this, [widget]( const QgsMimeDataUtils::Uri & ) {
612 widget->acceptPanel();
613 } );
614 connect( widget, &QgsPanelWidget::panelAccepted, this, [this, widget]() {
615 if ( widget->uri().uri.isEmpty() )
616 setValue( QVariant() );
617 else
618 {
619 // get fields for destination
620 auto dest = std::make_unique<QgsVectorLayer>( widget->uri().uri, QString(), widget->uri().providerKey );
621 if ( widget->uri().providerKey == "ogr"_L1 )
622 setAppendDestination( widget->uri().uri, dest->fields() );
623 else
624 setAppendDestination( QgsProcessingUtils::encodeProviderKeyAndUri( widget->uri().providerKey, widget->uri().uri ), dest->fields() );
625 }
626 } );
627 }
628}
629
630
631void QgsProcessingLayerOutputDestinationWidget::setAppendDestination( const QString &uri, const QgsFields &destFields )
632{
633 const QgsProcessingAlgorithm *alg = mParameter->algorithm();
634 QVariantMap props;
635 if ( mParametersGenerator )
636 props = mParametersGenerator->createProcessingParameters();
637 props.insert( mParameter->name(), uri );
638
639 const QgsProcessingAlgorithm::VectorProperties outputProps = alg->sinkProperties( mParameter->name(), props, *mContext, QMap<QString, QgsProcessingAlgorithm::VectorProperties>() );
641 {
642 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
643 {
644 // get mapping from fields output by algorithm to destination fields
645 QgsFieldMappingWidget *widget = new QgsFieldMappingWidget( nullptr, outputProps.fields, destFields );
646 widget->setPanelTitle( tr( "Append \"%1\" to Layer" ).arg( mParameter->description() ) );
647 if ( !mRemapDefinition.fieldMap().isEmpty() )
648 widget->setFieldPropertyMap( mRemapDefinition.fieldMap() );
649
650 panel->openPanel( widget );
651
652 connect( widget, &QgsPanelWidget::panelAccepted, this, [this, outputProps, widget, destFields, uri]() {
655 remap.setSourceCrs( outputProps.crs );
656 remap.setFieldMap( widget->fieldPropertyMap() );
657 remap.setDestinationFields( destFields );
658 def.setRemappingDefinition( remap );
659 setValue( def );
660 } );
661 }
662 }
663}
664
665void QgsProcessingLayerOutputDestinationWidget::selectEncoding()
666{
667 QgsEncodingSelectionDialog dialog( this, tr( "File encoding" ), mEncoding );
668 if ( dialog.exec() )
669 {
670 mEncoding = QgsProcessingUtils::resolveDefaultEncoding( dialog.encoding() );
671
672 QgsSettings settings;
673 settings.setValue( u"/Processing/encoding"_s, mEncoding );
674
675 emit destinationChanged();
676 }
677}
678
679void QgsProcessingLayerOutputDestinationWidget::textChanged( const QString &text )
680{
681 // since the text changed we can't use outputIsSkipped() so we check against the previous value
682 const bool prevSkip = !mUseTemporary && mPreviousValueString.isEmpty();
683 if ( prevSkip )
684 {
685 setupPlaceholderText();
686 emit skipOutputChanged( false );
687 }
688
689 mUseRemapping = false;
690
691 if ( couldBeTemporaryLayerName( text ) || text == "memory:"_L1 )
692 {
693 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
694 mUseTemporary = true;
695 }
696 else
697 {
698 leText->removeAction( mActionTemporaryOutputIcon );
699 mUseTemporary = false;
700 }
701
702 // emit destinationChanged only if the value actually changed
703 if ( mPreviousValueString != text )
704 {
705 emit destinationChanged();
706 }
707
708 mPreviousValueString = text;
709}
710
711
712QString QgsProcessingLayerOutputDestinationWidget::mimeDataToPath( const QMimeData *data )
713{
715 for ( const QgsMimeDataUtils::Uri &u : uriList )
716 {
717 if ( ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName()
719 || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
720 && u.layerType == "vector"_L1 && u.providerKey == "ogr"_L1 )
721 {
722 return u.uri;
723 }
724 else if ( ( mParameter->type() == QgsProcessingParameterRasterDestination::typeName()
725 || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
726 && u.layerType == "raster"_L1 && u.providerKey == "gdal"_L1 )
727 {
728 return u.uri;
729 }
730 else if ( ( mParameter->type() == QgsProcessingParameterPointCloudDestination::typeName()
731 || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
732 && u.layerType == "pointcloud"_L1 && ( u.providerKey == "ept"_L1 || u.providerKey == "pdal"_L1 ) )
733 {
734 return u.uri;
735 }
736#if 0
737 else if ( ( mParameter->type() == QgsProcessingParameterMeshDestination::typeName()
738 || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
739 && u.layerType == "mesh"_L1 && u.providerKey == "mdal"_L1 )
740 return u.uri;
741
742#endif
743 else if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName()
744 && u.layerType == "directory"_L1 )
745 {
746 return u.uri;
747 }
748 }
749 if ( !uriList.isEmpty() )
750 return QString();
751
752 // files dragged from file explorer, outside of QGIS
753 QStringList rawPaths;
754 if ( data->hasUrls() )
755 {
756 const QList<QUrl> urls = data->urls();
757 rawPaths.reserve( urls.count() );
758 for ( const QUrl &url : urls )
759 {
760 const QString local = url.toLocalFile();
761 if ( !rawPaths.contains( local ) )
762 rawPaths.append( local );
763 }
764 }
765 if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
766 rawPaths.append( data->text() );
767
768 for ( const QString &path : std::as_const( rawPaths ) )
769 {
770 QFileInfo file( path );
771 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() ) )
772 {
773 // TODO - we should check to see if it's a valid extension for the parameter, but that's non-trivial
774 return path;
775 }
776 else if ( file.isDir() && ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() ) )
777 return path;
778 }
779
780 return QString();
781}
782
783void QgsProcessingLayerOutputDestinationWidget::dragEnterEvent( QDragEnterEvent *event )
784{
785 if ( !( event->possibleActions() & Qt::CopyAction ) )
786 return;
787
788 const QString path = mimeDataToPath( event->mimeData() );
789 if ( !path.isEmpty() )
790 {
791 // dragged an acceptable path, phew
792 event->setDropAction( Qt::CopyAction );
793 event->accept();
794 leText->setHighlighted( true );
795 }
796}
797
798void QgsProcessingLayerOutputDestinationWidget::dragLeaveEvent( QDragLeaveEvent *event )
799{
800 QWidget::dragLeaveEvent( event );
801 if ( leText->isHighlighted() )
802 {
803 event->accept();
804 leText->setHighlighted( false );
805 }
806}
807
808void QgsProcessingLayerOutputDestinationWidget::dropEvent( QDropEvent *event )
809{
810 if ( !( event->possibleActions() & Qt::CopyAction ) )
811 return;
812
813 const QString path = mimeDataToPath( event->mimeData() );
814 if ( !path.isEmpty() )
815 {
816 // dropped an acceptable path, phew
817 setFocus( Qt::MouseFocusReason );
818 event->setDropAction( Qt::CopyAction );
819 event->accept();
820 setValue( path );
821 }
822 leText->setHighlighted( false );
823}
824
825QString QgsProcessingLayerOutputDestinationWidget::memoryProviderLayerName( const QString &value ) const
826{
827 if ( value == "memory:"_L1 )
828 return QString();
829
830 QString provider;
831 QString uri;
832 bool hasProviderAndUri = QgsProcessingUtils::decodeProviderKeyAndUri( value, provider, uri );
833
834 if ( hasProviderAndUri && provider == "memory"_L1 )
835 {
836 return uri;
837 }
838
839 return QString();
840}
841
842bool QgsProcessingLayerOutputDestinationWidget::couldBeTemporaryLayerName( const QString &value ) const
843{
844 if ( value == QgsProcessing::TEMPORARY_OUTPUT || mParameter->type() != QgsProcessingParameterFeatureSink::typeName() || !mParameter->supportsNonFileBasedOutput() )
845 return false;
846
847 if ( value.isEmpty() )
848 return true;
849
850 if ( value == "memory:"_L1 )
851 return false;
852
853 QString provider;
854 QString uri;
855 const bool hasProviderAndUri = QgsProcessingUtils::decodeProviderKeyAndUri( value, provider, uri );
856
857 if ( provider == "memory"_L1 )
858 return true;
859
860 if ( hasProviderAndUri )
861 return false;
862
863 if ( QFileInfo( value ).isAbsolute() || !QFileInfo( value ).suffix().isEmpty() )
864 return false;
865
866 return true;
867}
868
869bool QgsProcessingLayerOutputDestinationWidget::consideredEqualTemporaryOutputValues( const QString &val1, const QString &val2 ) const
870{
871 if ( ( val1 == "memory:"_L1 && val2 == QgsProcessing::TEMPORARY_OUTPUT ) || ( val1 == QgsProcessing::TEMPORARY_OUTPUT && val2 == "memory:"_L1 ) || val1 == val2 )
872 return true;
873
874 return false;
875}
876
877QString QgsProcessingLayerOutputDestinationWidget::variantToString( const QVariant &value ) const
878{
879 if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
880 {
881 return value.value<QgsProcessingOutputLayerDefinition>().sink.staticValue().toString();
882 }
883 else
884 {
885 return value.toString();
886 }
887}
888
@ 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.
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: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.