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