QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 setParameters( parameterValues );
238 } );
239
240 mButtonBox->addButton( mAdvancedButton, QDialogButtonBox::ResetRole );
241 break;
242 }
243
244 case DialogMode::Batch:
245 break;
246 }
247
248 if ( mAdvancedMenu )
249 {
250 connect( mAdvancedMenu, &QMenu::aboutToShow, this, [ = ]
251 {
252 mCopyAsQgisProcessCommand->setEnabled( algorithm()
254 mPasteJsonAction->setEnabled( !QApplication::clipboard()->text().isEmpty() );
255 } );
256 }
257
258 connect( mButtonRun, &QPushButton::clicked, this, &QgsProcessingAlgorithmDialogBase::runAlgorithm );
259 connect( mButtonChangeParameters, &QPushButton::clicked, this, &QgsProcessingAlgorithmDialogBase::showParameters );
260 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsProcessingAlgorithmDialogBase::closeClicked );
261 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsProcessingAlgorithmDialogBase::openHelp );
262 connect( mButtonCollapse, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::toggleCollapsed );
263 connect( splitter, &QSplitter::splitterMoved, this, &QgsProcessingAlgorithmDialogBase::splitterChanged );
264
265 connect( mButtonSaveLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::saveLog );
266 connect( mButtonCopyLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::copyLogToClipboard );
267 connect( mButtonClearLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::clearLog );
268
269 connect( mTabWidget, &QTabWidget::currentChanged, this, &QgsProcessingAlgorithmDialogBase::mTabWidget_currentChanged );
270
271 mMessageBar = new QgsMessageBar();
272 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
273 verticalLayout->insertWidget( 0, mMessageBar );
274
275 connect( QgsApplication::taskManager(), &QgsTaskManager::taskTriggered, this, &QgsProcessingAlgorithmDialogBase::taskTriggered );
276}
277
278QgsProcessingAlgorithmDialogBase::~QgsProcessingAlgorithmDialogBase() = default;
279
280void QgsProcessingAlgorithmDialogBase::setParameters( const QVariantMap & )
281{}
282
283void QgsProcessingAlgorithmDialogBase::setAlgorithm( QgsProcessingAlgorithm *algorithm )
284{
285 mAlgorithm.reset( algorithm );
286 QString title;
288 {
289 title = QgsStringUtils::capitalize( mAlgorithm->displayName(), Qgis::Capitalization::TitleCase );
290 }
291 else
292 {
293 title = mAlgorithm->displayName();
294 }
295 setWindowTitle( title );
296
297 const QString algHelp = formatHelp( algorithm );
298 if ( algHelp.isEmpty() )
299 textShortHelp->hide();
300 else
301 {
302 textShortHelp->document()->setDefaultStyleSheet( QStringLiteral( ".summary { margin-left: 10px; margin-right: 10px; }\n"
303 "h2 { color: #555555; padding-bottom: 15px; }\n"
304 "a { text - decoration: none; color: #3498db; font-weight: bold; }\n"
305 "p { color: #666666; }\n"
306 "b { color: #333333; }\n"
307 "dl dd { margin - bottom: 5px; }" ) );
308 textShortHelp->setHtml( algHelp );
309 connect( textShortHelp, &QTextBrowser::anchorClicked, this, &QgsProcessingAlgorithmDialogBase::linkClicked );
310 textShortHelp->show();
311 }
312
313 if ( algorithm->helpUrl().isEmpty() && algorithm->provider()->helpId().isEmpty() )
314 {
315 mButtonBox->removeButton( mButtonBox->button( QDialogButtonBox::Help ) );
316 }
317
318 const QString warning = algorithm->provider()->warningMessage();
319 if ( !warning.isEmpty() )
320 {
321 mMessageBar->pushMessage( warning, Qgis::MessageLevel::Warning );
322 }
323}
324
326{
327 return mAlgorithm.get();
328}
329
330void QgsProcessingAlgorithmDialogBase::setMainWidget( QgsPanelWidget *widget )
331{
332 if ( mMainWidget )
333 {
334 mMainWidget->deleteLater();
335 }
336
337 mPanelStack->setMainPanel( widget );
338 widget->setDockMode( true );
339
340 mMainWidget = widget;
341 connect( mMainWidget, &QgsPanelWidget::panelAccepted, this, &QDialog::reject );
342}
343
344QgsPanelWidget *QgsProcessingAlgorithmDialogBase::mainWidget()
345{
346 return mMainWidget;
347}
348
349void QgsProcessingAlgorithmDialogBase::saveLogToFile( const QString &path, const LogFormat format )
350{
351 QFile logFile( path );
352 if ( !logFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
353 {
354 return;
355 }
356 QTextStream fout( &logFile );
357
358 switch ( format )
359 {
360 case FormatPlainText:
361 fout << txtLog->toPlainText();
362 break;
363
364 case FormatHtml:
365 fout << txtLog->toHtml();
366 break;
367 }
368}
369
370QgsProcessingFeedback *QgsProcessingAlgorithmDialogBase::createFeedback()
371{
372 auto feedback = std::make_unique< QgsProcessingAlgorithmDialogFeedback >();
373 connect( feedback.get(), &QgsProcessingFeedback::progressChanged, this, &QgsProcessingAlgorithmDialogBase::setPercentage );
374 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::commandInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushCommandInfo );
375 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::consoleInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushConsoleInfo );
376 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::debugInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushDebugInfo );
377 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::errorReported, this, &QgsProcessingAlgorithmDialogBase::reportError );
378 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::warningPushed, this, &QgsProcessingAlgorithmDialogBase::pushWarning );
379 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::infoPushed, this, &QgsProcessingAlgorithmDialogBase::pushInfo );
380 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::progressTextChanged, this, &QgsProcessingAlgorithmDialogBase::setProgressText );
381 connect( buttonCancel, &QPushButton::clicked, feedback.get(), &QgsProcessingFeedback::cancel );
382 return feedback.release();
383}
384
385QDialogButtonBox *QgsProcessingAlgorithmDialogBase::buttonBox()
386{
387 return mButtonBox;
388}
389
390QTabWidget *QgsProcessingAlgorithmDialogBase::tabWidget()
391{
392 return mTabWidget;
393}
394
395void QgsProcessingAlgorithmDialogBase::showLog()
396{
397 mTabWidget->setCurrentIndex( 1 );
398}
399
400void QgsProcessingAlgorithmDialogBase::showParameters()
401{
402 mTabWidget->setCurrentIndex( 0 );
403}
404
405QPushButton *QgsProcessingAlgorithmDialogBase::runButton()
406{
407 return mButtonRun;
408}
409
410QPushButton *QgsProcessingAlgorithmDialogBase::cancelButton()
411{
412 return buttonCancel;
413}
414
415QPushButton *QgsProcessingAlgorithmDialogBase::changeParametersButton()
416{
417 return mButtonChangeParameters;
418}
419
420void QgsProcessingAlgorithmDialogBase::clearProgress()
421{
422 progressBar->setMaximum( 0 );
423}
424
425void QgsProcessingAlgorithmDialogBase::setExecuted( bool executed )
426{
427 mExecuted = executed;
428}
429
430void QgsProcessingAlgorithmDialogBase::setExecutedAnyResult( bool executedAnyResult )
431{
432 mExecutedAnyResult = executedAnyResult;
433}
434
435void QgsProcessingAlgorithmDialogBase::setResults( const QVariantMap &results )
436{
437 mResults = results;
438}
439
440void QgsProcessingAlgorithmDialogBase::finished( bool, const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * )
441{
442
443}
444
445void QgsProcessingAlgorithmDialogBase::openHelp()
446{
447 QUrl algHelp = mAlgorithm->helpUrl();
448 if ( algHelp.isEmpty() )
449 {
450 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() ) ) );
451 }
452
453 if ( !algHelp.isEmpty() )
454 QDesktopServices::openUrl( algHelp );
455}
456
457void QgsProcessingAlgorithmDialogBase::toggleCollapsed()
458{
459 if ( mHelpCollapsed )
460 {
461 splitter->restoreState( mSplitterState );
462 mButtonCollapse->setArrowType( Qt::RightArrow );
463 }
464 else
465 {
466 mSplitterState = splitter->saveState();
467 splitter->setSizes( QList<int>() << 1 << 0 );
468 mButtonCollapse->setArrowType( Qt::LeftArrow );
469 }
470 mHelpCollapsed = !mHelpCollapsed;
471}
472
473void QgsProcessingAlgorithmDialogBase::splitterChanged( int, int )
474{
475 if ( splitter->sizes().at( 1 ) == 0 )
476 {
477 mHelpCollapsed = true;
478 mButtonCollapse->setArrowType( Qt::LeftArrow );
479 }
480 else
481 {
482 mHelpCollapsed = false;
483 mButtonCollapse->setArrowType( Qt::RightArrow );
484 }
485}
486
487void QgsProcessingAlgorithmDialogBase::mTabWidget_currentChanged( int )
488{
489 updateRunButtonVisibility();
490}
491
492void QgsProcessingAlgorithmDialogBase::linkClicked( const QUrl &url )
493{
494 QDesktopServices::openUrl( url.toString() );
495}
496
497void QgsProcessingAlgorithmDialogBase::algExecuted( bool successful, const QVariantMap & )
498{
499 mAlgorithmTask = nullptr;
500
501 if ( !successful )
502 {
503 // show dialog to display errors
504 show();
505 raise();
506 setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
507 activateWindow();
508 showLog();
509 }
510 else
511 {
512 // delete dialog if closed
513 if ( isFinalized() && !isVisible() )
514 {
515 deleteLater();
516 }
517 }
518}
519
520void QgsProcessingAlgorithmDialogBase::taskTriggered( QgsTask *task )
521{
522 if ( task == mAlgorithmTask )
523 {
524 show();
525 raise();
526 setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
527 activateWindow();
528 showLog();
529 }
530}
531
532void QgsProcessingAlgorithmDialogBase::closeClicked()
533{
534 reject();
535 close();
536}
537
538QgsProcessingContext::LogLevel QgsProcessingAlgorithmDialogBase::logLevel() const
539{
540 return mLogLevel;
541}
542
543void QgsProcessingAlgorithmDialogBase::setLogLevel( QgsProcessingContext::LogLevel level )
544{
545 mLogLevel = level;
546}
547
548void QgsProcessingAlgorithmDialogBase::reportError( const QString &error, bool fatalError )
549{
550 setInfo( error, true );
551 if ( fatalError )
552 resetGui();
553 showLog();
554 processEvents();
555}
556
557void QgsProcessingAlgorithmDialogBase::pushWarning( const QString &warning )
558{
559 setInfo( warning, false, true, true );
560 processEvents();
561}
562
563void QgsProcessingAlgorithmDialogBase::pushInfo( const QString &info )
564{
565 setInfo( info );
566 processEvents();
567}
568
569void QgsProcessingAlgorithmDialogBase::pushCommandInfo( const QString &command )
570{
571 txtLog->append( QStringLiteral( "<code>%1<code>" ).arg( formatStringForLog( command.toHtmlEscaped() ) ) );
572 scrollToBottomOfLog();
573 processEvents();
574}
575
576void QgsProcessingAlgorithmDialogBase::pushDebugInfo( const QString &message )
577{
578 txtLog->append( QStringLiteral( "<span style=\"color:#777\">%1</span>" ).arg( formatStringForLog( message.toHtmlEscaped() ) ) );
579 scrollToBottomOfLog();
580 processEvents();
581}
582
583void QgsProcessingAlgorithmDialogBase::pushConsoleInfo( const QString &info )
584{
585 txtLog->append( QStringLiteral( "<code style=\"color:#777\">%1</code>" ).arg( formatStringForLog( info.toHtmlEscaped() ) ) );
586 scrollToBottomOfLog();
587 processEvents();
588}
589
590QDialog *QgsProcessingAlgorithmDialogBase::createProgressDialog()
591{
592 QgsProcessingAlgorithmProgressDialog *dialog = new QgsProcessingAlgorithmProgressDialog( this );
593 dialog->setWindowModality( Qt::ApplicationModal );
594 dialog->setWindowTitle( windowTitle() );
595 dialog->setGeometry( geometry() ); // match size/position to this dialog
596 connect( progressBar, &QProgressBar::valueChanged, dialog->progressBar(), &QProgressBar::setValue );
597 connect( dialog->cancelButton(), &QPushButton::clicked, buttonCancel, &QPushButton::click );
598 dialog->logTextEdit()->setHtml( txtLog->toHtml() );
599 connect( txtLog, &QTextEdit::textChanged, dialog, [this, dialog]()
600 {
601 dialog->logTextEdit()->setHtml( txtLog->toHtml() );
602 QScrollBar *sb = dialog->logTextEdit()->verticalScrollBar();
603 sb->setValue( sb->maximum() );
604 } );
605 return dialog;
606}
607
608void QgsProcessingAlgorithmDialogBase::clearLog()
609{
610 txtLog->clear();
611}
612
613void QgsProcessingAlgorithmDialogBase::saveLog()
614{
615 QgsSettings settings;
616 const QString lastUsedDir = settings.value( QStringLiteral( "/Processing/lastUsedLogDirectory" ), QDir::homePath() ).toString();
617
618 QString filter;
619 const QString txtExt = tr( "Text files" ) + QStringLiteral( " (*.txt *.TXT)" );
620 const QString htmlExt = tr( "HTML files" ) + QStringLiteral( " (*.html *.HTML)" );
621
622 const QString path = QFileDialog::getSaveFileName( this, tr( "Save Log to File" ), lastUsedDir, txtExt + ";;" + htmlExt, &filter );
623 if ( path.isEmpty() )
624 {
625 return;
626 }
627
628 settings.setValue( QStringLiteral( "/Processing/lastUsedLogDirectory" ), QFileInfo( path ).path() );
629
630 LogFormat format = FormatPlainText;
631 if ( filter == htmlExt )
632 {
633 format = FormatHtml;
634 }
635 saveLogToFile( path, format );
636}
637
638void QgsProcessingAlgorithmDialogBase::copyLogToClipboard()
639{
640 QMimeData *m = new QMimeData();
641 m->setText( txtLog->toPlainText() );
642 m->setHtml( txtLog->toHtml() );
643 QClipboard *cb = QApplication::clipboard();
644
645#ifdef Q_OS_LINUX
646 cb->setMimeData( m, QClipboard::Selection );
647#endif
648 cb->setMimeData( m, QClipboard::Clipboard );
649}
650
651void QgsProcessingAlgorithmDialogBase::closeEvent( QCloseEvent *e )
652{
653 if ( !mHelpCollapsed )
654 {
655 QgsSettings settings;
656 settings.setValue( QStringLiteral( "/Processing/dialogBaseSplitter" ), splitter->saveState() );
657 }
658
659 QDialog::closeEvent( e );
660
661 if ( !mAlgorithmTask && isFinalized() )
662 {
663 // when running a background task, the dialog is kept around and deleted only when the task
664 // completes. But if not running a task, we auto cleanup (later - gotta give callers a chance
665 // to retrieve results and execution status).
666 deleteLater();
667 }
668}
669
670void QgsProcessingAlgorithmDialogBase::runAlgorithm()
671{
672
673}
674
675void QgsProcessingAlgorithmDialogBase::setPercentage( double percent )
676{
677 // delay setting maximum progress value until we know algorithm reports progress
678 if ( progressBar->maximum() == 0 )
679 progressBar->setMaximum( 100 );
680 progressBar->setValue( percent );
681 processEvents();
682}
683
684void QgsProcessingAlgorithmDialogBase::setProgressText( const QString &text )
685{
686 lblProgress->setText( text );
687 setInfo( text, false );
688 scrollToBottomOfLog();
689 processEvents();
690}
691
692QString QgsProcessingAlgorithmDialogBase::formatHelp( QgsProcessingAlgorithm *algorithm )
693{
694 const QString text = algorithm->shortHelpString();
695 if ( !text.isEmpty() )
696 {
697 const QStringList paragraphs = text.split( '\n' );
698 QString help;
699 for ( const QString &paragraph : paragraphs )
700 {
701 help += QStringLiteral( "<p>%1</p>" ).arg( paragraph );
702 }
703 return QStringLiteral( "<h2>%1</h2>%2" ).arg( algorithm->displayName(), help );
704 }
705 else if ( !algorithm->shortDescription().isEmpty() )
706 {
707 return QStringLiteral( "<h2>%1</h2><p>%2</p>" ).arg( algorithm->displayName(), algorithm->shortDescription() );
708 }
709 else
710 return QString();
711}
712
713void QgsProcessingAlgorithmDialogBase::processEvents()
714{
715 if ( mAlgorithmTask )
716 {
717 // no need to call this - the algorithm is running in a thread.
718 // in fact, calling it causes a crash on Windows when the algorithm
719 // is running in a background thread... unfortunately we need something
720 // like this for non-threadable algorithms, otherwise there's no chance
721 // for users to hit cancel or see progress updates...
722 return;
723 }
724
725 // So that we get a chance of hitting the Abort button
726#ifdef Q_OS_LINUX
727 // One iteration is actually enough on Windows to get good interactivity
728 // whereas on Linux we must allow for far more iterations.
729 // For safety limit the number of iterations
730 int nIters = 0;
731 while ( ++nIters < 100 )
732#endif
733 {
734 QCoreApplication::processEvents();
735 }
736}
737
738void QgsProcessingAlgorithmDialogBase::scrollToBottomOfLog()
739{
740 QScrollBar *sb = txtLog->verticalScrollBar();
741 sb->setValue( sb->maximum() );
742}
743
744void QgsProcessingAlgorithmDialogBase::resetGui()
745{
746 lblProgress->clear();
747 progressBar->setMaximum( 100 );
748 progressBar->setValue( 0 );
749 mButtonRun->setEnabled( true );
750 mButtonChangeParameters->setEnabled( true );
751 mButtonClose->setEnabled( true );
752 if ( mMainWidget )
753 {
754 mMainWidget->setEnabled( true );
755 }
756 updateRunButtonVisibility();
757 resetAdditionalGui();
758}
759
760void QgsProcessingAlgorithmDialogBase::updateRunButtonVisibility()
761{
762 // Activate run button if current tab is Parameters
763 const bool runButtonVisible = mTabWidget->currentIndex() == 0;
764 mButtonRun->setVisible( runButtonVisible );
765 mButtonChangeParameters->setVisible( !runButtonVisible && mExecutedAnyResult && mButtonChangeParameters->isEnabled() );
766}
767
768void QgsProcessingAlgorithmDialogBase::resetAdditionalGui()
769{
770
771}
772
773void QgsProcessingAlgorithmDialogBase::blockControlsWhileRunning()
774{
775 mButtonRun->setEnabled( false );
776 mButtonChangeParameters->setEnabled( false );
777 if ( mMainWidget )
778 {
779 mMainWidget->setEnabled( false );
780 }
781 blockAdditionalControlsWhileRunning();
782}
783
784void QgsProcessingAlgorithmDialogBase::blockAdditionalControlsWhileRunning()
785{
786
787}
788
789QgsMessageBar *QgsProcessingAlgorithmDialogBase::messageBar()
790{
791 return mMessageBar;
792}
793
794void QgsProcessingAlgorithmDialogBase::hideShortHelp()
795{
796 textShortHelp->setVisible( false );
797}
798
799void QgsProcessingAlgorithmDialogBase::setCurrentTask( QgsProcessingAlgRunnerTask *task )
800{
801 mAlgorithmTask = task;
802 connect( mAlgorithmTask, &QgsProcessingAlgRunnerTask::executed, this, &QgsProcessingAlgorithmDialogBase::algExecuted );
803 QgsApplication::taskManager()->addTask( mAlgorithmTask );
804}
805
806QString QgsProcessingAlgorithmDialogBase::formatStringForLog( const QString &string )
807{
808 QString s = string;
809 s.replace( '\n', QLatin1String( "<br>" ) );
810 return s;
811}
812
813bool QgsProcessingAlgorithmDialogBase::isFinalized()
814{
815 return true;
816}
817
818void QgsProcessingAlgorithmDialogBase::setInfo( const QString &message, bool isError, bool escapeHtml, bool isWarning )
819{
820 constexpr int MESSAGE_COUNT_LIMIT = 10000;
821 // Avoid logging too many messages, which might blow memory.
822 if ( mMessageLoggedCount == MESSAGE_COUNT_LIMIT )
823 return;
824 ++mMessageLoggedCount;
825
826 // note -- we have to wrap the message in a span block, or QTextEdit::append sometimes gets confused
827 // and varies between treating it as a HTML string or a plain text string! (see https://github.com/qgis/QGIS/issues/37934)
828 if ( mMessageLoggedCount == MESSAGE_COUNT_LIMIT )
829 txtLog->append( QStringLiteral( "<span style=\"color:red\">%1</span>" ).arg( tr( "Message log truncated" ) ) );
830 else if ( isError || isWarning )
831 txtLog->append( QStringLiteral( "<span style=\"color:%1\">%2</span>" ).arg( isError ? QStringLiteral( "red" ) : QStringLiteral( "#b85a20" ), escapeHtml ? formatStringForLog( message.toHtmlEscaped() ) : formatStringForLog( message ) ) );
832 else if ( escapeHtml )
833 txtLog->append( QStringLiteral( "<span>%1</span" ).arg( formatStringForLog( message.toHtmlEscaped() ) ) );
834 else
835 txtLog->append( QStringLiteral( "<span>%1</span>" ).arg( formatStringForLog( message ) ) );
836 scrollToBottomOfLog();
837 processEvents();
838}
839
840void QgsProcessingAlgorithmDialogBase::reject()
841{
842 if ( !mAlgorithmTask && isFinalized() )
843 {
844 setAttribute( Qt::WA_DeleteOnClose );
845 }
846 QDialog::reject();
847}
848
849//
850// QgsProcessingAlgorithmProgressDialog
851//
852
853QgsProcessingAlgorithmProgressDialog::QgsProcessingAlgorithmProgressDialog( QWidget *parent )
854 : QDialog( parent )
855{
856 setupUi( this );
857}
858
859QProgressBar *QgsProcessingAlgorithmProgressDialog::progressBar()
860{
861 return mProgressBar;
862}
863
864QPushButton *QgsProcessingAlgorithmProgressDialog::cancelButton()
865{
866 return mButtonBox->button( QDialogButtonBox::Cancel );
867}
868
869QTextEdit *QgsProcessingAlgorithmProgressDialog::logTextEdit()
870{
871 return mTxtLog;
872}
873
874void QgsProcessingAlgorithmProgressDialog::reject()
875{
876
877}
878
879
880
@ 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.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
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