##// END OF EJS Templates
Finishing up the wx, qt4 and tk support. Still have to do gtk.
Brian Granger -
Show More
@@ -0,0 +1,99 b''
1 """A Simple wx example to test IPython's event loop integration.
2
3 To run this do:
4
5 In [5]: %gui wx
6
7 In [6]: %run gui-wx.py
8
9 Ref: Modified from wxPython source code wxPython/samples/simple/simple.py
10
11 This example can only be run once in a given IPython session.
12 """
13
14 import wx
15
16
17 class MyFrame(wx.Frame):
18 """
19 This is MyFrame. It just shows a few controls on a wxPanel,
20 and has a simple menu.
21 """
22 def __init__(self, parent, title):
23 wx.Frame.__init__(self, parent, -1, title,
24 pos=(150, 150), size=(350, 200))
25
26 # Create the menubar
27 menuBar = wx.MenuBar()
28
29 # and a menu
30 menu = wx.Menu()
31
32 # add an item to the menu, using \tKeyName automatically
33 # creates an accelerator, the third param is some help text
34 # that will show up in the statusbar
35 menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Exit this simple sample")
36
37 # bind the menu event to an event handler
38 self.Bind(wx.EVT_MENU, self.OnTimeToClose, id=wx.ID_EXIT)
39
40 # and put the menu on the menubar
41 menuBar.Append(menu, "&File")
42 self.SetMenuBar(menuBar)
43
44 self.CreateStatusBar()
45
46 # Now create the Panel to put the other controls on.
47 panel = wx.Panel(self)
48
49 # and a few controls
50 text = wx.StaticText(panel, -1, "Hello World!")
51 text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD))
52 text.SetSize(text.GetBestSize())
53 btn = wx.Button(panel, -1, "Close")
54 funbtn = wx.Button(panel, -1, "Just for fun...")
55
56 # bind the button events to handlers
57 self.Bind(wx.EVT_BUTTON, self.OnTimeToClose, btn)
58 self.Bind(wx.EVT_BUTTON, self.OnFunButton, funbtn)
59
60 # Use a sizer to layout the controls, stacked vertically and with
61 # a 10 pixel border around each
62 sizer = wx.BoxSizer(wx.VERTICAL)
63 sizer.Add(text, 0, wx.ALL, 10)
64 sizer.Add(btn, 0, wx.ALL, 10)
65 sizer.Add(funbtn, 0, wx.ALL, 10)
66 panel.SetSizer(sizer)
67 panel.Layout()
68
69
70 def OnTimeToClose(self, evt):
71 """Event handler for the button click."""
72 print "See ya later!"
73 self.Close()
74
75 def OnFunButton(self, evt):
76 """Event handler for the button click."""
77 print "Having fun yet?"
78
79
80 class MyApp(wx.App):
81 def OnInit(self):
82 frame = MyFrame(None, "Simple wxPython App")
83 self.SetTopWindow(frame)
84
85 print "Print statements go to this stdout window by default."
86
87 frame.Show(True)
88 return True
89
90 app = wx.GetApp()
91 if app is None:
92 app = MyApp(redirect=False, clearSigInt=False)
93
94 try:
95 from IPython.lib.inputhook import appstart_wx
96 appstart_wx(app)
97 except ImportError:
98 app.MainLoop()
99
@@ -63,6 +63,16 b' Shell = shell'
63 63 from IPython.core import ipapi
64 64 from IPython.core import iplib
65 65
66 from IPython.lib import (
67 enable_wx, disable_wx,
68 enable_gtk, disable_gtk,
69 enable_qt4, disable_qt4,
70 enable_tk, disable_tk,
71 set_inputhook, clear_inputhook,
72 current_gui, spin,
73 appstart_qt4, appstart_wx
74 )
75
66 76 # Release data
67 77 from IPython.core import release # do it explicitly so pydoc can see it - pydoc bug
68 78 __author__ = '%s <%s>\n%s <%s>\n%s <%s>' % \
@@ -20,7 +20,9 b' from IPython.lib.inputhook import ('
20 20 enable_gtk, disable_gtk,
21 21 enable_qt4, disable_qt4,
22 22 enable_tk, disable_tk,
23 set_inputhook, clear_inputhook
23 set_inputhook, clear_inputhook,
24 current_gui, spin,
25 appstart_qt4, appstart_wx
24 26 )
25 27
26 28 #-----------------------------------------------------------------------------
@@ -19,70 +19,104 b' import ctypes'
19 19 import sys
20 20
21 21 #-----------------------------------------------------------------------------
22 # Code
22 # Constants
23 #-----------------------------------------------------------------------------
24
25 # Constants for identifying the GUI toolkits.
26 GUI_WX = 'wx'
27 GUI_QT4 = 'qt4'
28 GUI_GTK = 'gtk'
29 GUI_TK = 'tk'
30
31 #-----------------------------------------------------------------------------
32 # Utility classes
23 33 #-----------------------------------------------------------------------------
24 34
25 def appstart_qt4():
26 from PyQt4 import QtCore, QtGui
27
28 app = QtCore.QCoreApplication.instance()
29 print 'qtapp:', app
30 if app is not None:
31 if current_gui() == 'qt4':
32 pass
33 else:
34 app.exec_()
35
36 35
37 36 class _DummyMainloop(object):
38 37 """A special manager to hijack GUI mainloops that is mostly a no-op.
39 38
40 This does have, however, special logic.
39 We are not using this class currently as it breaks GUI code that calls
40 a mainloop function after the app has started to process pending events.
41 41 """
42 42 def __init__(self, ml, ihm, gui_type):
43 43 self.ml = ml
44 44 self.ihm = ihm
45 45 self.gui_type = gui_type
46
47
46
48 47 def __call__(self, *args, **kw):
49 force = kw.pop('force', False)
50 force = False
51 if force:
52 #print 'forced spin' # dbg
53 self.ml(*args, **kw)
54
55 48 if self.ihm.current_gui() == self.gui_type:
56 49 pass
57 50 else:
58 51 self.ml(*args, **kw)
59 52
60 53
61 def spin_qt4():
54 #-----------------------------------------------------------------------------
55 # Appstart and spin functions
56 #-----------------------------------------------------------------------------
57
58
59 def appstart_qt4(app):
60 """Start the qt4 event loop in a way that plays with IPython.
61
62 When a qt4 app is run interactively in IPython, the event loop should
63 not be started. This function checks to see if IPython's qt4 integration
64 is activated and if so, it passes. If not, it will call the :meth:`exec_`
65 method of the main qt4 app.
66
67 This function should be used by users who want their qt4 scripts to work
68 both at the command line and in IPython. These users should put the
69 following logic at the bottom on their script, after they create a
70 :class:`QApplication` instance (called ``app`` here)::
71
72 try:
73 from IPython.lib.inputhook import appstart_qt4
74 appstart_qt4(app)
75 except ImportError:
76 app.exec_()
77 """
62 78 from PyQt4 import QtCore, QtGui
63 79
64 app = QtCore.QCoreApplication.instance()
65 if (app is not None) and (app.thread() == QtCore.QThread.currentThread()):
66 ## timer = QtCore.QTimer()
67 ## QtCore.QObject.connect(timer,
68 ## QtCore.SIGNAL('timeout()'),
69 ## app,
70 ## QtCore.SLOT('quit()'))
71 ## timer.start(100)
72 #QtCore.QCoreApplication.exec_(force=True)
73 QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
74 ##timer.stop()
75
76
77 def spin_wx():
78 app = wx.GetApp()
79 if app is not None and wx.Thread_IsMain():
80 evtloop = wx.EventLoop()
81 ea = wx.EventLoopActivator(evtloop)
82 while evtloop.Pending():
83 evtloop.Dispatch()
84 app.ProcessIdle()
85 del ea
80 assert isinstance(app, QtCore.QCoreApplication)
81 if app is not None:
82 if current_gui() == GUI_QT4:
83 pass
84 else:
85 app.exec_()
86
87
88 def appstart_wx(app):
89 """Start the wx event loop in a way that plays with IPython.
90
91 When a wx app is run interactively in IPython, the event loop should
92 not be started. This function checks to see if IPython's wx integration
93 is activated and if so, it passes. If not, it will call the
94 :meth:`MainLoop` method of the main qt4 app.
95
96 This function should be used by users who want their wx scripts to work
97 both at the command line and in IPython. These users should put the
98 following logic at the bottom on their script, after they create a
99 :class:`App` instance (called ``app`` here)::
100
101 try:
102 from IPython.lib.inputhook import appstart_wx
103 appstart_wx(app)
104 except ImportError:
105 app.MainLoop()
106 """
107 import wx
108
109 assert isinstance(app, wx.App)
110 if app is not None:
111 if current_gui() == GUI_WX:
112 pass
113 else:
114 app.MainLoop()
115
116
117 #-----------------------------------------------------------------------------
118 # Main InputHookManager class
119 #-----------------------------------------------------------------------------
86 120
87 121
88 122 class InputHookManager(object):
@@ -95,6 +129,11 b' class InputHookManager(object):'
95 129 def __init__(self):
96 130 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
97 131 self._apps = {}
132 self._spinner_dict = {
133 GUI_QT4 : self._spin_qt4,
134 GUI_WX : self._spin_wx,
135 GUI_GTK : self._spin_gtk,
136 GUI_TK : self._spin_tk}
98 137 self._reset()
99 138
100 139 def _reset(self):
@@ -104,7 +143,11 b' class InputHookManager(object):'
104 143 self._current_gui = None
105 144
106 145 def _hijack_wx(self):
107 """Hijack the wx mainloop so a user calling it won't cause badness."""
146 """Hijack the wx mainloop so a user calling it won't cause badness.
147
148 We are not currently using this as it breaks GUI code that calls a
149 mainloop at anytime but startup.
150 """
108 151 import wx
109 152 if hasattr(wx, '_core_'): core = getattr(wx, '_core_')
110 153 elif hasattr(wx, '_core'): core = getattr(wx, '_core')
@@ -114,28 +157,100 b' class InputHookManager(object):'
114 157 return orig_mainloop
115 158
116 159 def _hijack_qt4(self):
117 """Hijack the qt4 mainloop so a user calling it won't cause badness."""
160 """Hijack the qt4 mainloop so a user calling it won't cause badness.
161
162 We are not currently using this as it breaks GUI code that calls a
163 mainloop at anytime but startup.
164 """
118 165 from PyQt4 import QtGui, QtCore
119 166 orig_mainloop = QtGui.qApp.exec_
120 dumb_ml = _DummyMainloop(orig_mainloop, self, 'qt4')
167 dumb_ml = _DummyMainloop(orig_mainloop, self, GUI_QT4)
121 168 QtGui.qApp.exec_ = dumb_ml
122 169 QtGui.QApplication.exec_ = dumb_ml
123 170 QtCore.QCoreApplication.exec_ = dumb_ml
124 171 return orig_mainloop
125 172
126 173 def _hijack_gtk(self):
127 """Hijack the gtk mainloop so a user calling it won't cause badness."""
174 """Hijack the gtk mainloop so a user calling it won't cause badness.
175
176 We are not currently using this as it breaks GUI code that calls a
177 mainloop at anytime but startup.
178 """
128 179 import gtk
129 180 orig_mainloop = gtk.main
130 gtk.mainloop = _DummyMainloop
131 gtk.main = _DummyMainloop
181 dumb_ml = _DummyMainloop(orig_mainloop, self, GUI_GTK)
182 gtk.mainloop = dumb_ml
183 gtk.main = dumb_ml
132 184 return orig_mainloop
133 185
134 186 def _hijack_tk(self):
135 """Hijack the tk mainloop so a user calling it won't cause badness."""
187 """Hijack the tk mainloop so a user calling it won't cause badness.
188
189 We are not currently using this as it breaks GUI code that calls a
190 mainloop at anytime but startup.
191 """
136 192 import Tkinter
137 Tkinter.Misc.mainloop = _DummyMainloop
138 Tkinter.mainloop = _DummyMainloop
193 orig_mainloop = gtk.main
194 dumb_ml = _DummyMainloop(orig_mainloop, self, GUI_TK)
195 Tkinter.Misc.mainloop = dumb_ml
196 Tkinter.mainloop = dumb_ml
197
198 def _spin_qt4(self):
199 """Process all pending events in the qt4 event loop.
200
201 This is for internal IPython use only and user code should not call this.
202 Instead, they should issue the raw GUI calls themselves.
203 """
204 from PyQt4 import QtCore, QtGui
205
206 app = QtCore.QCoreApplication.instance()
207 if app is not None:
208 QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
209
210 def _spin_wx(self):
211 """Process all pending events in the wx event loop.
212
213 This is for internal IPython use only and user code should not call this.
214 Instead, they should issue the raw GUI calls themselves.
215 """
216 import wx
217 app = wx.GetApp()
218 if app is not None and wx.Thread_IsMain():
219 evtloop = wx.EventLoop()
220 ea = wx.EventLoopActivator(evtloop)
221 while evtloop.Pending():
222 evtloop.Dispatch()
223 app.ProcessIdle()
224 del ea
225
226 def _spin_gtk(self):
227 """Process all pending events in the gtk event loop.
228
229 This is for internal IPython use only and user code should not call this.
230 Instead, they should issue the raw GUI calls themselves.
231 """
232 pass
233
234 def _spin_tk(self):
235 """Process all pending events in the tk event loop.
236
237 This is for internal IPython use only and user code should not call this.
238 Instead, they should issue the raw GUI calls themselves.
239 """
240 app = self._apps.get(GUI_TK)
241 if app is not None:
242 app.update()
243
244 def spin(self):
245 """Process pending events in the current gui.
246
247 This method is just provided for IPython to use internally if needed
248 for things like testing. Third party projects should not call this
249 method, but instead should call the underlying GUI toolkit methods
250 that we are calling.
251 """
252 spinner = self._spinner_dict.get(self._current_gui, lambda: None)
253 spinner()
139 254
140 255 def get_pyos_inputhook(self):
141 256 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
@@ -211,14 +326,13 b' class InputHookManager(object):'
211 326 """
212 327 from IPython.lib.inputhookwx import inputhook_wx
213 328 self.set_inputhook(inputhook_wx)
214 self._current_gui = 'wx'
215 self._hijack_wx()
329 self._current_gui = GUI_WX
216 330 if app:
217 331 import wx
218 332 app = wx.GetApp()
219 333 if app is None:
220 334 app = wx.App(redirect=False, clearSigInt=False)
221 self._apps['wx'] = app
335 self._apps[GUI_WX] = app
222 336 return app
223 337
224 338 def disable_wx(self):
@@ -259,14 +373,13 b' class InputHookManager(object):'
259 373 QtCore.pyqtRestoreInputHook()
260 374 except AttributeError:
261 375 pass
262 self._current_gui = 'qt4'
263 #self._hijack_qt4()
376 self._current_gui = GUI_QT4
264 377 if app:
265 378 from PyQt4 import QtGui
266 379 app = QtCore.QCoreApplication.instance()
267 380 if app is None:
268 381 app = QtGui.QApplication(sys.argv)
269 self._apps['qt4'] = app
382 self._apps[GUI_QT4] = app
270 383 return app
271 384
272 385 def disable_qt4(self):
@@ -294,13 +407,12 b' class InputHookManager(object):'
294 407 import gtk
295 408 try:
296 409 gtk.set_interactive(True)
297 self._current_gui = 'gtk'
410 self._current_gui = GUI_GTK
298 411 except AttributeError:
299 412 # For older versions of gtk, use our own ctypes version
300 413 from IPython.lib.inputhookgtk import inputhook_gtk
301 414 self.set_inputhook(inputhook_gtk)
302 self._current_gui = 'gtk'
303 self._hijack_gtk()
415 self._current_gui = GUI_GTK
304 416
305 417 def disable_gtk(self):
306 418 """Disable event loop integration with PyGTK.
@@ -322,8 +434,13 b' class InputHookManager(object):'
322 434 Currently this is a no-op as creating a :class:`Tkinter.Tk` object
323 435 sets ``PyOS_InputHook``.
324 436 """
325 self._current_gui = 'tk'
326 self._hijack_tk()
437 self._current_gui = GUI_TK
438 if app:
439 import Tkinter
440 app = Tkinter.Tk()
441 app.withdraw()
442 self._apps[GUI_TK] = app
443 return app
327 444
328 445 def disable_tk(self):
329 446 """Disable event loop integration with Tkinter.
@@ -350,3 +467,4 b' clear_inputhook = inputhook_manager.clear_inputhook'
350 467 set_inputhook = inputhook_manager.set_inputhook
351 468 current_gui = inputhook_manager.current_gui
352 469 clear_app_refs = inputhook_manager.clear_app_refs
470 spin = inputhook_manager.spin
@@ -35,8 +35,6 b" if __name__ == '__main__':"
35 35 sw.show()
36 36
37 37 try:
38 import IPython.lib.inputhook as i; i.appstart_qt4()
38 from IPython import appstart_qt4; appstart_qt4(app)
39 39 except ImportError:
40 40 app.exec_()
41
42 #import time; time.sleep(10)
@@ -1,6 +1,8 b''
1 1 """Test the new %gui command. Run this in ipython as
2 2
3 %run switchgui [backend]
3 In [1]: %gui [backend]
4
5 In [2]: %run switchgui [backend]
4 6
5 7 where the optional backend can be one of: qt4, gtk, tk, wx.
6 8 """
@@ -8,23 +10,18 b' where the optional backend can be one of: qt4, gtk, tk, wx.'
8 10 import sys
9 11 import time
10 12
11 import IPython.core.ipapi as ipapi
12 ip = ipapi.get()
13
14 13 from IPython.lib import inputhook
15 14
16 try:
17 backend = sys.argv[1]
18 #a = ip.magic('gui -a %s' % backend)
19 #a = ip.magic('gui %s' % backend)
20 except IndexError:
21 backend = 'qt'
15 gui = inputhook.current_gui()
16 if gui is None:
17 gui = 'qt4'
18 inputhook.enable_qt4(app=True)
22 19
23 backends = dict(wx='wxagg', qt='qt4agg', gtk='gtkagg', tk='tkagg')
20 backends = dict(wx='wxagg', qt4='qt4agg', gtk='gtkagg', tk='tkagg')
24 21
25 22 import matplotlib
26 matplotlib.use(backends[backend])
27 #matplotlib.interactive(True)
23 matplotlib.use(backends[gui])
24 matplotlib.interactive(True)
28 25
29 26 import matplotlib
30 27 from matplotlib import pyplot as plt, mlab, pylab
@@ -33,25 +30,19 b' import numpy as np'
33 30 from numpy import *
34 31 from matplotlib.pyplot import *
35 32
36 x = np.linspace(0,pi,100)
33 x = np.linspace(0,pi,500)
37 34
38 35 print "A plot has been created"
39 36 line, = plot(x,sin(2*x))
40 plt.show()
41 inputhook.spin_qt4()
37 inputhook.spin()
42 38
43 #raw_input("Press Enter to continue")
44 39
45 print "I will now count until 10, please hit Ctrl-C before I'm done..."
46 print "IPython should stop counting and return to the prompt without crashing."
40 print "Now, we will update the plot..."
47 41 print
48 line_x = line.get_data()[0]
49 42 for i in range(1,51):
50 43 print i,
51 44 sys.stdout.flush()
52 line.set_data(line_x,sin(x*i))
45 line.set_data(x,sin(x*i))
53 46 plt.title('i=%d' % i)
54 #plt.show()
55 47 plt.draw()
56 inputhook.spin_qt4()
57 #time.sleep(0.04)
48 inputhook.spin()
General Comments 0
You need to be logged in to leave comments. Login now