Repository: spyder-ide/spyder

Issue:
%matplolib magic command in IPython terminal needs to be executed twice
## Description of your problem
`%matplolib` magic command in IPython terminal needs to be executed twice to actually work.

**What steps will reproduce the problem?**
```
In [1]: import matplotlib.pyplot as plt

In [2]: plt.plot([1,2,3])
Out[2]: [<matplotlib.lines.Line2D at 0x21347b252b0>]
￼GRAPH INLINE

In [3]: %matplotlib
Using matplotlib backend: Qt5Agg

In [4]: plt.plot([1,2,3])
Out[4]: [<matplotlib.lines.Line2D at 0x21349bf2438>]
￼GRAPH INLINE￼

In [5]: %matplotlib
Using matplotlib backend: Qt5Agg

In [6]: plt.plot([1,2,3])
Out[6]: [<matplotlib.lines.Line2D at 0x21349c5cf98>]
￼GRAPH IN EXTERNAL WINDOW
```

**What is the expected output? What do you see instead?**
Only one `%matplolib` call should be enough.

**Please provide any additional information below**
- It works correctly directly in IPython console
- It was also seen in Spyder 3.0.2

## Versions and main components

* Spyder Version: 3.1.0
* Python Version: 3.5.2
* Operating system: Windows 10 64bits


## Dependencies

jedi >=0.8.1     :  0.9.0 (OK)
matplotlib >=1.0 :  1.5.3 (OK)
nbconvert >=4.0  :  4.2.0 (OK)
numpy >=1.7      :  1.11.3 (OK)
pandas >=0.13.1  :  0.19.2 (OK)
pep8 >=0.6       :  1.7.0 (OK)
pyflakes >=0.6.0 :  1.5.0 (OK)
pygments >=2.0   :  2.1.3 (OK)
pylint >=0.25    :  1.6.4 (OK)
qtconsole >=4.2.0:  4.2.1 (OK)
rope >=0.9.4     :  0.9.4-1 (OK)
sphinx >=0.6.6   :  1.5.1 (OK)
sympy >=0.7.3    :  None (NOK)



Discussion:
This is because we internally run

    %matplotlib inline

when we start all our kernels by default. Your second command should be

    %matplotlib qt5

if you want to use Qt5 directly.
I think I didn't explain it well. The problem is that after the first `%matplotlib` the graph should appear in an external window and it doesn't. It does only after a second `%matplotlib`.
If you want to see your plot in a external window for sure, you need to use the command I gave you, i.e.

     %matplotlib qt5

not just

    %matplotlib

because `%matplotlib` with no args is used to set the default backend, not to change between inline and external.
I just tried and I get the same behavior than without args.
Actually with or without args I can see a window during an instant, but it disappears immediately and then it is plotted inline again. A second call fixes it.

PD: in Spyder 2 `%matplotlib` without args was working.
That's certainly odd, we take a look at it :-)
@mariacamilaremolinagutierrez, please work on this one.
@ccordoba12 @rubennj I am not able to reproduce this error. When I  run %matplotlib qt5 I get the external window right away without second calls:

![image](https://cloud.githubusercontent.com/assets/3608776/22221720/2b04dc84-e182-11e6-92aa-fa9851061da2.png)


I however just updated qt and pyqt. Ruben could you please update to our latest release (https://github.com/spyder-ide/spyder/releases/tag/v3.1.1) and tell me if this problem remains? 
@ccordoba12 to ease issue reporting we might also query for (is a anaconda install and provide the conda info and conda list on the report? of deps?)
@goanpeca, yep, sounds good!
In the way @mariacamilaremolinagutierrez has tested it also works on my system (not plotting `inline` first). But it does not work as I showed. This is a more typical situation. First you check some data and later you can be interested in a more dynamic view, like a zoom, for example.

I'm waiting for Continuum repositories to update Spyder to 3.1.1.

Below my current `conda list`:
<details>

_nb_ext_conf              0.3.0                    py35_0
alabaster                 0.7.9                    py35_0
anaconda-client           1.6.0                    py35_0
astroid                   1.4.9                    py35_0
autopep8                  1.2.4                     <pip>
babel                     2.3.4                    py35_0
bzip2                     1.0.6                    vc14_3  [vc14]
cffi                      1.9.1                    py35_0
chardet                   2.3.0                    py35_0
clyent                    1.2.2                    py35_0
colorama                  0.3.7                    py35_0
conda                     4.3.7                    py35_0
conda-env                 2.6.0                         0
console_shortcut          0.1.1                    py35_1
cryptography              1.7.1                    py35_0
cycler                    0.10.0                   py35_0
decorator                 4.0.11                   py35_0
docutils                  0.13.1                   py35_0
entrypoints               0.2.2                    py35_0
freetype                  2.5.5                    vc14_2  [vc14]
icu                       57.1                     vc14_0  [vc14]
idna                      2.2                      py35_0
imagesize                 0.7.1                    py35_0
ipykernel                 4.5.2                    py35_0
ipython                   5.1.0                    py35_0
ipython_genutils          0.1.0                    py35_0
ipywidgets                5.2.2                    py35_1
jedi                      0.9.0                    py35_1
jinja2                    2.9.4                    py35_0
jpeg                      9b                       vc14_0  [vc14]
jsonschema                2.5.1                    py35_0
jupyter                   1.0.0                    py35_3
jupyter_client            4.4.0                    py35_0
jupyter_console           5.0.0                    py35_0
jupyter_core              4.2.1                    py35_0
lazy-object-proxy         1.2.2                    py35_0
libpng                    1.6.27                   vc14_0  [vc14]
libtiff                   4.0.6                    vc14_3  [vc14]
markupsafe                0.23                     py35_2
matplotlib                1.5.3               np111py35_1
menuinst                  1.4.3                    py35_0
mistune                   0.7.3                    py35_0
mkl                       2017.0.1                      0
nb_anacondacloud          1.2.0                    py35_0
nb_conda                  2.0.0                    py35_0
nb_conda_kernels          2.0.0                    py35_0
nbconvert                 4.2.0                    py35_0
nbformat                  4.2.0                    py35_0
nbpresent                 3.0.2                    py35_0
notebook                  4.3.1                    py35_1
numexpr                   2.6.1               np111py35_2
numpy                     1.11.3                   py35_0
numpydoc                  0.6.0                    py35_0
openssl                   1.0.2j                   vc14_0  [vc14]
pandas                    0.19.2              np111py35_1
path.py                   10.0                     py35_0
patsy                     0.4.1                    py35_0
pep8                      1.7.0                    py35_0
pickleshare               0.7.4                    py35_0
pillow                    4.0.0                    py35_0
pip                       9.0.1                    py35_1
prompt_toolkit            1.0.9                    py35_0
psutil                    5.0.1                    py35_0
pyasn1                    0.1.9                    py35_0
pycosat                   0.6.1                    py35_1
pycparser                 2.17                     py35_0
pycrypto                  2.6.1                    py35_5
pyflakes                  1.5.0                    py35_0
pygments                  2.1.3                    py35_0
pylint                    1.6.4                    py35_0
pyopenssl                 16.2.0                   py35_0
pyparsing                 2.1.4                    py35_0
pyqt                      5.6.0                    py35_2
python                    3.5.2                         0
python-dateutil           2.6.0                    py35_0
pytz                      2016.10                  py35_0
pywin32                   220                      py35_2
pyyaml                    3.12                     py35_0
pyzmq                     16.0.2                   py35_0
qt                        5.6.2                    vc14_3  [vc14]
qtawesome                 0.4.1                    py35_0
qtconsole                 4.2.1                    py35_2
qtpy                      1.2.0                    py35_0
requests                  2.12.4                   py35_0
rope                      0.9.4                    py35_1
ruamel_yaml               0.11.14                  py35_1
scikit-learn              0.18.1              np111py35_1
scipy                     0.18.1              np111py35_1
setuptools                27.2.0                   py35_1
simplegeneric             0.8.1                    py35_1
sip                       4.18                     py35_0
six                       1.10.0                   py35_0
snowballstemmer           1.2.1                    py35_0
sphinx                    1.5.1                    py35_0
spyder                    3.1.0                    py35_0
statsmodels               0.6.1               np111py35_1
tk                        8.5.18                   vc14_0  [vc14]
tornado                   4.4.2                    py35_0
traitlets                 4.3.1                    py35_0
vs2015_runtime            14.0.25123                    0
wcwidth                   0.1.7                    py35_0
wheel                     0.29.0                   py35_0
widgetsnbextension        1.2.6                    py35_0
win_unicode_console       0.5                      py35_0
wrapt                     1.10.8                   py35_0
xlrd                      1.0.0                    py35_0
zlib                      1.2.8                    vc14_3  [vc14]
</details>
I got Spyder 3.1.2 and still the same

Resolving patch:
diff --git a/spyder/widgets/ipythonconsole/client.py b/spyder/widgets/ipythonconsole/client.py
--- a/spyder/widgets/ipythonconsole/client.py
+++ b/spyder/widgets/ipythonconsole/client.py
@@ -158,7 +158,8 @@ def configure_shellwidget(self, give_focus=True):
         self.shellwidget.executing.connect(self.add_to_history)
 
         # For Mayavi to run correctly
-        self.shellwidget.executing.connect(self.set_backend_for_mayavi)
+        self.shellwidget.executing.connect(
+            self.shellwidget.set_backend_for_mayavi)
 
         # To update history after execution
         self.shellwidget.executed.connect(self.update_history)
@@ -182,6 +183,10 @@ def configure_shellwidget(self, give_focus=True):
         self.shellwidget.sig_dbg_kernel_restart.connect(
                 self.restart_kernel)
 
+        # To correctly change Matplotlib backend interactively
+        self.shellwidget.executing.connect(
+            self.shellwidget.change_mpl_backend)
+
     def enable_stop_button(self):
         self.stop_button.setEnabled(True)
 
@@ -405,23 +410,6 @@ def reset_namespace(self):
     def update_history(self):
         self.history = self.shellwidget._history
 
-    def set_backend_for_mayavi(self, command):
-        """
-        Mayavi plots require the Qt backend, so we try to detect if one is
-        generated to change backends
-        """
-        calling_mayavi = False
-        lines = command.splitlines()
-        for l in lines:
-            if not l.startswith('#'):
-                if 'import mayavi' in l or 'from mayavi' in l:
-                    calling_mayavi = True
-                    break
-        if calling_mayavi:
-            message = _("Changing backend to Qt for Mayavi")
-            self.shellwidget._append_plain_text(message + '\n')
-            self.shellwidget.execute("%gui inline\n%gui qt")
-
     #------ Private API -------------------------------------------------------
     def _create_loading_page(self):
         """Create html page to show while the kernel is starting"""
diff --git a/spyder/widgets/ipythonconsole/shell.py b/spyder/widgets/ipythonconsole/shell.py
--- a/spyder/widgets/ipythonconsole/shell.py
+++ b/spyder/widgets/ipythonconsole/shell.py
@@ -265,6 +265,35 @@ def handle_exec_method(self, msg):
                 # Remove method after being processed
                 self._kernel_methods.pop(expression)
 
+    def set_backend_for_mayavi(self, command):
+        """
+        Mayavi plots require the Qt backend, so we try to detect if one is
+        generated to change backends
+        """
+        calling_mayavi = False
+        lines = command.splitlines()
+        for l in lines:
+            if not l.startswith('#'):
+                if 'import mayavi' in l or 'from mayavi' in l:
+                    calling_mayavi = True
+                    break
+        if calling_mayavi:
+            message = _("Changing backend to Qt for Mayavi")
+            self._append_plain_text(message + '\n')
+            self.silent_execute("%gui inline\n%gui qt")
+
+    def change_mpl_backend(self, command):
+        """
+        If the user is trying to change Matplotlib backends with
+        %matplotlib, send the same command again to the kernel to
+        correctly change it.
+
+        Fixes issue 4002
+        """
+        if command.startswith('%matplotlib') and \
+          len(command.splitlines()) == 1:
+            self.silent_execute(command)
+
     #---- Private methods (overrode by us) ---------------------------------
     def _context_menu_make(self, pos):
         """Reimplement the IPython context menu"""


Test patch:
diff --git a/spyder/plugins/tests/test_ipythonconsole.py b/spyder/plugins/tests/test_ipythonconsole.py
--- a/spyder/plugins/tests/test_ipythonconsole.py
+++ b/spyder/plugins/tests/test_ipythonconsole.py
@@ -59,6 +59,37 @@ def close_widget():
 #==============================================================================
 # Tests
 #==============================================================================
+@flaky(max_runs=10)
+@pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
+def test_mpl_backend_change(ipyconsole, qtbot):
+    """
+    Test that Matplotlib backend is changed correctly when
+    using the %matplotlib magic
+    """
+    shell = ipyconsole.get_current_shellwidget()
+    client = ipyconsole.get_current_client()
+    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
+
+    # Import Matplotlib
+    with qtbot.waitSignal(shell.executed):
+        shell.execute('import matplotlib.pyplot as plt')
+
+    # Generate an inline plot
+    with qtbot.waitSignal(shell.executed):
+        shell.execute('plt.plot(range(10))')
+
+    # Change backends
+    with qtbot.waitSignal(shell.executed):
+        shell.execute('%matplotlib tk')
+
+    # Generate another plot
+    with qtbot.waitSignal(shell.executed):
+        shell.execute('plt.plot(range(10))')
+
+    # Assert that there's a single inline plot in the console
+    assert shell._control.toHtml().count('img src') == 1
+
+
 @flaky(max_runs=10)
 @pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
 def test_forced_restart_kernel(ipyconsole, qtbot):
@@ -71,12 +102,13 @@ def test_forced_restart_kernel(ipyconsole, qtbot):
     qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
 
     # Do an assigment to verify that it's not there after restarting
-    shell.execute('a = 10')
-    qtbot.wait(500)
+    with qtbot.waitSignal(shell.executed):
+        shell.execute('a = 10')
 
     # Generate a traceback and enter debugging mode
-    shell.execute('1/0')
-    qtbot.wait(500)
+    with qtbot.waitSignal(shell.executed):
+        shell.execute('1/0')
+
     shell.execute('%debug')
     qtbot.wait(500)
 
@@ -99,8 +131,8 @@ def test_restart_kernel(ipyconsole, qtbot):
     qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
 
     # Do an assigment to verify that it's not there after restarting
-    shell.execute('a = 10')
-    qtbot.wait(500)
+    with qtbot.waitSignal(shell.executed):
+        shell.execute('a = 10')
 
     # Restart kernel and wait until it's up again
     shell._prompt_html = None
@@ -176,8 +208,8 @@ def test_load_kernel_file(ipyconsole, qtbot):
 
     new_client = ipyconsole.get_clients()[1]
     new_shell = new_client.shellwidget
-    new_shell.execute('a = 10')
-    qtbot.wait(500)
+    with qtbot.waitSignal(new_shell.executed):
+        new_shell.execute('a = 10')
 
     assert new_client.name == '1/B'
     assert shell.get_value('a') == new_shell.get_value('a')