##// END OF EJS Templates
Merge pull request #1782 from Carreau/magiclistinfo...
Bussonnier Matthias -
r7316:8b00a30d merge
parent child Browse files
Show More
@@ -1,576 +1,586 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Magic functions for InteractiveShell.
2 """Magic functions for InteractiveShell.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
6 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
7 # Copyright (C) 2001 Fernando Perez <fperez@colorado.edu>
7 # Copyright (C) 2001 Fernando Perez <fperez@colorado.edu>
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9
9
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Stdlib
17 # Stdlib
18 import os
18 import os
19 import re
19 import re
20 import sys
20 import sys
21 import types
21 import types
22 from getopt import getopt, GetoptError
22 from getopt import getopt, GetoptError
23
23
24 # Our own
24 # Our own
25 from IPython.config.configurable import Configurable
25 from IPython.config.configurable import Configurable
26 from IPython.core import oinspect
26 from IPython.core import oinspect
27 from IPython.core.error import UsageError
27 from IPython.core.error import UsageError
28 from IPython.core.prefilter import ESC_MAGIC
28 from IPython.core.prefilter import ESC_MAGIC
29 from IPython.external.decorator import decorator
29 from IPython.external.decorator import decorator
30 from IPython.utils.ipstruct import Struct
30 from IPython.utils.ipstruct import Struct
31 from IPython.utils.process import arg_split
31 from IPython.utils.process import arg_split
32 from IPython.utils.text import dedent
32 from IPython.utils.text import dedent
33 from IPython.utils.traitlets import Bool, Dict, Instance
33 from IPython.utils.traitlets import Bool, Dict, Instance
34 from IPython.utils.warn import error, warn
34 from IPython.utils.warn import error, warn
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Globals
37 # Globals
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 # A dict we'll use for each class that has magics, used as temporary storage to
40 # A dict we'll use for each class that has magics, used as temporary storage to
41 # pass information between the @line/cell_magic method decorators and the
41 # pass information between the @line/cell_magic method decorators and the
42 # @magics_class class decorator, because the method decorators have no
42 # @magics_class class decorator, because the method decorators have no
43 # access to the class when they run. See for more details:
43 # access to the class when they run. See for more details:
44 # http://stackoverflow.com/questions/2366713/can-a-python-decorator-of-an-instance-method-access-the-class
44 # http://stackoverflow.com/questions/2366713/can-a-python-decorator-of-an-instance-method-access-the-class
45
45
46 magics = dict(line={}, cell={})
46 magics = dict(line={}, cell={})
47
47
48 magic_kinds = ('line', 'cell')
48 magic_kinds = ('line', 'cell')
49 magic_spec = ('line', 'cell', 'line_cell')
49 magic_spec = ('line', 'cell', 'line_cell')
50
50
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52 # Utility classes and functions
52 # Utility classes and functions
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54
54
55 class Bunch: pass
55 class Bunch: pass
56
56
57
57
58 def on_off(tag):
58 def on_off(tag):
59 """Return an ON/OFF string for a 1/0 input. Simple utility function."""
59 """Return an ON/OFF string for a 1/0 input. Simple utility function."""
60 return ['OFF','ON'][tag]
60 return ['OFF','ON'][tag]
61
61
62
62
63 def compress_dhist(dh):
63 def compress_dhist(dh):
64 """Compress a directory history into a new one with at most 20 entries.
64 """Compress a directory history into a new one with at most 20 entries.
65
65
66 Return a new list made from the first and last 10 elements of dhist after
66 Return a new list made from the first and last 10 elements of dhist after
67 removal of duplicates.
67 removal of duplicates.
68 """
68 """
69 head, tail = dh[:-10], dh[-10:]
69 head, tail = dh[:-10], dh[-10:]
70
70
71 newhead = []
71 newhead = []
72 done = set()
72 done = set()
73 for h in head:
73 for h in head:
74 if h in done:
74 if h in done:
75 continue
75 continue
76 newhead.append(h)
76 newhead.append(h)
77 done.add(h)
77 done.add(h)
78
78
79 return newhead + tail
79 return newhead + tail
80
80
81
81
82 def needs_local_scope(func):
82 def needs_local_scope(func):
83 """Decorator to mark magic functions which need to local scope to run."""
83 """Decorator to mark magic functions which need to local scope to run."""
84 func.needs_local_scope = True
84 func.needs_local_scope = True
85 return func
85 return func
86
86
87 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
88 # Class and method decorators for registering magics
88 # Class and method decorators for registering magics
89 #-----------------------------------------------------------------------------
89 #-----------------------------------------------------------------------------
90
90
91 def magics_class(cls):
91 def magics_class(cls):
92 """Class decorator for all subclasses of the main Magics class.
92 """Class decorator for all subclasses of the main Magics class.
93
93
94 Any class that subclasses Magics *must* also apply this decorator, to
94 Any class that subclasses Magics *must* also apply this decorator, to
95 ensure that all the methods that have been decorated as line/cell magics
95 ensure that all the methods that have been decorated as line/cell magics
96 get correctly registered in the class instance. This is necessary because
96 get correctly registered in the class instance. This is necessary because
97 when method decorators run, the class does not exist yet, so they
97 when method decorators run, the class does not exist yet, so they
98 temporarily store their information into a module global. Application of
98 temporarily store their information into a module global. Application of
99 this class decorator copies that global data to the class instance and
99 this class decorator copies that global data to the class instance and
100 clears the global.
100 clears the global.
101
101
102 Obviously, this mechanism is not thread-safe, which means that the
102 Obviously, this mechanism is not thread-safe, which means that the
103 *creation* of subclasses of Magic should only be done in a single-thread
103 *creation* of subclasses of Magic should only be done in a single-thread
104 context. Instantiation of the classes has no restrictions. Given that
104 context. Instantiation of the classes has no restrictions. Given that
105 these classes are typically created at IPython startup time and before user
105 these classes are typically created at IPython startup time and before user
106 application code becomes active, in practice this should not pose any
106 application code becomes active, in practice this should not pose any
107 problems.
107 problems.
108 """
108 """
109 cls.registered = True
109 cls.registered = True
110 cls.magics = dict(line = magics['line'],
110 cls.magics = dict(line = magics['line'],
111 cell = magics['cell'])
111 cell = magics['cell'])
112 magics['line'] = {}
112 magics['line'] = {}
113 magics['cell'] = {}
113 magics['cell'] = {}
114 return cls
114 return cls
115
115
116
116
117 def record_magic(dct, magic_kind, magic_name, func):
117 def record_magic(dct, magic_kind, magic_name, func):
118 """Utility function to store a function as a magic of a specific kind.
118 """Utility function to store a function as a magic of a specific kind.
119
119
120 Parameters
120 Parameters
121 ----------
121 ----------
122 dct : dict
122 dct : dict
123 A dictionary with 'line' and 'cell' subdicts.
123 A dictionary with 'line' and 'cell' subdicts.
124
124
125 magic_kind : str
125 magic_kind : str
126 Kind of magic to be stored.
126 Kind of magic to be stored.
127
127
128 magic_name : str
128 magic_name : str
129 Key to store the magic as.
129 Key to store the magic as.
130
130
131 func : function
131 func : function
132 Callable object to store.
132 Callable object to store.
133 """
133 """
134 if magic_kind == 'line_cell':
134 if magic_kind == 'line_cell':
135 dct['line'][magic_name] = dct['cell'][magic_name] = func
135 dct['line'][magic_name] = dct['cell'][magic_name] = func
136 else:
136 else:
137 dct[magic_kind][magic_name] = func
137 dct[magic_kind][magic_name] = func
138
138
139
139
140 def validate_type(magic_kind):
140 def validate_type(magic_kind):
141 """Ensure that the given magic_kind is valid.
141 """Ensure that the given magic_kind is valid.
142
142
143 Check that the given magic_kind is one of the accepted spec types (stored
143 Check that the given magic_kind is one of the accepted spec types (stored
144 in the global `magic_spec`), raise ValueError otherwise.
144 in the global `magic_spec`), raise ValueError otherwise.
145 """
145 """
146 if magic_kind not in magic_spec:
146 if magic_kind not in magic_spec:
147 raise ValueError('magic_kind must be one of %s, %s given' %
147 raise ValueError('magic_kind must be one of %s, %s given' %
148 magic_kinds, magic_kind)
148 magic_kinds, magic_kind)
149
149
150
150
151 # The docstrings for the decorator below will be fairly similar for the two
151 # The docstrings for the decorator below will be fairly similar for the two
152 # types (method and function), so we generate them here once and reuse the
152 # types (method and function), so we generate them here once and reuse the
153 # templates below.
153 # templates below.
154 _docstring_template = \
154 _docstring_template = \
155 """Decorate the given {0} as {1} magic.
155 """Decorate the given {0} as {1} magic.
156
156
157 The decorator can be used with or without arguments, as follows.
157 The decorator can be used with or without arguments, as follows.
158
158
159 i) without arguments: it will create a {1} magic named as the {0} being
159 i) without arguments: it will create a {1} magic named as the {0} being
160 decorated::
160 decorated::
161
161
162 @deco
162 @deco
163 def foo(...)
163 def foo(...)
164
164
165 will create a {1} magic named `foo`.
165 will create a {1} magic named `foo`.
166
166
167 ii) with one string argument: which will be used as the actual name of the
167 ii) with one string argument: which will be used as the actual name of the
168 resulting magic::
168 resulting magic::
169
169
170 @deco('bar')
170 @deco('bar')
171 def foo(...)
171 def foo(...)
172
172
173 will create a {1} magic named `bar`.
173 will create a {1} magic named `bar`.
174 """
174 """
175
175
176 # These two are decorator factories. While they are conceptually very similar,
176 # These two are decorator factories. While they are conceptually very similar,
177 # there are enough differences in the details that it's simpler to have them
177 # there are enough differences in the details that it's simpler to have them
178 # written as completely standalone functions rather than trying to share code
178 # written as completely standalone functions rather than trying to share code
179 # and make a single one with convoluted logic.
179 # and make a single one with convoluted logic.
180
180
181 def _method_magic_marker(magic_kind):
181 def _method_magic_marker(magic_kind):
182 """Decorator factory for methods in Magics subclasses.
182 """Decorator factory for methods in Magics subclasses.
183 """
183 """
184
184
185 validate_type(magic_kind)
185 validate_type(magic_kind)
186
186
187 # This is a closure to capture the magic_kind. We could also use a class,
187 # This is a closure to capture the magic_kind. We could also use a class,
188 # but it's overkill for just that one bit of state.
188 # but it's overkill for just that one bit of state.
189 def magic_deco(arg):
189 def magic_deco(arg):
190 call = lambda f, *a, **k: f(*a, **k)
190 call = lambda f, *a, **k: f(*a, **k)
191
191
192 if callable(arg):
192 if callable(arg):
193 # "Naked" decorator call (just @foo, no args)
193 # "Naked" decorator call (just @foo, no args)
194 func = arg
194 func = arg
195 name = func.func_name
195 name = func.func_name
196 retval = decorator(call, func)
196 retval = decorator(call, func)
197 record_magic(magics, magic_kind, name, name)
197 record_magic(magics, magic_kind, name, name)
198 elif isinstance(arg, basestring):
198 elif isinstance(arg, basestring):
199 # Decorator called with arguments (@foo('bar'))
199 # Decorator called with arguments (@foo('bar'))
200 name = arg
200 name = arg
201 def mark(func, *a, **kw):
201 def mark(func, *a, **kw):
202 record_magic(magics, magic_kind, name, func.func_name)
202 record_magic(magics, magic_kind, name, func.func_name)
203 return decorator(call, func)
203 return decorator(call, func)
204 retval = mark
204 retval = mark
205 else:
205 else:
206 raise TypeError("Decorator can only be called with "
206 raise TypeError("Decorator can only be called with "
207 "string or function")
207 "string or function")
208 return retval
208 return retval
209
209
210 # Ensure the resulting decorator has a usable docstring
210 # Ensure the resulting decorator has a usable docstring
211 magic_deco.__doc__ = _docstring_template.format('method', magic_kind)
211 magic_deco.__doc__ = _docstring_template.format('method', magic_kind)
212 return magic_deco
212 return magic_deco
213
213
214
214
215 def _function_magic_marker(magic_kind):
215 def _function_magic_marker(magic_kind):
216 """Decorator factory for standalone functions.
216 """Decorator factory for standalone functions.
217 """
217 """
218 validate_type(magic_kind)
218 validate_type(magic_kind)
219
219
220 # This is a closure to capture the magic_kind. We could also use a class,
220 # This is a closure to capture the magic_kind. We could also use a class,
221 # but it's overkill for just that one bit of state.
221 # but it's overkill for just that one bit of state.
222 def magic_deco(arg):
222 def magic_deco(arg):
223 call = lambda f, *a, **k: f(*a, **k)
223 call = lambda f, *a, **k: f(*a, **k)
224
224
225 # Find get_ipython() in the caller's namespace
225 # Find get_ipython() in the caller's namespace
226 caller = sys._getframe(1)
226 caller = sys._getframe(1)
227 for ns in ['f_locals', 'f_globals', 'f_builtins']:
227 for ns in ['f_locals', 'f_globals', 'f_builtins']:
228 get_ipython = getattr(caller, ns).get('get_ipython')
228 get_ipython = getattr(caller, ns).get('get_ipython')
229 if get_ipython is not None:
229 if get_ipython is not None:
230 break
230 break
231 else:
231 else:
232 raise NameError('Decorator can only run in context where '
232 raise NameError('Decorator can only run in context where '
233 '`get_ipython` exists')
233 '`get_ipython` exists')
234
234
235 ip = get_ipython()
235 ip = get_ipython()
236
236
237 if callable(arg):
237 if callable(arg):
238 # "Naked" decorator call (just @foo, no args)
238 # "Naked" decorator call (just @foo, no args)
239 func = arg
239 func = arg
240 name = func.func_name
240 name = func.func_name
241 ip.register_magic_function(func, magic_kind, name)
241 ip.register_magic_function(func, magic_kind, name)
242 retval = decorator(call, func)
242 retval = decorator(call, func)
243 elif isinstance(arg, basestring):
243 elif isinstance(arg, basestring):
244 # Decorator called with arguments (@foo('bar'))
244 # Decorator called with arguments (@foo('bar'))
245 name = arg
245 name = arg
246 def mark(func, *a, **kw):
246 def mark(func, *a, **kw):
247 ip.register_magic_function(func, magic_kind, name)
247 ip.register_magic_function(func, magic_kind, name)
248 return decorator(call, func)
248 return decorator(call, func)
249 retval = mark
249 retval = mark
250 else:
250 else:
251 raise TypeError("Decorator can only be called with "
251 raise TypeError("Decorator can only be called with "
252 "string or function")
252 "string or function")
253 return retval
253 return retval
254
254
255 # Ensure the resulting decorator has a usable docstring
255 # Ensure the resulting decorator has a usable docstring
256 ds = _docstring_template.format('function', magic_kind)
256 ds = _docstring_template.format('function', magic_kind)
257
257
258 ds += dedent("""
258 ds += dedent("""
259 Note: this decorator can only be used in a context where IPython is already
259 Note: this decorator can only be used in a context where IPython is already
260 active, so that the `get_ipython()` call succeeds. You can therefore use
260 active, so that the `get_ipython()` call succeeds. You can therefore use
261 it in your startup files loaded after IPython initializes, but *not* in the
261 it in your startup files loaded after IPython initializes, but *not* in the
262 IPython configuration file itself, which is executed before IPython is
262 IPython configuration file itself, which is executed before IPython is
263 fully up and running. Any file located in the `startup` subdirectory of
263 fully up and running. Any file located in the `startup` subdirectory of
264 your configuration profile will be OK in this sense.
264 your configuration profile will be OK in this sense.
265 """)
265 """)
266
266
267 magic_deco.__doc__ = ds
267 magic_deco.__doc__ = ds
268 return magic_deco
268 return magic_deco
269
269
270
270
271 # Create the actual decorators for public use
271 # Create the actual decorators for public use
272
272
273 # These three are used to decorate methods in class definitions
273 # These three are used to decorate methods in class definitions
274 line_magic = _method_magic_marker('line')
274 line_magic = _method_magic_marker('line')
275 cell_magic = _method_magic_marker('cell')
275 cell_magic = _method_magic_marker('cell')
276 line_cell_magic = _method_magic_marker('line_cell')
276 line_cell_magic = _method_magic_marker('line_cell')
277
277
278 # These three decorate standalone functions and perform the decoration
278 # These three decorate standalone functions and perform the decoration
279 # immediately. They can only run where get_ipython() works
279 # immediately. They can only run where get_ipython() works
280 register_line_magic = _function_magic_marker('line')
280 register_line_magic = _function_magic_marker('line')
281 register_cell_magic = _function_magic_marker('cell')
281 register_cell_magic = _function_magic_marker('cell')
282 register_line_cell_magic = _function_magic_marker('line_cell')
282 register_line_cell_magic = _function_magic_marker('line_cell')
283
283
284 #-----------------------------------------------------------------------------
284 #-----------------------------------------------------------------------------
285 # Core Magic classes
285 # Core Magic classes
286 #-----------------------------------------------------------------------------
286 #-----------------------------------------------------------------------------
287
287
288 class MagicsManager(Configurable):
288 class MagicsManager(Configurable):
289 """Object that handles all magic-related functionality for IPython.
289 """Object that handles all magic-related functionality for IPython.
290 """
290 """
291 # Non-configurable class attributes
291 # Non-configurable class attributes
292
292
293 # A two-level dict, first keyed by magic type, then by magic function, and
293 # A two-level dict, first keyed by magic type, then by magic function, and
294 # holding the actual callable object as value. This is the dict used for
294 # holding the actual callable object as value. This is the dict used for
295 # magic function dispatch
295 # magic function dispatch
296 magics = Dict
296 magics = Dict
297
297
298 # A registry of the original objects that we've been given holding magics.
298 # A registry of the original objects that we've been given holding magics.
299 registry = Dict
299 registry = Dict
300
300
301 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
301 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
302
302
303 auto_magic = Bool(True, config=True, help=
303 auto_magic = Bool(True, config=True, help=
304 "Automatically call line magics without requiring explicit % prefix")
304 "Automatically call line magics without requiring explicit % prefix")
305
305
306 _auto_status = [
306 _auto_status = [
307 'Automagic is OFF, % prefix IS needed for line magics.',
307 'Automagic is OFF, % prefix IS needed for line magics.',
308 'Automagic is ON, % prefix IS NOT needed for line magics.']
308 'Automagic is ON, % prefix IS NOT needed for line magics.']
309
309
310 user_magics = Instance('IPython.core.magics.UserMagics')
310 user_magics = Instance('IPython.core.magics.UserMagics')
311
311
312 def __init__(self, shell=None, config=None, user_magics=None, **traits):
312 def __init__(self, shell=None, config=None, user_magics=None, **traits):
313
313
314 super(MagicsManager, self).__init__(shell=shell, config=config,
314 super(MagicsManager, self).__init__(shell=shell, config=config,
315 user_magics=user_magics, **traits)
315 user_magics=user_magics, **traits)
316 self.magics = dict(line={}, cell={})
316 self.magics = dict(line={}, cell={})
317 # Let's add the user_magics to the registry for uniformity, so *all*
317 # Let's add the user_magics to the registry for uniformity, so *all*
318 # registered magic containers can be found there.
318 # registered magic containers can be found there.
319 self.registry[user_magics.__class__.__name__] = user_magics
319 self.registry[user_magics.__class__.__name__] = user_magics
320
320
321 def auto_status(self):
321 def auto_status(self):
322 """Return descriptive string with automagic status."""
322 """Return descriptive string with automagic status."""
323 return self._auto_status[self.auto_magic]
323 return self._auto_status[self.auto_magic]
324
325 def lsmagic_info(self):
326 magic_list = []
327 for m_type in self.magics :
328 for m_name,mgc in self.magics[m_type].items():
329 try :
330 magic_list.append({'name':m_name,'type':m_type,'class':mgc.im_class.__name__})
331 except AttributeError :
332 magic_list.append({'name':m_name,'type':m_type,'class':'Other'})
333 return magic_list
324
334
325 def lsmagic(self):
335 def lsmagic(self):
326 """Return a dict of currently available magic functions.
336 """Return a dict of currently available magic functions.
327
337
328 The return dict has the keys 'line' and 'cell', corresponding to the
338 The return dict has the keys 'line' and 'cell', corresponding to the
329 two types of magics we support. Each value is a list of names.
339 two types of magics we support. Each value is a list of names.
330 """
340 """
331 return self.magics
341 return self.magics
332
342
333 def register(self, *magic_objects):
343 def register(self, *magic_objects):
334 """Register one or more instances of Magics.
344 """Register one or more instances of Magics.
335
345
336 Take one or more classes or instances of classes that subclass the main
346 Take one or more classes or instances of classes that subclass the main
337 `core.Magic` class, and register them with IPython to use the magic
347 `core.Magic` class, and register them with IPython to use the magic
338 functions they provide. The registration process will then ensure that
348 functions they provide. The registration process will then ensure that
339 any methods that have decorated to provide line and/or cell magics will
349 any methods that have decorated to provide line and/or cell magics will
340 be recognized with the `%x`/`%%x` syntax as a line/cell magic
350 be recognized with the `%x`/`%%x` syntax as a line/cell magic
341 respectively.
351 respectively.
342
352
343 If classes are given, they will be instantiated with the default
353 If classes are given, they will be instantiated with the default
344 constructor. If your classes need a custom constructor, you should
354 constructor. If your classes need a custom constructor, you should
345 instanitate them first and pass the instance.
355 instanitate them first and pass the instance.
346
356
347 The provided arguments can be an arbitrary mix of classes and instances.
357 The provided arguments can be an arbitrary mix of classes and instances.
348
358
349 Parameters
359 Parameters
350 ----------
360 ----------
351 magic_objects : one or more classes or instances
361 magic_objects : one or more classes or instances
352 """
362 """
353 # Start by validating them to ensure they have all had their magic
363 # Start by validating them to ensure they have all had their magic
354 # methods registered at the instance level
364 # methods registered at the instance level
355 for m in magic_objects:
365 for m in magic_objects:
356 if not m.registered:
366 if not m.registered:
357 raise ValueError("Class of magics %r was constructed without "
367 raise ValueError("Class of magics %r was constructed without "
358 "the @register_macics class decorator")
368 "the @register_macics class decorator")
359 if type(m) is type:
369 if type(m) is type:
360 # If we're given an uninstantiated class
370 # If we're given an uninstantiated class
361 m = m(self.shell)
371 m = m(self.shell)
362
372
363 # Now that we have an instance, we can register it and update the
373 # Now that we have an instance, we can register it and update the
364 # table of callables
374 # table of callables
365 self.registry[m.__class__.__name__] = m
375 self.registry[m.__class__.__name__] = m
366 for mtype in magic_kinds:
376 for mtype in magic_kinds:
367 self.magics[mtype].update(m.magics[mtype])
377 self.magics[mtype].update(m.magics[mtype])
368
378
369 def register_function(self, func, magic_kind='line', magic_name=None):
379 def register_function(self, func, magic_kind='line', magic_name=None):
370 """Expose a standalone function as magic function for IPython.
380 """Expose a standalone function as magic function for IPython.
371
381
372 This will create an IPython magic (line, cell or both) from a
382 This will create an IPython magic (line, cell or both) from a
373 standalone function. The functions should have the following
383 standalone function. The functions should have the following
374 signatures:
384 signatures:
375
385
376 * For line magics: `def f(line)`
386 * For line magics: `def f(line)`
377 * For cell magics: `def f(line, cell)`
387 * For cell magics: `def f(line, cell)`
378 * For a function that does both: `def f(line, cell=None)`
388 * For a function that does both: `def f(line, cell=None)`
379
389
380 In the latter case, the function will be called with `cell==None` when
390 In the latter case, the function will be called with `cell==None` when
381 invoked as `%f`, and with cell as a string when invoked as `%%f`.
391 invoked as `%f`, and with cell as a string when invoked as `%%f`.
382
392
383 Parameters
393 Parameters
384 ----------
394 ----------
385 func : callable
395 func : callable
386 Function to be registered as a magic.
396 Function to be registered as a magic.
387
397
388 magic_kind : str
398 magic_kind : str
389 Kind of magic, one of 'line', 'cell' or 'line_cell'
399 Kind of magic, one of 'line', 'cell' or 'line_cell'
390
400
391 magic_name : optional str
401 magic_name : optional str
392 If given, the name the magic will have in the IPython namespace. By
402 If given, the name the magic will have in the IPython namespace. By
393 default, the name of the function itself is used.
403 default, the name of the function itself is used.
394 """
404 """
395
405
396 # Create the new method in the user_magics and register it in the
406 # Create the new method in the user_magics and register it in the
397 # global table
407 # global table
398 validate_type(magic_kind)
408 validate_type(magic_kind)
399 magic_name = func.func_name if magic_name is None else magic_name
409 magic_name = func.func_name if magic_name is None else magic_name
400 setattr(self.user_magics, magic_name, func)
410 setattr(self.user_magics, magic_name, func)
401 record_magic(self.magics, magic_kind, magic_name, func)
411 record_magic(self.magics, magic_kind, magic_name, func)
402
412
403 def define_magic(self, name, func):
413 def define_magic(self, name, func):
404 """[Deprecated] Expose own function as magic function for IPython.
414 """[Deprecated] Expose own function as magic function for IPython.
405
415
406 Example::
416 Example::
407
417
408 def foo_impl(self, parameter_s=''):
418 def foo_impl(self, parameter_s=''):
409 'My very own magic!. (Use docstrings, IPython reads them).'
419 'My very own magic!. (Use docstrings, IPython reads them).'
410 print 'Magic function. Passed parameter is between < >:'
420 print 'Magic function. Passed parameter is between < >:'
411 print '<%s>' % parameter_s
421 print '<%s>' % parameter_s
412 print 'The self object is:', self
422 print 'The self object is:', self
413
423
414 ip.define_magic('foo',foo_impl)
424 ip.define_magic('foo',foo_impl)
415 """
425 """
416 meth = types.MethodType(func, self.user_magics)
426 meth = types.MethodType(func, self.user_magics)
417 setattr(self.user_magics, name, meth)
427 setattr(self.user_magics, name, meth)
418 record_magic(self.magics, 'line', name, meth)
428 record_magic(self.magics, 'line', name, meth)
419
429
420 # Key base class that provides the central functionality for magics.
430 # Key base class that provides the central functionality for magics.
421
431
422 class Magics(object):
432 class Magics(object):
423 """Base class for implementing magic functions.
433 """Base class for implementing magic functions.
424
434
425 Shell functions which can be reached as %function_name. All magic
435 Shell functions which can be reached as %function_name. All magic
426 functions should accept a string, which they can parse for their own
436 functions should accept a string, which they can parse for their own
427 needs. This can make some functions easier to type, eg `%cd ../`
437 needs. This can make some functions easier to type, eg `%cd ../`
428 vs. `%cd("../")`
438 vs. `%cd("../")`
429
439
430 Classes providing magic functions need to subclass this class, and they
440 Classes providing magic functions need to subclass this class, and they
431 MUST:
441 MUST:
432
442
433 - Use the method decorators `@line_magic` and `@cell_magic` to decorate
443 - Use the method decorators `@line_magic` and `@cell_magic` to decorate
434 individual methods as magic functions, AND
444 individual methods as magic functions, AND
435
445
436 - Use the class decorator `@magics_class` to ensure that the magic
446 - Use the class decorator `@magics_class` to ensure that the magic
437 methods are properly registered at the instance level upon instance
447 methods are properly registered at the instance level upon instance
438 initialization.
448 initialization.
439
449
440 See :mod:`magic_functions` for examples of actual implementation classes.
450 See :mod:`magic_functions` for examples of actual implementation classes.
441 """
451 """
442 # Dict holding all command-line options for each magic.
452 # Dict holding all command-line options for each magic.
443 options_table = None
453 options_table = None
444 # Dict for the mapping of magic names to methods, set by class decorator
454 # Dict for the mapping of magic names to methods, set by class decorator
445 magics = None
455 magics = None
446 # Flag to check that the class decorator was properly applied
456 # Flag to check that the class decorator was properly applied
447 registered = False
457 registered = False
448 # Instance of IPython shell
458 # Instance of IPython shell
449 shell = None
459 shell = None
450
460
451 def __init__(self, shell):
461 def __init__(self, shell):
452 if not(self.__class__.registered):
462 if not(self.__class__.registered):
453 raise ValueError('Magics subclass without registration - '
463 raise ValueError('Magics subclass without registration - '
454 'did you forget to apply @magics_class?')
464 'did you forget to apply @magics_class?')
455 self.shell = shell
465 self.shell = shell
456 self.options_table = {}
466 self.options_table = {}
457 # The method decorators are run when the instance doesn't exist yet, so
467 # The method decorators are run when the instance doesn't exist yet, so
458 # they can only record the names of the methods they are supposed to
468 # they can only record the names of the methods they are supposed to
459 # grab. Only now, that the instance exists, can we create the proper
469 # grab. Only now, that the instance exists, can we create the proper
460 # mapping to bound methods. So we read the info off the original names
470 # mapping to bound methods. So we read the info off the original names
461 # table and replace each method name by the actual bound method.
471 # table and replace each method name by the actual bound method.
462 for mtype in magic_kinds:
472 for mtype in magic_kinds:
463 tab = self.magics[mtype]
473 tab = self.magics[mtype]
464 # must explicitly use keys, as we're mutating this puppy
474 # must explicitly use keys, as we're mutating this puppy
465 for magic_name in tab.keys():
475 for magic_name in tab.keys():
466 meth_name = tab[magic_name]
476 meth_name = tab[magic_name]
467 if isinstance(meth_name, basestring):
477 if isinstance(meth_name, basestring):
468 tab[magic_name] = getattr(self, meth_name)
478 tab[magic_name] = getattr(self, meth_name)
469
479
470 def arg_err(self,func):
480 def arg_err(self,func):
471 """Print docstring if incorrect arguments were passed"""
481 """Print docstring if incorrect arguments were passed"""
472 print 'Error in arguments:'
482 print 'Error in arguments:'
473 print oinspect.getdoc(func)
483 print oinspect.getdoc(func)
474
484
475 def format_latex(self, strng):
485 def format_latex(self, strng):
476 """Format a string for latex inclusion."""
486 """Format a string for latex inclusion."""
477
487
478 # Characters that need to be escaped for latex:
488 # Characters that need to be escaped for latex:
479 escape_re = re.compile(r'(%|_|\$|#|&)',re.MULTILINE)
489 escape_re = re.compile(r'(%|_|\$|#|&)',re.MULTILINE)
480 # Magic command names as headers:
490 # Magic command names as headers:
481 cmd_name_re = re.compile(r'^(%s.*?):' % ESC_MAGIC,
491 cmd_name_re = re.compile(r'^(%s.*?):' % ESC_MAGIC,
482 re.MULTILINE)
492 re.MULTILINE)
483 # Magic commands
493 # Magic commands
484 cmd_re = re.compile(r'(?P<cmd>%s.+?\b)(?!\}\}:)' % ESC_MAGIC,
494 cmd_re = re.compile(r'(?P<cmd>%s.+?\b)(?!\}\}:)' % ESC_MAGIC,
485 re.MULTILINE)
495 re.MULTILINE)
486 # Paragraph continue
496 # Paragraph continue
487 par_re = re.compile(r'\\$',re.MULTILINE)
497 par_re = re.compile(r'\\$',re.MULTILINE)
488
498
489 # The "\n" symbol
499 # The "\n" symbol
490 newline_re = re.compile(r'\\n')
500 newline_re = re.compile(r'\\n')
491
501
492 # Now build the string for output:
502 # Now build the string for output:
493 #strng = cmd_name_re.sub(r'\n\\texttt{\\textsl{\\large \1}}:',strng)
503 #strng = cmd_name_re.sub(r'\n\\texttt{\\textsl{\\large \1}}:',strng)
494 strng = cmd_name_re.sub(r'\n\\bigskip\n\\texttt{\\textbf{ \1}}:',
504 strng = cmd_name_re.sub(r'\n\\bigskip\n\\texttt{\\textbf{ \1}}:',
495 strng)
505 strng)
496 strng = cmd_re.sub(r'\\texttt{\g<cmd>}',strng)
506 strng = cmd_re.sub(r'\\texttt{\g<cmd>}',strng)
497 strng = par_re.sub(r'\\\\',strng)
507 strng = par_re.sub(r'\\\\',strng)
498 strng = escape_re.sub(r'\\\1',strng)
508 strng = escape_re.sub(r'\\\1',strng)
499 strng = newline_re.sub(r'\\textbackslash{}n',strng)
509 strng = newline_re.sub(r'\\textbackslash{}n',strng)
500 return strng
510 return strng
501
511
502 def parse_options(self, arg_str, opt_str, *long_opts, **kw):
512 def parse_options(self, arg_str, opt_str, *long_opts, **kw):
503 """Parse options passed to an argument string.
513 """Parse options passed to an argument string.
504
514
505 The interface is similar to that of getopt(), but it returns back a
515 The interface is similar to that of getopt(), but it returns back a
506 Struct with the options as keys and the stripped argument string still
516 Struct with the options as keys and the stripped argument string still
507 as a string.
517 as a string.
508
518
509 arg_str is quoted as a true sys.argv vector by using shlex.split.
519 arg_str is quoted as a true sys.argv vector by using shlex.split.
510 This allows us to easily expand variables, glob files, quote
520 This allows us to easily expand variables, glob files, quote
511 arguments, etc.
521 arguments, etc.
512
522
513 Options:
523 Options:
514 -mode: default 'string'. If given as 'list', the argument string is
524 -mode: default 'string'. If given as 'list', the argument string is
515 returned as a list (split on whitespace) instead of a string.
525 returned as a list (split on whitespace) instead of a string.
516
526
517 -list_all: put all option values in lists. Normally only options
527 -list_all: put all option values in lists. Normally only options
518 appearing more than once are put in a list.
528 appearing more than once are put in a list.
519
529
520 -posix (True): whether to split the input line in POSIX mode or not,
530 -posix (True): whether to split the input line in POSIX mode or not,
521 as per the conventions outlined in the shlex module from the
531 as per the conventions outlined in the shlex module from the
522 standard library."""
532 standard library."""
523
533
524 # inject default options at the beginning of the input line
534 # inject default options at the beginning of the input line
525 caller = sys._getframe(1).f_code.co_name
535 caller = sys._getframe(1).f_code.co_name
526 arg_str = '%s %s' % (self.options_table.get(caller,''),arg_str)
536 arg_str = '%s %s' % (self.options_table.get(caller,''),arg_str)
527
537
528 mode = kw.get('mode','string')
538 mode = kw.get('mode','string')
529 if mode not in ['string','list']:
539 if mode not in ['string','list']:
530 raise ValueError,'incorrect mode given: %s' % mode
540 raise ValueError,'incorrect mode given: %s' % mode
531 # Get options
541 # Get options
532 list_all = kw.get('list_all',0)
542 list_all = kw.get('list_all',0)
533 posix = kw.get('posix', os.name == 'posix')
543 posix = kw.get('posix', os.name == 'posix')
534 strict = kw.get('strict', True)
544 strict = kw.get('strict', True)
535
545
536 # Check if we have more than one argument to warrant extra processing:
546 # Check if we have more than one argument to warrant extra processing:
537 odict = {} # Dictionary with options
547 odict = {} # Dictionary with options
538 args = arg_str.split()
548 args = arg_str.split()
539 if len(args) >= 1:
549 if len(args) >= 1:
540 # If the list of inputs only has 0 or 1 thing in it, there's no
550 # If the list of inputs only has 0 or 1 thing in it, there's no
541 # need to look for options
551 # need to look for options
542 argv = arg_split(arg_str, posix, strict)
552 argv = arg_split(arg_str, posix, strict)
543 # Do regular option processing
553 # Do regular option processing
544 try:
554 try:
545 opts,args = getopt(argv, opt_str, long_opts)
555 opts,args = getopt(argv, opt_str, long_opts)
546 except GetoptError,e:
556 except GetoptError,e:
547 raise UsageError('%s ( allowed: "%s" %s)' % (e.msg,opt_str,
557 raise UsageError('%s ( allowed: "%s" %s)' % (e.msg,opt_str,
548 " ".join(long_opts)))
558 " ".join(long_opts)))
549 for o,a in opts:
559 for o,a in opts:
550 if o.startswith('--'):
560 if o.startswith('--'):
551 o = o[2:]
561 o = o[2:]
552 else:
562 else:
553 o = o[1:]
563 o = o[1:]
554 try:
564 try:
555 odict[o].append(a)
565 odict[o].append(a)
556 except AttributeError:
566 except AttributeError:
557 odict[o] = [odict[o],a]
567 odict[o] = [odict[o],a]
558 except KeyError:
568 except KeyError:
559 if list_all:
569 if list_all:
560 odict[o] = [a]
570 odict[o] = [a]
561 else:
571 else:
562 odict[o] = a
572 odict[o] = a
563
573
564 # Prepare opts,args for return
574 # Prepare opts,args for return
565 opts = Struct(odict)
575 opts = Struct(odict)
566 if mode == 'string':
576 if mode == 'string':
567 args = ' '.join(args)
577 args = ' '.join(args)
568
578
569 return opts,args
579 return opts,args
570
580
571 def default_option(self, fn, optstr):
581 def default_option(self, fn, optstr):
572 """Make an entry in the options_table for fn, with value optstr"""
582 """Make an entry in the options_table for fn, with value optstr"""
573
583
574 if fn not in self.lsmagic():
584 if fn not in self.lsmagic():
575 error("%s is not a magic function" % fn)
585 error("%s is not a magic function" % fn)
576 self.options_table[fn] = optstr
586 self.options_table[fn] = optstr
@@ -1,910 +1,950 b''
1 """The Qt MainWindow for the QtConsole
1 """The Qt MainWindow for the QtConsole
2
2
3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
4 common actions.
4 common actions.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 # stdlib imports
21 # stdlib imports
22 import sys
22 import sys
23 import re
23 import re
24 import webbrowser
24 import webbrowser
25 import ast
25 from threading import Thread
26 from threading import Thread
26
27
27 # System library imports
28 # System library imports
28 from IPython.external.qt import QtGui,QtCore
29 from IPython.external.qt import QtGui,QtCore
29
30
30 def background(f):
31 def background(f):
31 """call a function in a simple thread, to prevent blocking"""
32 """call a function in a simple thread, to prevent blocking"""
32 t = Thread(target=f)
33 t = Thread(target=f)
33 t.start()
34 t.start()
34 return t
35 return t
35
36
36 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
37 # Classes
38 # Classes
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39
40
40 class MainWindow(QtGui.QMainWindow):
41 class MainWindow(QtGui.QMainWindow):
41
42
42 #---------------------------------------------------------------------------
43 #---------------------------------------------------------------------------
43 # 'object' interface
44 # 'object' interface
44 #---------------------------------------------------------------------------
45 #---------------------------------------------------------------------------
45
46
47 _magic_menu_dict = {}
48
46 def __init__(self, app,
49 def __init__(self, app,
47 confirm_exit=True,
50 confirm_exit=True,
48 new_frontend_factory=None, slave_frontend_factory=None,
51 new_frontend_factory=None, slave_frontend_factory=None,
49 ):
52 ):
50 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
53 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
51
54
52 Parameters
55 Parameters
53 ----------
56 ----------
54
57
55 app : reference to QApplication parent
58 app : reference to QApplication parent
56 confirm_exit : bool, optional
59 confirm_exit : bool, optional
57 Whether we should prompt on close of tabs
60 Whether we should prompt on close of tabs
58 new_frontend_factory : callable
61 new_frontend_factory : callable
59 A callable that returns a new IPythonWidget instance, attached to
62 A callable that returns a new IPythonWidget instance, attached to
60 its own running kernel.
63 its own running kernel.
61 slave_frontend_factory : callable
64 slave_frontend_factory : callable
62 A callable that takes an existing IPythonWidget, and returns a new
65 A callable that takes an existing IPythonWidget, and returns a new
63 IPythonWidget instance, attached to the same kernel.
66 IPythonWidget instance, attached to the same kernel.
64 """
67 """
65
68
66 super(MainWindow, self).__init__()
69 super(MainWindow, self).__init__()
67 self._kernel_counter = 0
70 self._kernel_counter = 0
68 self._app = app
71 self._app = app
69 self.confirm_exit = confirm_exit
72 self.confirm_exit = confirm_exit
70 self.new_frontend_factory = new_frontend_factory
73 self.new_frontend_factory = new_frontend_factory
71 self.slave_frontend_factory = slave_frontend_factory
74 self.slave_frontend_factory = slave_frontend_factory
72
75
73 self.tab_widget = QtGui.QTabWidget(self)
76 self.tab_widget = QtGui.QTabWidget(self)
74 self.tab_widget.setDocumentMode(True)
77 self.tab_widget.setDocumentMode(True)
75 self.tab_widget.setTabsClosable(True)
78 self.tab_widget.setTabsClosable(True)
76 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
79 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
77
80
78 self.setCentralWidget(self.tab_widget)
81 self.setCentralWidget(self.tab_widget)
79 # hide tab bar at first, since we have no tabs:
82 # hide tab bar at first, since we have no tabs:
80 self.tab_widget.tabBar().setVisible(False)
83 self.tab_widget.tabBar().setVisible(False)
81 # prevent focus in tab bar
84 # prevent focus in tab bar
82 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
85 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
83
86
84 def update_tab_bar_visibility(self):
87 def update_tab_bar_visibility(self):
85 """ update visibility of the tabBar depending of the number of tab
88 """ update visibility of the tabBar depending of the number of tab
86
89
87 0 or 1 tab, tabBar hidden
90 0 or 1 tab, tabBar hidden
88 2+ tabs, tabBar visible
91 2+ tabs, tabBar visible
89
92
90 send a self.close if number of tab ==0
93 send a self.close if number of tab ==0
91
94
92 need to be called explicitely, or be connected to tabInserted/tabRemoved
95 need to be called explicitely, or be connected to tabInserted/tabRemoved
93 """
96 """
94 if self.tab_widget.count() <= 1:
97 if self.tab_widget.count() <= 1:
95 self.tab_widget.tabBar().setVisible(False)
98 self.tab_widget.tabBar().setVisible(False)
96 else:
99 else:
97 self.tab_widget.tabBar().setVisible(True)
100 self.tab_widget.tabBar().setVisible(True)
98 if self.tab_widget.count()==0 :
101 if self.tab_widget.count()==0 :
99 self.close()
102 self.close()
100
103
101 @property
104 @property
102 def next_kernel_id(self):
105 def next_kernel_id(self):
103 """constantly increasing counter for kernel IDs"""
106 """constantly increasing counter for kernel IDs"""
104 c = self._kernel_counter
107 c = self._kernel_counter
105 self._kernel_counter += 1
108 self._kernel_counter += 1
106 return c
109 return c
107
110
108 @property
111 @property
109 def active_frontend(self):
112 def active_frontend(self):
110 return self.tab_widget.currentWidget()
113 return self.tab_widget.currentWidget()
111
114
112 def create_tab_with_new_frontend(self):
115 def create_tab_with_new_frontend(self):
113 """create a new frontend and attach it to a new tab"""
116 """create a new frontend and attach it to a new tab"""
114 widget = self.new_frontend_factory()
117 widget = self.new_frontend_factory()
115 self.add_tab_with_frontend(widget)
118 self.add_tab_with_frontend(widget)
116
119
117 def create_tab_with_current_kernel(self):
120 def create_tab_with_current_kernel(self):
118 """create a new frontend attached to the same kernel as the current tab"""
121 """create a new frontend attached to the same kernel as the current tab"""
119 current_widget = self.tab_widget.currentWidget()
122 current_widget = self.tab_widget.currentWidget()
120 current_widget_index = self.tab_widget.indexOf(current_widget)
123 current_widget_index = self.tab_widget.indexOf(current_widget)
121 current_widget_name = self.tab_widget.tabText(current_widget_index)
124 current_widget_name = self.tab_widget.tabText(current_widget_index)
122 widget = self.slave_frontend_factory(current_widget)
125 widget = self.slave_frontend_factory(current_widget)
123 if 'slave' in current_widget_name:
126 if 'slave' in current_widget_name:
124 # don't keep stacking slaves
127 # don't keep stacking slaves
125 name = current_widget_name
128 name = current_widget_name
126 else:
129 else:
127 name = '(%s) slave' % current_widget_name
130 name = '(%s) slave' % current_widget_name
128 self.add_tab_with_frontend(widget,name=name)
131 self.add_tab_with_frontend(widget,name=name)
129
132
130 def close_tab(self,current_tab):
133 def close_tab(self,current_tab):
131 """ Called when you need to try to close a tab.
134 """ Called when you need to try to close a tab.
132
135
133 It takes the number of the tab to be closed as argument, or a referece
136 It takes the number of the tab to be closed as argument, or a referece
134 to the wiget insite this tab
137 to the wiget insite this tab
135 """
138 """
136
139
137 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
140 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
138 # and a reference to the trontend to close
141 # and a reference to the trontend to close
139 if type(current_tab) is not int :
142 if type(current_tab) is not int :
140 current_tab = self.tab_widget.indexOf(current_tab)
143 current_tab = self.tab_widget.indexOf(current_tab)
141 closing_widget=self.tab_widget.widget(current_tab)
144 closing_widget=self.tab_widget.widget(current_tab)
142
145
143
146
144 # when trying to be closed, widget might re-send a request to be closed again, but will
147 # when trying to be closed, widget might re-send a request to be closed again, but will
145 # be deleted when event will be processed. So need to check that widget still exist and
148 # be deleted when event will be processed. So need to check that widget still exist and
146 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
149 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
147 # re-send by this fonction on the master widget, which ask all slaves widget to exit
150 # re-send by this fonction on the master widget, which ask all slaves widget to exit
148 if closing_widget==None:
151 if closing_widget==None:
149 return
152 return
150
153
151 #get a list of all slave widgets on the same kernel.
154 #get a list of all slave widgets on the same kernel.
152 slave_tabs = self.find_slave_widgets(closing_widget)
155 slave_tabs = self.find_slave_widgets(closing_widget)
153
156
154 keepkernel = None #Use the prompt by default
157 keepkernel = None #Use the prompt by default
155 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
158 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
156 keepkernel = closing_widget._keep_kernel_on_exit
159 keepkernel = closing_widget._keep_kernel_on_exit
157 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
160 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
158 # we set local slave tabs._hidden to True to avoid prompting for kernel
161 # we set local slave tabs._hidden to True to avoid prompting for kernel
159 # restart when they get the signal. and then "forward" the 'exit'
162 # restart when they get the signal. and then "forward" the 'exit'
160 # to the main window
163 # to the main window
161 if keepkernel is not None:
164 if keepkernel is not None:
162 for tab in slave_tabs:
165 for tab in slave_tabs:
163 tab._hidden = True
166 tab._hidden = True
164 if closing_widget in slave_tabs:
167 if closing_widget in slave_tabs:
165 try :
168 try :
166 self.find_master_tab(closing_widget).execute('exit')
169 self.find_master_tab(closing_widget).execute('exit')
167 except AttributeError:
170 except AttributeError:
168 self.log.info("Master already closed or not local, closing only current tab")
171 self.log.info("Master already closed or not local, closing only current tab")
169 self.tab_widget.removeTab(current_tab)
172 self.tab_widget.removeTab(current_tab)
170 self.update_tab_bar_visibility()
173 self.update_tab_bar_visibility()
171 return
174 return
172
175
173 kernel_manager = closing_widget.kernel_manager
176 kernel_manager = closing_widget.kernel_manager
174
177
175 if keepkernel is None and not closing_widget._confirm_exit:
178 if keepkernel is None and not closing_widget._confirm_exit:
176 # don't prompt, just terminate the kernel if we own it
179 # don't prompt, just terminate the kernel if we own it
177 # or leave it alone if we don't
180 # or leave it alone if we don't
178 keepkernel = closing_widget._existing
181 keepkernel = closing_widget._existing
179 if keepkernel is None: #show prompt
182 if keepkernel is None: #show prompt
180 if kernel_manager and kernel_manager.channels_running:
183 if kernel_manager and kernel_manager.channels_running:
181 title = self.window().windowTitle()
184 title = self.window().windowTitle()
182 cancel = QtGui.QMessageBox.Cancel
185 cancel = QtGui.QMessageBox.Cancel
183 okay = QtGui.QMessageBox.Ok
186 okay = QtGui.QMessageBox.Ok
184 if closing_widget._may_close:
187 if closing_widget._may_close:
185 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
188 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
186 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
189 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
187 justthis = QtGui.QPushButton("&No, just this Tab", self)
190 justthis = QtGui.QPushButton("&No, just this Tab", self)
188 justthis.setShortcut('N')
191 justthis.setShortcut('N')
189 closeall = QtGui.QPushButton("&Yes, close all", self)
192 closeall = QtGui.QPushButton("&Yes, close all", self)
190 closeall.setShortcut('Y')
193 closeall.setShortcut('Y')
191 # allow ctrl-d ctrl-d exit, like in terminal
194 # allow ctrl-d ctrl-d exit, like in terminal
192 closeall.setShortcut('Ctrl+D')
195 closeall.setShortcut('Ctrl+D')
193 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
196 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
194 title, msg)
197 title, msg)
195 box.setInformativeText(info)
198 box.setInformativeText(info)
196 box.addButton(cancel)
199 box.addButton(cancel)
197 box.addButton(justthis, QtGui.QMessageBox.NoRole)
200 box.addButton(justthis, QtGui.QMessageBox.NoRole)
198 box.addButton(closeall, QtGui.QMessageBox.YesRole)
201 box.addButton(closeall, QtGui.QMessageBox.YesRole)
199 box.setDefaultButton(closeall)
202 box.setDefaultButton(closeall)
200 box.setEscapeButton(cancel)
203 box.setEscapeButton(cancel)
201 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
204 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
202 box.setIconPixmap(pixmap)
205 box.setIconPixmap(pixmap)
203 reply = box.exec_()
206 reply = box.exec_()
204 if reply == 1: # close All
207 if reply == 1: # close All
205 for slave in slave_tabs:
208 for slave in slave_tabs:
206 background(slave.kernel_manager.stop_channels)
209 background(slave.kernel_manager.stop_channels)
207 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
210 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
208 closing_widget.execute("exit")
211 closing_widget.execute("exit")
209 self.tab_widget.removeTab(current_tab)
212 self.tab_widget.removeTab(current_tab)
210 background(kernel_manager.stop_channels)
213 background(kernel_manager.stop_channels)
211 elif reply == 0: # close Console
214 elif reply == 0: # close Console
212 if not closing_widget._existing:
215 if not closing_widget._existing:
213 # Have kernel: don't quit, just close the tab
216 # Have kernel: don't quit, just close the tab
214 closing_widget.execute("exit True")
217 closing_widget.execute("exit True")
215 self.tab_widget.removeTab(current_tab)
218 self.tab_widget.removeTab(current_tab)
216 background(kernel_manager.stop_channels)
219 background(kernel_manager.stop_channels)
217 else:
220 else:
218 reply = QtGui.QMessageBox.question(self, title,
221 reply = QtGui.QMessageBox.question(self, title,
219 "Are you sure you want to close this Console?"+
222 "Are you sure you want to close this Console?"+
220 "\nThe Kernel and other Consoles will remain active.",
223 "\nThe Kernel and other Consoles will remain active.",
221 okay|cancel,
224 okay|cancel,
222 defaultButton=okay
225 defaultButton=okay
223 )
226 )
224 if reply == okay:
227 if reply == okay:
225 self.tab_widget.removeTab(current_tab)
228 self.tab_widget.removeTab(current_tab)
226 elif keepkernel: #close console but leave kernel running (no prompt)
229 elif keepkernel: #close console but leave kernel running (no prompt)
227 self.tab_widget.removeTab(current_tab)
230 self.tab_widget.removeTab(current_tab)
228 background(kernel_manager.stop_channels)
231 background(kernel_manager.stop_channels)
229 else: #close console and kernel (no prompt)
232 else: #close console and kernel (no prompt)
230 self.tab_widget.removeTab(current_tab)
233 self.tab_widget.removeTab(current_tab)
231 if kernel_manager and kernel_manager.channels_running:
234 if kernel_manager and kernel_manager.channels_running:
232 for slave in slave_tabs:
235 for slave in slave_tabs:
233 background(slave.kernel_manager.stop_channels)
236 background(slave.kernel_manager.stop_channels)
234 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
237 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
235 kernel_manager.shutdown_kernel()
238 kernel_manager.shutdown_kernel()
236 background(kernel_manager.stop_channels)
239 background(kernel_manager.stop_channels)
237
240
238 self.update_tab_bar_visibility()
241 self.update_tab_bar_visibility()
239
242
240 def add_tab_with_frontend(self,frontend,name=None):
243 def add_tab_with_frontend(self,frontend,name=None):
241 """ insert a tab with a given frontend in the tab bar, and give it a name
244 """ insert a tab with a given frontend in the tab bar, and give it a name
242
245
243 """
246 """
244 if not name:
247 if not name:
245 name = 'kernel %i' % self.next_kernel_id
248 name = 'kernel %i' % self.next_kernel_id
246 self.tab_widget.addTab(frontend,name)
249 self.tab_widget.addTab(frontend,name)
247 self.update_tab_bar_visibility()
250 self.update_tab_bar_visibility()
248 self.make_frontend_visible(frontend)
251 self.make_frontend_visible(frontend)
249 frontend.exit_requested.connect(self.close_tab)
252 frontend.exit_requested.connect(self.close_tab)
250
253
251 def next_tab(self):
254 def next_tab(self):
252 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
255 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
253
256
254 def prev_tab(self):
257 def prev_tab(self):
255 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
258 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
256
259
257 def make_frontend_visible(self,frontend):
260 def make_frontend_visible(self,frontend):
258 widget_index=self.tab_widget.indexOf(frontend)
261 widget_index=self.tab_widget.indexOf(frontend)
259 if widget_index > 0 :
262 if widget_index > 0 :
260 self.tab_widget.setCurrentIndex(widget_index)
263 self.tab_widget.setCurrentIndex(widget_index)
261
264
262 def find_master_tab(self,tab,as_list=False):
265 def find_master_tab(self,tab,as_list=False):
263 """
266 """
264 Try to return the frontend that own the kernel attached to the given widget/tab.
267 Try to return the frontend that own the kernel attached to the given widget/tab.
265
268
266 Only find frontend owed by the current application. Selection
269 Only find frontend owed by the current application. Selection
267 based on port of the kernel, might be inacurate if several kernel
270 based on port of the kernel, might be inacurate if several kernel
268 on different ip use same port number.
271 on different ip use same port number.
269
272
270 This fonction does the conversion tabNumber/widget if needed.
273 This fonction does the conversion tabNumber/widget if needed.
271 Might return None if no master widget (non local kernel)
274 Might return None if no master widget (non local kernel)
272 Will crash IPython if more than 1 masterWidget
275 Will crash IPython if more than 1 masterWidget
273
276
274 When asList set to True, always return a list of widget(s) owning
277 When asList set to True, always return a list of widget(s) owning
275 the kernel. The list might be empty or containing several Widget.
278 the kernel. The list might be empty or containing several Widget.
276 """
279 """
277
280
278 #convert from/to int/richIpythonWidget if needed
281 #convert from/to int/richIpythonWidget if needed
279 if isinstance(tab, int):
282 if isinstance(tab, int):
280 tab = self.tab_widget.widget(tab)
283 tab = self.tab_widget.widget(tab)
281 km=tab.kernel_manager
284 km=tab.kernel_manager
282
285
283 #build list of all widgets
286 #build list of all widgets
284 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
287 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
285
288
286 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
289 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
287 # And should have a _may_close attribute
290 # And should have a _may_close attribute
288 filtered_widget_list = [ widget for widget in widget_list if
291 filtered_widget_list = [ widget for widget in widget_list if
289 widget.kernel_manager.connection_file == km.connection_file and
292 widget.kernel_manager.connection_file == km.connection_file and
290 hasattr(widget,'_may_close') ]
293 hasattr(widget,'_may_close') ]
291 # the master widget is the one that may close the kernel
294 # the master widget is the one that may close the kernel
292 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
295 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
293 if as_list:
296 if as_list:
294 return master_widget
297 return master_widget
295 assert(len(master_widget)<=1 )
298 assert(len(master_widget)<=1 )
296 if len(master_widget)==0:
299 if len(master_widget)==0:
297 return None
300 return None
298
301
299 return master_widget[0]
302 return master_widget[0]
300
303
301 def find_slave_widgets(self,tab):
304 def find_slave_widgets(self,tab):
302 """return all the frontends that do not own the kernel attached to the given widget/tab.
305 """return all the frontends that do not own the kernel attached to the given widget/tab.
303
306
304 Only find frontends owned by the current application. Selection
307 Only find frontends owned by the current application. Selection
305 based on connection file of the kernel.
308 based on connection file of the kernel.
306
309
307 This function does the conversion tabNumber/widget if needed.
310 This function does the conversion tabNumber/widget if needed.
308 """
311 """
309 #convert from/to int/richIpythonWidget if needed
312 #convert from/to int/richIpythonWidget if needed
310 if isinstance(tab, int):
313 if isinstance(tab, int):
311 tab = self.tab_widget.widget(tab)
314 tab = self.tab_widget.widget(tab)
312 km=tab.kernel_manager
315 km=tab.kernel_manager
313
316
314 #build list of all widgets
317 #build list of all widgets
315 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
318 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
316
319
317 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
320 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
318 filtered_widget_list = ( widget for widget in widget_list if
321 filtered_widget_list = ( widget for widget in widget_list if
319 widget.kernel_manager.connection_file == km.connection_file)
322 widget.kernel_manager.connection_file == km.connection_file)
320 # Get a list of all widget owning the same kernel and removed it from
323 # Get a list of all widget owning the same kernel and removed it from
321 # the previous cadidate. (better using sets ?)
324 # the previous cadidate. (better using sets ?)
322 master_widget_list = self.find_master_tab(tab, as_list=True)
325 master_widget_list = self.find_master_tab(tab, as_list=True)
323 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
326 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
324
327
325 return slave_list
328 return slave_list
326
329
327 # Populate the menu bar with common actions and shortcuts
330 # Populate the menu bar with common actions and shortcuts
328 def add_menu_action(self, menu, action, defer_shortcut=False):
331 def add_menu_action(self, menu, action, defer_shortcut=False):
329 """Add action to menu as well as self
332 """Add action to menu as well as self
330
333
331 So that when the menu bar is invisible, its actions are still available.
334 So that when the menu bar is invisible, its actions are still available.
332
335
333 If defer_shortcut is True, set the shortcut context to widget-only,
336 If defer_shortcut is True, set the shortcut context to widget-only,
334 where it will avoid conflict with shortcuts already bound to the
337 where it will avoid conflict with shortcuts already bound to the
335 widgets themselves.
338 widgets themselves.
336 """
339 """
337 menu.addAction(action)
340 menu.addAction(action)
338 self.addAction(action)
341 self.addAction(action)
339
342
340 if defer_shortcut:
343 if defer_shortcut:
341 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
344 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
342
345
343 def init_menu_bar(self):
346 def init_menu_bar(self):
344 #create menu in the order they should appear in the menu bar
347 #create menu in the order they should appear in the menu bar
345 self.init_file_menu()
348 self.init_file_menu()
346 self.init_edit_menu()
349 self.init_edit_menu()
347 self.init_view_menu()
350 self.init_view_menu()
348 self.init_kernel_menu()
351 self.init_kernel_menu()
349 self.init_magic_menu()
352 self.init_magic_menu()
350 self.init_window_menu()
353 self.init_window_menu()
351 self.init_help_menu()
354 self.init_help_menu()
352
355
353 def init_file_menu(self):
356 def init_file_menu(self):
354 self.file_menu = self.menuBar().addMenu("&File")
357 self.file_menu = self.menuBar().addMenu("&File")
355
358
356 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
359 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
357 self,
360 self,
358 shortcut="Ctrl+T",
361 shortcut="Ctrl+T",
359 triggered=self.create_tab_with_new_frontend)
362 triggered=self.create_tab_with_new_frontend)
360 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
363 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
361
364
362 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
365 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
363 self,
366 self,
364 shortcut="Ctrl+Shift+T",
367 shortcut="Ctrl+Shift+T",
365 triggered=self.create_tab_with_current_kernel)
368 triggered=self.create_tab_with_current_kernel)
366 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
369 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
367
370
368 self.file_menu.addSeparator()
371 self.file_menu.addSeparator()
369
372
370 self.close_action=QtGui.QAction("&Close Tab",
373 self.close_action=QtGui.QAction("&Close Tab",
371 self,
374 self,
372 shortcut=QtGui.QKeySequence.Close,
375 shortcut=QtGui.QKeySequence.Close,
373 triggered=self.close_active_frontend
376 triggered=self.close_active_frontend
374 )
377 )
375 self.add_menu_action(self.file_menu, self.close_action)
378 self.add_menu_action(self.file_menu, self.close_action)
376
379
377 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
380 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
378 self,
381 self,
379 shortcut=QtGui.QKeySequence.Save,
382 shortcut=QtGui.QKeySequence.Save,
380 triggered=self.export_action_active_frontend
383 triggered=self.export_action_active_frontend
381 )
384 )
382 self.add_menu_action(self.file_menu, self.export_action, True)
385 self.add_menu_action(self.file_menu, self.export_action, True)
383
386
384 self.file_menu.addSeparator()
387 self.file_menu.addSeparator()
385
388
386 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
389 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
387 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
390 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
388 # Only override the default if there is a collision.
391 # Only override the default if there is a collision.
389 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
392 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
390 printkey = "Ctrl+Shift+P"
393 printkey = "Ctrl+Shift+P"
391 self.print_action = QtGui.QAction("&Print",
394 self.print_action = QtGui.QAction("&Print",
392 self,
395 self,
393 shortcut=printkey,
396 shortcut=printkey,
394 triggered=self.print_action_active_frontend)
397 triggered=self.print_action_active_frontend)
395 self.add_menu_action(self.file_menu, self.print_action, True)
398 self.add_menu_action(self.file_menu, self.print_action, True)
396
399
397 if sys.platform != 'darwin':
400 if sys.platform != 'darwin':
398 # OSX always has Quit in the Application menu, only add it
401 # OSX always has Quit in the Application menu, only add it
399 # to the File menu elsewhere.
402 # to the File menu elsewhere.
400
403
401 self.file_menu.addSeparator()
404 self.file_menu.addSeparator()
402
405
403 self.quit_action = QtGui.QAction("&Quit",
406 self.quit_action = QtGui.QAction("&Quit",
404 self,
407 self,
405 shortcut=QtGui.QKeySequence.Quit,
408 shortcut=QtGui.QKeySequence.Quit,
406 triggered=self.close,
409 triggered=self.close,
407 )
410 )
408 self.add_menu_action(self.file_menu, self.quit_action)
411 self.add_menu_action(self.file_menu, self.quit_action)
409
412
410
413
411 def init_edit_menu(self):
414 def init_edit_menu(self):
412 self.edit_menu = self.menuBar().addMenu("&Edit")
415 self.edit_menu = self.menuBar().addMenu("&Edit")
413
416
414 self.undo_action = QtGui.QAction("&Undo",
417 self.undo_action = QtGui.QAction("&Undo",
415 self,
418 self,
416 shortcut=QtGui.QKeySequence.Undo,
419 shortcut=QtGui.QKeySequence.Undo,
417 statusTip="Undo last action if possible",
420 statusTip="Undo last action if possible",
418 triggered=self.undo_active_frontend
421 triggered=self.undo_active_frontend
419 )
422 )
420 self.add_menu_action(self.edit_menu, self.undo_action)
423 self.add_menu_action(self.edit_menu, self.undo_action)
421
424
422 self.redo_action = QtGui.QAction("&Redo",
425 self.redo_action = QtGui.QAction("&Redo",
423 self,
426 self,
424 shortcut=QtGui.QKeySequence.Redo,
427 shortcut=QtGui.QKeySequence.Redo,
425 statusTip="Redo last action if possible",
428 statusTip="Redo last action if possible",
426 triggered=self.redo_active_frontend)
429 triggered=self.redo_active_frontend)
427 self.add_menu_action(self.edit_menu, self.redo_action)
430 self.add_menu_action(self.edit_menu, self.redo_action)
428
431
429 self.edit_menu.addSeparator()
432 self.edit_menu.addSeparator()
430
433
431 self.cut_action = QtGui.QAction("&Cut",
434 self.cut_action = QtGui.QAction("&Cut",
432 self,
435 self,
433 shortcut=QtGui.QKeySequence.Cut,
436 shortcut=QtGui.QKeySequence.Cut,
434 triggered=self.cut_active_frontend
437 triggered=self.cut_active_frontend
435 )
438 )
436 self.add_menu_action(self.edit_menu, self.cut_action, True)
439 self.add_menu_action(self.edit_menu, self.cut_action, True)
437
440
438 self.copy_action = QtGui.QAction("&Copy",
441 self.copy_action = QtGui.QAction("&Copy",
439 self,
442 self,
440 shortcut=QtGui.QKeySequence.Copy,
443 shortcut=QtGui.QKeySequence.Copy,
441 triggered=self.copy_active_frontend
444 triggered=self.copy_active_frontend
442 )
445 )
443 self.add_menu_action(self.edit_menu, self.copy_action, True)
446 self.add_menu_action(self.edit_menu, self.copy_action, True)
444
447
445 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
448 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
446 self,
449 self,
447 shortcut="Ctrl+Shift+C",
450 shortcut="Ctrl+Shift+C",
448 triggered=self.copy_raw_active_frontend
451 triggered=self.copy_raw_active_frontend
449 )
452 )
450 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
453 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
451
454
452 self.paste_action = QtGui.QAction("&Paste",
455 self.paste_action = QtGui.QAction("&Paste",
453 self,
456 self,
454 shortcut=QtGui.QKeySequence.Paste,
457 shortcut=QtGui.QKeySequence.Paste,
455 triggered=self.paste_active_frontend
458 triggered=self.paste_active_frontend
456 )
459 )
457 self.add_menu_action(self.edit_menu, self.paste_action, True)
460 self.add_menu_action(self.edit_menu, self.paste_action, True)
458
461
459 self.edit_menu.addSeparator()
462 self.edit_menu.addSeparator()
460
463
461 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
464 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
462 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
465 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
463 # Only override the default if there is a collision.
466 # Only override the default if there is a collision.
464 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
467 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
465 selectall = "Ctrl+Shift+A"
468 selectall = "Ctrl+Shift+A"
466 self.select_all_action = QtGui.QAction("Select &All",
469 self.select_all_action = QtGui.QAction("Select &All",
467 self,
470 self,
468 shortcut=selectall,
471 shortcut=selectall,
469 triggered=self.select_all_active_frontend
472 triggered=self.select_all_active_frontend
470 )
473 )
471 self.add_menu_action(self.edit_menu, self.select_all_action, True)
474 self.add_menu_action(self.edit_menu, self.select_all_action, True)
472
475
473
476
474 def init_view_menu(self):
477 def init_view_menu(self):
475 self.view_menu = self.menuBar().addMenu("&View")
478 self.view_menu = self.menuBar().addMenu("&View")
476
479
477 if sys.platform != 'darwin':
480 if sys.platform != 'darwin':
478 # disable on OSX, where there is always a menu bar
481 # disable on OSX, where there is always a menu bar
479 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
482 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
480 self,
483 self,
481 shortcut="Ctrl+Shift+M",
484 shortcut="Ctrl+Shift+M",
482 statusTip="Toggle visibility of menubar",
485 statusTip="Toggle visibility of menubar",
483 triggered=self.toggle_menu_bar)
486 triggered=self.toggle_menu_bar)
484 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
487 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
485
488
486 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
489 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
487 self.full_screen_act = QtGui.QAction("&Full Screen",
490 self.full_screen_act = QtGui.QAction("&Full Screen",
488 self,
491 self,
489 shortcut=fs_key,
492 shortcut=fs_key,
490 statusTip="Toggle between Fullscreen and Normal Size",
493 statusTip="Toggle between Fullscreen and Normal Size",
491 triggered=self.toggleFullScreen)
494 triggered=self.toggleFullScreen)
492 self.add_menu_action(self.view_menu, self.full_screen_act)
495 self.add_menu_action(self.view_menu, self.full_screen_act)
493
496
494 self.view_menu.addSeparator()
497 self.view_menu.addSeparator()
495
498
496 self.increase_font_size = QtGui.QAction("Zoom &In",
499 self.increase_font_size = QtGui.QAction("Zoom &In",
497 self,
500 self,
498 shortcut=QtGui.QKeySequence.ZoomIn,
501 shortcut=QtGui.QKeySequence.ZoomIn,
499 triggered=self.increase_font_size_active_frontend
502 triggered=self.increase_font_size_active_frontend
500 )
503 )
501 self.add_menu_action(self.view_menu, self.increase_font_size, True)
504 self.add_menu_action(self.view_menu, self.increase_font_size, True)
502
505
503 self.decrease_font_size = QtGui.QAction("Zoom &Out",
506 self.decrease_font_size = QtGui.QAction("Zoom &Out",
504 self,
507 self,
505 shortcut=QtGui.QKeySequence.ZoomOut,
508 shortcut=QtGui.QKeySequence.ZoomOut,
506 triggered=self.decrease_font_size_active_frontend
509 triggered=self.decrease_font_size_active_frontend
507 )
510 )
508 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
511 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
509
512
510 self.reset_font_size = QtGui.QAction("Zoom &Reset",
513 self.reset_font_size = QtGui.QAction("Zoom &Reset",
511 self,
514 self,
512 shortcut="Ctrl+0",
515 shortcut="Ctrl+0",
513 triggered=self.reset_font_size_active_frontend
516 triggered=self.reset_font_size_active_frontend
514 )
517 )
515 self.add_menu_action(self.view_menu, self.reset_font_size, True)
518 self.add_menu_action(self.view_menu, self.reset_font_size, True)
516
519
517 self.view_menu.addSeparator()
520 self.view_menu.addSeparator()
518
521
519 self.clear_action = QtGui.QAction("&Clear Screen",
522 self.clear_action = QtGui.QAction("&Clear Screen",
520 self,
523 self,
521 shortcut='Ctrl+L',
524 shortcut='Ctrl+L',
522 statusTip="Clear the console",
525 statusTip="Clear the console",
523 triggered=self.clear_magic_active_frontend)
526 triggered=self.clear_magic_active_frontend)
524 self.add_menu_action(self.view_menu, self.clear_action)
527 self.add_menu_action(self.view_menu, self.clear_action)
525
528
526 def init_kernel_menu(self):
529 def init_kernel_menu(self):
527 self.kernel_menu = self.menuBar().addMenu("&Kernel")
530 self.kernel_menu = self.menuBar().addMenu("&Kernel")
528 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
531 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
529 # keep the signal shortcuts to ctrl, rather than
532 # keep the signal shortcuts to ctrl, rather than
530 # platform-default like we do elsewhere.
533 # platform-default like we do elsewhere.
531
534
532 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
535 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
533
536
534 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
537 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
535 self,
538 self,
536 triggered=self.interrupt_kernel_active_frontend,
539 triggered=self.interrupt_kernel_active_frontend,
537 shortcut=ctrl+"+C",
540 shortcut=ctrl+"+C",
538 )
541 )
539 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
542 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
540
543
541 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
544 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
542 self,
545 self,
543 triggered=self.restart_kernel_active_frontend,
546 triggered=self.restart_kernel_active_frontend,
544 shortcut=ctrl+"+.",
547 shortcut=ctrl+"+.",
545 )
548 )
546 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
549 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
547
550
548 self.kernel_menu.addSeparator()
551 self.kernel_menu.addSeparator()
549
552
550 def _make_dynamic_magic(self,magic):
553 def _make_dynamic_magic(self,magic):
551 """Return a function `fun` that will execute `magic` on active frontend.
554 """Return a function `fun` that will execute `magic` on active frontend.
552
555
553 Parameters
556 Parameters
554 ----------
557 ----------
555 magic : string
558 magic : string
556 string that will be executed as is when the returned function is called
559 string that will be executed as is when the returned function is called
557
560
558 Returns
561 Returns
559 -------
562 -------
560 fun : function
563 fun : function
561 function with no parameters, when called will execute `magic` on the
564 function with no parameters, when called will execute `magic` on the
562 current active frontend at call time
565 current active frontend at call time
563
566
564 See Also
567 See Also
565 --------
568 --------
566 populate_all_magic_menu : generate the "All Magics..." menu
569 populate_all_magic_menu : generate the "All Magics..." menu
567
570
568 Notes
571 Notes
569 -----
572 -----
570 `fun` execute `magic` an active frontend at the moment it is triggerd,
573 `fun` execute `magic` an active frontend at the moment it is triggerd,
571 not the active frontend at the moment it has been created.
574 not the active frontend at the moment it has been created.
572
575
573 This function is mostly used to create the "All Magics..." Menu at run time.
576 This function is mostly used to create the "All Magics..." Menu at run time.
574 """
577 """
575 # need to level nested function to be sure to past magic
578 # need to level nested function to be sure to past magic
576 # on active frontend **at run time**.
579 # on active frontend **at run time**.
577 def inner_dynamic_magic():
580 def inner_dynamic_magic():
578 self.active_frontend.execute(magic)
581 self.active_frontend.execute(magic)
579 inner_dynamic_magic.__name__ = "dynamics_magic_s"
582 inner_dynamic_magic.__name__ = "dynamics_magic_s"
580 return inner_dynamic_magic
583 return inner_dynamic_magic
581
584
582 def populate_all_magic_menu(self, listofmagic=None):
585 def populate_all_magic_menu(self, listofmagic=None):
583 """Clean "All Magics..." menu and repopulate it with `listofmagic`
586 """Clean "All Magics..." menu and repopulate it with `listofmagic`
584
587
585 Parameters
588 Parameters
586 ----------
589 ----------
587 listofmagic : string,
590 listofmagic : string,
588 repr() of a list of strings, send back by the kernel
591 repr() of a list of strings, send back by the kernel
589
592
590 Notes
593 Notes
591 -----
594 -----
592 `listofmagic`is a repr() of list because it is fed with the result of
595 `listofmagic`is a repr() of list because it is fed with the result of
593 a 'user_expression'
596 a 'user_expression'
594 """
597 """
595 alm_magic_menu = self.all_magic_menu
598 for k,v in self._magic_menu_dict.items():
596 alm_magic_menu.clear()
599 v.clear()
597
600 self.all_magic_menu.clear()
598 # list of protected magic that don't like to be called without argument
601
599 # append '?' to the end to print the docstring when called from the menu
602
600 protected_magic = set(["more","less","load_ext","pycat","loadpy","load","save"])
603 protected_magic = set(["more","less","load_ext","pycat","loadpy","load","save","psource"])
601 magics=re.findall('\w+', listofmagic)
604 mlist=ast.literal_eval(listofmagic)
602 for magic in magics:
605 for magic in mlist:
603 if magic in protected_magic:
606 cell = (magic['type'] == 'cell')
604 pmagic = '%s%s%s'%('%',magic,'?')
607 name = magic['name']
605 else:
608 mclass = magic['class']
606 pmagic = '%s%s'%('%',magic)
609 if cell :
610 prefix='%%'
611 else :
612 prefix='%'
613 magic_menu = self._get_magic_menu(mclass)
614
615 if name in protected_magic:
616 suffix = '?'
617 else :
618 suffix = ''
619 pmagic = '%s%s%s'%(prefix,name,suffix)
620
607 xaction = QtGui.QAction(pmagic,
621 xaction = QtGui.QAction(pmagic,
608 self,
622 self,
609 triggered=self._make_dynamic_magic(pmagic)
623 triggered=self._make_dynamic_magic(pmagic)
610 )
624 )
611 alm_magic_menu.addAction(xaction)
625 magic_menu.addAction(xaction)
626 self.all_magic_menu.addAction(xaction)
612
627
613 def update_all_magic_menu(self):
628 def update_all_magic_menu(self):
614 """ Update the list on magic in the "All Magics..." Menu
629 """ Update the list on magic in the "All Magics..." Menu
615
630
616 Request the kernel with the list of availlable magic and populate the
631 Request the kernel with the list of availlable magic and populate the
617 menu with the list received back
632 menu with the list received back
618
633
619 """
634 """
620 # first define a callback which will get the list of all magic and put it in the menu.
635 self.active_frontend._silent_exec_callback('get_ipython().magics_manager.lsmagic_info()',
621 self.active_frontend._silent_exec_callback('get_ipython().lsmagic()', self.populate_all_magic_menu)
636 self.populate_all_magic_menu)
637
638 def _get_magic_menu(self,menuidentifier, menulabel=None):
639 """return a submagic menu by name, and create it if needed
640
641 parameters:
642 -----------
643
644 menulabel : str
645 Label for the menu
646
647 Will infere the menu name from the identifier at creation if menulabel not given.
648 To do so you have too give menuidentifier as a CamelCassedString
649 """
650 menu = self._magic_menu_dict.get(menuidentifier,None)
651 if not menu :
652 if not menulabel:
653 menulabel = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>",menuidentifier)
654 menu = QtGui.QMenu(menulabel,self.magic_menu)
655 self._magic_menu_dict[menuidentifier]=menu
656 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
657 return menu
658
622
659
660
623 def init_magic_menu(self):
661 def init_magic_menu(self):
624 self.magic_menu = self.menuBar().addMenu("&Magic")
662 self.magic_menu = self.menuBar().addMenu("&Magic")
625 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
663 self.magic_menu_separator = self.magic_menu.addSeparator()
664
665 self.all_magic_menu = self._get_magic_menu("AllMagics", menulabel="&All Magics...")
626
666
627 # This action should usually not appear as it will be cleared when menu
667 # This action should usually not appear as it will be cleared when menu
628 # is updated at first kernel response. Though, it is necessary when
668 # is updated at first kernel response. Though, it is necessary when
629 # connecting through X-forwarding, as in this case, the menu is not
669 # connecting through X-forwarding, as in this case, the menu is not
630 # auto updated, SO DO NOT DELETE.
670 # auto updated, SO DO NOT DELETE.
631 self.pop = QtGui.QAction("&Update All Magic Menu ",
671 self.pop = QtGui.QAction("&Update All Magic Menu ",
632 self, triggered=self.update_all_magic_menu)
672 self, triggered=self.update_all_magic_menu)
633 self.add_menu_action(self.all_magic_menu, self.pop)
673 self.add_menu_action(self.all_magic_menu, self.pop)
634 # we need to populate the 'Magic Menu' once the kernel has answer at
674 # we need to populate the 'Magic Menu' once the kernel has answer at
635 # least once let's do it immedialy, but it's assured to works
675 # least once let's do it immedialy, but it's assured to works
636 self.pop.trigger()
676 self.pop.trigger()
637
677
638 self.reset_action = QtGui.QAction("&Reset",
678 self.reset_action = QtGui.QAction("&Reset",
639 self,
679 self,
640 statusTip="Clear all varible from workspace",
680 statusTip="Clear all varible from workspace",
641 triggered=self.reset_magic_active_frontend)
681 triggered=self.reset_magic_active_frontend)
642 self.add_menu_action(self.magic_menu, self.reset_action)
682 self.add_menu_action(self.magic_menu, self.reset_action)
643
683
644 self.history_action = QtGui.QAction("&History",
684 self.history_action = QtGui.QAction("&History",
645 self,
685 self,
646 statusTip="show command history",
686 statusTip="show command history",
647 triggered=self.history_magic_active_frontend)
687 triggered=self.history_magic_active_frontend)
648 self.add_menu_action(self.magic_menu, self.history_action)
688 self.add_menu_action(self.magic_menu, self.history_action)
649
689
650 self.save_action = QtGui.QAction("E&xport History ",
690 self.save_action = QtGui.QAction("E&xport History ",
651 self,
691 self,
652 statusTip="Export History as Python File",
692 statusTip="Export History as Python File",
653 triggered=self.save_magic_active_frontend)
693 triggered=self.save_magic_active_frontend)
654 self.add_menu_action(self.magic_menu, self.save_action)
694 self.add_menu_action(self.magic_menu, self.save_action)
655
695
656 self.who_action = QtGui.QAction("&Who",
696 self.who_action = QtGui.QAction("&Who",
657 self,
697 self,
658 statusTip="List interactive variable",
698 statusTip="List interactive variable",
659 triggered=self.who_magic_active_frontend)
699 triggered=self.who_magic_active_frontend)
660 self.add_menu_action(self.magic_menu, self.who_action)
700 self.add_menu_action(self.magic_menu, self.who_action)
661
701
662 self.who_ls_action = QtGui.QAction("Wh&o ls",
702 self.who_ls_action = QtGui.QAction("Wh&o ls",
663 self,
703 self,
664 statusTip="Return a list of interactive variable",
704 statusTip="Return a list of interactive variable",
665 triggered=self.who_ls_magic_active_frontend)
705 triggered=self.who_ls_magic_active_frontend)
666 self.add_menu_action(self.magic_menu, self.who_ls_action)
706 self.add_menu_action(self.magic_menu, self.who_ls_action)
667
707
668 self.whos_action = QtGui.QAction("Who&s",
708 self.whos_action = QtGui.QAction("Who&s",
669 self,
709 self,
670 statusTip="List interactive variable with detail",
710 statusTip="List interactive variable with detail",
671 triggered=self.whos_magic_active_frontend)
711 triggered=self.whos_magic_active_frontend)
672 self.add_menu_action(self.magic_menu, self.whos_action)
712 self.add_menu_action(self.magic_menu, self.whos_action)
673
713
674 def init_window_menu(self):
714 def init_window_menu(self):
675 self.window_menu = self.menuBar().addMenu("&Window")
715 self.window_menu = self.menuBar().addMenu("&Window")
676 if sys.platform == 'darwin':
716 if sys.platform == 'darwin':
677 # add min/maximize actions to OSX, which lacks default bindings.
717 # add min/maximize actions to OSX, which lacks default bindings.
678 self.minimizeAct = QtGui.QAction("Mini&mize",
718 self.minimizeAct = QtGui.QAction("Mini&mize",
679 self,
719 self,
680 shortcut="Ctrl+m",
720 shortcut="Ctrl+m",
681 statusTip="Minimize the window/Restore Normal Size",
721 statusTip="Minimize the window/Restore Normal Size",
682 triggered=self.toggleMinimized)
722 triggered=self.toggleMinimized)
683 # maximize is called 'Zoom' on OSX for some reason
723 # maximize is called 'Zoom' on OSX for some reason
684 self.maximizeAct = QtGui.QAction("&Zoom",
724 self.maximizeAct = QtGui.QAction("&Zoom",
685 self,
725 self,
686 shortcut="Ctrl+Shift+M",
726 shortcut="Ctrl+Shift+M",
687 statusTip="Maximize the window/Restore Normal Size",
727 statusTip="Maximize the window/Restore Normal Size",
688 triggered=self.toggleMaximized)
728 triggered=self.toggleMaximized)
689
729
690 self.add_menu_action(self.window_menu, self.minimizeAct)
730 self.add_menu_action(self.window_menu, self.minimizeAct)
691 self.add_menu_action(self.window_menu, self.maximizeAct)
731 self.add_menu_action(self.window_menu, self.maximizeAct)
692 self.window_menu.addSeparator()
732 self.window_menu.addSeparator()
693
733
694 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
734 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
695 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
735 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
696 self,
736 self,
697 shortcut=prev_key,
737 shortcut=prev_key,
698 statusTip="Select previous tab",
738 statusTip="Select previous tab",
699 triggered=self.prev_tab)
739 triggered=self.prev_tab)
700 self.add_menu_action(self.window_menu, self.prev_tab_act)
740 self.add_menu_action(self.window_menu, self.prev_tab_act)
701
741
702 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
742 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
703 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
743 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
704 self,
744 self,
705 shortcut=next_key,
745 shortcut=next_key,
706 statusTip="Select next tab",
746 statusTip="Select next tab",
707 triggered=self.next_tab)
747 triggered=self.next_tab)
708 self.add_menu_action(self.window_menu, self.next_tab_act)
748 self.add_menu_action(self.window_menu, self.next_tab_act)
709
749
710 def init_help_menu(self):
750 def init_help_menu(self):
711 # please keep the Help menu in Mac Os even if empty. It will
751 # please keep the Help menu in Mac Os even if empty. It will
712 # automatically contain a search field to search inside menus and
752 # automatically contain a search field to search inside menus and
713 # please keep it spelled in English, as long as Qt Doesn't support
753 # please keep it spelled in English, as long as Qt Doesn't support
714 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
754 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
715 # this search field fonctionality
755 # this search field fonctionality
716
756
717 self.help_menu = self.menuBar().addMenu("&Help")
757 self.help_menu = self.menuBar().addMenu("&Help")
718
758
719
759
720 # Help Menu
760 # Help Menu
721
761
722 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
762 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
723 self,
763 self,
724 triggered=self.intro_active_frontend
764 triggered=self.intro_active_frontend
725 )
765 )
726 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
766 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
727
767
728 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
768 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
729 self,
769 self,
730 triggered=self.quickref_active_frontend
770 triggered=self.quickref_active_frontend
731 )
771 )
732 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
772 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
733
773
734 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
774 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
735 self,
775 self,
736 triggered=self.guiref_active_frontend
776 triggered=self.guiref_active_frontend
737 )
777 )
738 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
778 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
739
779
740 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
780 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
741 self,
781 self,
742 triggered=self._open_online_help)
782 triggered=self._open_online_help)
743 self.add_menu_action(self.help_menu, self.onlineHelpAct)
783 self.add_menu_action(self.help_menu, self.onlineHelpAct)
744
784
745 # minimize/maximize/fullscreen actions:
785 # minimize/maximize/fullscreen actions:
746
786
747 def toggle_menu_bar(self):
787 def toggle_menu_bar(self):
748 menu_bar = self.menuBar()
788 menu_bar = self.menuBar()
749 if menu_bar.isVisible():
789 if menu_bar.isVisible():
750 menu_bar.setVisible(False)
790 menu_bar.setVisible(False)
751 else:
791 else:
752 menu_bar.setVisible(True)
792 menu_bar.setVisible(True)
753
793
754 def toggleMinimized(self):
794 def toggleMinimized(self):
755 if not self.isMinimized():
795 if not self.isMinimized():
756 self.showMinimized()
796 self.showMinimized()
757 else:
797 else:
758 self.showNormal()
798 self.showNormal()
759
799
760 def _open_online_help(self):
800 def _open_online_help(self):
761 filename="http://ipython.org/ipython-doc/stable/index.html"
801 filename="http://ipython.org/ipython-doc/stable/index.html"
762 webbrowser.open(filename, new=1, autoraise=True)
802 webbrowser.open(filename, new=1, autoraise=True)
763
803
764 def toggleMaximized(self):
804 def toggleMaximized(self):
765 if not self.isMaximized():
805 if not self.isMaximized():
766 self.showMaximized()
806 self.showMaximized()
767 else:
807 else:
768 self.showNormal()
808 self.showNormal()
769
809
770 # Min/Max imizing while in full screen give a bug
810 # Min/Max imizing while in full screen give a bug
771 # when going out of full screen, at least on OSX
811 # when going out of full screen, at least on OSX
772 def toggleFullScreen(self):
812 def toggleFullScreen(self):
773 if not self.isFullScreen():
813 if not self.isFullScreen():
774 self.showFullScreen()
814 self.showFullScreen()
775 if sys.platform == 'darwin':
815 if sys.platform == 'darwin':
776 self.maximizeAct.setEnabled(False)
816 self.maximizeAct.setEnabled(False)
777 self.minimizeAct.setEnabled(False)
817 self.minimizeAct.setEnabled(False)
778 else:
818 else:
779 self.showNormal()
819 self.showNormal()
780 if sys.platform == 'darwin':
820 if sys.platform == 'darwin':
781 self.maximizeAct.setEnabled(True)
821 self.maximizeAct.setEnabled(True)
782 self.minimizeAct.setEnabled(True)
822 self.minimizeAct.setEnabled(True)
783
823
784 def close_active_frontend(self):
824 def close_active_frontend(self):
785 self.close_tab(self.active_frontend)
825 self.close_tab(self.active_frontend)
786
826
787 def restart_kernel_active_frontend(self):
827 def restart_kernel_active_frontend(self):
788 self.active_frontend.request_restart_kernel()
828 self.active_frontend.request_restart_kernel()
789
829
790 def interrupt_kernel_active_frontend(self):
830 def interrupt_kernel_active_frontend(self):
791 self.active_frontend.request_interrupt_kernel()
831 self.active_frontend.request_interrupt_kernel()
792
832
793 def cut_active_frontend(self):
833 def cut_active_frontend(self):
794 widget = self.active_frontend
834 widget = self.active_frontend
795 if widget.can_cut():
835 if widget.can_cut():
796 widget.cut()
836 widget.cut()
797
837
798 def copy_active_frontend(self):
838 def copy_active_frontend(self):
799 widget = self.active_frontend
839 widget = self.active_frontend
800 widget.copy()
840 widget.copy()
801
841
802 def copy_raw_active_frontend(self):
842 def copy_raw_active_frontend(self):
803 self.active_frontend._copy_raw_action.trigger()
843 self.active_frontend._copy_raw_action.trigger()
804
844
805 def paste_active_frontend(self):
845 def paste_active_frontend(self):
806 widget = self.active_frontend
846 widget = self.active_frontend
807 if widget.can_paste():
847 if widget.can_paste():
808 widget.paste()
848 widget.paste()
809
849
810 def undo_active_frontend(self):
850 def undo_active_frontend(self):
811 self.active_frontend.undo()
851 self.active_frontend.undo()
812
852
813 def redo_active_frontend(self):
853 def redo_active_frontend(self):
814 self.active_frontend.redo()
854 self.active_frontend.redo()
815
855
816 def reset_magic_active_frontend(self):
856 def reset_magic_active_frontend(self):
817 self.active_frontend.execute("%reset")
857 self.active_frontend.execute("%reset")
818
858
819 def history_magic_active_frontend(self):
859 def history_magic_active_frontend(self):
820 self.active_frontend.execute("%history")
860 self.active_frontend.execute("%history")
821
861
822 def save_magic_active_frontend(self):
862 def save_magic_active_frontend(self):
823 self.active_frontend.save_magic()
863 self.active_frontend.save_magic()
824
864
825 def clear_magic_active_frontend(self):
865 def clear_magic_active_frontend(self):
826 self.active_frontend.execute("%clear")
866 self.active_frontend.execute("%clear")
827
867
828 def who_magic_active_frontend(self):
868 def who_magic_active_frontend(self):
829 self.active_frontend.execute("%who")
869 self.active_frontend.execute("%who")
830
870
831 def who_ls_magic_active_frontend(self):
871 def who_ls_magic_active_frontend(self):
832 self.active_frontend.execute("%who_ls")
872 self.active_frontend.execute("%who_ls")
833
873
834 def whos_magic_active_frontend(self):
874 def whos_magic_active_frontend(self):
835 self.active_frontend.execute("%whos")
875 self.active_frontend.execute("%whos")
836
876
837 def print_action_active_frontend(self):
877 def print_action_active_frontend(self):
838 self.active_frontend.print_action.trigger()
878 self.active_frontend.print_action.trigger()
839
879
840 def export_action_active_frontend(self):
880 def export_action_active_frontend(self):
841 self.active_frontend.export_action.trigger()
881 self.active_frontend.export_action.trigger()
842
882
843 def select_all_active_frontend(self):
883 def select_all_active_frontend(self):
844 self.active_frontend.select_all_action.trigger()
884 self.active_frontend.select_all_action.trigger()
845
885
846 def increase_font_size_active_frontend(self):
886 def increase_font_size_active_frontend(self):
847 self.active_frontend.increase_font_size.trigger()
887 self.active_frontend.increase_font_size.trigger()
848
888
849 def decrease_font_size_active_frontend(self):
889 def decrease_font_size_active_frontend(self):
850 self.active_frontend.decrease_font_size.trigger()
890 self.active_frontend.decrease_font_size.trigger()
851
891
852 def reset_font_size_active_frontend(self):
892 def reset_font_size_active_frontend(self):
853 self.active_frontend.reset_font_size.trigger()
893 self.active_frontend.reset_font_size.trigger()
854
894
855 def guiref_active_frontend(self):
895 def guiref_active_frontend(self):
856 self.active_frontend.execute("%guiref")
896 self.active_frontend.execute("%guiref")
857
897
858 def intro_active_frontend(self):
898 def intro_active_frontend(self):
859 self.active_frontend.execute("?")
899 self.active_frontend.execute("?")
860
900
861 def quickref_active_frontend(self):
901 def quickref_active_frontend(self):
862 self.active_frontend.execute("%quickref")
902 self.active_frontend.execute("%quickref")
863 #---------------------------------------------------------------------------
903 #---------------------------------------------------------------------------
864 # QWidget interface
904 # QWidget interface
865 #---------------------------------------------------------------------------
905 #---------------------------------------------------------------------------
866
906
867 def closeEvent(self, event):
907 def closeEvent(self, event):
868 """ Forward the close event to every tabs contained by the windows
908 """ Forward the close event to every tabs contained by the windows
869 """
909 """
870 if self.tab_widget.count() == 0:
910 if self.tab_widget.count() == 0:
871 # no tabs, just close
911 # no tabs, just close
872 event.accept()
912 event.accept()
873 return
913 return
874 # Do Not loop on the widget count as it change while closing
914 # Do Not loop on the widget count as it change while closing
875 title = self.window().windowTitle()
915 title = self.window().windowTitle()
876 cancel = QtGui.QMessageBox.Cancel
916 cancel = QtGui.QMessageBox.Cancel
877 okay = QtGui.QMessageBox.Ok
917 okay = QtGui.QMessageBox.Ok
878
918
879 if self.confirm_exit:
919 if self.confirm_exit:
880 if self.tab_widget.count() > 1:
920 if self.tab_widget.count() > 1:
881 msg = "Close all tabs, stop all kernels, and Quit?"
921 msg = "Close all tabs, stop all kernels, and Quit?"
882 else:
922 else:
883 msg = "Close console, stop kernel, and Quit?"
923 msg = "Close console, stop kernel, and Quit?"
884 info = "Kernels not started here (e.g. notebooks) will be left alone."
924 info = "Kernels not started here (e.g. notebooks) will be left alone."
885 closeall = QtGui.QPushButton("&Quit", self)
925 closeall = QtGui.QPushButton("&Quit", self)
886 closeall.setShortcut('Q')
926 closeall.setShortcut('Q')
887 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
927 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
888 title, msg)
928 title, msg)
889 box.setInformativeText(info)
929 box.setInformativeText(info)
890 box.addButton(cancel)
930 box.addButton(cancel)
891 box.addButton(closeall, QtGui.QMessageBox.YesRole)
931 box.addButton(closeall, QtGui.QMessageBox.YesRole)
892 box.setDefaultButton(closeall)
932 box.setDefaultButton(closeall)
893 box.setEscapeButton(cancel)
933 box.setEscapeButton(cancel)
894 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
934 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
895 box.setIconPixmap(pixmap)
935 box.setIconPixmap(pixmap)
896 reply = box.exec_()
936 reply = box.exec_()
897 else:
937 else:
898 reply = okay
938 reply = okay
899
939
900 if reply == cancel:
940 if reply == cancel:
901 event.ignore()
941 event.ignore()
902 return
942 return
903 if reply == okay:
943 if reply == okay:
904 while self.tab_widget.count() >= 1:
944 while self.tab_widget.count() >= 1:
905 # prevent further confirmations:
945 # prevent further confirmations:
906 widget = self.active_frontend
946 widget = self.active_frontend
907 widget._confirm_exit = False
947 widget._confirm_exit = False
908 self.close_tab(widget)
948 self.close_tab(widget)
909 event.accept()
949 event.accept()
910
950
General Comments 0
You need to be logged in to leave comments. Login now