29 #include <QToolButton>
30 #include <QDesktopServices>
32 #include <QApplication>
34 #include <QFileDialog>
37 #include <nlohmann/json.hpp>
42 QgsProcessingAlgorithmDialogFeedback::QgsProcessingAlgorithmDialogFeedback()
46 void QgsProcessingAlgorithmDialogFeedback::setProgressText(
const QString &text )
49 emit progressTextChanged( text );
52 void QgsProcessingAlgorithmDialogFeedback::reportError(
const QString &error,
bool fatalError )
55 emit errorReported( error, fatalError );
58 void QgsProcessingAlgorithmDialogFeedback::pushWarning(
const QString &warning )
61 emit warningPushed( warning );
64 void QgsProcessingAlgorithmDialogFeedback::pushInfo(
const QString &info )
67 emit infoPushed( info );
70 void QgsProcessingAlgorithmDialogFeedback::pushCommandInfo(
const QString &info )
73 emit commandInfoPushed( info );
76 void QgsProcessingAlgorithmDialogFeedback::pushDebugInfo(
const QString &info )
79 emit debugInfoPushed( info );
82 void QgsProcessingAlgorithmDialogFeedback::pushConsoleInfo(
const QString &info )
85 emit consoleInfoPushed( info );
92 QgsProcessingAlgorithmDialogBase::QgsProcessingAlgorithmDialogBase( QWidget *parent, Qt::WindowFlags flags, DialogMode mode )
93 : QDialog( parent, flags )
99 splitter->setCollapsible( 0,
false );
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 );
116 splitter->restoreState( settings.
value( QStringLiteral(
"/Processing/dialogBaseSplitter" ), QByteArray() ).toByteArray() );
117 mSplitterState = splitter->saveState();
118 splitterChanged( 0, 0 );
121 mButtonRun = mButtonBox->button( QDialogButtonBox::Ok );
122 mButtonRun->setText( tr(
"Run" ) );
125 mButtonChangeParameters = mButtonBox->button( QDialogButtonBox::Yes );
126 mButtonChangeParameters->setText( tr(
"Change Parameters" ) );
128 buttonCancel->setEnabled(
false );
129 mButtonClose = mButtonBox->button( QDialogButtonBox::Close );
133 case DialogMode::Single:
135 mAdvancedButton =
new QPushButton( tr(
"Advanced" ) );
136 mAdvancedMenu =
new QMenu(
this );
137 mAdvancedButton->setMenu( mAdvancedMenu );
139 QAction *copyAsPythonCommand =
new QAction( tr(
"Copy as Python Command" ), mAdvancedMenu );
142 mAdvancedMenu->addAction( copyAsPythonCommand );
143 connect( copyAsPythonCommand, &QAction::triggered,
this, [
this]
151 const QString command = alg->asPythonCommand( createProcessingParameters(), *context );
152 QMimeData *m =
new QMimeData();
153 m->setText( command );
154 QClipboard *cb = QApplication::clipboard();
157 cb->setMimeData( m, QClipboard::Selection );
159 cb->setMimeData( m, QClipboard::Clipboard );
163 mCopyAsQgisProcessCommand =
new QAction( tr(
"Copy as qgis_process Command" ), mAdvancedMenu );
165 mAdvancedMenu->addAction( mCopyAsQgisProcessCommand );
167 connect( mCopyAsQgisProcessCommand, &QAction::triggered,
this, [
this]
176 const QString command = alg->asQgisProcessCommand( createProcessingParameters(), *context, ok );
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 );
183 QMimeData *m =
new QMimeData();
184 m->setText( command );
185 QClipboard *cb = QApplication::clipboard();
188 cb->setMimeData( m, QClipboard::Selection );
190 cb->setMimeData( m, QClipboard::Clipboard );
195 mAdvancedMenu->addSeparator();
197 QAction *copyAsJson =
new QAction( tr(
"Copy as JSON" ), mAdvancedMenu );
200 mAdvancedMenu->addAction( copyAsJson );
201 connect( copyAsJson, &QAction::triggered,
this, [
this]
209 const QVariantMap properties = alg->asMap( createProcessingParameters(), *context );
212 QMimeData *m =
new QMimeData();
214 QClipboard *cb = QApplication::clipboard();
217 cb->setMimeData( m, QClipboard::Selection );
219 cb->setMimeData( m, QClipboard::Clipboard );
223 mPasteJsonAction =
new QAction( tr(
"Paste Settings" ), mAdvancedMenu );
226 mAdvancedMenu->addAction( mPasteJsonAction );
227 connect( mPasteJsonAction, &QAction::triggered,
this, [
this]
229 const QString text = QApplication::clipboard()->text();
230 if ( text.isEmpty() )
233 const QVariantMap parameterValues =
QgsJsonUtils::parseJson( text ).toMap().value( QStringLiteral(
"inputs" ) ).toMap();
234 if ( parameterValues.isEmpty() )
237 setParameters( parameterValues );
240 mButtonBox->addButton( mAdvancedButton, QDialogButtonBox::ResetRole );
244 case DialogMode::Batch:
250 connect( mAdvancedMenu, &QMenu::aboutToShow,
this, [ = ]
252 mCopyAsQgisProcessCommand->setEnabled(
algorithm()
254 mPasteJsonAction->setEnabled( !QApplication::clipboard()->text().isEmpty() );
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 );
265 connect( mButtonSaveLog, &QToolButton::clicked,
this, &QgsProcessingAlgorithmDialogBase::saveLog );
266 connect( mButtonCopyLog, &QToolButton::clicked,
this, &QgsProcessingAlgorithmDialogBase::copyLogToClipboard );
267 connect( mButtonClearLog, &QToolButton::clicked,
this, &QgsProcessingAlgorithmDialogBase::clearLog );
269 connect( mTabWidget, &QTabWidget::currentChanged,
this, &QgsProcessingAlgorithmDialogBase::mTabWidget_currentChanged );
272 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
273 verticalLayout->insertWidget( 0, mMessageBar );
278 QgsProcessingAlgorithmDialogBase::~QgsProcessingAlgorithmDialogBase() =
default;
280 void QgsProcessingAlgorithmDialogBase::setParameters(
const QVariantMap & )
293 title = mAlgorithm->displayName();
295 setWindowTitle( title );
297 const QString algHelp = formatHelp(
algorithm );
298 if ( algHelp.isEmpty() )
299 textShortHelp->hide();
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();
315 mButtonBox->removeButton( mButtonBox->button( QDialogButtonBox::Help ) );
319 if ( !warning.isEmpty() )
321 mMessageBar->pushMessage( warning, Qgis::MessageLevel::Warning );
327 return mAlgorithm.get();
330 void QgsProcessingAlgorithmDialogBase::setMainWidget(
QgsPanelWidget *widget )
334 mMainWidget->deleteLater();
337 mPanelStack->setMainPanel( widget );
340 mMainWidget = widget;
349 void QgsProcessingAlgorithmDialogBase::saveLogToFile(
const QString &path,
const LogFormat format )
351 QFile logFile( path );
352 if ( !logFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
356 QTextStream fout( &logFile );
360 case FormatPlainText:
361 fout << txtLog->toPlainText();
365 fout << txtLog->toHtml();
372 auto feedback = std::make_unique< QgsProcessingAlgorithmDialogFeedback >();
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 );
382 return feedback.release();
385 QDialogButtonBox *QgsProcessingAlgorithmDialogBase::buttonBox()
390 QTabWidget *QgsProcessingAlgorithmDialogBase::tabWidget()
395 void QgsProcessingAlgorithmDialogBase::showLog()
397 mTabWidget->setCurrentIndex( 1 );
400 void QgsProcessingAlgorithmDialogBase::showParameters()
402 mTabWidget->setCurrentIndex( 0 );
405 QPushButton *QgsProcessingAlgorithmDialogBase::runButton()
410 QPushButton *QgsProcessingAlgorithmDialogBase::cancelButton()
415 QPushButton *QgsProcessingAlgorithmDialogBase::changeParametersButton()
417 return mButtonChangeParameters;
420 void QgsProcessingAlgorithmDialogBase::clearProgress()
422 progressBar->setMaximum( 0 );
425 void QgsProcessingAlgorithmDialogBase::setExecuted(
bool executed )
427 mExecuted = executed;
430 void QgsProcessingAlgorithmDialogBase::setExecutedAnyResult(
bool executedAnyResult )
432 mExecutedAnyResult = executedAnyResult;
435 void QgsProcessingAlgorithmDialogBase::setResults(
const QVariantMap &results )
445 void QgsProcessingAlgorithmDialogBase::openHelp()
447 QUrl algHelp = mAlgorithm->helpUrl();
448 if ( algHelp.isEmpty() )
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() ) ) );
453 if ( !algHelp.isEmpty() )
454 QDesktopServices::openUrl( algHelp );
457 void QgsProcessingAlgorithmDialogBase::toggleCollapsed()
459 if ( mHelpCollapsed )
461 splitter->restoreState( mSplitterState );
462 mButtonCollapse->setArrowType( Qt::RightArrow );
466 mSplitterState = splitter->saveState();
467 splitter->setSizes( QList<int>() << 1 << 0 );
468 mButtonCollapse->setArrowType( Qt::LeftArrow );
470 mHelpCollapsed = !mHelpCollapsed;
473 void QgsProcessingAlgorithmDialogBase::splitterChanged(
int,
int )
475 if ( splitter->sizes().at( 1 ) == 0 )
477 mHelpCollapsed =
true;
478 mButtonCollapse->setArrowType( Qt::LeftArrow );
482 mHelpCollapsed =
false;
483 mButtonCollapse->setArrowType( Qt::RightArrow );
487 void QgsProcessingAlgorithmDialogBase::mTabWidget_currentChanged(
int )
489 updateRunButtonVisibility();
492 void QgsProcessingAlgorithmDialogBase::linkClicked(
const QUrl &url )
494 QDesktopServices::openUrl( url.toString() );
497 void QgsProcessingAlgorithmDialogBase::algExecuted(
bool successful,
const QVariantMap & )
499 mAlgorithmTask =
nullptr;
506 setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
513 if ( isFinalized() && !isVisible() )
520 void QgsProcessingAlgorithmDialogBase::taskTriggered(
QgsTask *task )
522 if ( task == mAlgorithmTask )
526 setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
532 void QgsProcessingAlgorithmDialogBase::closeClicked()
548 void QgsProcessingAlgorithmDialogBase::reportError(
const QString &error,
bool fatalError )
550 setInfo( error,
true );
557 void QgsProcessingAlgorithmDialogBase::pushWarning(
const QString &warning )
559 setInfo( warning,
false,
true,
true );
563 void QgsProcessingAlgorithmDialogBase::pushInfo(
const QString &info )
569 void QgsProcessingAlgorithmDialogBase::pushCommandInfo(
const QString &command )
571 txtLog->append( QStringLiteral(
"<code>%1<code>" ).arg( formatStringForLog( command.toHtmlEscaped() ) ) );
572 scrollToBottomOfLog();
576 void QgsProcessingAlgorithmDialogBase::pushDebugInfo(
const QString &message )
578 txtLog->append( QStringLiteral(
"<span style=\"color:#777\">%1</span>" ).arg( formatStringForLog( message.toHtmlEscaped() ) ) );
579 scrollToBottomOfLog();
583 void QgsProcessingAlgorithmDialogBase::pushConsoleInfo(
const QString &info )
585 txtLog->append( QStringLiteral(
"<code style=\"color:#777\">%1</code>" ).arg( formatStringForLog( info.toHtmlEscaped() ) ) );
586 scrollToBottomOfLog();
590 QDialog *QgsProcessingAlgorithmDialogBase::createProgressDialog()
592 QgsProcessingAlgorithmProgressDialog *dialog =
new QgsProcessingAlgorithmProgressDialog(
this );
593 dialog->setWindowModality( Qt::ApplicationModal );
594 dialog->setWindowTitle( windowTitle() );
595 dialog->setGeometry( geometry() );
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]()
601 dialog->logTextEdit()->setHtml( txtLog->toHtml() );
602 QScrollBar *sb = dialog->logTextEdit()->verticalScrollBar();
603 sb->setValue( sb->maximum() );
608 void QgsProcessingAlgorithmDialogBase::clearLog()
613 void QgsProcessingAlgorithmDialogBase::saveLog()
616 const QString lastUsedDir = settings.
value( QStringLiteral(
"/Processing/lastUsedLogDirectory" ), QDir::homePath() ).toString();
619 const QString txtExt = tr(
"Text files" ) + QStringLiteral(
" (*.txt *.TXT)" );
620 const QString htmlExt = tr(
"HTML files" ) + QStringLiteral(
" (*.html *.HTML)" );
622 const QString path = QFileDialog::getSaveFileName(
this, tr(
"Save Log to File" ), lastUsedDir, txtExt +
";;" + htmlExt, &filter );
623 if ( path.isEmpty() )
628 settings.
setValue( QStringLiteral(
"/Processing/lastUsedLogDirectory" ), QFileInfo( path ).path() );
630 LogFormat format = FormatPlainText;
631 if ( filter == htmlExt )
635 saveLogToFile( path, format );
638 void QgsProcessingAlgorithmDialogBase::copyLogToClipboard()
640 QMimeData *m =
new QMimeData();
641 m->setText( txtLog->toPlainText() );
642 m->setHtml( txtLog->toHtml() );
643 QClipboard *cb = QApplication::clipboard();
646 cb->setMimeData( m, QClipboard::Selection );
648 cb->setMimeData( m, QClipboard::Clipboard );
651 void QgsProcessingAlgorithmDialogBase::closeEvent( QCloseEvent *e )
653 if ( !mHelpCollapsed )
656 settings.
setValue( QStringLiteral(
"/Processing/dialogBaseSplitter" ), splitter->saveState() );
659 QDialog::closeEvent( e );
661 if ( !mAlgorithmTask && isFinalized() )
670 void QgsProcessingAlgorithmDialogBase::runAlgorithm()
675 void QgsProcessingAlgorithmDialogBase::setPercentage(
double percent )
678 if ( progressBar->maximum() == 0 )
679 progressBar->setMaximum( 100 );
680 progressBar->setValue( percent );
684 void QgsProcessingAlgorithmDialogBase::setProgressText(
const QString &text )
686 lblProgress->setText( text );
687 setInfo( text,
false );
688 scrollToBottomOfLog();
695 if ( !text.isEmpty() )
697 const QStringList paragraphs = text.split(
'\n' );
699 for (
const QString ¶graph : paragraphs )
701 help += QStringLiteral(
"<p>%1</p>" ).arg( paragraph );
713 void QgsProcessingAlgorithmDialogBase::processEvents()
715 if ( mAlgorithmTask )
731 while ( ++nIters < 100 )
734 QCoreApplication::processEvents();
738 void QgsProcessingAlgorithmDialogBase::scrollToBottomOfLog()
740 QScrollBar *sb = txtLog->verticalScrollBar();
741 sb->setValue( sb->maximum() );
744 void QgsProcessingAlgorithmDialogBase::resetGui()
746 lblProgress->clear();
747 progressBar->setMaximum( 100 );
748 progressBar->setValue( 0 );
749 mButtonRun->setEnabled(
true );
750 mButtonChangeParameters->setEnabled(
true );
751 mButtonClose->setEnabled(
true );
754 mMainWidget->setEnabled(
true );
756 updateRunButtonVisibility();
757 resetAdditionalGui();
760 void QgsProcessingAlgorithmDialogBase::updateRunButtonVisibility()
763 const bool runButtonVisible = mTabWidget->currentIndex() == 0;
764 mButtonRun->setVisible( runButtonVisible );
765 mButtonChangeParameters->setVisible( !runButtonVisible && mExecutedAnyResult && mButtonChangeParameters->isEnabled() );
768 void QgsProcessingAlgorithmDialogBase::resetAdditionalGui()
773 void QgsProcessingAlgorithmDialogBase::blockControlsWhileRunning()
775 mButtonRun->setEnabled(
false );
776 mButtonChangeParameters->setEnabled(
false );
779 mMainWidget->setEnabled(
false );
781 blockAdditionalControlsWhileRunning();
784 void QgsProcessingAlgorithmDialogBase::blockAdditionalControlsWhileRunning()
789 QgsMessageBar *QgsProcessingAlgorithmDialogBase::messageBar()
794 void QgsProcessingAlgorithmDialogBase::hideShortHelp()
796 textShortHelp->setVisible(
false );
801 mAlgorithmTask = task;
806 QString QgsProcessingAlgorithmDialogBase::formatStringForLog(
const QString &
string )
809 s.replace(
'\n', QLatin1String(
"<br>" ) );
813 bool QgsProcessingAlgorithmDialogBase::isFinalized()
818 void QgsProcessingAlgorithmDialogBase::setInfo(
const QString &message,
bool isError,
bool escapeHtml,
bool isWarning )
820 constexpr
int MESSAGE_COUNT_LIMIT = 10000;
822 if ( mMessageLoggedCount == MESSAGE_COUNT_LIMIT )
824 ++mMessageLoggedCount;
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() ) ) );
835 txtLog->append( QStringLiteral(
"<span>%1</span>" ).arg( formatStringForLog( message ) ) );
836 scrollToBottomOfLog();
840 void QgsProcessingAlgorithmDialogBase::reject()
842 if ( !mAlgorithmTask && isFinalized() )
844 setAttribute( Qt::WA_DeleteOnClose );
853 QgsProcessingAlgorithmProgressDialog::QgsProcessingAlgorithmProgressDialog( QWidget *parent )
859 QProgressBar *QgsProcessingAlgorithmProgressDialog::progressBar()
864 QPushButton *QgsProcessingAlgorithmProgressDialog::cancelButton()
866 return mButtonBox->button( QDialogButtonBox::Cancel );
869 QTextEdit *QgsProcessingAlgorithmProgressDialog::logTextEdit()
874 void QgsProcessingAlgorithmProgressDialog::reject()