##############
Responsive GUI
##############

And now for the hard part of making the widget responsive. We will do this
by offloading the learner evaluations into a separate thread.

First read up on `threading basics`_ in Qt and in particular the subject
of `threads and qobjects`_ and how they interact with the Qt's event loop.

.. _threading basics:
    http://doc.qt.io/qt-5/thread-basics.html

.. _threads and qobjects:
    http://doc.qt.io/qt-5/threads-qobject.html

We must also take special care that we can cancel/interrupt our task when
the user changes algorithm parameters or removes the widget from the canvas.
For that we use a strategy known as cooperative cancellation where we 'ask'
the pending task to stop executing (in the GUI thread), then in the worker
thread periodically check (at known predetermined points) whether we should
continue, and if not return early (in our case by raising an exception).


We use :class:`Orange.widgets.utils.concurrent.ThreadExecutor` for thread
allocation/management (but could easily replace it with stdlib's
:class:`concurrent.futures.ThreadPoolExecutor`).

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-1
   :end-before: end-snippet-1


We will reorganize our code to make the learner evaluation an explicit
task as we will need to track its progress and state. For this we define
a `Task` class.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-2
   :end-before: end-snippet-2


In the widget's ``__init__`` we create an instance of the `ThreadExector`
and initialize the task field.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-3
   :end-before: end-snippet-3


In `handleNewSignals` we call `_update`.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-4
   :end-before: end-snippet-4


And finally the `_update` function that will start/schedule all updates.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-5
   :end-before: end-snippet-5


At the start we cancel pending tasks if they are not yet completed. It is
important to do this, we cannot allow the widget to schedule tasks and
then just forget about them. Next we make some checks and return early if
there is nothing to be done.

Continue by setting up the learner evaluations as a partial function
capturing the necessary arguments:

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-6
   :end-before: end-snippet-6


Setup the task state and the communication between the main and worker thread.
The only `state` flowing from the GUI to the worker thread is the
`task.cancelled` field which is a simple trip wire causing the
`learning_curve`'s callback argument to raise an exception. In the other
direction we report the `percent` of work done.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-7
   :end-before: end-snippet-7

.. seealso::
   :func:`~Orange.widgets.widget.OWWidget.progressBarInit`,
   :func:`~Orange.widgets.widget.OWWidget.progressBarSet`,
   :func:`~Orange.widgets.widget.OWWidget.progressBarFinished`


Next, we submit the function to be run in a worker thread and instrument
a FutureWatcher instance to notify us when the task completes (via a
`_task_finished` slot).

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-8
   :end-before: end-snippet-8


In `_task_finished` we handle the completed task (either success or failure)
and then update the displayed score table.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-9
   :end-before: end-snippet-9


Also of interest is the `cancel` method. Note that we also disconnect the
`_task_finished` slot so that `_task_finished` does not receive stale
results.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-10
   :end-before: end-snippet-10

We also use cancel in :func:`~Orange.widgets.widget.OWWidget.onDeleteWidget`
to stop if/when the widget is removed from the canvas.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-11
   :end-before: end-snippet-11
