Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #1052 from fperez/pylab-fix

Fix bug in pylab support introduced in #648, and refactor the pylab/gui support to eliminate a lot of code duplication we had in a number of places. 

Now all duplicate code is gone, and the only real difference is how gui event loops are integrated, which is reduced to a single static method that each relevant class grabs from its specific machinery.
  • Loading branch information...
commit f15123ac96a192589c2613c4733a4a26ae2dac15 2 parents a1e4911 + 3f11378
@fperez fperez authored
View
41 IPython/core/interactiveshell.py
@@ -62,6 +62,7 @@
from IPython.core.plugin import PluginManager
from IPython.core.prefilter import PrefilterManager, ESC_MAGIC
from IPython.core.profiledir import ProfileDir
+from IPython.core.pylabtools import pylab_activate
from IPython.external.Itpl import ItplNS
from IPython.utils import PyColorize
from IPython.utils import io
@@ -2531,8 +2532,46 @@ def run_code(self, code_obj):
# Things related to GUI support and pylab
#-------------------------------------------------------------------------
+ def enable_gui(self, gui=None):
+ raise NotImplementedError('Implement enable_gui in a subclass')
+
def enable_pylab(self, gui=None, import_all=True):
- raise NotImplementedError('Implement enable_pylab in a subclass')
+ """Activate pylab support at runtime.
+
+ This turns on support for matplotlib, preloads into the interactive
+ namespace all of numpy and pylab, and configures IPython to correctly
+ interact with the GUI event loop. The GUI backend to be used can be
+ optionally selected with the optional :param:`gui` argument.
+
+ Parameters
+ ----------
+ gui : optional, string
+
+ If given, dictates the choice of matplotlib GUI backend to use
+ (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
+ 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
+ matplotlib (as dictated by the matplotlib build-time options plus the
+ user's matplotlibrc configuration file). Note that not all backends
+ make sense in all contexts, for example a terminal ipython can't
+ display figures inline.
+ """
+
+ # We want to prevent the loading of pylab to pollute the user's
+ # namespace as shown by the %who* magics, so we execute the activation
+ # code in an empty namespace, and we update *both* user_ns and
+ # user_ns_hidden with this information.
+ ns = {}
+ try:
+ gui = pylab_activate(ns, gui, import_all, self)
+ except KeyError:
+ error("Backend %r not supported" % gui)
+ return
+ self.user_ns.update(ns)
+ self.user_ns_hidden.update(ns)
+ # Now we must activate the gui pylab wants to use, and fix %run to take
+ # plot updates into account
+ self.enable_gui(gui)
+ self.magic_run = self._pylab_magic_run
#-------------------------------------------------------------------------
# Utilities
View
24 IPython/core/magic.py
@@ -50,7 +50,7 @@
from IPython.core.macro import Macro
from IPython.core import magic_arguments, page
from IPython.core.prefilter import ESC_MAGIC
-from IPython.lib.pylabtools import mpl_runner
+from IPython.core.pylabtools import mpl_runner
from IPython.testing.skipdoctest import skip_doctest
from IPython.utils import py3compat
from IPython.utils.io import file_read, nlprint
@@ -3305,24 +3305,30 @@ def magic_gui(self, parameter_s=''):
This magic replaces IPython's threaded shells that were activated
using the (pylab/wthread/etc.) command line flags. GUI toolkits
- can now be enabled, disabled and changed at runtime and keyboard
+ can now be enabled at runtime and keyboard
interrupts should work without any problems. The following toolkits
- are supported: wxPython, PyQt4, PyGTK, and Tk::
+ are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
%gui wx # enable wxPython event loop integration
%gui qt4|qt # enable PyQt4 event loop integration
%gui gtk # enable PyGTK event loop integration
%gui tk # enable Tk event loop integration
+ %gui OSX # enable Cocoa event loop integration
+ # (requires %matplotlib 1.1)
%gui # disable all event loop integration
WARNING: after any of these has been called you can simply create
an application object, but DO NOT start the event loop yourself, as
we have already handled that.
"""
- from IPython.lib.inputhook import enable_gui
opts, arg = self.parse_options(parameter_s, '')
if arg=='': arg = None
- return enable_gui(arg)
+ try:
+ return self.enable_gui(arg)
+ except Exception as e:
+ # print simple error message, rather than traceback if we can't
+ # hook up the GUI
+ error(str(e))
def magic_load_ext(self, module_str):
"""Load an IPython extension by its module name."""
@@ -3416,9 +3422,9 @@ def magic_pylab(self, s):
Parameters
----------
guiname : optional
- One of the valid arguments to the %gui magic ('qt', 'wx', 'gtk', 'osx' or
- 'tk'). If given, the corresponding Matplotlib backend is used,
- otherwise matplotlib's default (which you can override in your
+ One of the valid arguments to the %gui magic ('qt', 'wx', 'gtk',
+ 'osx' or 'tk'). If given, the corresponding Matplotlib backend is
+ used, otherwise matplotlib's default (which you can override in your
matplotlib config file) is used.
Examples
@@ -3449,7 +3455,7 @@ def magic_pylab(self, s):
else:
import_all_status = True
- self.shell.enable_pylab(s,import_all=import_all_status)
+ self.shell.enable_pylab(s, import_all=import_all_status)
def magic_tb(self, s):
"""Print the last traceback with the currently active exception mode.
View
99 IPython/lib/pylabtools.py → IPython/core/pylabtools.py
@@ -232,7 +232,8 @@ def activate_matplotlib(backend):
# For this, we wrap it into a decorator which adds a 'called' flag.
pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
-def import_pylab(user_ns, backend, import_all=True, shell=None):
+
+def import_pylab(user_ns, import_all=True):
"""Import the standard pylab symbols into user_ns."""
# Import numpy as np/pyplot as plt are conventions we're trying to
@@ -246,48 +247,62 @@ def import_pylab(user_ns, backend, import_all=True, shell=None):
)
exec s in user_ns
- if shell is not None:
- exec s in shell.user_ns_hidden
- # If using our svg payload backend, register the post-execution
- # function that will pick up the results for display. This can only be
- # done with access to the real shell object.
- #
- from IPython.zmq.pylab.backend_inline import InlineBackend
-
- cfg = InlineBackend.instance(config=shell.config)
- cfg.shell = shell
- if cfg not in shell.configurables:
- shell.configurables.append(cfg)
-
- if backend == backends['inline']:
- from IPython.zmq.pylab.backend_inline import flush_figures
- from matplotlib import pyplot
- shell.register_post_execute(flush_figures)
- # load inline_rc
- pyplot.rcParams.update(cfg.rc)
-
- # Add 'figsize' to pyplot and to the user's namespace
- user_ns['figsize'] = pyplot.figsize = figsize
- shell.user_ns_hidden['figsize'] = figsize
-
- # Setup the default figure format
- fmt = cfg.figure_format
- select_figure_format(shell, fmt)
-
- # The old pastefig function has been replaced by display
- from IPython.core.display import display
- # Add display and display_png to the user's namespace
- user_ns['display'] = display
- shell.user_ns_hidden['display'] = display
- user_ns['getfigs'] = getfigs
- shell.user_ns_hidden['getfigs'] = getfigs
-
if import_all:
s = ("from matplotlib.pylab import *\n"
"from numpy import *\n")
exec s in user_ns
- if shell is not None:
- exec s in shell.user_ns_hidden
+
+
+def configure_inline_support(shell, backend, user_ns=None):
+ """Configure an IPython shell object for matplotlib use.
+
+ Parameters
+ ----------
+ shell : InteractiveShell instance
+
+ backend : matplotlib backend
+
+ user_ns : dict
+ A namespace where all configured variables will be placed. If not given,
+ the `user_ns` attribute of the shell object is used.
+ """
+ # If using our svg payload backend, register the post-execution
+ # function that will pick up the results for display. This can only be
+ # done with access to the real shell object.
+
+ # Note: if we can't load the inline backend, then there's no point
+ # continuing (such as in terminal-only shells in environments without
+ # zeromq available).
+ try:
+ from IPython.zmq.pylab.backend_inline import InlineBackend
+ except ImportError:
+ return
+
+ user_ns = shell.user_ns if user_ns is None else user_ns
+
+ cfg = InlineBackend.instance(config=shell.config)
+ cfg.shell = shell
+ if cfg not in shell.configurables:
+ shell.configurables.append(cfg)
+
+ if backend == backends['inline']:
+ from IPython.zmq.pylab.backend_inline import flush_figures
+ from matplotlib import pyplot
+ shell.register_post_execute(flush_figures)
+ # load inline_rc
+ pyplot.rcParams.update(cfg.rc)
+ # Add 'figsize' to pyplot and to the user's namespace
+ user_ns['figsize'] = pyplot.figsize = figsize
+
+ # Setup the default figure format
+ fmt = cfg.figure_format
+ select_figure_format(shell, fmt)
+
+ # The old pastefig function has been replaced by display
+ from IPython.core.display import display
+ # Add display and getfigs to the user's namespace
+ user_ns['display'] = display
+ user_ns['getfigs'] = getfigs
def pylab_activate(user_ns, gui=None, import_all=True, shell=None):
@@ -313,8 +328,10 @@ def pylab_activate(user_ns, gui=None, import_all=True, shell=None):
"""
gui, backend = find_gui_and_backend(gui)
activate_matplotlib(backend)
- import_pylab(user_ns, backend, import_all, shell)
-
+ import_pylab(user_ns, import_all)
+ if shell is not None:
+ configure_inline_support(shell, backend, user_ns)
+
print """
Welcome to pylab, a matplotlib-based Python environment [backend: %s].
For more information, type 'help(pylab)'.""" % backend
View
9 IPython/lib/tests/test_pylabtools.py → IPython/core/tests/test_pylabtools.py
@@ -20,6 +20,7 @@
import nose.tools as nt
from matplotlib import pyplot as plt
+import numpy as np
# Our own imports
from IPython.testing import decorators as dec
@@ -52,3 +53,11 @@ def test_figure_to_svg():
plt.draw()
svg = pt.print_figure(fig, 'svg')[:100].lower()
yield nt.assert_true('doctype svg' in svg)
+
+
+def test_import_pylab():
+ ip = get_ipython()
+ ns = {}
+ pt.import_pylab(ns, import_all=False)
+ nt.assert_true('plt' in ns)
+ nt.assert_equal(ns['np'], np)
View
53 IPython/frontend/terminal/interactiveshell.py
@@ -29,8 +29,7 @@
from IPython.core.error import TryNext
from IPython.core.usage import interactive_usage, default_banner
from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
-from IPython.lib.inputhook import enable_gui
-from IPython.lib.pylabtools import pylab_activate
+from IPython.core.pylabtools import pylab_activate
from IPython.testing.skipdoctest import skip_doctest
from IPython.utils import py3compat
from IPython.utils.terminal import toggle_set_term_title, set_term_title
@@ -171,10 +170,13 @@ class TerminalInteractiveShell(InteractiveShell):
help="Enable auto setting the terminal title."
)
- def __init__(self, config=None, ipython_dir=None, profile_dir=None, user_ns=None,
- user_module=None, custom_exceptions=((),None),
- usage=None, banner1=None, banner2=None,
- display_banner=None):
+ # In the terminal, GUI control is done via PyOS_InputHook
+ from IPython.lib.inputhook import enable_gui
+ enable_gui = staticmethod(enable_gui)
+
+ def __init__(self, config=None, ipython_dir=None, profile_dir=None,
+ user_ns=None, user_module=None, custom_exceptions=((),None),
+ usage=None, banner1=None, banner2=None, display_banner=None):
super(TerminalInteractiveShell, self).__init__(
config=config, profile_dir=profile_dir, user_ns=user_ns,
@@ -517,45 +519,6 @@ def int0(x):
return True
#-------------------------------------------------------------------------
- # Things related to GUI support and pylab
- #-------------------------------------------------------------------------
-
- def enable_pylab(self, gui=None, import_all=True):
- """Activate pylab support at runtime.
-
- This turns on support for matplotlib, preloads into the interactive
- namespace all of numpy and pylab, and configures IPython to correcdtly
- interact with the GUI event loop. The GUI backend to be used can be
- optionally selected with the optional :param:`gui` argument.
-
- Parameters
- ----------
- gui : optional, string
-
- If given, dictates the choice of matplotlib GUI backend to use
- (should be one of IPython's supported backends, 'tk', 'qt', 'wx' or
- 'gtk'), otherwise we use the default chosen by matplotlib (as
- dictated by the matplotlib build-time options plus the user's
- matplotlibrc configuration file).
- """
- # We want to prevent the loading of pylab to pollute the user's
- # namespace as shown by the %who* magics, so we execute the activation
- # code in an empty namespace, and we update *both* user_ns and
- # user_ns_hidden with this information.
- ns = {}
- try:
- gui = pylab_activate(ns, gui, import_all)
- except KeyError:
- error("Backend %r not supported" % gui)
- return
- self.user_ns.update(ns)
- self.user_ns_hidden.update(ns)
- # Now we must activate the gui pylab wants to use, and fix %run to take
- # plot updates into account
- enable_gui(gui)
- self.magic_run = self._pylab_magic_run
-
- #-------------------------------------------------------------------------
# Things related to exiting
#-------------------------------------------------------------------------
View
4 IPython/testing/iptest.py
@@ -238,8 +238,8 @@ def make_exclude():
exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
if not have['matplotlib']:
- exclusions.extend([ipjoin('lib', 'pylabtools'),
- ipjoin('lib', 'tests', 'test_pylabtools')])
+ exclusions.extend([ipjoin('core', 'pylabtools'),
+ ipjoin('core', 'tests', 'test_pylabtools')])
if not have['tornado']:
exclusions.append(ipjoin('frontend', 'html'))
View
206 IPython/zmq/eventloops.py
@@ -0,0 +1,206 @@
+# encoding: utf-8
+"""Event loop integration for the ZeroMQ-based kernels.
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2011 The IPython Development Team
+
+# Distributed under the terms of the BSD License. The full license is in
+# the file COPYING, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import sys
+
+# System library imports.
+import zmq
+
+# Local imports.
+from IPython.utils import io
+
+#------------------------------------------------------------------------------
+# Eventloops for integrating the Kernel into different GUIs
+#------------------------------------------------------------------------------
+
+def loop_qt4(kernel):
+ """Start a kernel with PyQt4 event loop integration."""
+
+ from IPython.external.qt_for_kernel import QtCore
+ from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
+
+ kernel.app = get_app_qt4([" "])
+ kernel.app.setQuitOnLastWindowClosed(False)
+ kernel.timer = QtCore.QTimer()
+ kernel.timer.timeout.connect(kernel.do_one_iteration)
+ # Units for the timer are in milliseconds
+ kernel.timer.start(1000*kernel._poll_interval)
+ start_event_loop_qt4(kernel.app)
+
+
+def loop_wx(kernel):
+ """Start a kernel with wx event loop support."""
+
+ import wx
+ from IPython.lib.guisupport import start_event_loop_wx
+
+ doi = kernel.do_one_iteration
+ # Wx uses milliseconds
+ poll_interval = int(1000*kernel._poll_interval)
+
+ # We have to put the wx.Timer in a wx.Frame for it to fire properly.
+ # We make the Frame hidden when we create it in the main app below.
+ class TimerFrame(wx.Frame):
+ def __init__(self, func):
+ wx.Frame.__init__(self, None, -1)
+ self.timer = wx.Timer(self)
+ # Units for the timer are in milliseconds
+ self.timer.Start(poll_interval)
+ self.Bind(wx.EVT_TIMER, self.on_timer)
+ self.func = func
+
+ def on_timer(self, event):
+ self.func()
+
+ # We need a custom wx.App to create our Frame subclass that has the
+ # wx.Timer to drive the ZMQ event loop.
+ class IPWxApp(wx.App):
+ def OnInit(self):
+ self.frame = TimerFrame(doi)
+ self.frame.Show(False)
+ return True
+
+ # The redirect=False here makes sure that wx doesn't replace
+ # sys.stdout/stderr with its own classes.
+ kernel.app = IPWxApp(redirect=False)
+ start_event_loop_wx(kernel.app)
+
+
+def loop_tk(kernel):
+ """Start a kernel with the Tk event loop."""
+
+ import Tkinter
+ doi = kernel.do_one_iteration
+ # Tk uses milliseconds
+ poll_interval = int(1000*kernel._poll_interval)
+ # For Tkinter, we create a Tk object and call its withdraw method.
+ class Timer(object):
+ def __init__(self, func):
+ self.app = Tkinter.Tk()
+ self.app.withdraw()
+ self.func = func
+
+ def on_timer(self):
+ self.func()
+ self.app.after(poll_interval, self.on_timer)
+
+ def start(self):
+ self.on_timer() # Call it once to get things going.
+ self.app.mainloop()
+
+ kernel.timer = Timer(doi)
+ kernel.timer.start()
+
+
+def loop_gtk(kernel):
+ """Start the kernel, coordinating with the GTK event loop"""
+ from .gui.gtkembed import GTKEmbed
+
+ gtk_kernel = GTKEmbed(kernel)
+ gtk_kernel.start()
+
+
+def loop_cocoa(kernel):
+ """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
+ via the matplotlib MacOSX backend.
+ """
+ import matplotlib
+ if matplotlib.__version__ < '1.1.0':
+ kernel.log.warn(
+ "MacOSX backend in matplotlib %s doesn't have a Timer, "
+ "falling back on Tk for CFRunLoop integration. Note that "
+ "even this won't work if Tk is linked against X11 instead of "
+ "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
+ "you must use matplotlib >= 1.1.0, or a native libtk."
+ )
+ return loop_tk(kernel)
+
+ from matplotlib.backends.backend_macosx import TimerMac, show
+
+ # scale interval for sec->ms
+ poll_interval = int(1000*kernel._poll_interval)
+
+ real_excepthook = sys.excepthook
+ def handle_int(etype, value, tb):
+ """don't let KeyboardInterrupts look like crashes"""
+ if etype is KeyboardInterrupt:
+ io.raw_print("KeyboardInterrupt caught in CFRunLoop")
+ else:
+ real_excepthook(etype, value, tb)
+
+ # add doi() as a Timer to the CFRunLoop
+ def doi():
+ # restore excepthook during IPython code
+ sys.excepthook = real_excepthook
+ kernel.do_one_iteration()
+ # and back:
+ sys.excepthook = handle_int
+
+ t = TimerMac(poll_interval)
+ t.add_callback(doi)
+ t.start()
+
+ # but still need a Poller for when there are no active windows,
+ # during which time mainloop() returns immediately
+ poller = zmq.Poller()
+ poller.register(kernel.shell_socket, zmq.POLLIN)
+
+ while True:
+ try:
+ # double nested try/except, to properly catch KeyboardInterrupt
+ # due to pyzmq Issue #130
+ try:
+ # don't let interrupts during mainloop invoke crash_handler:
+ sys.excepthook = handle_int
+ show.mainloop()
+ sys.excepthook = real_excepthook
+ # use poller if mainloop returned (no windows)
+ # scale by extra factor of 10, since it's a real poll
+ poller.poll(10*poll_interval)
+ kernel.do_one_iteration()
+ except:
+ raise
+ except KeyboardInterrupt:
+ # Ctrl-C shouldn't crash the kernel
+ io.raw_print("KeyboardInterrupt caught in kernel")
+ finally:
+ # ensure excepthook is restored
+ sys.excepthook = real_excepthook
+
+# mapping of keys to loop functions
+loop_map = {
+ 'qt' : loop_qt4,
+ 'qt4': loop_qt4,
+ 'inline': None,
+ 'osx': loop_cocoa,
+ 'wx' : loop_wx,
+ 'tk' : loop_tk,
+ 'gtk': loop_gtk,
+ None : None,
+}
+
+
+def enable_gui(gui, kernel=None):
+ """Enable integration with a given GUI"""
+ if kernel is None:
+ from .ipkernel import IPKernelApp
+ kernel = IPKernelApp.instance().kernel
+ if gui not in loop_map:
+ raise ValueError("GUI %r not supported" % gui)
+ loop = loop_map[gui]
+ if kernel.eventloop is not None and kernel.eventloop is not loop:
+ raise RuntimeError("Cannot activate multiple GUI eventloops")
+ kernel.eventloop = loop
View
231 IPython/zmq/ipkernel.py
@@ -39,12 +39,11 @@
from IPython.utils.jsonutil import json_clean
from IPython.lib import pylabtools
from IPython.utils.traitlets import (
- Any, List, Instance, Float, Dict, Bool, Unicode, CaselessStrEnum
+ Any, Instance, Float, Dict, CaselessStrEnum
)
from entry_point import base_launch_kernel
from kernelapp import KernelApp, kernel_flags, kernel_aliases
-from iostream import OutStream
from session import Session, Message
from zmqshell import ZMQInteractiveShell
@@ -212,16 +211,16 @@ def record_ports(self, ports):
def _publish_pyin(self, code, parent):
"""Publish the code request on the pyin stream."""
- pyin_msg = self.session.send(self.iopub_socket, u'pyin',{u'code':code}, parent=parent)
+ self.session.send(self.iopub_socket, u'pyin', {u'code':code},
+ parent=parent)
def execute_request(self, ident, parent):
- status_msg = self.session.send(self.iopub_socket,
- u'status',
- {u'execution_state':u'busy'},
- parent=parent
- )
-
+ self.session.send(self.iopub_socket,
+ u'status',
+ {u'execution_state':u'busy'},
+ parent=parent )
+
try:
content = parent[u'content']
code = content[u'code']
@@ -331,11 +330,10 @@ def execute_request(self, ident, parent):
if reply_msg['content']['status'] == u'error':
self._abort_queue()
- status_msg = self.session.send(self.iopub_socket,
- u'status',
- {u'execution_state':u'idle'},
- parent=parent
- )
+ self.session.send(self.iopub_socket,
+ u'status',
+ {u'execution_state':u'idle'},
+ parent=parent )
def complete_request(self, ident, parent):
txt, matches = self._complete(parent)
@@ -375,7 +373,8 @@ def history_request(self, ident, parent):
elif hist_access_type == 'search':
pattern = parent['content']['pattern']
- hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
+ hist = self.shell.history_manager.search(pattern, raw=raw,
+ output=output)
else:
hist = []
@@ -396,7 +395,8 @@ def connect_request(self, ident, parent):
def shutdown_request(self, ident, parent):
self.shell.exit_now = True
- self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
+ self._shutdown_message = self.session.msg(u'shutdown_reply',
+ parent['content'], parent)
sys.exit(0)
#---------------------------------------------------------------------------
@@ -427,8 +427,10 @@ def _abort_queue(self):
time.sleep(0.1)
def _no_raw_input(self):
- """Raise StdinNotImplentedError if active frontend doesn't support stdin."""
- raise StdinNotImplementedError("raw_input was called, but this frontend does not support stdin.")
+ """Raise StdinNotImplentedError if active frontend doesn't support
+ stdin."""
+ raise StdinNotImplementedError("raw_input was called, but this "
+ "frontend does not support stdin.")
def _raw_input(self, prompt, ident, parent):
# Flush output before making the request.
@@ -437,7 +439,8 @@ def _raw_input(self, prompt, ident, parent):
# Send the input request.
content = json_clean(dict(prompt=prompt))
- msg = self.session.send(self.stdin_socket, u'input_request', content, parent, ident=ident)
+ self.session.send(self.stdin_socket, u'input_request', content, parent,
+ ident=ident)
# Await a response.
while True:
@@ -510,189 +513,6 @@ def _at_shutdown(self):
# before Python truly shuts down.
time.sleep(0.01)
-
-#------------------------------------------------------------------------------
-# Eventloops for integrating the Kernel into different GUIs
-#------------------------------------------------------------------------------
-
-
-def loop_qt4(kernel):
- """Start a kernel with PyQt4 event loop integration."""
-
- from IPython.external.qt_for_kernel import QtCore
- from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
-
- kernel.app = get_app_qt4([" "])
- kernel.app.setQuitOnLastWindowClosed(False)
- kernel.timer = QtCore.QTimer()
- kernel.timer.timeout.connect(kernel.do_one_iteration)
- # Units for the timer are in milliseconds
- kernel.timer.start(1000*kernel._poll_interval)
- start_event_loop_qt4(kernel.app)
-
-
-def loop_wx(kernel):
- """Start a kernel with wx event loop support."""
-
- import wx
- from IPython.lib.guisupport import start_event_loop_wx
-
- doi = kernel.do_one_iteration
- # Wx uses milliseconds
- poll_interval = int(1000*kernel._poll_interval)
-
- # We have to put the wx.Timer in a wx.Frame for it to fire properly.
- # We make the Frame hidden when we create it in the main app below.
- class TimerFrame(wx.Frame):
- def __init__(self, func):
- wx.Frame.__init__(self, None, -1)
- self.timer = wx.Timer(self)
- # Units for the timer are in milliseconds
- self.timer.Start(poll_interval)
- self.Bind(wx.EVT_TIMER, self.on_timer)
- self.func = func
-
- def on_timer(self, event):
- self.func()
-
- # We need a custom wx.App to create our Frame subclass that has the
- # wx.Timer to drive the ZMQ event loop.
- class IPWxApp(wx.App):
- def OnInit(self):
- self.frame = TimerFrame(doi)
- self.frame.Show(False)
- return True
-
- # The redirect=False here makes sure that wx doesn't replace
- # sys.stdout/stderr with its own classes.
- kernel.app = IPWxApp(redirect=False)
- start_event_loop_wx(kernel.app)
-
-
-def loop_tk(kernel):
- """Start a kernel with the Tk event loop."""
-
- import Tkinter
- doi = kernel.do_one_iteration
- # Tk uses milliseconds
- poll_interval = int(1000*kernel._poll_interval)
- # For Tkinter, we create a Tk object and call its withdraw method.
- class Timer(object):
- def __init__(self, func):
- self.app = Tkinter.Tk()
- self.app.withdraw()
- self.func = func
-
- def on_timer(self):
- self.func()
- self.app.after(poll_interval, self.on_timer)
-
- def start(self):
- self.on_timer() # Call it once to get things going.
- self.app.mainloop()
-
- kernel.timer = Timer(doi)
- kernel.timer.start()
-
-
-def loop_gtk(kernel):
- """Start the kernel, coordinating with the GTK event loop"""
- from .gui.gtkembed import GTKEmbed
-
- gtk_kernel = GTKEmbed(kernel)
- gtk_kernel.start()
-
-
-def loop_cocoa(kernel):
- """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
- via the matplotlib MacOSX backend.
- """
- import matplotlib
- if matplotlib.__version__ < '1.1.0':
- kernel.log.warn(
- "MacOSX backend in matplotlib %s doesn't have a Timer, "
- "falling back on Tk for CFRunLoop integration. Note that "
- "even this won't work if Tk is linked against X11 instead of "
- "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
- "you must use matplotlib >= 1.1.0, or a native libtk."
- )
- return loop_tk(kernel)
-
- from matplotlib.backends.backend_macosx import TimerMac, show
-
- # scale interval for sec->ms
- poll_interval = int(1000*kernel._poll_interval)
-
- real_excepthook = sys.excepthook
- def handle_int(etype, value, tb):
- """don't let KeyboardInterrupts look like crashes"""
- if etype is KeyboardInterrupt:
- io.raw_print("KeyboardInterrupt caught in CFRunLoop")
- else:
- real_excepthook(etype, value, tb)
-
- # add doi() as a Timer to the CFRunLoop
- def doi():
- # restore excepthook during IPython code
- sys.excepthook = real_excepthook
- kernel.do_one_iteration()
- # and back:
- sys.excepthook = handle_int
-
- t = TimerMac(poll_interval)
- t.add_callback(doi)
- t.start()
-
- # but still need a Poller for when there are no active windows,
- # during which time mainloop() returns immediately
- poller = zmq.Poller()
- poller.register(kernel.shell_socket, zmq.POLLIN)
-
- while True:
- try:
- # double nested try/except, to properly catch KeyboardInterrupt
- # due to pyzmq Issue #130
- try:
- # don't let interrupts during mainloop invoke crash_handler:
- sys.excepthook = handle_int
- show.mainloop()
- sys.excepthook = real_excepthook
- # use poller if mainloop returned (no windows)
- # scale by extra factor of 10, since it's a real poll
- poller.poll(10*poll_interval)
- kernel.do_one_iteration()
- except:
- raise
- except KeyboardInterrupt:
- # Ctrl-C shouldn't crash the kernel
- io.raw_print("KeyboardInterrupt caught in kernel")
- finally:
- # ensure excepthook is restored
- sys.excepthook = real_excepthook
-
-# mapping of keys to loop functions
-loop_map = {
- 'qt' : loop_qt4,
- 'qt4': loop_qt4,
- 'inline': None,
- 'osx': loop_cocoa,
- 'wx' : loop_wx,
- 'tk' : loop_tk,
- 'gtk': loop_gtk,
-}
-
-def enable_gui(gui, kernel=None):
- """Enable integration with a give GUI"""
- if kernel is None:
- kernel = IPKernelApp.instance().kernel
- if gui not in loop_map:
- raise ValueError("GUI %r not supported" % gui)
- loop = loop_map[gui]
- if kernel.eventloop is not None and kernel.eventloop is not loop:
- raise RuntimeError("Cannot activate multiple GUI eventloops")
- kernel.eventloop = loop
-
-
#-----------------------------------------------------------------------------
# Aliases and Flags for the IPKernelApp
#-----------------------------------------------------------------------------
@@ -767,7 +587,8 @@ def init_kernel(self):
# replace pyerr-sending traceback with stdout
_showtraceback = shell._showtraceback
def print_tb(etype, evalue, stb):
- print ("Error initializing pylab, pylab mode will not be active", file=io.stderr)
+ print ("Error initializing pylab, pylab mode will not "
+ "be active", file=io.stderr)
print (shell.InteractiveTB.stb2text(stb), file=io.stdout)
shell._showtraceback = print_tb
@@ -790,8 +611,8 @@ def init_shell(self):
def launch_kernel(*args, **kwargs):
"""Launches a localhost IPython kernel, binding to the specified ports.
- This function simply calls entry_point.base_launch_kernel with the right first
- command to start an ipkernel. See base_launch_kernel for arguments.
+ This function simply calls entry_point.base_launch_kernel with the right
+ first command to start an ipkernel. See base_launch_kernel for arguments.
Returns
-------
View
2  IPython/zmq/pylab/backend_inline.py
@@ -16,7 +16,7 @@
# Local imports.
from IPython.config.configurable import SingletonConfigurable
from IPython.core.displaypub import publish_display_data
-from IPython.lib.pylabtools import print_figure, select_figure_format
+from IPython.core.pylabtools import print_figure, select_figure_format
from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, CBool
from IPython.utils.warn import warn
View
80 IPython/zmq/zmqshell.py
@@ -109,6 +109,11 @@ def _exiter_default(self):
keepkernel_on_exit = None
+ # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
+ # interactive input being read; we provide event loop support in ipkernel
+ from .eventloops import enable_gui
+ enable_gui = staticmethod(enable_gui)
+
def init_environment(self):
"""Configure the user's environment.
@@ -390,79 +395,6 @@ def magic_edit(self,parameter_s='',last_call=['','']):
}
self.payload_manager.write_payload(payload)
- def magic_gui(self, parameter_s=''):
- """Enable or disable IPython GUI event loop integration.
-
- %gui [GUINAME]
-
- This magic replaces IPython's threaded shells that were activated
- using the (pylab/wthread/etc.) command line flags. GUI toolkits
- can now be enabled at runtime and keyboard
- interrupts should work without any problems. The following toolkits
- are supported: wxPython, PyQt4, PyGTK, Cocoa, and Tk::
-
- %gui wx # enable wxPython event loop integration
- %gui qt4|qt # enable PyQt4 event loop integration
- %gui gtk # enable PyGTK event loop integration
- %gui OSX # enable Cocoa event loop integration (requires matplotlib 1.1)
- %gui tk # enable Tk event loop integration
-
- WARNING: after any of these has been called you can simply create
- an application object, but DO NOT start the event loop yourself, as
- we have already handled that.
- """
- from IPython.zmq.ipkernel import enable_gui
- opts, arg = self.parse_options(parameter_s, '')
- if arg=='': arg = None
- try:
- enable_gui(arg)
- except Exception as e:
- # print simple error message, rather than traceback if we can't
- # hook up the GUI
- error(str(e))
-
- def enable_pylab(self, gui=None, import_all=True):
- """Activate pylab support at runtime.
-
- This turns on support for matplotlib, preloads into the interactive
- namespace all of numpy and pylab, and configures IPython to correcdtly
- interact with the GUI event loop. The GUI backend to be used can be
- optionally selected with the optional :param:`gui` argument.
-
- Parameters
- ----------
- gui : optional, string [default: inline]
-
- If given, dictates the choice of matplotlib GUI backend to use
- (should be one of IPython's supported backends, 'inline', 'qt', 'osx',
- 'tk', or 'gtk'), otherwise we use the default chosen by matplotlib
- (as dictated by the matplotlib build-time options plus the user's
- matplotlibrc configuration file).
- """
- from IPython.zmq.ipkernel import enable_gui
- # We want to prevent the loading of pylab to pollute the user's
- # namespace as shown by the %who* magics, so we execute the activation
- # code in an empty namespace, and we update *both* user_ns and
- # user_ns_hidden with this information.
- ns = {}
- try:
- gui = pylabtools.pylab_activate(ns, gui, import_all, self)
- except KeyError:
- error("Backend %r not supported" % gui)
- return
- self.user_ns.update(ns)
- self.user_ns_hidden.update(ns)
- # Now we must activate the gui pylab wants to use, and fix %run to take
- # plot updates into account
- try:
- enable_gui(gui)
- except Exception as e:
- # print simple error message, rather than traceback if we can't
- # hook up the GUI
- error(str(e))
- self.magic_run = self._pylab_magic_run
-
-
# A few magics that are adapted to the specifics of using pexpect and a
# remote terminal
@@ -567,7 +499,6 @@ def magic_qtconsole(self, arg_s):
except Exception as e:
error("Could not start qtconsole: %r" % e)
return
-
def set_next_input(self, text):
"""Send the specified text to the frontend to be presented at the next
@@ -578,4 +509,5 @@ def set_next_input(self, text):
)
self.payload_manager.write_payload(payload)
+
InteractiveShellABC.register(ZMQInteractiveShell)
Please sign in to comment.
Something went wrong with that request. Please try again.