Skip to content

Commit

Permalink
[processing] Show a modal progress dialog when running algorithms
Browse files Browse the repository at this point in the history
which cannot run in background tasks

This is not fantastic UX, but we have lots of constraints here:

- The algorithm dialog itself cannot be made modal. There's child
widgets (such as the point and extent parameter widgets) which
interact with the main QGIS window.
- There is no reliable way in Qt to make a dialog modal after
it's shown (e.g. make it modal only when the algorithm is
running). Trust me - I've tried everything, and all approaches
break with some corner case.
- For non-background algorithms, we must have processEvents calls
in order to show the algorithm feedback and progress to users,
and detect cancel button clicks. Yet these processEvents calls
means that users can interact with other parts of QGIS, e.g.
removing layers from a project, and other operations which
could cause the algorithm to crash. So we MUST have some modal
dialog in order to block interactions outside of allowing
the cancel button clicks/progress repainting.

I've tried many approaches, but this is the only one which
works reliably...
  • Loading branch information
nyalldawson committed Jan 9, 2018
1 parent 2eb68de commit 240c52a
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 7 deletions.
7 changes: 7 additions & 0 deletions python/gui/processing/qgsprocessingalgorithmdialogbase.sip
Expand Up @@ -133,6 +133,12 @@ Sets a progress text message.
void pushConsoleInfo( const QString &info );
%Docstring
Pushes a console info string to the dialog's log.
%End

QDialog *createProgressDialog();
%Docstring
Creates a modal progress dialog showing progress and log messages
from this dialog.
%End

protected:
Expand Down Expand Up @@ -210,6 +216,7 @@ Called when the algorithm has finished executing.
};



/************************************************************************
* This file has been generated automatically from *
* *
Expand Down
24 changes: 18 additions & 6 deletions python/plugins/processing/gui/AlgorithmDialog.py
Expand Up @@ -63,6 +63,7 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):

def __init__(self, alg):
super().__init__()
self.feedback_dialog = None

self.setAlgorithm(alg)
self.setMainWidget(self.getParametersPanel(alg, self))
Expand Down Expand Up @@ -196,12 +197,6 @@ def accept(self):

self.clearProgress()
self.setProgressText(self.tr('Processing algorithm...'))
# Make sure the Log tab is visible before executing the algorithm
try:
self.showLog()
self.repaint()
except:
pass

self.setInfo(
self.tr('<b>Algorithm \'{0}\' starting...</b>').format(self.algorithm().displayName()), escapeHtml=False)
Expand All @@ -215,6 +210,13 @@ def accept(self):
start_time = time.time()

if self.iterateParam:
# Make sure the Log tab is visible before executing the algorithm
try:
self.showLog()
self.repaint()
except:
pass

self.cancelButton().setEnabled(self.algorithm().flags() & QgsProcessingAlgorithm.FlagCanCancel)
if executeIterating(self.algorithm(), parameters, self.iterateParam, context, feedback):
feedback.pushInfo(
Expand All @@ -240,15 +242,25 @@ def on_complete(ok, results):
self.tr('Execution failed after {0:0.2f} seconds'.format(time.time() - start_time)))
feedback.pushInfo('')

if self.feedback_dialog is not None:
self.feedback_dialog.close()
self.feedback_dialog.deleteLater()
self.feedback_dialog = None

self.cancelButton().setEnabled(False)

self.finish(ok, results, context, feedback)

if self.algorithm().flags() & QgsProcessingAlgorithm.FlagCanRunInBackground:
# Make sure the Log tab is visible before executing the algorithm
self.showLog()

task = QgsProcessingAlgRunnerTask(self.algorithm(), parameters, context, feedback)
task.executed.connect(on_complete)
QgsApplication.taskManager().addTask(task)
else:
self.feedback_dialog = self.createProgressDialog()
self.feedback_dialog.show()
ok, results = execute(self.algorithm(), parameters, context, feedback)
on_complete(ok, results)

Expand Down
50 changes: 50 additions & 0 deletions src/gui/processing/qgsprocessingalgorithmdialogbase.cpp
Expand Up @@ -306,6 +306,23 @@ void QgsProcessingAlgorithmDialogBase::pushConsoleInfo( const QString &info )
processEvents();
}

QDialog *QgsProcessingAlgorithmDialogBase::createProgressDialog()
{
QgsProcessingAlgorithmProgressDialog *dialog = new QgsProcessingAlgorithmProgressDialog( this );
dialog->setWindowModality( Qt::ApplicationModal );
dialog->setWindowTitle( windowTitle() );
connect( progressBar, &QProgressBar::valueChanged, dialog->progressBar(), &QProgressBar::setValue );
connect( dialog->cancelButton(), &QPushButton::clicked, buttonCancel, &QPushButton::click );
dialog->logTextEdit()->setHtml( txtLog->toHtml() );
connect( txtLog, &QTextEdit::textChanged, dialog, [this, dialog]()
{
dialog->logTextEdit()->setHtml( txtLog->toHtml() );
QScrollBar *sb = dialog->logTextEdit()->verticalScrollBar();
sb->setValue( sb->maximum() );
} );
return dialog;
}

void QgsProcessingAlgorithmDialogBase::setPercentage( double percent )
{
// delay setting maximum progress value until we know algorithm reports progress
Expand Down Expand Up @@ -393,4 +410,37 @@ void QgsProcessingAlgorithmDialogBase::setInfo( const QString &message, bool isE
processEvents();
}

//
// QgsProcessingAlgorithmProgressDialog
//

QgsProcessingAlgorithmProgressDialog::QgsProcessingAlgorithmProgressDialog( QWidget *parent )
: QDialog( parent )
{
setWindowFlags( Qt::Dialog ); // hide close button
setupUi( this );
}

QProgressBar *QgsProcessingAlgorithmProgressDialog::progressBar()
{
return mProgressBar;
}

QPushButton *QgsProcessingAlgorithmProgressDialog::cancelButton()
{
return mButtonBox->button( QDialogButtonBox::Cancel );
}

QTextEdit *QgsProcessingAlgorithmProgressDialog::logTextEdit()
{
return mTxtLog;
}

void QgsProcessingAlgorithmProgressDialog::reject()
{

}



///@endcond
51 changes: 50 additions & 1 deletion src/gui/processing/qgsprocessingalgorithmdialogbase.h
Expand Up @@ -19,6 +19,7 @@
#include "qgis.h"
#include "qgis_gui.h"
#include "ui_qgsprocessingalgorithmdialogbase.h"
#include "ui_qgsprocessingalgorithmprogressdialogbase.h"
#include "processing/qgsprocessingcontext.h"
#include "processing/qgsprocessingfeedback.h"

Expand Down Expand Up @@ -180,6 +181,12 @@ class GUI_EXPORT QgsProcessingAlgorithmDialogBase : public QDialog, private Ui::
*/
void pushConsoleInfo( const QString &info );

/**
* Creates a modal progress dialog showing progress and log messages
* from this dialog.
*/
QDialog *createProgressDialog();

protected:

/**
Expand Down Expand Up @@ -271,11 +278,53 @@ class GUI_EXPORT QgsProcessingAlgorithmDialogBase : public QDialog, private Ui::
bool mHelpCollapsed = false;

QString formatHelp( QgsProcessingAlgorithm *algorithm );
void processEvents();
void scrollToBottomOfLog();
void processEvents();

};

#ifndef SIP_RUN

/**
* \ingroup gui
* \brief A modal dialog for showing algorithm progress and log messages.
* \note This is not considered stable API and may change in future QGIS versions.
* \since QGIS 3.0
*/
class QgsProcessingAlgorithmProgressDialog : public QDialog, private Ui::QgsProcessingProgressDialogBase
{
Q_OBJECT

public:

/**
* Constructor for QgsProcessingAlgorithmProgressDialog.
*/
QgsProcessingAlgorithmProgressDialog( QWidget *parent = nullptr );

/**
* Returns the dialog's progress bar.
*/
QProgressBar *progressBar();

/**
* Returns the dialog's cancel button.
*/
QPushButton *cancelButton();

/**
* Returns the dialog's text log.
*/
QTextEdit *logTextEdit();

public slots:

void reject() override;

};

#endif

///@endcond

#endif // QGSPROCESSINGALGORITHMDIALOGBASE_H
55 changes: 55 additions & 0 deletions src/ui/processing/qgsprocessingalgorithmprogressdialogbase.ui
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsProcessingProgressDialogBase</class>
<widget class="QDialog" name="QgsProcessingProgressDialogBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1603</width>
<height>1239</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextEdit" name="mTxtLog">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QProgressBar" name="mProgressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="mButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

0 comments on commit 240c52a

Please sign in to comment.