QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
qgsprocessingalgorithmdialogbase.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprocessingalgorithmdialogbase.cpp
3 ------------------------------------
4 Date : November 2017
5 Copyright : (C) 2017 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#include "qgssettings.h"
18#include "qgshelp.h"
19#include "qgsmessagebar.h"
20#include "qgsgui.h"
23#include "qgstaskmanager.h"
25#include "qgsstringutils.h"
26#include "qgsapplication.h"
27#include "qgspanelwidget.h"
28#include "qgsjsonutils.h"
29#include <QToolButton>
30#include <QDesktopServices>
31#include <QScrollBar>
32#include <QApplication>
33#include <QClipboard>
34#include <QFileDialog>
35#include <QMimeData>
36#include <QMenu>
37#include <nlohmann/json.hpp>
38
39
41
42QgsProcessingAlgorithmDialogFeedback::QgsProcessingAlgorithmDialogFeedback()
43 : QgsProcessingFeedback( false )
44{}
45
46void QgsProcessingAlgorithmDialogFeedback::setProgressText( const QString &text )
47{
49 emit progressTextChanged( text );
50}
51
52void QgsProcessingAlgorithmDialogFeedback::reportError( const QString &error, bool fatalError )
53{
54 QgsProcessingFeedback::reportError( error, fatalError );
55 emit errorReported( error, fatalError );
56}
57
58void QgsProcessingAlgorithmDialogFeedback::pushWarning( const QString &warning )
59{
61 emit warningPushed( warning );
62}
63
64void QgsProcessingAlgorithmDialogFeedback::pushInfo( const QString &info )
65{
67 emit infoPushed( info );
68}
69
70void QgsProcessingAlgorithmDialogFeedback::pushCommandInfo( const QString &info )
71{
73 emit commandInfoPushed( info );
74}
75
76void QgsProcessingAlgorithmDialogFeedback::pushDebugInfo( const QString &info )
77{
79 emit debugInfoPushed( info );
80}
81
82void QgsProcessingAlgorithmDialogFeedback::pushConsoleInfo( const QString &info )
83{
85 emit consoleInfoPushed( info );
86}
87
88//
89// QgsProcessingAlgorithmDialogBase
90//
91
92QgsProcessingAlgorithmDialogBase::QgsProcessingAlgorithmDialogBase( QWidget *parent, Qt::WindowFlags flags, DialogMode mode )
93 : QDialog( parent, flags )
94 , mMode( mode )
95{
96 setupUi( this );
97
98 //don't collapse parameters panel
99 splitter->setCollapsible( 0, false );
100
101 // add collapse button to splitter
102 QSplitterHandle *splitterHandle = splitter->handle( 1 );
103 QVBoxLayout *handleLayout = new QVBoxLayout();
104 handleLayout->setContentsMargins( 0, 0, 0, 0 );
105 mButtonCollapse = new QToolButton( splitterHandle );
106 mButtonCollapse->setAutoRaise( true );
107 mButtonCollapse->setFixedSize( 12, 12 );
108 mButtonCollapse->setCursor( Qt::ArrowCursor );
109 handleLayout->addWidget( mButtonCollapse );
110 handleLayout->addStretch();
111 splitterHandle->setLayout( handleLayout );
112
114
115 const QgsSettings settings;
116 splitter->restoreState( settings.value( QStringLiteral( "/Processing/dialogBaseSplitter" ), QByteArray() ).toByteArray() );
117 mSplitterState = splitter->saveState();
118 splitterChanged( 0, 0 );
119
120 // Rename OK button to Run
121 mButtonRun = mButtonBox->button( QDialogButtonBox::Ok );
122 mButtonRun->setText( tr( "Run" ) );
123
124 // Rename Yes button. Yes is used to ensure same position of Run and Change Parameters with respect to Close button.
125 mButtonChangeParameters = mButtonBox->button( QDialogButtonBox::Yes );
126 mButtonChangeParameters->setText( tr( "Change Parameters" ) );
127
128 buttonCancel->setEnabled( false );
129 mButtonClose = mButtonBox->button( QDialogButtonBox::Close );
130
131 switch ( mMode )
132 {
133 case DialogMode::Single:
134 {
135 mAdvancedButton = new QPushButton( tr( "Advanced" ) );
136 mAdvancedMenu = new QMenu( this );
137 mAdvancedButton->setMenu( mAdvancedMenu );
138
139 QAction *copyAsPythonCommand = new QAction( tr( "Copy as Python Command" ), mAdvancedMenu );
140 copyAsPythonCommand->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconPythonFile.svg" ) ) );
141
142 mAdvancedMenu->addAction( copyAsPythonCommand );
143 connect( copyAsPythonCommand, &QAction::triggered, this, [this]
144 {
145 if ( const QgsProcessingAlgorithm *alg = algorithm() )
146 {
147 QgsProcessingContext *context = processingContext();
148 if ( !context )
149 return;
150
151 const QString command = alg->asPythonCommand( createProcessingParameters(), *context );
152 QMimeData *m = new QMimeData();
153 m->setText( command );
154 QClipboard *cb = QApplication::clipboard();
155
156#ifdef Q_OS_LINUX
157 cb->setMimeData( m, QClipboard::Selection );
158#endif
159 cb->setMimeData( m, QClipboard::Clipboard );
160 }
161 } );
162
163 mCopyAsQgisProcessCommand = new QAction( tr( "Copy as qgis_process Command" ), mAdvancedMenu );
164 mCopyAsQgisProcessCommand->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionTerminal.svg" ) ) );
165 mAdvancedMenu->addAction( mCopyAsQgisProcessCommand );
166
167 connect( mCopyAsQgisProcessCommand, &QAction::triggered, this, [this]
168 {
169 if ( const QgsProcessingAlgorithm *alg = algorithm() )
170 {
171 QgsProcessingContext *context = processingContext();
172 if ( !context )
173 return;
174
175 bool ok = false;
176 const QString command = alg->asQgisProcessCommand( createProcessingParameters(), *context, ok );
177 if ( ! ok )
178 {
179 mMessageBar->pushMessage( tr( "Current settings cannot be specified as arguments to qgis_process (Pipe parameters as JSON to qgis_process instead)" ), Qgis::MessageLevel::Warning );
180 }
181 else
182 {
183 QMimeData *m = new QMimeData();
184 m->setText( command );
185 QClipboard *cb = QApplication::clipboard();
186
187#ifdef Q_OS_LINUX
188 cb->setMimeData( m, QClipboard::Selection );
189#endif
190 cb->setMimeData( m, QClipboard::Clipboard );
191 }
192 }
193 } );
194
195 mAdvancedMenu->addSeparator();
196
197 QAction *copyAsJson = new QAction( tr( "Copy as JSON" ), mAdvancedMenu );
198 copyAsJson->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionEditCopy.svg" ) ) );
199
200 mAdvancedMenu->addAction( copyAsJson );
201 connect( copyAsJson, &QAction::triggered, this, [this]
202 {
203 if ( const QgsProcessingAlgorithm *alg = algorithm() )
204 {
205 QgsProcessingContext *context = processingContext();
206 if ( !context )
207 return;
208
209 const QVariantMap properties = alg->asMap( createProcessingParameters(), *context );
210 const QString json = QString::fromStdString( QgsJsonUtils::jsonFromVariant( properties ).dump( 2 ) );
211
212 QMimeData *m = new QMimeData();
213 m->setText( json );
214 QClipboard *cb = QApplication::clipboard();
215
216#ifdef Q_OS_LINUX
217 cb->setMimeData( m, QClipboard::Selection );
218#endif
219 cb->setMimeData( m, QClipboard::Clipboard );
220 }
221 } );
222
223 mPasteJsonAction = new QAction( tr( "Paste Settings" ), mAdvancedMenu );
224 mPasteJsonAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionEditPaste.svg" ) ) );
225
226 mAdvancedMenu->addAction( mPasteJsonAction );
227 connect( mPasteJsonAction, &QAction::triggered, this, [this]
228 {
229 const QString text = QApplication::clipboard()->text();
230 if ( text.isEmpty() )
231 return;
232
233 const QVariantMap parameterValues = QgsJsonUtils::parseJson( text ).toMap().value( QStringLiteral( "inputs" ) ).toMap();
234 if ( parameterValues.isEmpty() )
235 return;
236
237 bool ok = false;
238 QString error;
239 const QVariantMap preparedValues = QgsProcessingUtils::preprocessQgisProcessParameters( parameterValues, ok, error );
240
241 setParameters( preparedValues );
242 } );
243
244 mButtonBox->addButton( mAdvancedButton, QDialogButtonBox::ResetRole );
245 break;
246 }
247
248 case DialogMode::Batch:
249 break;
250 }
251
252 if ( mAdvancedMenu )
253 {
254 connect( mAdvancedMenu, &QMenu::aboutToShow, this, [ = ]
255 {
256 mCopyAsQgisProcessCommand->setEnabled( algorithm()
258 mPasteJsonAction->setEnabled( !QApplication::clipboard()->text().isEmpty() );
259 } );
260 }
261
262 connect( mButtonRun, &QPushButton::clicked, this, &QgsProcessingAlgorithmDialogBase::runAlgorithm );
263 connect( mButtonChangeParameters, &QPushButton::clicked, this, &QgsProcessingAlgorithmDialogBase::showParameters );
264 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsProcessingAlgorithmDialogBase::closeClicked );
265 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsProcessingAlgorithmDialogBase::openHelp );
266 connect( mButtonCollapse, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::toggleCollapsed );
267 connect( splitter, &QSplitter::splitterMoved, this, &QgsProcessingAlgorithmDialogBase::splitterChanged );
268
269 connect( mButtonSaveLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::saveLog );
270 connect( mButtonCopyLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::copyLogToClipboard );
271 connect( mButtonClearLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::clearLog );
272
273 connect( mTabWidget, &QTabWidget::currentChanged, this, &QgsProcessingAlgorithmDialogBase::mTabWidget_currentChanged );
274
275 mMessageBar = new QgsMessageBar();
276 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
277 verticalLayout->insertWidget( 0, mMessageBar );
278
279 connect( QgsApplication::taskManager(), &QgsTaskManager::taskTriggered, this, &QgsProcessingAlgorithmDialogBase::taskTriggered );
280}
281
282QgsProcessingAlgorithmDialogBase::~QgsProcessingAlgorithmDialogBase() = default;
283
284void QgsProcessingAlgorithmDialogBase::setParameters( const QVariantMap & )
285{}
286
287void QgsProcessingAlgorithmDialogBase::setAlgorithm( QgsProcessingAlgorithm *algorithm )
288{
289 mAlgorithm.reset( algorithm );
290 QString title;
292 {
293 title = QgsStringUtils::capitalize( mAlgorithm->displayName(), Qgis::Capitalization::TitleCase );
294 }
295 else
296 {
297 title = mAlgorithm->displayName();
298 }
299 setWindowTitle( title );
300
301 const QString algHelp = formatHelp( algorithm );
302 if ( algHelp.isEmpty() )
303 textShortHelp->hide();
304 else
305 {
306 textShortHelp->document()->setDefaultStyleSheet( QStringLiteral( ".summary { margin-left: 10px; margin-right: 10px; }\n"
307 "h2 { color: #555555; padding-bottom: 15px; }\n"
308 "a { text - decoration: none; color: #3498db; font-weight: bold; }\n"
309 "p { color: #666666; }\n"
310 "b { color: #333333; }\n"
311 "dl dd { margin - bottom: 5px; }" ) );
312 textShortHelp->setHtml( algHelp );
313 connect( textShortHelp, &QTextBrowser::anchorClicked, this, &QgsProcessingAlgorithmDialogBase::linkClicked );
314 textShortHelp->show();
315 }
316
317 if ( algorithm->helpUrl().isEmpty() && ( !algorithm->provider() || algorithm->provider()->helpId().isEmpty() ) )
318 {
319 mButtonBox->removeButton( mButtonBox->button( QDialogButtonBox::Help ) );
320 }
321
322 const QString warning = algorithm->provider() ? algorithm->provider()->warningMessage() : QString();
323 if ( !warning.isEmpty() )
324 {
325 mMessageBar->pushMessage( warning, Qgis::MessageLevel::Warning );
326 }
327}
328
330{
331 return mAlgorithm.get();
332}
333
334void QgsProcessingAlgorithmDialogBase::setMainWidget( QgsPanelWidget *widget )
335{
336 if ( mMainWidget )
337 {
338 mMainWidget->deleteLater();
339 }
340
341 mPanelStack->setMainPanel( widget );
342 widget->setDockMode( true );
343
344 mMainWidget = widget;
345 connect( mMainWidget, &QgsPanelWidget::panelAccepted, this, &QDialog::reject );
346}
347
348QgsPanelWidget *QgsProcessingAlgorithmDialogBase::mainWidget()
349{
350 return mMainWidget;
351}
352
353void QgsProcessingAlgorithmDialogBase::saveLogToFile( const QString &path, const LogFormat format )
354{
355 QFile logFile( path );
356 if ( !logFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
357 {
358 return;
359 }
360 QTextStream fout( &logFile );
361
362 switch ( format )
363 {
364 case FormatPlainText:
365 fout << txtLog->toPlainText();
366 break;
367
368 case FormatHtml:
369 fout << txtLog->toHtml();
370 break;
371 }
372}
373
374QgsProcessingFeedback *QgsProcessingAlgorithmDialogBase::createFeedback()
375{
376 auto feedback = std::make_unique< QgsProcessingAlgorithmDialogFeedback >();
377 connect( feedback.get(), &QgsProcessingFeedback::progressChanged, this, &QgsProcessingAlgorithmDialogBase::setPercentage );
378 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::commandInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushCommandInfo );
379 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::consoleInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushConsoleInfo );
380 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::debugInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushDebugInfo );
381 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::errorReported, this, &QgsProcessingAlgorithmDialogBase::reportError );
382 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::warningPushed, this, &QgsProcessingAlgorithmDialogBase::pushWarning );
383 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::infoPushed, this, &QgsProcessingAlgorithmDialogBase::pushInfo );
384 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::progressTextChanged, this, &QgsProcessingAlgorithmDialogBase::setProgressText );
385 connect( buttonCancel, &QPushButton::clicked, feedback.get(), &QgsProcessingFeedback::cancel );
386 return feedback.release();
387}
388
389QDialogButtonBox *QgsProcessingAlgorithmDialogBase::buttonBox()
390{
391 return mButtonBox;
392}
393
394QTabWidget *QgsProcessingAlgorithmDialogBase::tabWidget()
395{
396 return mTabWidget;
397}
398
399void QgsProcessingAlgorithmDialogBase::showLog()
400{
401 mTabWidget->setCurrentIndex( 1 );
402}
403
404void QgsProcessingAlgorithmDialogBase::showParameters()
405{
406 mTabWidget->setCurrentIndex( 0 );
407}
408
409QPushButton *QgsProcessingAlgorithmDialogBase::runButton()
410{
411 return mButtonRun;
412}
413
414QPushButton *QgsProcessingAlgorithmDialogBase::cancelButton()
415{
416 return buttonCancel;
417}
418
419QPushButton *QgsProcessingAlgorithmDialogBase::changeParametersButton()
420{
421 return mButtonChangeParameters;
422}
423
424void QgsProcessingAlgorithmDialogBase::clearProgress()
425{
426 progressBar->setMaximum( 0 );
427}
428
429void QgsProcessingAlgorithmDialogBase::setExecuted( bool executed )
430{
431 mExecuted = executed;
432}
433
434void QgsProcessingAlgorithmDialogBase::setExecutedAnyResult( bool executedAnyResult )
435{
436 mExecutedAnyResult = executedAnyResult;
437}
438
439void QgsProcessingAlgorithmDialogBase::setResults( const QVariantMap &results )
440{
441 mResults = results;
442}
443
444void QgsProcessingAlgorithmDialogBase::finished( bool, const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * )
445{
446
447}
448
449void QgsProcessingAlgorithmDialogBase::openHelp()
450{
451 QUrl algHelp = mAlgorithm->helpUrl();
452 if ( algHelp.isEmpty() && mAlgorithm->provider() )
453 {
454 algHelp = QgsHelp::helpUrl( QStringLiteral( "processing_algs/%1/%2.html#%3" ).arg( mAlgorithm->provider()->helpId(), mAlgorithm->groupId(), QStringLiteral( "%1%2" ).arg( mAlgorithm->provider()->helpId() ).arg( mAlgorithm->name() ) ) );
455 }
456
457 if ( !algHelp.isEmpty() )
458 QDesktopServices::openUrl( algHelp );
459}
460
461void QgsProcessingAlgorithmDialogBase::toggleCollapsed()
462{
463 if ( mHelpCollapsed )
464 {
465 splitter->restoreState( mSplitterState );
466 mButtonCollapse->setArrowType( Qt::RightArrow );
467 }
468 else
469 {
470 mSplitterState = splitter->saveState();
471 splitter->setSizes( QList<int>() << 1 << 0 );
472 mButtonCollapse->setArrowType( Qt::LeftArrow );
473 }
474 mHelpCollapsed = !mHelpCollapsed;
475}
476
477void QgsProcessingAlgorithmDialogBase::splitterChanged( int, int )
478{
479 if ( splitter->sizes().at( 1 ) == 0 )
480 {
481 mHelpCollapsed = true;
482 mButtonCollapse->setArrowType( Qt::LeftArrow );
483 }
484 else
485 {
486 mHelpCollapsed = false;
487 mButtonCollapse->setArrowType( Qt::RightArrow );
488 }
489}
490
491void QgsProcessingAlgorithmDialogBase::mTabWidget_currentChanged( int )
492{
493 updateRunButtonVisibility();
494}
495
496void QgsProcessingAlgorithmDialogBase::linkClicked( const QUrl &url )
497{
498 QDesktopServices::openUrl( url.toString() );
499}
500
501void QgsProcessingAlgorithmDialogBase::algExecuted( bool successful, const QVariantMap & )
502{
503 mAlgorithmTask = nullptr;
504
505 if ( !successful )
506 {
507 // show dialog to display errors
508 show();
509 raise();
510 setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
511 activateWindow();
512 showLog();
513 }
514 else
515 {
516 // delete dialog if closed
517 if ( isFinalized() && !isVisible() )
518 {
519 deleteLater();
520 }
521 }
522}
523
524void QgsProcessingAlgorithmDialogBase::taskTriggered( QgsTask *task )
525{
526 if ( task == mAlgorithmTask )
527 {
528 show();
529 raise();
530 setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
531 activateWindow();
532 showLog();
533 }
534}
535
536void QgsProcessingAlgorithmDialogBase::closeClicked()
537{
538 reject();
539 close();
540}
541
542QgsProcessingContext::LogLevel QgsProcessingAlgorithmDialogBase::logLevel() const
543{
544 return mLogLevel;
545}
546
547void QgsProcessingAlgorithmDialogBase::setLogLevel( QgsProcessingContext::LogLevel level )
548{
549 mLogLevel = level;
550}
551
552void QgsProcessingAlgorithmDialogBase::reportError( const QString &error, bool fatalError )
553{
554 setInfo( error, true );
555 if ( fatalError )
556 resetGui();
557 showLog();
558 processEvents();
559}
560
561void QgsProcessingAlgorithmDialogBase::pushWarning( const QString &warning )
562{
563 setInfo( warning, false, true, true );
564 processEvents();
565}
566
567void QgsProcessingAlgorithmDialogBase::pushInfo( const QString &info )
568{
569 setInfo( info );
570 processEvents();
571}
572
573void QgsProcessingAlgorithmDialogBase::pushCommandInfo( const QString &command )
574{
575 txtLog->append( QStringLiteral( "<code>%1<code>" ).arg( formatStringForLog( command.toHtmlEscaped() ) ) );
576 scrollToBottomOfLog();
577 processEvents();
578}
579
580void QgsProcessingAlgorithmDialogBase::pushDebugInfo( const QString &message )
581{
582 txtLog->append( QStringLiteral( "<span style=\"color:#777\">%1</span>" ).arg( formatStringForLog( message.toHtmlEscaped() ) ) );
583 scrollToBottomOfLog();
584 processEvents();
585}
586
587void QgsProcessingAlgorithmDialogBase::pushConsoleInfo( const QString &info )
588{
589 txtLog->append( QStringLiteral( "<code style=\"color:#777\">%1</code>" ).arg( formatStringForLog( info.toHtmlEscaped() ) ) );
590 scrollToBottomOfLog();
591 processEvents();
592}
593
594QDialog *QgsProcessingAlgorithmDialogBase::createProgressDialog()
595{
596 QgsProcessingAlgorithmProgressDialog *dialog = new QgsProcessingAlgorithmProgressDialog( this );
597 dialog->setWindowModality( Qt::ApplicationModal );
598 dialog->setWindowTitle( windowTitle() );
599 dialog->setGeometry( geometry() ); // match size/position to this dialog
600 connect( progressBar, &QProgressBar::valueChanged, dialog->progressBar(), &QProgressBar::setValue );
601 connect( dialog->cancelButton(), &QPushButton::clicked, buttonCancel, &QPushButton::click );
602 dialog->logTextEdit()->setHtml( txtLog->toHtml() );
603 connect( txtLog, &QTextEdit::textChanged, dialog, [this, dialog]()
604 {
605 dialog->logTextEdit()->setHtml( txtLog->toHtml() );
606 QScrollBar *sb = dialog->logTextEdit()->verticalScrollBar();
607 sb->setValue( sb->maximum() );
608 } );
609 return dialog;
610}
611
612void QgsProcessingAlgorithmDialogBase::clearLog()
613{
614 txtLog->clear();
615}
616
617void QgsProcessingAlgorithmDialogBase::saveLog()
618{
619 QgsSettings settings;
620 const QString lastUsedDir = settings.value( QStringLiteral( "/Processing/lastUsedLogDirectory" ), QDir::homePath() ).toString();
621
622 QString filter;
623 const QString txtExt = tr( "Text files" ) + QStringLiteral( " (*.txt *.TXT)" );
624 const QString htmlExt = tr( "HTML files" ) + QStringLiteral( " (*.html *.HTML)" );
625
626 const QString path = QFileDialog::getSaveFileName( this, tr( "Save Log to File" ), lastUsedDir, txtExt + ";;" + htmlExt, &filter );
627 if ( path.isEmpty() )
628 {
629 return;
630 }
631
632 settings.setValue( QStringLiteral( "/Processing/lastUsedLogDirectory" ), QFileInfo( path ).path() );
633
634 LogFormat format = FormatPlainText;
635 if ( filter == htmlExt )
636 {
637 format = FormatHtml;
638 }
639 saveLogToFile( path, format );
640}
641
642void QgsProcessingAlgorithmDialogBase::copyLogToClipboard()
643{
644 QMimeData *m = new QMimeData();
645 m->setText( txtLog->toPlainText() );
646 m->setHtml( txtLog->toHtml() );
647 QClipboard *cb = QApplication::clipboard();
648
649#ifdef Q_OS_LINUX
650 cb->setMimeData( m, QClipboard::Selection );
651#endif
652 cb->setMimeData( m, QClipboard::Clipboard );
653}
654
655void QgsProcessingAlgorithmDialogBase::closeEvent( QCloseEvent *e )
656{
657 if ( !mHelpCollapsed )
658 {
659 QgsSettings settings;
660 settings.setValue( QStringLiteral( "/Processing/dialogBaseSplitter" ), splitter->saveState() );
661 }
662
663 QDialog::closeEvent( e );
664
665 if ( !mAlgorithmTask && isFinalized() )
666 {
667 // when running a background task, the dialog is kept around and deleted only when the task
668 // completes. But if not running a task, we auto cleanup (later - gotta give callers a chance
669 // to retrieve results and execution status).
670 deleteLater();
671 }
672}
673
674void QgsProcessingAlgorithmDialogBase::runAlgorithm()
675{
676
677}
678
679void QgsProcessingAlgorithmDialogBase::setPercentage( double percent )
680{
681 // delay setting maximum progress value until we know algorithm reports progress
682 if ( progressBar->maximum() == 0 )
683 progressBar->setMaximum( 100 );
684 progressBar->setValue( percent );
685 processEvents();
686}
687
688void QgsProcessingAlgorithmDialogBase::setProgressText( const QString &text )
689{
690 lblProgress->setText( text );
691 setInfo( text, false );
692 scrollToBottomOfLog();
693 processEvents();
694}
695
696QString QgsProcessingAlgorithmDialogBase::formatHelp( QgsProcessingAlgorithm *algorithm )
697{
698 const QString text = algorithm->shortHelpString();
699 if ( !text.isEmpty() )
700 {
701 const QStringList paragraphs = text.split( '\n' );
702 QString help;
703 for ( const QString &paragraph : paragraphs )
704 {
705 help += QStringLiteral( "<p>%1</p>" ).arg( paragraph );
706 }
707 return QStringLiteral( "<h2>%1</h2>%2" ).arg( algorithm->displayName(), help );
708 }
709 else if ( !algorithm->shortDescription().isEmpty() )
710 {
711 return QStringLiteral( "<h2>%1</h2><p>%2</p>" ).arg( algorithm->displayName(), algorithm->shortDescription() );
712 }
713 else
714 return QString();
715}
716
717void QgsProcessingAlgorithmDialogBase::processEvents()
718{
719 if ( mAlgorithmTask )
720 {
721 // no need to call this - the algorithm is running in a thread.
722 // in fact, calling it causes a crash on Windows when the algorithm
723 // is running in a background thread... unfortunately we need something
724 // like this for non-threadable algorithms, otherwise there's no chance
725 // for users to hit cancel or see progress updates...
726 return;
727 }
728
729 // So that we get a chance of hitting the Abort button
730#ifdef Q_OS_LINUX
731 // One iteration is actually enough on Windows to get good interactivity
732 // whereas on Linux we must allow for far more iterations.
733 // For safety limit the number of iterations
734 int nIters = 0;
735 while ( ++nIters < 100 )
736#endif
737 {
738 QCoreApplication::processEvents();
739 }
740}
741
742void QgsProcessingAlgorithmDialogBase::scrollToBottomOfLog()
743{
744 QScrollBar *sb = txtLog->verticalScrollBar();
745 sb->setValue( sb->maximum() );
746}
747
748void QgsProcessingAlgorithmDialogBase::resetGui()
749{
750 lblProgress->clear();
751 progressBar->setMaximum( 100 );
752 progressBar->setValue( 0 );
753 mButtonRun->setEnabled( true );
754 mButtonChangeParameters->setEnabled( true );
755 mButtonClose->setEnabled( true );
756 if ( mMainWidget )
757 {
758 mMainWidget->setEnabled( true );
759 }
760 updateRunButtonVisibility();
761 resetAdditionalGui();
762}
763
764void QgsProcessingAlgorithmDialogBase::updateRunButtonVisibility()
765{
766 // Activate run button if current tab is Parameters
767 const bool runButtonVisible = mTabWidget->currentIndex() == 0;
768 mButtonRun->setVisible( runButtonVisible );
769 mButtonChangeParameters->setVisible( !runButtonVisible && mExecutedAnyResult && mButtonChangeParameters->isEnabled() );
770}
771
772void QgsProcessingAlgorithmDialogBase::resetAdditionalGui()
773{
774
775}
776
777void QgsProcessingAlgorithmDialogBase::blockControlsWhileRunning()
778{
779 mButtonRun->setEnabled( false );
780 mButtonChangeParameters->setEnabled( false );
781 if ( mMainWidget )
782 {
783 mMainWidget->setEnabled( false );
784 }
785 blockAdditionalControlsWhileRunning();
786}
787
788void QgsProcessingAlgorithmDialogBase::blockAdditionalControlsWhileRunning()
789{
790
791}
792
793QgsMessageBar *QgsProcessingAlgorithmDialogBase::messageBar()
794{
795 return mMessageBar;
796}
797
798void QgsProcessingAlgorithmDialogBase::hideShortHelp()
799{
800 textShortHelp->setVisible( false );
801}
802
803void QgsProcessingAlgorithmDialogBase::setCurrentTask( QgsProcessingAlgRunnerTask *task )
804{
805 mAlgorithmTask = task;
806 connect( mAlgorithmTask, &QgsProcessingAlgRunnerTask::executed, this, &QgsProcessingAlgorithmDialogBase::algExecuted );
807 QgsApplication::taskManager()->addTask( mAlgorithmTask );
808}
809
810QString QgsProcessingAlgorithmDialogBase::formatStringForLog( const QString &string )
811{
812 QString s = string;
813 s.replace( '\n', QLatin1String( "<br>" ) );
814 return s;
815}
816
817bool QgsProcessingAlgorithmDialogBase::isFinalized()
818{
819 return true;
820}
821
822void QgsProcessingAlgorithmDialogBase::setInfo( const QString &message, bool isError, bool escapeHtml, bool isWarning )
823{
824 constexpr int MESSAGE_COUNT_LIMIT = 10000;
825 // Avoid logging too many messages, which might blow memory.
826 if ( mMessageLoggedCount == MESSAGE_COUNT_LIMIT )
827 return;
828 ++mMessageLoggedCount;
829
830 // note -- we have to wrap the message in a span block, or QTextEdit::append sometimes gets confused
831 // and varies between treating it as a HTML string or a plain text string! (see https://github.com/qgis/QGIS/issues/37934)
832 if ( mMessageLoggedCount == MESSAGE_COUNT_LIMIT )
833 txtLog->append( QStringLiteral( "<span style=\"color:red\">%1</span>" ).arg( tr( "Message log truncated" ) ) );
834 else if ( isError || isWarning )
835 txtLog->append( QStringLiteral( "<span style=\"color:%1\">%2</span>" ).arg( isError ? QStringLiteral( "red" ) : QStringLiteral( "#b85a20" ), escapeHtml ? formatStringForLog( message.toHtmlEscaped() ) : formatStringForLog( message ) ) );
836 else if ( escapeHtml )
837 txtLog->append( QStringLiteral( "<span>%1</span" ).arg( formatStringForLog( message.toHtmlEscaped() ) ) );
838 else
839 txtLog->append( QStringLiteral( "<span>%1</span>" ).arg( formatStringForLog( message ) ) );
840 scrollToBottomOfLog();
841 processEvents();
842}
843
844void QgsProcessingAlgorithmDialogBase::reject()
845{
846 if ( !mAlgorithmTask && isFinalized() )
847 {
848 setAttribute( Qt::WA_DeleteOnClose );
849 }
850 QDialog::reject();
851}
852
853//
854// QgsProcessingAlgorithmProgressDialog
855//
856
857QgsProcessingAlgorithmProgressDialog::QgsProcessingAlgorithmProgressDialog( QWidget *parent )
858 : QDialog( parent )
859{
860 setupUi( this );
861}
862
863QProgressBar *QgsProcessingAlgorithmProgressDialog::progressBar()
864{
865 return mProgressBar;
866}
867
868QPushButton *QgsProcessingAlgorithmProgressDialog::cancelButton()
869{
870 return mButtonBox->button( QDialogButtonBox::Cancel );
871}
872
873QTextEdit *QgsProcessingAlgorithmProgressDialog::logTextEdit()
874{
875 return mTxtLog;
876}
877
878void QgsProcessingAlgorithmProgressDialog::reject()
879{
880
881}
882
883
884
@ TitleCase
Simple title case conversion - does not fully grammatically parse the text and uses simple rules only...
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
void cancel()
Tells the internal routines that the current operation should be canceled. This should be run by the ...
Definition: qgsfeedback.h:108
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition: qgsgui.cpp:178
@ HigDialogTitleIsTitleCase
Dialog titles should be title case.
Definition: qgsgui.h:235
static QgsGui::HigFlags higFlags()
Returns the platform's HIG flags.
Definition: qgsgui.cpp:197
static QUrl helpUrl(const QString &key)
Returns URI of the help topic for the given key.
Definition: qgshelp.cpp:43
static QVariant parseJson(const std::string &jsonString)
Converts JSON jsonString to a QVariant, in case of parsing error an invalid QVariant is returned and ...
static json jsonFromVariant(const QVariant &v)
Converts a QVariant v to a json object.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
Base class for any widget that can be shown as a inline panel.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
virtual void setDockMode(bool dockMode)
Set the widget in dock mode which tells the widget to emit panel widgets and not open dialogs.
QgsTask task which runs a QgsProcessingAlgorithm in a background task.
void executed(bool successful, const QVariantMap &results)
Emitted when the algorithm has finished execution.
Abstract base class for processing algorithms.
virtual QString helpUrl() const
Returns a url pointing to the algorithm's help page.
virtual QString shortHelpString() const
Returns a localised short helper string for the algorithm.
virtual QString shortDescription() const
Returns an optional translated short description of the algorithm.
@ FlagNotAvailableInStandaloneTool
Algorithm should not be available from the standalone "qgis_process" tool. Used to flag algorithms wh...
@ FlagDisplayNameIsLiteral
Algorithm's display name is a static literal string, and should not be translated or automatically fo...
virtual QString displayName() const =0
Returns the translated algorithm name, which should be used for any user-visible display of the algor...
QgsProcessingProvider * provider() const
Returns the provider to which this algorithm belongs.
Contains information about the context in which a processing algorithm is executed.
LogLevel
Logging level for algorithms to use when pushing feedback messages.
QgsProcessingAlgorithm::Flags flags() const override
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
Base class for providing feedback from a processing algorithm.
virtual void pushCommandInfo(const QString &info)
Pushes an informational message containing a command from the algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
virtual void pushConsoleInfo(const QString &info)
Pushes a console feedback message from the algorithm.
virtual void setProgressText(const QString &text)
Sets a progress report text string.
virtual QString helpId() const
Returns the provider help id string, used for creating QgsHelp urls for algorithms belong to this pro...
virtual QString warningMessage() const
Returns an optional warning message to show users when running algorithms from this provider.
static QVariantMap preprocessQgisProcessParameters(const QVariantMap &parameters, bool &ok, QString &error)
Pre-processes a set of parameter values for the qgis_process command.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:63
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
static QString capitalize(const QString &string, Qgis::Capitalization capitalization)
Converts a string by applying capitalization rules to the string.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
void taskTriggered(QgsTask *task)
Emitted when a task is triggered.
Abstract base class for long running background tasks.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into allowing algorithms to be written in pure substantial changes are required in order to port existing x Processing algorithms for QGIS x The most significant changes are outlined not GeoAlgorithm For algorithms which operate on features one by consider subclassing the QgsProcessingFeatureBasedAlgorithm class This class allows much of the boilerplate code for looping over features from a vector layer to be bypassed and instead requires implementation of a processFeature method Ensure that your algorithm(or algorithm 's parent class) implements the new pure virtual createInstance(self) call