##// END OF EJS Templates
Small fix to docstring and qt example.
Fernando Perez -
Show More
@@ -1,147 +1,149 b''
1 1 #!/usr/bin/env python
2 2 # coding: utf-8
3 3 """
4 4 Support for creating GUI apps and starting event loops.
5 5
6 6 IPython's GUI integration allows interative plotting and GUI usage in IPython
7 7 session. IPython has two different types of GUI integration:
8 8
9 9 1. The terminal based IPython supports GUI event loops through Python's
10 10 PyOS_InputHook. PyOS_InputHook is a hook that Python calls periodically
11 11 whenever raw_input is waiting for a user to type code. We implement GUI
12 12 support in the terminal by setting PyOS_InputHook to a function that
13 13 iterates the event loop for a short while. It is important to note that
14 14 in this situation, the real GUI event loop is NOT run in the normal
15 15 manner, so you can't use the normal means to detect that it is running.
16 16 2. In the two process IPython kernel/frontend, the GUI event loop is run in
17 17 the kernel. In this case, the event loop is run in the normal manner by
18 18 calling the function or method of the GUI toolkit that starts the event
19 19 loop.
20 20
21 21 In addition to starting the GUI event loops in one of these two ways, IPython
22 22 will *always* create an appropriate GUI application object when GUi
23 23 integration is enabled.
24 24
25 25 If you want your GUI apps to run in IPython you need to do two things:
26 26
27 27 1. Test to see if there is already an existing main application object. If
28 28 there is, you should use it. If there is not an existing application object
29 29 you should create one.
30 30 2. Test to see if the GUI event loop is running. If it is, you should not
31 31 start it. If the event loop is not running you may start it.
32 32
33 33 This module contains functions for each toolkit that perform these things
34 34 in a consistent manner. Because of how PyOS_InputHook runs the event loop
35 35 you cannot detect if the event loop is running using the traditional calls
36 36 (such as ``wx.GetApp.IsMainLoopRunning()`` in wxPython). If PyOS_InputHook is
37 37 set These methods will return a false negative. That is, they will say the
38 38 event loop is not running, when is actually is. To work around this limitation
39 39 we proposed the following informal protocol:
40 40
41 41 * Whenever someone starts the event loop, they *must* set the ``_in_event_loop``
42 42 attribute of the main application object to ``True``. This should be done
43 43 regardless of how the event loop is actually run.
44 44 * Whenever someone stops the event loop, they *must* set the ``_in_event_loop``
45 45 attribute of the main application object to ``False``.
46 46 * If you want to see if the event loop is running, you *must* use ``hasattr``
47 47 to see if ``_in_event_loop`` attribute has been set. If it is set, you
48 48 *must* use its value. If it has not been set, you can query the toolkit
49 49 in the normal manner.
50 50 * If you want GUI support and no one else has created an application or
51 51 started the event loop you *must* do this. We don't want projects to
52 52 attempt to defer these things to someone else if they themselves need it.
53 53
54 54 The functions below implement this logic for each GUI toolkit. If you need
55 55 to create custom application subclasses, you will likely have to modify this
56 56 code for your own purposes. This code can be copied into your own project
57 57 so you don't have to depend on IPython.
58 58
59 59 """
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Copyright (C) 2008-2010 The IPython Development Team
63 63 #
64 64 # Distributed under the terms of the BSD License. The full license is in
65 65 # the file COPYING, distributed as part of this software.
66 66 #-----------------------------------------------------------------------------
67 67
68 68 #-----------------------------------------------------------------------------
69 69 # Imports
70 70 #-----------------------------------------------------------------------------
71 71
72 72 #-----------------------------------------------------------------------------
73 73 # wx
74 74 #-----------------------------------------------------------------------------
75 75
76 76 def get_app_wx(*args, **kwargs):
77 77 """Create a new wx app or return an exiting one."""
78 78 import wx
79 79 app = wx.GetApp()
80 80 if app is None:
81 81 if not kwargs.has_key('redirect'):
82 82 kwargs['redirect'] = False
83 83 app = wx.PySimpleApp(*args, **kwargs)
84 84 return app
85 85
86 86 def is_event_loop_running_wx(app=None):
87 87 """Is the wx event loop running."""
88 88 if app is None:
89 89 app = get_app_wx()
90 90 if hasattr(app, '_in_event_loop'):
91 91 return app._in_event_loop
92 92 else:
93 93 return app.IsMainLoopRunning()
94 94
95 95 def start_event_loop_wx(app=None):
96 96 """Start the wx event loop in a consistent manner."""
97 97 if app is None:
98 98 app = get_app_wx()
99 99 if not is_event_loop_running_wx(app):
100 100 app._in_event_loop = True
101 101 app.MainLoop()
102 102 app._in_event_loop = False
103 103 else:
104 104 app._in_event_loop = True
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # qt4
108 108 #-----------------------------------------------------------------------------
109 109
110 110 def get_app_qt4(*args, **kwargs):
111 111 """Create a new qt4 app or return an existing one."""
112 112 from IPython.external.qt_for_kernel import QtGui
113 113 app = QtGui.QApplication.instance()
114 114 if app is None:
115 115 if not args:
116 116 args = ([''],)
117 117 app = QtGui.QApplication(*args, **kwargs)
118 118 return app
119 119
120 120 def is_event_loop_running_qt4(app=None):
121 121 """Is the qt4 event loop running."""
122 122 if app is None:
123 123 app = get_app_qt4([''])
124 124 if hasattr(app, '_in_event_loop'):
125 125 return app._in_event_loop
126 126 else:
127 127 # Does qt4 provide a other way to detect this?
128 128 return False
129 129
130 130 def start_event_loop_qt4(app=None):
131 131 """Start the qt4 event loop in a consistent manner."""
132 132 if app is None:
133 133 app = get_app_qt4([''])
134 134 if not is_event_loop_running_qt4(app):
135 from .inputhook import enable_qt4
136 #enable_qt4(app)
135 137 app._in_event_loop = True
136 138 app.exec_()
137 139 app._in_event_loop = False
138 140 else:
139 141 app._in_event_loop = True
140 142
141 143 #-----------------------------------------------------------------------------
142 144 # Tk
143 145 #-----------------------------------------------------------------------------
144 146
145 147 #-----------------------------------------------------------------------------
146 148 # gtk
147 149 #-----------------------------------------------------------------------------
@@ -1,345 +1,345 b''
1 1 #!/usr/bin/env python
2 2 # coding: utf-8
3 3 """
4 4 Inputhook management for GUI event loop integration.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 import ctypes
19 19 import sys
20 20 import warnings
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Constants
24 24 #-----------------------------------------------------------------------------
25 25
26 26 # Constants for identifying the GUI toolkits.
27 27 GUI_WX = 'wx'
28 28 GUI_QT = 'qt'
29 29 GUI_QT4 = 'qt4'
30 30 GUI_GTK = 'gtk'
31 31 GUI_TK = 'tk'
32 32 GUI_OSX = 'osx'
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Utility classes
36 36 #-----------------------------------------------------------------------------
37 37
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Main InputHookManager class
41 41 #-----------------------------------------------------------------------------
42 42
43 43
44 44 class InputHookManager(object):
45 45 """Manage PyOS_InputHook for different GUI toolkits.
46 46
47 47 This class installs various hooks under ``PyOSInputHook`` to handle
48 48 GUI event loop integration.
49 49 """
50 50
51 51 def __init__(self):
52 52 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
53 53 self._apps = {}
54 54 self._reset()
55 55
56 56 def _reset(self):
57 57 self._callback_pyfunctype = None
58 58 self._callback = None
59 59 self._installed = False
60 60 self._current_gui = None
61 61
62 62 def get_pyos_inputhook(self):
63 63 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
64 64 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
65 65
66 66 def get_pyos_inputhook_as_func(self):
67 67 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
68 68 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
69 69
70 70 def set_inputhook(self, callback):
71 71 """Set PyOS_InputHook to callback and return the previous one."""
72 72 self._callback = callback
73 73 self._callback_pyfunctype = self.PYFUNC(callback)
74 74 pyos_inputhook_ptr = self.get_pyos_inputhook()
75 75 original = self.get_pyos_inputhook_as_func()
76 76 pyos_inputhook_ptr.value = \
77 77 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
78 78 self._installed = True
79 79 return original
80 80
81 81 def clear_inputhook(self, app=None):
82 82 """Set PyOS_InputHook to NULL and return the previous one.
83 83
84 84 Parameters
85 85 ----------
86 86 app : optional, ignored
87 87 This parameter is allowed only so that clear_inputhook() can be
88 88 called with a similar interface as all the ``enable_*`` methods. But
89 89 the actual value of the parameter is ignored. This uniform interface
90 90 makes it easier to have user-level entry points in the main IPython
91 91 app like :meth:`enable_gui`."""
92 92 pyos_inputhook_ptr = self.get_pyos_inputhook()
93 93 original = self.get_pyos_inputhook_as_func()
94 94 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
95 95 self._reset()
96 96 return original
97 97
98 98 def clear_app_refs(self, gui=None):
99 99 """Clear IPython's internal reference to an application instance.
100 100
101 101 Whenever we create an app for a user on qt4 or wx, we hold a
102 102 reference to the app. This is needed because in some cases bad things
103 103 can happen if a user doesn't hold a reference themselves. This
104 104 method is provided to clear the references we are holding.
105 105
106 106 Parameters
107 107 ----------
108 108 gui : None or str
109 109 If None, clear all app references. If ('wx', 'qt4') clear
110 110 the app for that toolkit. References are not held for gtk or tk
111 111 as those toolkits don't have the notion of an app.
112 112 """
113 113 if gui is None:
114 114 self._apps = {}
115 115 elif self._apps.has_key(gui):
116 116 del self._apps[gui]
117 117
118 118 def enable_wx(self, app=None):
119 119 """Enable event loop integration with wxPython.
120 120
121 121 Parameters
122 122 ----------
123 123 app : WX Application, optional.
124 124 Running application to use. If not given, we probe WX for an
125 125 existing application object, and create a new one if none is found.
126 126
127 127 Notes
128 128 -----
129 129 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
130 130 the wxPython to integrate with terminal based applications like
131 131 IPython.
132 132
133 133 If ``app`` is not given we probe for an existing one, and return it if
134 134 found. If no existing app is found, we create an :class:`wx.App` as
135 135 follows::
136 136
137 137 import wx
138 138 app = wx.App(redirect=False, clearSigInt=False)
139 139 """
140 140 from IPython.lib.inputhookwx import inputhook_wx
141 141 self.set_inputhook(inputhook_wx)
142 142 self._current_gui = GUI_WX
143 143 import wx
144 144 if app is None:
145 145 app = wx.GetApp()
146 146 if app is None:
147 147 app = wx.App(redirect=False, clearSigInt=False)
148 148 app._in_event_loop = True
149 149 self._apps[GUI_WX] = app
150 150 return app
151 151
152 152 def disable_wx(self):
153 153 """Disable event loop integration with wxPython.
154 154
155 155 This merely sets PyOS_InputHook to NULL.
156 156 """
157 157 if self._apps.has_key(GUI_WX):
158 158 self._apps[GUI_WX]._in_event_loop = False
159 159 self.clear_inputhook()
160 160
161 161 def enable_qt4(self, app=None):
162 162 """Enable event loop integration with PyQt4.
163 163
164 164 Parameters
165 165 ----------
166 166 app : Qt Application, optional.
167 167 Running application to use. If not given, we probe Qt for an
168 168 existing application object, and create a new one if none is found.
169 169
170 170 Notes
171 171 -----
172 172 This methods sets the PyOS_InputHook for PyQt4, which allows
173 173 the PyQt4 to integrate with terminal based applications like
174 174 IPython.
175 175
176 176 If ``app`` is not given we probe for an existing one, and return it if
177 177 found. If no existing app is found, we create an :class:`QApplication`
178 178 as follows::
179 179
180 180 from PyQt4 import QtCore
181 181 app = QtGui.QApplication(sys.argv)
182 182 """
183 183 from IPython.external.qt_for_kernel import QtCore, QtGui
184 184
185 185 if 'pyreadline' in sys.modules:
186 186 # see IPython GitHub Issue #281 for more info on this issue
187 187 # Similar intermittent behavior has been reported on OSX,
188 188 # but not consistently reproducible
189 189 warnings.warn("""PyReadline's inputhook can conflict with Qt, causing delays
190 190 in interactive input. If you do see this issue, we recommend using another GUI
191 191 toolkit if you can, or disable readline with the configuration option
192 192 'TerminalInteractiveShell.readline_use=False', specified in a config file or
193 193 at the command-line""",
194 194 RuntimeWarning)
195 195
196 196 # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook
197 197 # was set when QtCore was imported, but if it ever got removed,
198 198 # you couldn't reset it. For earlier versions we can
199 199 # probably implement a ctypes version.
200 200 try:
201 201 QtCore.pyqtRestoreInputHook()
202 202 except AttributeError:
203 203 pass
204 204
205 205 self._current_gui = GUI_QT4
206 206 if app is None:
207 207 app = QtCore.QCoreApplication.instance()
208 208 if app is None:
209 209 app = QtGui.QApplication([" "])
210 210 app._in_event_loop = True
211 211 self._apps[GUI_QT4] = app
212 212 return app
213 213
214 214 def disable_qt4(self):
215 215 """Disable event loop integration with PyQt4.
216 216
217 217 This merely sets PyOS_InputHook to NULL.
218 218 """
219 219 if self._apps.has_key(GUI_QT4):
220 220 self._apps[GUI_QT4]._in_event_loop = False
221 221 self.clear_inputhook()
222 222
223 223 def enable_gtk(self, app=None):
224 224 """Enable event loop integration with PyGTK.
225 225
226 226 Parameters
227 227 ----------
228 228 app : ignored
229 229 Ignored, it's only a placeholder to keep the call signature of all
230 230 gui activation methods consistent, which simplifies the logic of
231 231 supporting magics.
232 232
233 233 Notes
234 234 -----
235 235 This methods sets the PyOS_InputHook for PyGTK, which allows
236 236 the PyGTK to integrate with terminal based applications like
237 237 IPython.
238 238 """
239 239 import gtk
240 240 try:
241 241 gtk.set_interactive(True)
242 242 self._current_gui = GUI_GTK
243 243 except AttributeError:
244 244 # For older versions of gtk, use our own ctypes version
245 245 from IPython.lib.inputhookgtk import inputhook_gtk
246 246 self.set_inputhook(inputhook_gtk)
247 247 self._current_gui = GUI_GTK
248 248
249 249 def disable_gtk(self):
250 250 """Disable event loop integration with PyGTK.
251 251
252 252 This merely sets PyOS_InputHook to NULL.
253 253 """
254 254 self.clear_inputhook()
255 255
256 256 def enable_tk(self, app=None):
257 257 """Enable event loop integration with Tk.
258 258
259 259 Parameters
260 260 ----------
261 261 app : toplevel :class:`Tkinter.Tk` widget, optional.
262 Running application to use. If not given, we probe Qt for an
263 existing application object, and create a new one if none is found.
262 Running toplevel widget to use. If not given, we probe Tk for an
263 existing one, and create a new one if none is found.
264 264
265 265 Notes
266 266 -----
267 267 If you have already created a :class:`Tkinter.Tk` object, the only
268 268 thing done by this method is to register with the
269 269 :class:`InputHookManager`, since creating that object automatically
270 270 sets ``PyOS_InputHook``.
271 271 """
272 272 self._current_gui = GUI_TK
273 273 if app is None:
274 274 import Tkinter
275 275 app = Tkinter.Tk()
276 276 app.withdraw()
277 277 self._apps[GUI_TK] = app
278 278 return app
279 279
280 280 def disable_tk(self):
281 281 """Disable event loop integration with Tkinter.
282 282
283 283 This merely sets PyOS_InputHook to NULL.
284 284 """
285 285 self.clear_inputhook()
286 286
287 287 def current_gui(self):
288 288 """Return a string indicating the currently active GUI or None."""
289 289 return self._current_gui
290 290
291 291 inputhook_manager = InputHookManager()
292 292
293 293 enable_wx = inputhook_manager.enable_wx
294 294 disable_wx = inputhook_manager.disable_wx
295 295 enable_qt4 = inputhook_manager.enable_qt4
296 296 disable_qt4 = inputhook_manager.disable_qt4
297 297 enable_gtk = inputhook_manager.enable_gtk
298 298 disable_gtk = inputhook_manager.disable_gtk
299 299 enable_tk = inputhook_manager.enable_tk
300 300 disable_tk = inputhook_manager.disable_tk
301 301 clear_inputhook = inputhook_manager.clear_inputhook
302 302 set_inputhook = inputhook_manager.set_inputhook
303 303 current_gui = inputhook_manager.current_gui
304 304 clear_app_refs = inputhook_manager.clear_app_refs
305 305
306 306
307 307 # Convenience function to switch amongst them
308 308 def enable_gui(gui=None, app=None):
309 309 """Switch amongst GUI input hooks by name.
310 310
311 311 This is just a utility wrapper around the methods of the InputHookManager
312 312 object.
313 313
314 314 Parameters
315 315 ----------
316 316 gui : optional, string or None
317 317 If None, clears input hook, otherwise it must be one of the recognized
318 318 GUI names (see ``GUI_*`` constants in module).
319 319
320 320 app : optional, existing application object.
321 321 For toolkits that have the concept of a global app, you can supply an
322 322 existing one. If not given, the toolkit will be probed for one, and if
323 323 none is found, a new one will be created. Note that GTK does not have
324 324 this concept, and passing an app if `gui`=="GTK" will raise an error.
325 325
326 326 Returns
327 327 -------
328 328 The output of the underlying gui switch routine, typically the actual
329 329 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
330 330 one.
331 331 """
332 332 guis = {None: clear_inputhook,
333 333 GUI_OSX: lambda app=False: None,
334 334 GUI_TK: enable_tk,
335 335 GUI_GTK: enable_gtk,
336 336 GUI_WX: enable_wx,
337 337 GUI_QT: enable_qt4, # qt3 not supported
338 338 GUI_QT4: enable_qt4 }
339 339 try:
340 340 gui_hook = guis[gui]
341 341 except KeyError:
342 342 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
343 343 raise ValueError(e)
344 344 return gui_hook(app)
345 345
General Comments 0
You need to be logged in to leave comments. Login now