##// END OF EJS Templates
add copy_raw, interrupt kernel and restart kernel into menu
Matthias BUSSONNIER -
Show More
@@ -1,638 +1,645
1 1 from __future__ import print_function
2 2
3 3 # Standard library imports
4 4 from collections import namedtuple
5 5 import sys
6 6 import time
7 7
8 8 # System library imports
9 9 from pygments.lexers import PythonLexer
10 10 from IPython.external.qt import QtCore, QtGui
11 11
12 12 # Local imports
13 13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
14 14 from IPython.core.oinspect import call_tip
15 15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
16 16 from IPython.utils.traitlets import Bool, Instance, Unicode
17 17 from bracket_matcher import BracketMatcher
18 18 from call_tip_widget import CallTipWidget
19 19 from completion_lexer import CompletionLexer
20 20 from history_console_widget import HistoryConsoleWidget
21 21 from pygments_highlighter import PygmentsHighlighter
22 22
23 23
24 24 class FrontendHighlighter(PygmentsHighlighter):
25 25 """ A PygmentsHighlighter that understands and ignores prompts.
26 26 """
27 27
28 28 def __init__(self, frontend):
29 29 super(FrontendHighlighter, self).__init__(frontend._control.document())
30 30 self._current_offset = 0
31 31 self._frontend = frontend
32 32 self.highlighting_on = False
33 33
34 34 def highlightBlock(self, string):
35 35 """ Highlight a block of text. Reimplemented to highlight selectively.
36 36 """
37 37 if not self.highlighting_on:
38 38 return
39 39
40 40 # The input to this function is a unicode string that may contain
41 41 # paragraph break characters, non-breaking spaces, etc. Here we acquire
42 42 # the string as plain text so we can compare it.
43 43 current_block = self.currentBlock()
44 44 string = self._frontend._get_block_plain_text(current_block)
45 45
46 46 # Decide whether to check for the regular or continuation prompt.
47 47 if current_block.contains(self._frontend._prompt_pos):
48 48 prompt = self._frontend._prompt
49 49 else:
50 50 prompt = self._frontend._continuation_prompt
51 51
52 52 # Only highlight if we can identify a prompt, but make sure not to
53 53 # highlight the prompt.
54 54 if string.startswith(prompt):
55 55 self._current_offset = len(prompt)
56 56 string = string[len(prompt):]
57 57 super(FrontendHighlighter, self).highlightBlock(string)
58 58
59 59 def rehighlightBlock(self, block):
60 60 """ Reimplemented to temporarily enable highlighting if disabled.
61 61 """
62 62 old = self.highlighting_on
63 63 self.highlighting_on = True
64 64 super(FrontendHighlighter, self).rehighlightBlock(block)
65 65 self.highlighting_on = old
66 66
67 67 def setFormat(self, start, count, format):
68 68 """ Reimplemented to highlight selectively.
69 69 """
70 70 start += self._current_offset
71 71 super(FrontendHighlighter, self).setFormat(start, count, format)
72 72
73 73
74 74 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
75 75 """ A Qt frontend for a generic Python kernel.
76 76 """
77 77
78 78 # The text to show when the kernel is (re)started.
79 79 banner = Unicode()
80 80
81 81 # An option and corresponding signal for overriding the default kernel
82 82 # interrupt behavior.
83 83 custom_interrupt = Bool(False)
84 84 custom_interrupt_requested = QtCore.Signal()
85 85
86 86 # An option and corresponding signals for overriding the default kernel
87 87 # restart behavior.
88 88 custom_restart = Bool(False)
89 89 custom_restart_kernel_died = QtCore.Signal(float)
90 90 custom_restart_requested = QtCore.Signal()
91 91
92 92 # Whether to automatically show calltips on open-parentheses.
93 93 enable_calltips = Bool(True, config=True,
94 94 help="Whether to draw information calltips on open-parentheses.")
95 95
96 96 # Emitted when a user visible 'execute_request' has been submitted to the
97 97 # kernel from the FrontendWidget. Contains the code to be executed.
98 98 executing = QtCore.Signal(object)
99 99
100 100 # Emitted when a user-visible 'execute_reply' has been received from the
101 101 # kernel and processed by the FrontendWidget. Contains the response message.
102 102 executed = QtCore.Signal(object)
103 103
104 104 # Emitted when an exit request has been received from the kernel.
105 105 exit_requested = QtCore.Signal(object)
106 106
107 107 # Protected class variables.
108 108 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
109 109 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
110 110 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
111 111 _input_splitter_class = InputSplitter
112 112 _local_kernel = False
113 113 _highlighter = Instance(FrontendHighlighter)
114 114
115 115 #---------------------------------------------------------------------------
116 116 # 'object' interface
117 117 #---------------------------------------------------------------------------
118 118
119 119 def __init__(self, *args, **kw):
120 120 super(FrontendWidget, self).__init__(*args, **kw)
121 121
122 122 # FrontendWidget protected variables.
123 123 self._bracket_matcher = BracketMatcher(self._control)
124 124 self._call_tip_widget = CallTipWidget(self._control)
125 125 self._completion_lexer = CompletionLexer(PythonLexer())
126 126 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
127 127 self._hidden = False
128 128 self._highlighter = FrontendHighlighter(self)
129 129 self._input_splitter = self._input_splitter_class(input_mode='cell')
130 130 self._kernel_manager = None
131 131 self._request_info = {}
132 132
133 133 # Configure the ConsoleWidget.
134 134 self.tab_width = 4
135 135 self._set_continuation_prompt('... ')
136 136
137 137 # Configure the CallTipWidget.
138 138 self._call_tip_widget.setFont(self.font)
139 139 self.font_changed.connect(self._call_tip_widget.setFont)
140 140
141 141 # Configure actions.
142 142 action = self._copy_raw_action
143 143 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
144 144 action.setEnabled(False)
145 145 action.setShortcut(QtGui.QKeySequence(key))
146 146 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
147 147 action.triggered.connect(self.copy_raw)
148 148 self.copy_available.connect(action.setEnabled)
149 149 self.addAction(action)
150 150
151 151 # Connect signal handlers.
152 152 document = self._control.document()
153 153 document.contentsChange.connect(self._document_contents_change)
154 154
155 155 # Set flag for whether we are connected via localhost.
156 156 self._local_kernel = kw.get('local_kernel',
157 157 FrontendWidget._local_kernel)
158 158
159 159 #---------------------------------------------------------------------------
160 160 # 'ConsoleWidget' public interface
161 161 #---------------------------------------------------------------------------
162 162
163 163 def copy(self):
164 164 """ Copy the currently selected text to the clipboard, removing prompts.
165 165 """
166 166 text = self._control.textCursor().selection().toPlainText()
167 167 if text:
168 168 lines = map(transform_classic_prompt, text.splitlines())
169 169 text = '\n'.join(lines)
170 170 QtGui.QApplication.clipboard().setText(text)
171 171
172 172 #---------------------------------------------------------------------------
173 173 # 'ConsoleWidget' abstract interface
174 174 #---------------------------------------------------------------------------
175 175
176 176 def _is_complete(self, source, interactive):
177 177 """ Returns whether 'source' can be completely processed and a new
178 178 prompt created. When triggered by an Enter/Return key press,
179 179 'interactive' is True; otherwise, it is False.
180 180 """
181 181 complete = self._input_splitter.push(source)
182 182 if interactive:
183 183 complete = not self._input_splitter.push_accepts_more()
184 184 return complete
185 185
186 186 def _execute(self, source, hidden):
187 187 """ Execute 'source'. If 'hidden', do not show any output.
188 188
189 189 See parent class :meth:`execute` docstring for full details.
190 190 """
191 191 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
192 192 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
193 193 self._hidden = hidden
194 194 if not hidden:
195 195 self.executing.emit(source)
196 196
197 197 def _prompt_started_hook(self):
198 198 """ Called immediately after a new prompt is displayed.
199 199 """
200 200 if not self._reading:
201 201 self._highlighter.highlighting_on = True
202 202
203 203 def _prompt_finished_hook(self):
204 204 """ Called immediately after a prompt is finished, i.e. when some input
205 205 will be processed and a new prompt displayed.
206 206 """
207 207 # Flush all state from the input splitter so the next round of
208 208 # reading input starts with a clean buffer.
209 209 self._input_splitter.reset()
210 210
211 211 if not self._reading:
212 212 self._highlighter.highlighting_on = False
213 213
214 214 def _tab_pressed(self):
215 215 """ Called when the tab key is pressed. Returns whether to continue
216 216 processing the event.
217 217 """
218 218 # Perform tab completion if:
219 219 # 1) The cursor is in the input buffer.
220 220 # 2) There is a non-whitespace character before the cursor.
221 221 text = self._get_input_buffer_cursor_line()
222 222 if text is None:
223 223 return False
224 224 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
225 225 if complete:
226 226 self._complete()
227 227 return not complete
228 228
229 229 #---------------------------------------------------------------------------
230 230 # 'ConsoleWidget' protected interface
231 231 #---------------------------------------------------------------------------
232 232
233 233 def _context_menu_make(self, pos):
234 234 """ Reimplemented to add an action for raw copy.
235 235 """
236 236 menu = super(FrontendWidget, self)._context_menu_make(pos)
237 237 for before_action in menu.actions():
238 238 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
239 239 QtGui.QKeySequence.ExactMatch:
240 240 menu.insertAction(before_action, self._copy_raw_action)
241 241 break
242 242 return menu
243 243
244 def request_interrupt_kernel(self):
245 if self._executing:
246 self.interrupt_kernel()
247
248 def request_restart_kernel(self):
249 message = 'Are you sure you want to restart the kernel?'
250 self.restart_kernel(message, now=False)
251
244 252 def _event_filter_console_keypress(self, event):
245 253 """ Reimplemented for execution interruption and smart backspace.
246 254 """
247 255 key = event.key()
248 256 if self._control_key_down(event.modifiers(), include_command=False):
249 257
250 258 if key == QtCore.Qt.Key_C and self._executing:
251 self.interrupt_kernel()
259 self.request_interrupt_kernel()
252 260 return True
253 261
254 262 elif key == QtCore.Qt.Key_Period:
255 message = 'Are you sure you want to restart the kernel?'
256 self.restart_kernel(message, now=False)
263 self.request_restart_kernel()
257 264 return True
258 265
259 266 elif not event.modifiers() & QtCore.Qt.AltModifier:
260 267
261 268 # Smart backspace: remove four characters in one backspace if:
262 269 # 1) everything left of the cursor is whitespace
263 270 # 2) the four characters immediately left of the cursor are spaces
264 271 if key == QtCore.Qt.Key_Backspace:
265 272 col = self._get_input_buffer_cursor_column()
266 273 cursor = self._control.textCursor()
267 274 if col > 3 and not cursor.hasSelection():
268 275 text = self._get_input_buffer_cursor_line()[:col]
269 276 if text.endswith(' ') and not text.strip():
270 277 cursor.movePosition(QtGui.QTextCursor.Left,
271 278 QtGui.QTextCursor.KeepAnchor, 4)
272 279 cursor.removeSelectedText()
273 280 return True
274 281
275 282 return super(FrontendWidget, self)._event_filter_console_keypress(event)
276 283
277 284 def _insert_continuation_prompt(self, cursor):
278 285 """ Reimplemented for auto-indentation.
279 286 """
280 287 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
281 288 cursor.insertText(' ' * self._input_splitter.indent_spaces)
282 289
283 290 #---------------------------------------------------------------------------
284 291 # 'BaseFrontendMixin' abstract interface
285 292 #---------------------------------------------------------------------------
286 293
287 294 def _handle_complete_reply(self, rep):
288 295 """ Handle replies for tab completion.
289 296 """
290 297 self.log.debug("complete: %s", rep.get('content', ''))
291 298 cursor = self._get_cursor()
292 299 info = self._request_info.get('complete')
293 300 if info and info.id == rep['parent_header']['msg_id'] and \
294 301 info.pos == cursor.position():
295 302 text = '.'.join(self._get_context())
296 303 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
297 304 self._complete_with_items(cursor, rep['content']['matches'])
298 305
299 306 def _handle_execute_reply(self, msg):
300 307 """ Handles replies for code execution.
301 308 """
302 309 self.log.debug("execute: %s", msg.get('content', ''))
303 310 info = self._request_info.get('execute')
304 311 if info and info.id == msg['parent_header']['msg_id'] and \
305 312 info.kind == 'user' and not self._hidden:
306 313 # Make sure that all output from the SUB channel has been processed
307 314 # before writing a new prompt.
308 315 self.kernel_manager.sub_channel.flush()
309 316
310 317 # Reset the ANSI style information to prevent bad text in stdout
311 318 # from messing up our colors. We're not a true terminal so we're
312 319 # allowed to do this.
313 320 if self.ansi_codes:
314 321 self._ansi_processor.reset_sgr()
315 322
316 323 content = msg['content']
317 324 status = content['status']
318 325 if status == 'ok':
319 326 self._process_execute_ok(msg)
320 327 elif status == 'error':
321 328 self._process_execute_error(msg)
322 329 elif status == 'abort':
323 330 self._process_execute_abort(msg)
324 331
325 332 self._show_interpreter_prompt_for_reply(msg)
326 333 self.executed.emit(msg)
327 334 else:
328 335 super(FrontendWidget, self)._handle_execute_reply(msg)
329 336
330 337 def _handle_input_request(self, msg):
331 338 """ Handle requests for raw_input.
332 339 """
333 340 self.log.debug("input: %s", msg.get('content', ''))
334 341 if self._hidden:
335 342 raise RuntimeError('Request for raw input during hidden execution.')
336 343
337 344 # Make sure that all output from the SUB channel has been processed
338 345 # before entering readline mode.
339 346 self.kernel_manager.sub_channel.flush()
340 347
341 348 def callback(line):
342 349 self.kernel_manager.stdin_channel.input(line)
343 350 self._readline(msg['content']['prompt'], callback=callback)
344 351
345 352 def _handle_kernel_died(self, since_last_heartbeat):
346 353 """ Handle the kernel's death by asking if the user wants to restart.
347 354 """
348 355 self.log.debug("kernel died: %s", since_last_heartbeat)
349 356 if self.custom_restart:
350 357 self.custom_restart_kernel_died.emit(since_last_heartbeat)
351 358 else:
352 359 message = 'The kernel heartbeat has been inactive for %.2f ' \
353 360 'seconds. Do you want to restart the kernel? You may ' \
354 361 'first want to check the network connection.' % \
355 362 since_last_heartbeat
356 363 self.restart_kernel(message, now=True)
357 364
358 365 def _handle_object_info_reply(self, rep):
359 366 """ Handle replies for call tips.
360 367 """
361 368 self.log.debug("oinfo: %s", rep.get('content', ''))
362 369 cursor = self._get_cursor()
363 370 info = self._request_info.get('call_tip')
364 371 if info and info.id == rep['parent_header']['msg_id'] and \
365 372 info.pos == cursor.position():
366 373 # Get the information for a call tip. For now we format the call
367 374 # line as string, later we can pass False to format_call and
368 375 # syntax-highlight it ourselves for nicer formatting in the
369 376 # calltip.
370 377 content = rep['content']
371 378 # if this is from pykernel, 'docstring' will be the only key
372 379 if content.get('ismagic', False):
373 380 # Don't generate a call-tip for magics. Ideally, we should
374 381 # generate a tooltip, but not on ( like we do for actual
375 382 # callables.
376 383 call_info, doc = None, None
377 384 else:
378 385 call_info, doc = call_tip(content, format_call=True)
379 386 if call_info or doc:
380 387 self._call_tip_widget.show_call_info(call_info, doc)
381 388
382 389 def _handle_pyout(self, msg):
383 390 """ Handle display hook output.
384 391 """
385 392 self.log.debug("pyout: %s", msg.get('content', ''))
386 393 if not self._hidden and self._is_from_this_session(msg):
387 394 text = msg['content']['data']
388 395 self._append_plain_text(text + '\n', before_prompt=True)
389 396
390 397 def _handle_stream(self, msg):
391 398 """ Handle stdout, stderr, and stdin.
392 399 """
393 400 self.log.debug("stream: %s", msg.get('content', ''))
394 401 if not self._hidden and self._is_from_this_session(msg):
395 402 # Most consoles treat tabs as being 8 space characters. Convert tabs
396 403 # to spaces so that output looks as expected regardless of this
397 404 # widget's tab width.
398 405 text = msg['content']['data'].expandtabs(8)
399 406
400 407 self._append_plain_text(text, before_prompt=True)
401 408 self._control.moveCursor(QtGui.QTextCursor.End)
402 409
403 410 def _handle_shutdown_reply(self, msg):
404 411 """ Handle shutdown signal, only if from other console.
405 412 """
406 413 self.log.debug("shutdown: %s", msg.get('content', ''))
407 414 if not self._hidden and not self._is_from_this_session(msg):
408 415 if self._local_kernel:
409 416 if not msg['content']['restart']:
410 417 self.exit_requested.emit(self)
411 418 else:
412 419 # we just got notified of a restart!
413 420 time.sleep(0.25) # wait 1/4 sec to reset
414 421 # lest the request for a new prompt
415 422 # goes to the old kernel
416 423 self.reset()
417 424 else: # remote kernel, prompt on Kernel shutdown/reset
418 425 title = self.window().windowTitle()
419 426 if not msg['content']['restart']:
420 427 reply = QtGui.QMessageBox.question(self, title,
421 428 "Kernel has been shutdown permanently. "
422 429 "Close the Console?",
423 430 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
424 431 if reply == QtGui.QMessageBox.Yes:
425 432 self.exit_requested.emit(self)
426 433 else:
427 434 reply = QtGui.QMessageBox.question(self, title,
428 435 "Kernel has been reset. Clear the Console?",
429 436 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
430 437 if reply == QtGui.QMessageBox.Yes:
431 438 time.sleep(0.25) # wait 1/4 sec to reset
432 439 # lest the request for a new prompt
433 440 # goes to the old kernel
434 441 self.reset()
435 442
436 443 def _started_channels(self):
437 444 """ Called when the KernelManager channels have started listening or
438 445 when the frontend is assigned an already listening KernelManager.
439 446 """
440 447 self.reset()
441 448
442 449 #---------------------------------------------------------------------------
443 450 # 'FrontendWidget' public interface
444 451 #---------------------------------------------------------------------------
445 452
446 453 def copy_raw(self):
447 454 """ Copy the currently selected text to the clipboard without attempting
448 455 to remove prompts or otherwise alter the text.
449 456 """
450 457 self._control.copy()
451 458
452 459 def execute_file(self, path, hidden=False):
453 460 """ Attempts to execute file with 'path'. If 'hidden', no output is
454 461 shown.
455 462 """
456 463 self.execute('execfile(%r)' % path, hidden=hidden)
457 464
458 465 def interrupt_kernel(self):
459 466 """ Attempts to interrupt the running kernel.
460 467 """
461 468 if self.custom_interrupt:
462 469 self.custom_interrupt_requested.emit()
463 470 elif self.kernel_manager.has_kernel:
464 471 self.kernel_manager.interrupt_kernel()
465 472 else:
466 473 self._append_plain_text('Kernel process is either remote or '
467 474 'unspecified. Cannot interrupt.\n')
468 475
469 476 def reset(self):
470 477 """ Resets the widget to its initial state. Similar to ``clear``, but
471 478 also re-writes the banner and aborts execution if necessary.
472 479 """
473 480 if self._executing:
474 481 self._executing = False
475 482 self._request_info['execute'] = None
476 483 self._reading = False
477 484 self._highlighter.highlighting_on = False
478 485
479 486 self._control.clear()
480 487 self._append_plain_text(self.banner)
481 488 self._show_interpreter_prompt()
482 489
483 490 def restart_kernel(self, message, now=False):
484 491 """ Attempts to restart the running kernel.
485 492 """
486 493 # FIXME: now should be configurable via a checkbox in the dialog. Right
487 494 # now at least the heartbeat path sets it to True and the manual restart
488 495 # to False. But those should just be the pre-selected states of a
489 496 # checkbox that the user could override if so desired. But I don't know
490 497 # enough Qt to go implementing the checkbox now.
491 498
492 499 if self.custom_restart:
493 500 self.custom_restart_requested.emit()
494 501
495 502 elif self.kernel_manager.has_kernel:
496 503 # Pause the heart beat channel to prevent further warnings.
497 504 self.kernel_manager.hb_channel.pause()
498 505
499 506 # Prompt the user to restart the kernel. Un-pause the heartbeat if
500 507 # they decline. (If they accept, the heartbeat will be un-paused
501 508 # automatically when the kernel is restarted.)
502 509 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
503 510 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
504 511 message, buttons)
505 512 if result == QtGui.QMessageBox.Yes:
506 513 try:
507 514 self.kernel_manager.restart_kernel(now=now)
508 515 except RuntimeError:
509 516 self._append_plain_text('Kernel started externally. '
510 517 'Cannot restart.\n')
511 518 else:
512 519 self.reset()
513 520 else:
514 521 self.kernel_manager.hb_channel.unpause()
515 522
516 523 else:
517 524 self._append_plain_text('Kernel process is either remote or '
518 525 'unspecified. Cannot restart.\n')
519 526
520 527 #---------------------------------------------------------------------------
521 528 # 'FrontendWidget' protected interface
522 529 #---------------------------------------------------------------------------
523 530
524 531 def _call_tip(self):
525 532 """ Shows a call tip, if appropriate, at the current cursor location.
526 533 """
527 534 # Decide if it makes sense to show a call tip
528 535 if not self.enable_calltips:
529 536 return False
530 537 cursor = self._get_cursor()
531 538 cursor.movePosition(QtGui.QTextCursor.Left)
532 539 if cursor.document().characterAt(cursor.position()) != '(':
533 540 return False
534 541 context = self._get_context(cursor)
535 542 if not context:
536 543 return False
537 544
538 545 # Send the metadata request to the kernel
539 546 name = '.'.join(context)
540 547 msg_id = self.kernel_manager.shell_channel.object_info(name)
541 548 pos = self._get_cursor().position()
542 549 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
543 550 return True
544 551
545 552 def _complete(self):
546 553 """ Performs completion at the current cursor location.
547 554 """
548 555 context = self._get_context()
549 556 if context:
550 557 # Send the completion request to the kernel
551 558 msg_id = self.kernel_manager.shell_channel.complete(
552 559 '.'.join(context), # text
553 560 self._get_input_buffer_cursor_line(), # line
554 561 self._get_input_buffer_cursor_column(), # cursor_pos
555 562 self.input_buffer) # block
556 563 pos = self._get_cursor().position()
557 564 info = self._CompletionRequest(msg_id, pos)
558 565 self._request_info['complete'] = info
559 566
560 567 def _get_context(self, cursor=None):
561 568 """ Gets the context for the specified cursor (or the current cursor
562 569 if none is specified).
563 570 """
564 571 if cursor is None:
565 572 cursor = self._get_cursor()
566 573 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
567 574 QtGui.QTextCursor.KeepAnchor)
568 575 text = cursor.selection().toPlainText()
569 576 return self._completion_lexer.get_context(text)
570 577
571 578 def _process_execute_abort(self, msg):
572 579 """ Process a reply for an aborted execution request.
573 580 """
574 581 self._append_plain_text("ERROR: execution aborted\n")
575 582
576 583 def _process_execute_error(self, msg):
577 584 """ Process a reply for an execution request that resulted in an error.
578 585 """
579 586 content = msg['content']
580 587 # If a SystemExit is passed along, this means exit() was called - also
581 588 # all the ipython %exit magic syntax of '-k' to be used to keep
582 589 # the kernel running
583 590 if content['ename']=='SystemExit':
584 591 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
585 592 self._keep_kernel_on_exit = keepkernel
586 593 self.exit_requested.emit(self)
587 594 else:
588 595 traceback = ''.join(content['traceback'])
589 596 self._append_plain_text(traceback)
590 597
591 598 def _process_execute_ok(self, msg):
592 599 """ Process a reply for a successful execution equest.
593 600 """
594 601 payload = msg['content']['payload']
595 602 for item in payload:
596 603 if not self._process_execute_payload(item):
597 604 warning = 'Warning: received unknown payload of type %s'
598 605 print(warning % repr(item['source']))
599 606
600 607 def _process_execute_payload(self, item):
601 608 """ Process a single payload item from the list of payload items in an
602 609 execution reply. Returns whether the payload was handled.
603 610 """
604 611 # The basic FrontendWidget doesn't handle payloads, as they are a
605 612 # mechanism for going beyond the standard Python interpreter model.
606 613 return False
607 614
608 615 def _show_interpreter_prompt(self):
609 616 """ Shows a prompt for the interpreter.
610 617 """
611 618 self._show_prompt('>>> ')
612 619
613 620 def _show_interpreter_prompt_for_reply(self, msg):
614 621 """ Shows a prompt for the interpreter given an 'execute_reply' message.
615 622 """
616 623 self._show_interpreter_prompt()
617 624
618 625 #------ Signal handlers ----------------------------------------------------
619 626
620 627 def _document_contents_change(self, position, removed, added):
621 628 """ Called whenever the document's content changes. Display a call tip
622 629 if appropriate.
623 630 """
624 631 # Calculate where the cursor should be *after* the change:
625 632 position += added
626 633
627 634 document = self._control.document()
628 635 if position == self._get_cursor().position():
629 636 self._call_tip()
630 637
631 638 #------ Trait default initializers -----------------------------------------
632 639
633 640 def _banner_default(self):
634 641 """ Returns the standard Python banner.
635 642 """
636 643 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
637 644 '"license" for more information.'
638 645 return banner % (sys.version, sys.platform)
@@ -1,1164 +1,1194
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
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
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib imports
20 20 import json
21 21 import os
22 22 import signal
23 23 import sys
24 24 import webbrowser
25 25 from getpass import getpass
26 26
27 27 # System library imports
28 28 from IPython.external.qt import QtGui,QtCore
29 29 from pygments.styles import get_all_styles
30 30
31 31 # Local imports
32 32 from IPython.config.application import boolean_flag
33 33 from IPython.core.application import BaseIPythonApplication
34 34 from IPython.core.profiledir import ProfileDir
35 35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 39 from IPython.frontend.qt.console import styles
40 40 from IPython.frontend.qt.kernelmanager import QtKernelManager
41 41 from IPython.utils.path import filefind
42 42 from IPython.utils.py3compat import str_to_bytes
43 43 from IPython.utils.traitlets import (
44 44 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
45 45 )
46 46 from IPython.zmq.ipkernel import (
47 47 flags as ipkernel_flags,
48 48 aliases as ipkernel_aliases,
49 49 IPKernelApp
50 50 )
51 51 from IPython.zmq.session import Session, default_secure
52 52 from IPython.zmq.zmqshell import ZMQInteractiveShell
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Network Constants
56 56 #-----------------------------------------------------------------------------
57 57
58 58 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Globals
62 62 #-----------------------------------------------------------------------------
63 63
64 64 _examples = """
65 65 ipython qtconsole # start the qtconsole
66 66 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
67 67 """
68 68
69 69 #-----------------------------------------------------------------------------
70 70 # Classes
71 71 #-----------------------------------------------------------------------------
72 72
73 73 class MainWindow(QtGui.QMainWindow):
74 74
75 75 #---------------------------------------------------------------------------
76 76 # 'object' interface
77 77 #---------------------------------------------------------------------------
78 78
79 79 def __init__(self, app, frontend, existing=False, may_close=True,
80 80 confirm_exit=True):
81 81 """ Create a MainWindow for the specified FrontendWidget.
82 82
83 83 The app is passed as an argument to allow for different
84 84 closing behavior depending on whether we are the Kernel's parent.
85 85
86 86 If existing is True, then this Console does not own the Kernel.
87 87
88 88 If may_close is True, then this Console is permitted to close the kernel
89 89 """
90 90
91 91 super(MainWindow, self).__init__()
92 92 self._app = app
93 93
94 94 self.tab_widget = QtGui.QTabWidget(self)
95 95 self.tab_widget.setDocumentMode(True)
96 96 self.tab_widget.setTabsClosable(True)
97 97 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
98 98
99 99 self.setCentralWidget(self.tab_widget)
100 100 self.update_tab_bar_visibility()
101 101
102 102 def update_tab_bar_visibility(self):
103 103 """ update visibility of the tabBar depending of the number of tab
104 104
105 105 0 or 1 tab, tabBar hidden
106 106 2+ tabs, tabBar visible
107 107
108 108 send a self.close if number of tab ==0
109 109
110 110 need to be called explicitely, or be connected to tabInserted/tabRemoved
111 111 """
112 112 if self.tab_widget.count() <= 1:
113 113 self.tab_widget.tabBar().setVisible(False)
114 114 else:
115 115 self.tab_widget.tabBar().setVisible(True)
116 116 if self.tab_widget.count()==0 :
117 117 self.close()
118 118
119 119 @property
120 120 def active_frontend(self):
121 121 return self.tab_widget.currentWidget()
122 122
123 123 def close_tab(self,current_tab):
124 124 """ Called when you need to try to close a tab.
125 125
126 126 It takes the number of the tab to be closed as argument, or a referece
127 127 to the wiget insite this tab
128 128 """
129 129
130 130 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
131 131 # and a reference to the trontend to close
132 132 if type(current_tab) is not int :
133 133 current_tab = self.tab_widget.indexOf(current_tab)
134 134 closing_widget=self.tab_widget.widget(current_tab)
135 135
136 136
137 137 # when trying to be closed, widget might re-send a request to be closed again, but will
138 138 # be deleted when event will be processed. So need to check that widget still exist and
139 139 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
140 140 # re-send by this fonction on the master widget, which ask all slaves widget to exit
141 141 if closing_widget==None:
142 142 return
143 143
144 144 #get a list of all wwidget not owning the kernel.
145 145 slave_tabs=self.find_slaves_tabs(closing_widget)
146 146
147 147 keepkernel = None #Use the prompt by default
148 148 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
149 149 keepkernel = closing_widget._keep_kernel_on_exit
150 150 # If signal sent by exist magic (_keep_kernel_on_exit, exist and not None)
151 151 # we set local slave tabs._hidden to True to avoit prompting for kernel
152 152 # restart when they litt get the signal. and the "forward" the 'exit'
153 153 # to the main win
154 154 if keepkernel is not None:
155 155 for tab in slave_tabs:
156 156 tab._hidden = True
157 157 if closing_widget in slave_tabs :
158 158 try :
159 159 self.find_master_tab(closing_widget).execute('exit')
160 160 except AttributeError:
161 161 self.log.info("Master already closed or not local, closing only current tab")
162 162 self.tab_widget.removeTab(current_tab)
163 163 return
164 164
165 165 kernel_manager = closing_widget.kernel_manager
166 166
167 167 if keepkernel is None and not closing_widget._confirm_exit:
168 168 # don't prompt, just terminate the kernel if we own it
169 169 # or leave it alone if we don't
170 170 keepkernel = not closing_widget._existing
171 171
172 172 if keepkernel is None: #show prompt
173 173 if kernel_manager and kernel_manager.channels_running:
174 174 title = self.window().windowTitle()
175 175 cancel = QtGui.QMessageBox.Cancel
176 176 okay = QtGui.QMessageBox.Ok
177 177 if closing_widget._may_close:
178 178 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
179 179 info = "Would you like to quit the Kernel and all attached Consoles as well?"
180 180 justthis = QtGui.QPushButton("&No, just this Console", self)
181 181 justthis.setShortcut('N')
182 182 closeall = QtGui.QPushButton("&Yes, quit everything", self)
183 183 closeall.setShortcut('Y')
184 184 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
185 185 title, msg)
186 186 box.setInformativeText(info)
187 187 box.addButton(cancel)
188 188 box.addButton(justthis, QtGui.QMessageBox.NoRole)
189 189 box.addButton(closeall, QtGui.QMessageBox.YesRole)
190 190 box.setDefaultButton(closeall)
191 191 box.setEscapeButton(cancel)
192 192 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
193 193 box.setIconPixmap(pixmap)
194 194 reply = box.exec_()
195 195 if reply == 1: # close All
196 196 for slave in slave_tabs:
197 197 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
198 198 closing_widget.execute("exit")
199 199 self.tab_widget.removeTab(current_tab)
200 200 elif reply == 0: # close Console
201 201 if not closing_widget._existing:
202 202 # Have kernel: don't quit, just close the window
203 203 self._app.setQuitOnLastWindowClosed(False)
204 204 closing_widget.execute("exit True")
205 205 else:
206 206 reply = QtGui.QMessageBox.question(self, title,
207 207 "Are you sure you want to close this Console?"+
208 208 "\nThe Kernel and other Consoles will remain active.",
209 209 okay|cancel,
210 210 defaultButton=okay
211 211 )
212 212 if reply == okay:
213 213 self.tab_widget.removeTab(current_tab)
214 214 elif keepkernel: #close console but leave kernel running (no prompt)
215 215 if kernel_manager and kernel_manager.channels_running:
216 216 if not closing_widget._existing:
217 217 # I have the kernel: don't quit, just close the window
218 218 self.tab_widget.removeTab(current_tab)
219 219 else: #close console and kernel (no prompt)
220 220 if kernel_manager and kernel_manager.channels_running:
221 221 for slave in slave_tabs:
222 222 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
223 223 self.tab_widget.removeTab(current_tab)
224 224 kernel_manager.shutdown_kernel()
225 225 self.update_tab_bar_visibility()
226 226
227 227 def add_tab_with_frontend(self,frontend,name=None):
228 228 """ insert a tab with a given frontend in the tab bar, and give it a name
229 229
230 230 """
231 231 if not name:
232 232 name=str('kernel '+str(self.tab_widget.count()))
233 233 self.tab_widget.addTab(frontend,name)
234 234 self.update_tab_bar_visibility()
235 235 self.make_frontend_visible(frontend)
236 236 frontend.exit_requested.connect(self.close_tab)
237 237
238 238 def next_tab(self):
239 239 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
240 240
241 241 def prev_tab(self):
242 242 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
243 243
244 244 def make_frontend_visible(self,frontend):
245 245 widget_index=self.tab_widget.indexOf(frontend)
246 246 if widget_index > 0 :
247 247 self.tab_widget.setCurrentIndex(widget_index)
248 248
249 249 def find_master_tab(self,tab,as_list=False):
250 250 """
251 251 Try to return the frontend that own the kernel attached to the given widget/tab.
252 252
253 253 Only find frontend owed by the current application. Selection
254 254 based on port of the kernel, might be inacurate if several kernel
255 255 on different ip use same port number.
256 256
257 257 This fonction does the conversion tabNumber/widget if needed.
258 258 Might return None if no master widget (non local kernel)
259 259 Will crash IPython if more than 1 masterWidget
260 260
261 261 When asList set to True, always return a list of widget(s) owning
262 262 the kernel. The list might be empty or containing several Widget.
263 263 """
264 264
265 265 #convert from/to int/richIpythonWidget if needed
266 266 if type(tab) == int:
267 267 tab = self.tab_widget.widget(tab)
268 268 km=tab.kernel_manager;
269 269
270 270 #build list of all widgets
271 271 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
272 272
273 273 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
274 274 # And should have a _may_close attribute
275 275 filtred_widget_list = [ widget for widget in widget_list if
276 276 widget.kernel_manager.shell_address == km.shell_address and
277 277 widget.kernel_manager.sub_address == km.sub_address and
278 278 widget.kernel_manager.stdin_address == km.stdin_address and
279 279 widget.kernel_manager.hb_address == km.hb_address and
280 280 hasattr(widget,'_may_close') ]
281 281 # the master widget is the one that may close the kernel
282 282 master_widget= [ widget for widget in filtred_widget_list if widget._may_close]
283 283 if as_list:
284 284 return master_widget
285 285 assert(len(master_widget)<=1 )
286 286 if len(master_widget)==0:
287 287 return None
288 288
289 289 return master_widget[0]
290 290
291 291 def find_slaves_tabs(self,tab):
292 292 """
293 293 Try to return all the frontend that do not own the kernel attached to the given widget/tab.
294 294
295 295 Only find frontend owed by the current application. Selection
296 296 based on port of the kernel, might be innacurate if several kernel
297 297 on different ip use same port number.
298 298
299 299 This fonction does the conversion tabNumber/widget if needed.
300 300 """
301 301 #convert from/to int/richIpythonWidget if needed
302 302 if type(tab) == int:
303 303 tab = self.tab_widget.widget(tab)
304 304 km=tab.kernel_manager;
305 305
306 306 #build list of all widgets
307 307 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
308 308
309 309 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
310 310 filtered_widget_list = ( widget for widget in widget_list if
311 311 widget.kernel_manager.shell_address == km.shell_address and
312 312 widget.kernel_manager.sub_address == km.sub_address and
313 313 widget.kernel_manager.stdin_address == km.stdin_address and
314 314 widget.kernel_manager.hb_address == km.hb_address)
315 315 # Get a list of all widget owning the same kernel and removed it from
316 316 # the previous cadidate. (better using sets ?)
317 317 master_widget_list = self.find_master_tab(tab,as_list=True)
318 318 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
319 319
320 320 return slave_list
321 321
322 322 # MenuBar is always present on Mac Os, so let's populate it with possible
323 323 # action, don't do it on other platform as some user might not want the
324 324 # menu bar, or give them an option to remove it
325 325 def init_menu_bar(self):
326 326 #create menu in the order they should appear in the menu bar
327 327 self.file_menu = self.menuBar().addMenu("&File")
328 328 self.edit_menu = self.menuBar().addMenu("&Edit")
329 329 self.kernel_menu = self.menuBar().addMenu("&Kernel")
330 330 self.window_menu = self.menuBar().addMenu("&Window")
331 331 self.magic_menu = self.menuBar().addMenu("&Magic")
332 332 self.all_magic_menu = self.magic_menu.addMenu("&All Magic")
333 333
334 334 # please keep the Help menu in Mac Os even if empty. It will
335 335 # automatically contain a search field to search inside menus and
336 336 # please keep it spelled in English, as long as Qt Doesn't support
337 337 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
338 338 # this search field fonctionnality
339 339
340 340 self.help_menu = self.menuBar().addMenu("&Help")
341 341
342 342 self.print_action = QtGui.QAction("&Print",
343 343 self,
344 344 shortcut="Ctrl+P",
345 345 triggered=self.print_action_active_frontend)
346 346 self.file_menu.addAction(self.print_action)
347 347
348 348 self.export_action=QtGui.QAction("E&xport",
349 349 self,
350 350 shortcut="Ctrl+S",
351 351 triggered=self.export_action_active_frontend
352 352 )
353 353 self.file_menu.addAction(self.export_action)
354 354
355 355 self.select_all_action = QtGui.QAction("Select &All",
356 356 self,
357 357 shortcut="Ctrl+A",
358 358 triggered=self.select_all_active_frontend
359 359 )
360 360 self.file_menu.addAction(self.select_all_action)
361 361
362 362 self.paste_action = QtGui.QAction("&Paste",
363 363 self,
364 364 shortcut=QtGui.QKeySequence.Paste,
365 365 triggered=self.paste_active_frontend
366 366 )
367 367 self.edit_menu.addAction(self.paste_action)
368 368
369 369 self.copy_action = QtGui.QAction("&Copy",
370 370 self,
371 371 shortcut=QtGui.QKeySequence.Copy,
372 372 triggered=self.copy_active_frontend
373 373 )
374 374 self.edit_menu.addAction(self.copy_action)
375 375
376 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
377 self,
378 shortcut="Ctrl+Shift+C",
379 triggered=self.copy_raw_active_frontend
380 )
381 self.edit_menu.addAction(self.copy_raw_action)
382
376 383 self.cut_action = QtGui.QAction("&Cut",
377 384 self,
378 385 shortcut=QtGui.QKeySequence.Cut,
379 386 triggered=self.cut_active_frontend
380 387 )
381 388 self.edit_menu.addAction(self.cut_action)
382 389
383 390 self.edit_menu.addSeparator()
384 391
385 392 self.undo_action = QtGui.QAction("&Undo",
386 393 self,
387 394 shortcut="Ctrl+Z",
388 395 statusTip="Undo last action if possible",
389 396 triggered=self.undo_active_frontend
390 397 )
391 398 self.edit_menu.addAction(self.undo_action)
392 399
393 400 self.redo_action = QtGui.QAction("&Redo",
394 401 self,
395 402 shortcut="Ctrl+Shift+Z",
396 403 statusTip="Redo last action if possible",
397 404 triggered=self.redo_active_frontend)
398 405 self.edit_menu.addAction(self.redo_action)
399 406
400 407 self.window_menu.addSeparator()
401 408
402 409 self.increase_font_size = QtGui.QAction("&Increase Font Size",
403 410 self,
404 411 shortcut="Ctrl++",
405 412 triggered=self.increase_font_size_active_frontend
406 413 )
407 414 self.window_menu.addAction(self.increase_font_size)
408 415
409 416 self.decrease_font_size = QtGui.QAction("&Decrease Font Size",
410 417 self,
411 418 shortcut="Ctrl+-",
412 419 triggered=self.decrease_font_size_active_frontend
413 420 )
414 421 self.window_menu.addAction(self.decrease_font_size)
415 422
416 423 self.reset_font_size = QtGui.QAction("&Reset Font Size",
417 424 self,
418 425 shortcut="Ctrl+0",
419 426 triggered=self.reset_font_size_active_frontend
420 427 )
421 428 self.window_menu.addAction(self.reset_font_size)
422 429
423 430 self.window_menu.addSeparator()
424 431
425 432 self.reset_action = QtGui.QAction("&Reset",
426 433 self,
427 434 statusTip="Clear all varible from workspace",
428 435 triggered=self.reset_magic_active_frontend)
429 436 self.magic_menu.addAction(self.reset_action)
430 437
431 438 self.history_action = QtGui.QAction("&History",
432 439 self,
433 440 statusTip="show command history",
434 441 triggered=self.history_magic_active_frontend)
435 442 self.magic_menu.addAction(self.history_action)
436 443
437 444 self.save_action = QtGui.QAction("E&xport History ",
438 445 self,
439 446 statusTip="Export History as Python File",
440 447 triggered=self.save_magic_active_frontend)
441 448 self.magic_menu.addAction(self.save_action)
442 449
443 450 self.clear_action = QtGui.QAction("&Clear Screen",
444 451 self,
445 452 shortcut='Ctrl+L',
446 453 statusTip="Clear the console",
447 454 triggered=self.clear_magic_active_frontend)
448 455 self.window_menu.addAction(self.clear_action)
449 456
450 457 self.who_action = QtGui.QAction("&Who",
451 458 self,
452 459 statusTip="List interactive variable",
453 460 triggered=self.who_magic_active_frontend)
454 461 self.magic_menu.addAction(self.who_action)
455 462
456 463 self.who_ls_action = QtGui.QAction("Wh&o ls",
457 464 self,
458 465 statusTip="Return a list of interactive variable",
459 466 triggered=self.who_ls_magic_active_frontend)
460 467 self.magic_menu.addAction(self.who_ls_action)
461 468
462 469 self.whos_action = QtGui.QAction("Who&s",
463 470 self,
464 471 statusTip="List interactive variable with detail",
465 472 triggered=self.whos_magic_active_frontend)
466 473 self.magic_menu.addAction(self.whos_action)
467 474
468 475 self.intro_active_frontend_action = QtGui.QAction("Intro",
469 476 self,
470 477 triggered=self.intro_active_frontend
471 478 )
472 479 self.help_menu.addAction(self.intro_active_frontend_action)
473 480
474 481 self.guiref_active_frontend_action = QtGui.QAction("Gui references",
475 482 self,
476 483 triggered=self.guiref_active_frontend
477 484 )
478 485 self.help_menu.addAction(self.guiref_active_frontend_action)
479 486
480 487 self.quickref_active_frontend_action = QtGui.QAction("Quick references",
481 488 self,
482 489 triggered=self.quickref_active_frontend
483 490 )
484 491 self.help_menu.addAction(self.quickref_active_frontend_action)
485 492
493 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
494 self,
495 triggered=self.interrupt_kernel_active_frontend
496 )
497 self.kernel_menu.addAction(self.interrupt_kernel_action)
498
499 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
500 self,
501 triggered=self.restart_kernel_active_frontend
502 )
503 self.kernel_menu.addAction(self.restart_kernel_action)
504 self.kernel_menu.addSeparator()
505
506 #for now this is just a copy and paste, but we should get this dynamically
486 507 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
487 508 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
488 509 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
489 510 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
490 511 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
491 512 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
492 513 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
493 514 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
494 515 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
495 516 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
496 517
497 518 def make_dynamic_magic(i):
498 519 def inner_dynamic_magic():
499 520 self.active_frontend.execute(i)
500 521 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
501 522 return inner_dynamic_magic
502 523
503 524 for magic in magiclist:
504 525 xaction = QtGui.QAction(magic,
505 526 self,
506 527 triggered=make_dynamic_magic(magic)
507 528 )
508 529 self.all_magic_menu.addAction(xaction)
509 530
531 def restart_kernel_active_frontend(self):
532 self.active_frontend.request_restart_kernel()
533
534 def interrupt_kernel_active_frontend(self):
535 self.active_frontend.request_interrupt_kernel()
536
510 537 def cut_active_frontend(self):
511 538 self.active_frontend.cut_action.trigger()
512 539
513 540 def copy_active_frontend(self):
514 541 self.active_frontend.copy_action.trigger()
515 542
543 def copy_raw_active_frontend(self):
544 self.active_frontend._copy_raw_action.trigger()
545
516 546 def paste_active_frontend(self):
517 547 self.active_frontend.paste_action.trigger()
518 548
519 549 def undo_active_frontend(self):
520 550 self.active_frontend.undo()
521 551
522 552 def redo_active_frontend(self):
523 553 self.active_frontend.redo()
524 554
525 555 def reset_magic_active_frontend(self):
526 556 self.active_frontend.execute("%reset")
527 557
528 558 def history_magic_active_frontend(self):
529 559 self.active_frontend.execute("%history")
530 560
531 561 def save_magic_active_frontend(self):
532 562 self.active_frontend.save_magic()
533 563
534 564 def clear_magic_active_frontend(self):
535 565 self.active_frontend.execute("%clear")
536 566
537 567 def who_magic_active_frontend(self):
538 568 self.active_frontend.execute("%who")
539 569
540 570 def who_ls_magic_active_frontend(self):
541 571 self.active_frontend.execute("%who_ls")
542 572
543 573 def whos_magic_active_frontend(self):
544 574 self.active_frontend.execute("%whos")
545 575
546 576 def print_action_active_frontend(self):
547 577 self.active_frontend.print_action.trigger()
548 578
549 579 def export_action_active_frontend(self):
550 580 self.active_frontend.export_action.trigger()
551 581
552 582 def select_all_active_frontend(self):
553 583 self.active_frontend.select_all_action.trigger()
554 584
555 585 def increase_font_size_active_frontend(self):
556 586 self.active_frontend.increase_font_size.trigger()
557 587
558 588 def decrease_font_size_active_frontend(self):
559 589 self.active_frontend.decrease_font_size.trigger()
560 590
561 591 def reset_font_size_active_frontend(self):
562 592 self.active_frontend.reset_font_size.trigger()
563 593
564 594 def guiref_active_frontend(self):
565 595 self.active_frontend.execute("%guiref")
566 596
567 597 def intro_active_frontend(self):
568 598 self.active_frontend.execute("?")
569 599
570 600 def quickref_active_frontend(self):
571 601 self.active_frontend.execute("%quickref")
572 602 #---------------------------------------------------------------------------
573 603 # QWidget interface
574 604 #---------------------------------------------------------------------------
575 605
576 606 def closeEvent(self, event):
577 607 """ Forward the close event to every tabs contained by the windows
578 608 """
579 609 # Do Not loop on the widget count as it change while closing
580 610 widget_list=[ self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
581 611 for widget in widget_list:
582 612 self.close_tab(widget)
583 613 event.accept()
584 614
585 615 #-----------------------------------------------------------------------------
586 616 # Aliases and Flags
587 617 #-----------------------------------------------------------------------------
588 618
589 619 flags = dict(ipkernel_flags)
590 620 qt_flags = {
591 621 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
592 622 "Connect to an existing kernel. If no argument specified, guess most recent"),
593 623 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
594 624 "Use a pure Python kernel instead of an IPython kernel."),
595 625 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
596 626 "Disable rich text support."),
597 627 }
598 628 qt_flags.update(boolean_flag(
599 629 'gui-completion', 'ConsoleWidget.gui_completion',
600 630 "use a GUI widget for tab completion",
601 631 "use plaintext output for completion"
602 632 ))
603 633 qt_flags.update(boolean_flag(
604 634 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
605 635 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
606 636 to force a direct exit without any confirmation.
607 637 """,
608 638 """Don't prompt the user when exiting. This will terminate the kernel
609 639 if it is owned by the frontend, and leave it alive if it is external.
610 640 """
611 641 ))
612 642 flags.update(qt_flags)
613 643
614 644 aliases = dict(ipkernel_aliases)
615 645
616 646 qt_aliases = dict(
617 647 hb = 'IPythonQtConsoleApp.hb_port',
618 648 shell = 'IPythonQtConsoleApp.shell_port',
619 649 iopub = 'IPythonQtConsoleApp.iopub_port',
620 650 stdin = 'IPythonQtConsoleApp.stdin_port',
621 651 ip = 'IPythonQtConsoleApp.ip',
622 652 existing = 'IPythonQtConsoleApp.existing',
623 653 f = 'IPythonQtConsoleApp.connection_file',
624 654
625 655 style = 'IPythonWidget.syntax_style',
626 656 stylesheet = 'IPythonQtConsoleApp.stylesheet',
627 657 colors = 'ZMQInteractiveShell.colors',
628 658
629 659 editor = 'IPythonWidget.editor',
630 660 paging = 'ConsoleWidget.paging',
631 661 ssh = 'IPythonQtConsoleApp.sshserver',
632 662 )
633 663 aliases.update(qt_aliases)
634 664
635 665
636 666 #-----------------------------------------------------------------------------
637 667 # IPythonQtConsole
638 668 #-----------------------------------------------------------------------------
639 669
640 670
641 671 class IPythonQtConsoleApp(BaseIPythonApplication):
642 672 name = 'ipython-qtconsole'
643 673 default_config_file_name='ipython_config.py'
644 674
645 675 description = """
646 676 The IPython QtConsole.
647 677
648 678 This launches a Console-style application using Qt. It is not a full
649 679 console, in that launched terminal subprocesses will not be able to accept
650 680 input.
651 681
652 682 The QtConsole supports various extra features beyond the Terminal IPython
653 683 shell, such as inline plotting with matplotlib, via:
654 684
655 685 ipython qtconsole --pylab=inline
656 686
657 687 as well as saving your session as HTML, and printing the output.
658 688
659 689 """
660 690 examples = _examples
661 691
662 692 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
663 693 flags = Dict(flags)
664 694 aliases = Dict(aliases)
665 695
666 696 kernel_argv = List(Unicode)
667 697
668 698 # create requested profiles by default, if they don't exist:
669 699 auto_create = CBool(True)
670 700 # connection info:
671 701 ip = Unicode(LOCALHOST, config=True,
672 702 help="""Set the kernel\'s IP address [default localhost].
673 703 If the IP address is something other than localhost, then
674 704 Consoles on other machines will be able to connect
675 705 to the Kernel, so be careful!"""
676 706 )
677 707
678 708 sshserver = Unicode('', config=True,
679 709 help="""The SSH server to use to connect to the kernel.""")
680 710 sshkey = Unicode('', config=True,
681 711 help="""Path to the ssh key to use for logging in to the ssh server.""")
682 712
683 713 hb_port = Int(0, config=True,
684 714 help="set the heartbeat port [default: random]")
685 715 shell_port = Int(0, config=True,
686 716 help="set the shell (XREP) port [default: random]")
687 717 iopub_port = Int(0, config=True,
688 718 help="set the iopub (PUB) port [default: random]")
689 719 stdin_port = Int(0, config=True,
690 720 help="set the stdin (XREQ) port [default: random]")
691 721 connection_file = Unicode('', config=True,
692 722 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
693 723
694 724 This file will contain the IP, ports, and authentication key needed to connect
695 725 clients to this kernel. By default, this file will be created in the security-dir
696 726 of the current profile, but can be specified by absolute path.
697 727 """)
698 728 def _connection_file_default(self):
699 729 return 'kernel-%i.json' % os.getpid()
700 730
701 731 existing = Unicode('', config=True,
702 732 help="""Connect to an already running kernel""")
703 733
704 734 stylesheet = Unicode('', config=True,
705 735 help="path to a custom CSS stylesheet")
706 736
707 737 pure = CBool(False, config=True,
708 738 help="Use a pure Python kernel instead of an IPython kernel.")
709 739 plain = CBool(False, config=True,
710 740 help="Use a plaintext widget instead of rich text (plain can't print/save).")
711 741
712 742 def _pure_changed(self, name, old, new):
713 743 kind = 'plain' if self.plain else 'rich'
714 744 self.config.ConsoleWidget.kind = kind
715 745 if self.pure:
716 746 self.widget_factory = FrontendWidget
717 747 elif self.plain:
718 748 self.widget_factory = IPythonWidget
719 749 else:
720 750 self.widget_factory = RichIPythonWidget
721 751
722 752 _plain_changed = _pure_changed
723 753
724 754 confirm_exit = CBool(True, config=True,
725 755 help="""
726 756 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
727 757 to force a direct exit without any confirmation.""",
728 758 )
729 759
730 760 # the factory for creating a widget
731 761 widget_factory = Any(RichIPythonWidget)
732 762
733 763 def parse_command_line(self, argv=None):
734 764 super(IPythonQtConsoleApp, self).parse_command_line(argv)
735 765 if argv is None:
736 766 argv = sys.argv[1:]
737 767
738 768 self.kernel_argv = list(argv) # copy
739 769 # kernel should inherit default config file from frontend
740 770 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
741 771 # Scrub frontend-specific flags
742 772 for a in argv:
743 773 if a.startswith('-') and a.lstrip('-') in qt_flags:
744 774 self.kernel_argv.remove(a)
745 775 swallow_next = False
746 776 for a in argv:
747 777 if swallow_next:
748 778 self.kernel_argv.remove(a)
749 779 swallow_next = False
750 780 continue
751 781 if a.startswith('-'):
752 782 split = a.lstrip('-').split('=')
753 783 alias = split[0]
754 784 if alias in qt_aliases:
755 785 self.kernel_argv.remove(a)
756 786 if len(split) == 1:
757 787 # alias passed with arg via space
758 788 swallow_next = True
759 789
760 790 def init_connection_file(self):
761 791 """find the connection file, and load the info if found.
762 792
763 793 The current working directory and the current profile's security
764 794 directory will be searched for the file if it is not given by
765 795 absolute path.
766 796
767 797 When attempting to connect to an existing kernel and the `--existing`
768 798 argument does not match an existing file, it will be interpreted as a
769 799 fileglob, and the matching file in the current profile's security dir
770 800 with the latest access time will be used.
771 801 """
772 802 if self.existing:
773 803 try:
774 804 cf = find_connection_file(self.existing)
775 805 except Exception:
776 806 self.log.critical("Could not find existing kernel connection file %s", self.existing)
777 807 self.exit(1)
778 808 self.log.info("Connecting to existing kernel: %s" % cf)
779 809 self.connection_file = cf
780 810 # should load_connection_file only be used for existing?
781 811 # as it is now, this allows reusing ports if an existing
782 812 # file is requested
783 813 try:
784 814 self.load_connection_file()
785 815 except Exception:
786 816 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
787 817 self.exit(1)
788 818
789 819 def load_connection_file(self):
790 820 """load ip/port/hmac config from JSON connection file"""
791 821 # this is identical to KernelApp.load_connection_file
792 822 # perhaps it can be centralized somewhere?
793 823 try:
794 824 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
795 825 except IOError:
796 826 self.log.debug("Connection File not found: %s", self.connection_file)
797 827 return
798 828 self.log.debug(u"Loading connection file %s", fname)
799 829 with open(fname) as f:
800 830 s = f.read()
801 831 cfg = json.loads(s)
802 832 if self.ip == LOCALHOST and 'ip' in cfg:
803 833 # not overridden by config or cl_args
804 834 self.ip = cfg['ip']
805 835 for channel in ('hb', 'shell', 'iopub', 'stdin'):
806 836 name = channel + '_port'
807 837 if getattr(self, name) == 0 and name in cfg:
808 838 # not overridden by config or cl_args
809 839 setattr(self, name, cfg[name])
810 840 if 'key' in cfg:
811 841 self.config.Session.key = str_to_bytes(cfg['key'])
812 842
813 843 def init_ssh(self):
814 844 """set up ssh tunnels, if needed."""
815 845 if not self.sshserver and not self.sshkey:
816 846 return
817 847
818 848 if self.sshkey and not self.sshserver:
819 849 # specifying just the key implies that we are connecting directly
820 850 self.sshserver = self.ip
821 851 self.ip = LOCALHOST
822 852
823 853 # build connection dict for tunnels:
824 854 info = dict(ip=self.ip,
825 855 shell_port=self.shell_port,
826 856 iopub_port=self.iopub_port,
827 857 stdin_port=self.stdin_port,
828 858 hb_port=self.hb_port
829 859 )
830 860
831 861 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
832 862
833 863 # tunnels return a new set of ports, which will be on localhost:
834 864 self.ip = LOCALHOST
835 865 try:
836 866 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
837 867 except:
838 868 # even catch KeyboardInterrupt
839 869 self.log.error("Could not setup tunnels", exc_info=True)
840 870 self.exit(1)
841 871
842 872 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
843 873
844 874 cf = self.connection_file
845 875 base,ext = os.path.splitext(cf)
846 876 base = os.path.basename(base)
847 877 self.connection_file = os.path.basename(base)+'-ssh'+ext
848 878 self.log.critical("To connect another client via this tunnel, use:")
849 879 self.log.critical("--existing %s" % self.connection_file)
850 880
851 881 def init_kernel_manager(self):
852 882 # Don't let Qt or ZMQ swallow KeyboardInterupts.
853 883 signal.signal(signal.SIGINT, signal.SIG_DFL)
854 884 sec = self.profile_dir.security_dir
855 885 try:
856 886 cf = filefind(self.connection_file, ['.', sec])
857 887 except IOError:
858 888 # file might not exist
859 889 if self.connection_file == os.path.basename(self.connection_file):
860 890 # just shortname, put it in security dir
861 891 cf = os.path.join(sec, self.connection_file)
862 892 else:
863 893 cf = self.connection_file
864 894
865 895 # Create a KernelManager and start a kernel.
866 896 self.kernel_manager = QtKernelManager(
867 897 ip=self.ip,
868 898 shell_port=self.shell_port,
869 899 iopub_port=self.iopub_port,
870 900 stdin_port=self.stdin_port,
871 901 hb_port=self.hb_port,
872 902 connection_file=cf,
873 903 config=self.config,
874 904 )
875 905 # start the kernel
876 906 if not self.existing:
877 907 kwargs = dict(ipython=not self.pure)
878 908 kwargs['extra_arguments'] = self.kernel_argv
879 909 self.kernel_manager.start_kernel(**kwargs)
880 910 elif self.sshserver:
881 911 # ssh, write new connection file
882 912 self.kernel_manager.write_connection_file()
883 913 self.kernel_manager.start_channels()
884 914
885 915 def create_tab_with_new_frontend(self):
886 916 """ Create new tab attached to new kernel, launched on localhost.
887 917 """
888 918 kernel_manager = QtKernelManager(
889 919 shell_address=(LOCALHOST,0 ),
890 920 sub_address=(LOCALHOST, 0),
891 921 stdin_address=(LOCALHOST, 0),
892 922 hb_address=(LOCALHOST, 0),
893 923 config=self.config
894 924 )
895 925 # start the kernel
896 926 kwargs = dict(ip=LOCALHOST, ipython=not self.pure)
897 927 kwargs['extra_arguments'] = self.kernel_argv
898 928 kernel_manager.start_kernel(**kwargs)
899 929 kernel_manager.start_channels()
900 930 local_kernel = (not False) or self.ip in LOCAL_IPS
901 931 widget = self.widget_factory(config=self.config,
902 932 local_kernel=local_kernel)
903 933 widget.kernel_manager = kernel_manager
904 934 widget._existing=False;
905 935 widget._confirm_exit=True;
906 936 widget._may_close=True;
907 937 self.window.add_tab_with_frontend(widget)
908 938
909 939 def create_tab_attached_to_current_tab_kernel(self):
910 940 current_widget = self.window.tab_widget.currentWidget()
911 941 current_widget_index = self.window.tab_widget.indexOf(current_widget)
912 942 current_widget.kernel_manager = current_widget.kernel_manager;
913 943 current_widget_name = self.window.tab_widget.tabText(current_widget_index);
914 944 kernel_manager = QtKernelManager(
915 945 shell_address = current_widget.kernel_manager.shell_address,
916 946 sub_address = current_widget.kernel_manager.sub_address,
917 947 stdin_address = current_widget.kernel_manager.stdin_address,
918 948 hb_address = current_widget.kernel_manager.hb_address,
919 949 config = self.config
920 950 )
921 951 kernel_manager.start_channels()
922 952 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
923 953 widget = self.widget_factory(config=self.config,
924 954 local_kernel=False)
925 955 widget._confirm_exit=True;
926 956 widget._may_close=False;
927 957 widget.kernel_manager = kernel_manager
928 958 self.window.add_tab_with_frontend(widget,name=str('('+current_widget_name+') slave'))
929 959
930 960 def init_qt_elements(self):
931 961 # Create the widget.
932 962 self.app = QtGui.QApplication([])
933 963
934 964 base_path = os.path.abspath(os.path.dirname(__file__))
935 965 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
936 966 self.app.icon = QtGui.QIcon(icon_path)
937 967 QtGui.QApplication.setWindowIcon(self.app.icon)
938 968
939 969 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
940 970 self.widget = self.widget_factory(config=self.config,
941 971 local_kernel=local_kernel)
942 972 self.widget._existing = self.existing;
943 973 self.widget._may_close = not self.existing;
944 974 self.widget._confirm_exit = not self.existing;
945 975
946 976 self.widget.kernel_manager = self.kernel_manager
947 977 self.window = MainWindow(self.app, self.widget, self.existing,
948 978 may_close=local_kernel,
949 979 confirm_exit=self.confirm_exit)
950 980 self.window.log = self.log
951 981 self.window.add_tab_with_frontend(self.widget)
952 982 self.window.init_menu_bar()
953 983 self.window.setWindowTitle('Python' if self.pure else 'IPython')
954 984
955 985 def init_colors(self):
956 986 """Configure the coloring of the widget"""
957 987 # Note: This will be dramatically simplified when colors
958 988 # are removed from the backend.
959 989
960 990 if self.pure:
961 991 # only IPythonWidget supports styling
962 992 return
963 993
964 994 # parse the colors arg down to current known labels
965 995 try:
966 996 colors = self.config.ZMQInteractiveShell.colors
967 997 except AttributeError:
968 998 colors = None
969 999 try:
970 1000 style = self.config.IPythonWidget.colors
971 1001 except AttributeError:
972 1002 style = None
973 1003
974 1004 # find the value for colors:
975 1005 if colors:
976 1006 colors=colors.lower()
977 1007 if colors in ('lightbg', 'light'):
978 1008 colors='lightbg'
979 1009 elif colors in ('dark', 'linux'):
980 1010 colors='linux'
981 1011 else:
982 1012 colors='nocolor'
983 1013 elif style:
984 1014 if style=='bw':
985 1015 colors='nocolor'
986 1016 elif styles.dark_style(style):
987 1017 colors='linux'
988 1018 else:
989 1019 colors='lightbg'
990 1020 else:
991 1021 colors=None
992 1022
993 1023 # Configure the style.
994 1024 widget = self.widget
995 1025 if style:
996 1026 widget.style_sheet = styles.sheet_from_template(style, colors)
997 1027 widget.syntax_style = style
998 1028 widget._syntax_style_changed()
999 1029 widget._style_sheet_changed()
1000 1030 elif colors:
1001 1031 # use a default style
1002 1032 widget.set_default_style(colors=colors)
1003 1033 else:
1004 1034 # this is redundant for now, but allows the widget's
1005 1035 # defaults to change
1006 1036 widget.set_default_style()
1007 1037
1008 1038 if self.stylesheet:
1009 1039 # we got an expicit stylesheet
1010 1040 if os.path.isfile(self.stylesheet):
1011 1041 with open(self.stylesheet) as f:
1012 1042 sheet = f.read()
1013 1043 widget.style_sheet = sheet
1014 1044 widget._style_sheet_changed()
1015 1045 else:
1016 1046 raise IOError("Stylesheet %r not found."%self.stylesheet)
1017 1047
1018 1048 def initialize(self, argv=None):
1019 1049 super(IPythonQtConsoleApp, self).initialize(argv)
1020 1050 self.init_connection_file()
1021 1051 default_secure(self.config)
1022 1052 self.init_ssh()
1023 1053 self.init_kernel_manager()
1024 1054 self.init_qt_elements()
1025 1055 self.init_colors()
1026 1056 self.init_window_shortcut()
1027 1057
1028 1058 def init_window_shortcut(self):
1029 1059
1030 1060 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
1031 1061 self.window,
1032 1062 shortcut="Ctrl+PgDown",
1033 1063 statusTip="Cahange to next tab",
1034 1064 triggered=self.window.prev_tab)
1035 1065
1036 1066 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
1037 1067 self.window,
1038 1068 shortcut="Ctrl+PgUp",
1039 1069 statusTip="Cahange to next tab",
1040 1070 triggered=self.window.next_tab)
1041 1071
1042 1072 self.fullScreenAct = QtGui.QAction("&Full Screen",
1043 1073 self.window,
1044 1074 shortcut="Ctrl+Meta+Space",
1045 1075 statusTip="Toggle between Fullscreen and Normal Size",
1046 1076 triggered=self.toggleFullScreen)
1047 1077
1048 1078
1049 1079
1050 1080 self.tabAndNewKernelAct =QtGui.QAction("Tab with &New kernel",
1051 1081 self.window,
1052 1082 shortcut="Ctrl+T",
1053 1083 triggered=self.create_tab_with_new_frontend)
1054 1084 self.window.kernel_menu.addAction(self.tabAndNewKernelAct)
1055 1085
1056 1086 self.tabSameKernalAct =QtGui.QAction("Tab with Sa&me kernel",
1057 1087 self.window,
1058 1088 shortcut="Ctrl+Shift+T",
1059 1089 triggered=self.create_tab_attached_to_current_tab_kernel)
1060 1090 self.window.kernel_menu.addAction(self.tabSameKernalAct)
1061 1091 self.window.kernel_menu.addSeparator()
1062 1092
1063 1093 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
1064 1094 self.window,
1065 1095 triggered=self._open_online_help)
1066 1096 self.window.help_menu.addAction(self.onlineHelpAct)
1067 1097 # creating shortcut in menubar only for Mac OS as I don't
1068 1098 # know the shortcut or if the windows manager assign it in
1069 1099 # other platform.
1070 1100 if sys.platform == 'darwin':
1071 1101 self.minimizeAct = QtGui.QAction("Mini&mize",
1072 1102 self.window,
1073 1103 shortcut="Ctrl+m",
1074 1104 statusTip="Minimize the window/Restore Normal Size",
1075 1105 triggered=self.toggleMinimized)
1076 1106 self.maximizeAct = QtGui.QAction("Ma&ximize",
1077 1107 self.window,
1078 1108 shortcut="Ctrl+Shift+M",
1079 1109 statusTip="Maximize the window/Restore Normal Size",
1080 1110 triggered=self.toggleMaximized)
1081 1111
1082 1112
1083 1113 self.window_menu = self.window.window_menu
1084 1114 self.kernel_menu = self.window.kernel_menu
1085 1115
1086 1116 self.kernel_menu.addAction(self.next_tab_act)
1087 1117 self.kernel_menu.addAction(self.prev_tab_act)
1088 1118 self.window_menu.addSeparator()
1089 1119 self.window_menu.addAction(self.minimizeAct)
1090 1120 self.window_menu.addAction(self.maximizeAct)
1091 1121 self.window_menu.addSeparator()
1092 1122 self.window_menu.addAction(self.fullScreenAct)
1093 1123
1094 1124 else:
1095 1125 # if we don't put it in a menu, we add it to the window so
1096 1126 # that it can still be triggerd by shortcut
1097 1127 self.window.addAction(self.fullScreenAct)
1098 1128
1099 1129 # Don't activate toggleMenubar on mac, doen't work,
1100 1130 # as toolbar always here
1101 1131 self.toggle_menu_bar_act = QtGui.QAction("&Toggle Menu Bar",
1102 1132 self.window,
1103 1133 shortcut="Ctrl+Meta+H",
1104 1134 statusTip="Toggle menubar betwin visible and not",
1105 1135 triggered=self.toggle_menu_bar)
1106 1136 self.window_menu.addAction(self.toggle_menu_bar_act)
1107 1137
1108 1138 def toggle_menu_bar(self):
1109 1139 menu_bar = self.window.menuBar();
1110 1140 if not menu_bar.isVisible():
1111 1141 menu_bar.setVisible(False)
1112 1142 else:
1113 1143 menu_bar.setVisible(True)
1114 1144
1115 1145 def toggleMinimized(self):
1116 1146 if not self.window.isMinimized():
1117 1147 self.window.showMinimized()
1118 1148 else:
1119 1149 self.window.showNormal()
1120 1150
1121 1151 def _open_online_help(self):
1122 1152 filename="http://ipython.org/ipython-doc/stable/index.html"
1123 1153 webbrowser.open(filename, new=1, autoraise=True)
1124 1154
1125 1155 def toggleMaximized(self):
1126 1156 if not self.window.isMaximized():
1127 1157 self.window.showMaximized()
1128 1158 else:
1129 1159 self.window.showNormal()
1130 1160
1131 1161 # Min/Max imizing while in full screen give a bug
1132 1162 # when going out of full screen, at least on OSX
1133 1163 def toggleFullScreen(self):
1134 1164 if not self.window.isFullScreen():
1135 1165 self.window.showFullScreen()
1136 1166 if sys.platform == 'darwin':
1137 1167 self.maximizeAct.setEnabled(False)
1138 1168 self.minimizeAct.setEnabled(False)
1139 1169 else:
1140 1170 self.window.showNormal()
1141 1171 if sys.platform == 'darwin':
1142 1172 self.maximizeAct.setEnabled(True)
1143 1173 self.minimizeAct.setEnabled(True)
1144 1174
1145 1175 def start(self):
1146 1176
1147 1177 # draw the window
1148 1178 self.window.show()
1149 1179
1150 1180 # Start the application main loop.
1151 1181 self.app.exec_()
1152 1182
1153 1183 #-----------------------------------------------------------------------------
1154 1184 # Main entry point
1155 1185 #-----------------------------------------------------------------------------
1156 1186
1157 1187 def main():
1158 1188 app = IPythonQtConsoleApp()
1159 1189 app.initialize()
1160 1190 app.start()
1161 1191
1162 1192
1163 1193 if __name__ == '__main__':
1164 1194 main()
General Comments 0
You need to be logged in to leave comments. Login now