##// END OF EJS Templates
Make gui support code and examples uniform and all working correctly....
Fernando Perez -
Show More
@@ -1,339 +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 def enable_wx(self):
118 def enable_wx(self, app=None):
119 119 """Enable event loop integration with wxPython.
120 120
121 121 Parameters
122 122 ----------
123 app : bool
124 Create a running application object or not.
123 app : WX Application, optional.
124 Running application to use. If not given, we probe WX for an
125 existing application object, and create a new one if none is found.
125 126
126 127 Notes
127 128 -----
128 129 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
129 130 the wxPython to integrate with terminal based applications like
130 131 IPython.
131 132
132 If ``app`` is True, we create an :class:`wx.App` as follows::
133 If ``app`` is not given we probe for an existing one, and return it if
134 found. If no existing app is found, we create an :class:`wx.App` as
135 follows::
133 136
134 137 import wx
135 138 app = wx.App(redirect=False, clearSigInt=False)
136
137 Both options this constructor are important for things to work
138 properly in an interactive context.
139
140 But, we first check to see if an application has already been
141 created. If so, we simply return that instance.
142 139 """
143 140 from IPython.lib.inputhookwx import inputhook_wx
144 141 self.set_inputhook(inputhook_wx)
145 142 self._current_gui = GUI_WX
146 143 import wx
147 app = wx.GetApp()
144 if app is None:
145 app = wx.GetApp()
148 146 if app is None:
149 147 app = wx.App(redirect=False, clearSigInt=False)
150 148 app._in_event_loop = True
151 149 self._apps[GUI_WX] = app
152 150 return app
153 151
154 152 def disable_wx(self):
155 153 """Disable event loop integration with wxPython.
156 154
157 155 This merely sets PyOS_InputHook to NULL.
158 156 """
159 157 if self._apps.has_key(GUI_WX):
160 158 self._apps[GUI_WX]._in_event_loop = False
161 159 self.clear_inputhook()
162 160
163 def enable_qt4(self):
161 def enable_qt4(self, app=None):
164 162 """Enable event loop integration with PyQt4.
165 163
166 164 Parameters
167 165 ----------
168 app : bool
169 Create a running application object or not.
166 app : Qt Application, optional.
167 Running application to use. If not given, we probe Qt for an
168 existing application object, and create a new one if none is found.
170 169
171 170 Notes
172 171 -----
173 172 This methods sets the PyOS_InputHook for PyQt4, which allows
174 173 the PyQt4 to integrate with terminal based applications like
175 174 IPython.
176 175
177 If ``app`` is True, we create an :class:`QApplication` as follows::
176 If ``app`` is not given we probe for an existing one, and return it if
177 found. If no existing app is found, we create an :class:`QApplication`
178 as follows::
178 179
179 180 from PyQt4 import QtCore
180 181 app = QtGui.QApplication(sys.argv)
181
182 But, we first check to see if an application has already been
183 created. If so, we simply return that instance.
184 182 """
185 183 from IPython.external.qt_for_kernel import QtCore, QtGui
186 184
187 185 if 'pyreadline' in sys.modules:
188 186 # see IPython GitHub Issue #281 for more info on this issue
189 187 # Similar intermittent behavior has been reported on OSX,
190 188 # but not consistently reproducible
191 189 warnings.warn("""PyReadline's inputhook can conflict with Qt, causing delays
192 190 in interactive input. If you do see this issue, we recommend using another GUI
193 191 toolkit if you can, or disable readline with the configuration option
194 192 'TerminalInteractiveShell.readline_use=False', specified in a config file or
195 193 at the command-line""",
196 194 RuntimeWarning)
197 195
198 196 # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook
199 197 # was set when QtCore was imported, but if it ever got removed,
200 198 # you couldn't reset it. For earlier versions we can
201 199 # probably implement a ctypes version.
202 200 try:
203 201 QtCore.pyqtRestoreInputHook()
204 202 except AttributeError:
205 203 pass
206 204
207 205 self._current_gui = GUI_QT4
208 app = QtCore.QCoreApplication.instance()
206 if app is None:
207 app = QtCore.QCoreApplication.instance()
209 208 if app is None:
210 209 app = QtGui.QApplication([" "])
211 210 app._in_event_loop = True
212 211 self._apps[GUI_QT4] = app
213 212 return app
214 213
215 214 def disable_qt4(self):
216 215 """Disable event loop integration with PyQt4.
217 216
218 217 This merely sets PyOS_InputHook to NULL.
219 218 """
220 219 if self._apps.has_key(GUI_QT4):
221 220 self._apps[GUI_QT4]._in_event_loop = False
222 221 self.clear_inputhook()
223 222
224 def enable_gtk(self, app=False):
223 def enable_gtk(self, app=None):
225 224 """Enable event loop integration with PyGTK.
226 225
227 226 Parameters
228 227 ----------
229 app : bool
230 Create a running application object or not. Because gtk does't
231 have an app class, this does nothing.
228 app : ignored
229 Ignored, it's only a placeholder to keep the call signature of all
230 gui activation methods consistent, which simplifies the logic of
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 def enable_tk(self, app=False):
256 def enable_tk(self, app=None):
257 257 """Enable event loop integration with Tk.
258 258
259 259 Parameters
260 260 ----------
261 app : bool
262 Create a running application object or not.
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.
263 264
264 265 Notes
265 266 -----
266 Currently this is a no-op as creating a :class:`Tkinter.Tk` object
267 If you have already created a :class:`Tkinter.Tk` object, the only
268 thing done by this method is to register with the
269 :class:`InputHookManager`, since creating that object automatically
267 270 sets ``PyOS_InputHook``.
268 271 """
269 272 self._current_gui = GUI_TK
270 if app:
273 if app is None:
271 274 import Tkinter
272 275 app = Tkinter.Tk()
273 276 app.withdraw()
274 277 self._apps[GUI_TK] = app
275 278 return app
276 279
277 280 def disable_tk(self):
278 281 """Disable event loop integration with Tkinter.
279 282
280 283 This merely sets PyOS_InputHook to NULL.
281 284 """
282 285 self.clear_inputhook()
283 286
284 287 def current_gui(self):
285 288 """Return a string indicating the currently active GUI or None."""
286 289 return self._current_gui
287 290
288 291 inputhook_manager = InputHookManager()
289 292
290 293 enable_wx = inputhook_manager.enable_wx
291 294 disable_wx = inputhook_manager.disable_wx
292 295 enable_qt4 = inputhook_manager.enable_qt4
293 296 disable_qt4 = inputhook_manager.disable_qt4
294 297 enable_gtk = inputhook_manager.enable_gtk
295 298 disable_gtk = inputhook_manager.disable_gtk
296 299 enable_tk = inputhook_manager.enable_tk
297 300 disable_tk = inputhook_manager.disable_tk
298 301 clear_inputhook = inputhook_manager.clear_inputhook
299 302 set_inputhook = inputhook_manager.set_inputhook
300 303 current_gui = inputhook_manager.current_gui
301 304 clear_app_refs = inputhook_manager.clear_app_refs
302 305
303 306
304 307 # Convenience function to switch amongst them
305 def enable_gui(gui=None):
308 def enable_gui(gui=None, app=None):
306 309 """Switch amongst GUI input hooks by name.
307 310
308 311 This is just a utility wrapper around the methods of the InputHookManager
309 312 object.
310 313
311 314 Parameters
312 315 ----------
313 316 gui : optional, string or None
314 317 If None, clears input hook, otherwise it must be one of the recognized
315 318 GUI names (see ``GUI_*`` constants in module).
316 319
317 app : optional, bool
318 If true, create an app object and return it.
320 app : optional, existing application object.
321 For toolkits that have the concept of a global app, you can supply an
322 existing one. If not given, the toolkit will be probed for one, and if
323 none is found, a new one will be created. Note that GTK does not have
324 this concept, and passing an app if `gui`=="GTK" will raise an error.
319 325
320 326 Returns
321 327 -------
322 328 The output of the underlying gui switch routine, typically the actual
323 329 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
324 330 one.
325 331 """
326 332 guis = {None: clear_inputhook,
327 333 GUI_OSX: lambda app=False: None,
328 334 GUI_TK: enable_tk,
329 335 GUI_GTK: enable_gtk,
330 336 GUI_WX: enable_wx,
331 337 GUI_QT: enable_qt4, # qt3 not supported
332 338 GUI_QT4: enable_qt4 }
333 339 try:
334 340 gui_hook = guis[gui]
335 341 except KeyError:
336 e="Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
342 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
337 343 raise ValueError(e)
338 return gui_hook()
344 return gui_hook(app)
339 345
@@ -1,43 +1,40 b''
1 1 #!/usr/bin/env python
2 2 """Simple GTK example to manually test event loop integration.
3 3
4 4 This is meant to run tests manually in ipython as:
5 5
6 6 In [5]: %gui gtk
7 7
8 8 In [6]: %run gui-gtk.py
9 9 """
10 10
11 11
12 12 import pygtk
13 13 pygtk.require('2.0')
14 14 import gtk
15 15
16 16
17 17 def hello_world(wigdet, data=None):
18 18 print "Hello World"
19 19
20 20 def delete_event(widget, event, data=None):
21 21 return False
22 22
23 23 def destroy(widget, data=None):
24 24 gtk.main_quit()
25 25
26 26 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
27 27 window.connect("delete_event", delete_event)
28 28 window.connect("destroy", destroy)
29 29 button = gtk.Button("Hello World")
30 30 button.connect("clicked", hello_world, None)
31 31
32 32 window.add(button)
33 33 button.show()
34 34 window.show()
35 35
36 36 try:
37 37 from IPython.lib.inputhook import enable_gtk
38 38 enable_gtk()
39 39 except ImportError:
40 40 gtk.main()
41
42
43
@@ -1,41 +1,49 b''
1 1 #!/usr/bin/env python
2 2 """Simple Qt4 example to manually test event loop integration.
3 3
4 4 This is meant to run tests manually in ipython as:
5 5
6 6 In [5]: %gui qt
7 7
8 8 In [6]: %run gui-qt.py
9 9
10 10 Ref: Modified from http://zetcode.com/tutorials/pyqt4/firstprograms/
11 11 """
12 12
13 13 import sys
14 14 from PyQt4 import QtGui, QtCore
15 15
16 16 class SimpleWindow(QtGui.QWidget):
17 17 def __init__(self, parent=None):
18 18 QtGui.QWidget.__init__(self, parent)
19 19
20 20 self.setGeometry(300, 300, 200, 80)
21 21 self.setWindowTitle('Hello World')
22 22
23 23 quit = QtGui.QPushButton('Close', self)
24 24 quit.setGeometry(10, 10, 60, 35)
25 25
26 26 self.connect(quit, QtCore.SIGNAL('clicked()'),
27 27 self, QtCore.SLOT('close()'))
28 28
29 29 if __name__ == '__main__':
30 30 app = QtCore.QCoreApplication.instance()
31 31 if app is None:
32 32 app = QtGui.QApplication([])
33 33
34 34 sw = SimpleWindow()
35 35 sw.show()
36 36
37 37 try:
38 from IPython.lib.inputhook import enable_qt4
39 enable_qt4()
38 # Note: the following form allows this script to work both inside
39 # ipython and without it, but `%gui qt` MUST be run first (or
40 # equivalently, ipython could have been started with `--gui=qt`).
41 from IPython.lib.guisupport import start_event_loop_qt4
42 start_event_loop_qt4(app)
43
44 # This from doesn't require the gui support to have been enabled in
45 # advance, but it won't work if the script is run as a standalone app
46 # outside of IPython while the user does have IPython available.
47 #from IPython.lib.inputhook import enable_qt4; enable_qt4(app)
40 48 except ImportError:
41 49 app.exec_()
@@ -1,32 +1,32 b''
1 1 #!/usr/bin/env python
2 2 """Simple Tk example to manually test event loop integration.
3 3
4 4 This is meant to run tests manually in ipython as:
5 5
6 6 In [5]: %gui tk
7 7
8 8 In [6]: %run gui-tk.py
9 9 """
10 10
11 11 from Tkinter import *
12 12
13 13 class MyApp:
14 14
15 15 def __init__(self, root):
16 16 frame = Frame(root)
17 17 frame.pack()
18 18
19 19 self.button = Button(frame, text="Hello", command=self.hello_world)
20 20 self.button.pack(side=LEFT)
21 21
22 22 def hello_world(self):
23 23 print "Hello World!"
24 24
25 25 root = Tk()
26 26
27 27 app = MyApp(root)
28 28
29 29 try:
30 from IPython import enable_tk; enable_tk(root)
30 from IPython.lib.inputhook import enable_tk; enable_tk(root)
31 31 except ImportError:
32 32 root.mainloop()
@@ -1,109 +1,111 b''
1 #!/usr/bin/env python
1 2 """A Simple wx example to test IPython's event loop integration.
2 3
3 4 To run this do:
4 5
5 6 In [5]: %gui wx
6 7
7 8 In [6]: %run gui-wx.py
8 9
9 10 Ref: Modified from wxPython source code wxPython/samples/simple/simple.py
10 11
11 12 This example can only be run once in a given IPython session because when
12 13 the frame is closed, wx goes through its shutdown sequence, killing further
13 14 attempts. I am sure someone who knows wx can fix this issue.
14 15
15 16 Furthermore, once this example is run, the Wx event loop is mostly dead, so
16 17 even other new uses of Wx may not work correctly. If you know how to better
17 18 handle this, please contact the ipython developers and let us know.
18 19
19 20 Note however that we will work with the Matplotlib and Enthought developers so
20 21 that the main interactive uses of Wx we are aware of, namely these tools, will
21 22 continue to work well with IPython interactively.
22 23 """
23 24
24 25 import wx
25 26
26 27
27 28 class MyFrame(wx.Frame):
28 29 """
29 30 This is MyFrame. It just shows a few controls on a wxPanel,
30 31 and has a simple menu.
31 32 """
32 33 def __init__(self, parent, title):
33 34 wx.Frame.__init__(self, parent, -1, title,
34 35 pos=(150, 150), size=(350, 200))
35 36
36 37 # Create the menubar
37 38 menuBar = wx.MenuBar()
38 39
39 40 # and a menu
40 41 menu = wx.Menu()
41 42
42 43 # add an item to the menu, using \tKeyName automatically
43 44 # creates an accelerator, the third param is some help text
44 45 # that will show up in the statusbar
45 46 menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Exit this simple sample")
46 47
47 48 # bind the menu event to an event handler
48 49 self.Bind(wx.EVT_MENU, self.OnTimeToClose, id=wx.ID_EXIT)
49 50
50 51 # and put the menu on the menubar
51 52 menuBar.Append(menu, "&File")
52 53 self.SetMenuBar(menuBar)
53 54
54 55 self.CreateStatusBar()
55 56
56 57 # Now create the Panel to put the other controls on.
57 58 panel = wx.Panel(self)
58 59
59 60 # and a few controls
60 61 text = wx.StaticText(panel, -1, "Hello World!")
61 62 text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD))
62 63 text.SetSize(text.GetBestSize())
63 64 btn = wx.Button(panel, -1, "Close")
64 65 funbtn = wx.Button(panel, -1, "Just for fun...")
65 66
66 67 # bind the button events to handlers
67 68 self.Bind(wx.EVT_BUTTON, self.OnTimeToClose, btn)
68 69 self.Bind(wx.EVT_BUTTON, self.OnFunButton, funbtn)
69 70
70 71 # Use a sizer to layout the controls, stacked vertically and with
71 72 # a 10 pixel border around each
72 73 sizer = wx.BoxSizer(wx.VERTICAL)
73 74 sizer.Add(text, 0, wx.ALL, 10)
74 75 sizer.Add(btn, 0, wx.ALL, 10)
75 76 sizer.Add(funbtn, 0, wx.ALL, 10)
76 77 panel.SetSizer(sizer)
77 78 panel.Layout()
78 79
79 80
80 81 def OnTimeToClose(self, evt):
81 82 """Event handler for the button click."""
82 83 print "See ya later!"
83 84 self.Close()
84 85
85 86 def OnFunButton(self, evt):
86 87 """Event handler for the button click."""
87 88 print "Having fun yet?"
88 89
89 90
90 91 class MyApp(wx.App):
91 92 def OnInit(self):
92 93 frame = MyFrame(None, "Simple wxPython App")
93 94 self.SetTopWindow(frame)
94 95
95 96 print "Print statements go to this stdout window by default."
96 97
97 98 frame.Show(True)
98 99 return True
99 100
100 app = wx.GetApp()
101 if app is None:
102 app = MyApp(redirect=False, clearSigInt=False)
101 if __name__ == '__main__':
102 app = wx.GetApp()
103 if app is None:
104 app = MyApp(redirect=False, clearSigInt=False)
103 105
104 try:
105 from IPython.lib.inputhook import enable_wx
106 enable_wx(app)
107 except ImportError:
108 app.MainLoop()
106 try:
107 from IPython.lib.inputhook import enable_wx
108 enable_wx(app)
109 except ImportError:
110 app.MainLoop()
109 111
General Comments 0
You need to be logged in to leave comments. Login now