##// END OF EJS Templates
Work on QtConsole Menu...
MinRK -
Show More
This diff has been collapsed as it changes many lines, (577 lines changed) Show them Hide them
@@ -1,1187 +1,1290 b''
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 import uuid
26 26 from getpass import getpass
27 27
28 28 # System library imports
29 29 from IPython.external.qt import QtGui,QtCore
30 30 from pygments.styles import get_all_styles
31 31
32 32 # Local imports
33 33 from IPython.config.application import boolean_flag
34 34 from IPython.core.application import BaseIPythonApplication
35 35 from IPython.core.profiledir import ProfileDir
36 36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
37 37 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
38 38 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
39 39 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
40 40 from IPython.frontend.qt.console import styles
41 41 from IPython.frontend.qt.kernelmanager import QtKernelManager
42 42 from IPython.utils.path import filefind
43 43 from IPython.utils.py3compat import str_to_bytes
44 44 from IPython.utils.traitlets import (
45 45 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
46 46 )
47 47 from IPython.zmq.ipkernel import (
48 48 flags as ipkernel_flags,
49 49 aliases as ipkernel_aliases,
50 50 IPKernelApp
51 51 )
52 52 from IPython.zmq.session import Session, default_secure
53 53 from IPython.zmq.zmqshell import ZMQInteractiveShell
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Network Constants
57 57 #-----------------------------------------------------------------------------
58 58
59 59 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Globals
63 63 #-----------------------------------------------------------------------------
64 64
65 65 _examples = """
66 66 ipython qtconsole # start the qtconsole
67 67 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
68 68 """
69 69
70 70 #-----------------------------------------------------------------------------
71 71 # Classes
72 72 #-----------------------------------------------------------------------------
73 73
74 74 class MainWindow(QtGui.QMainWindow):
75 75
76 76 #---------------------------------------------------------------------------
77 77 # 'object' interface
78 78 #---------------------------------------------------------------------------
79 79
80 80 def __init__(self, app, frontend, existing=False, may_close=True,
81 confirm_exit=True):
82 """ Create a MainWindow for the specified FrontendWidget.
81 confirm_exit=True,
82 new_frontend_factory=None, slave_frontend_factory=None,
83 ):
84 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
83 85
84 The app is passed as an argument to allow for different
85 closing behavior depending on whether we are the Kernel's parent.
86 Parameters
87 ----------
86 88
87 If existing is True, then this Console does not own the Kernel.
88
89 If may_close is True, then this Console is permitted to close the kernel
89 app : reference to QApplication parent
90 frontend : IPythonWidget
91 The first IPython frontend to start with
92 existing : bool, optional
93 Whether the first frontend is connected to en existing Kernel,
94 or if we own it.
95 may_close : bool, optional
96 Whether we are permitted to close the kernel (determines close dialog behavior)
97 confirm_exit : bool, optional
98 Whether we should prompt on close of tabs
99 new_frontend_factory : callable
100 A callable that returns a new IPythonWidget instance, attached to
101 its own running kernel.
102 slave_frontend_factory : callable
103 A callable that takes an existing IPythonWidget, and returns a new
104 IPythonWidget instance, attached to the same kernel.
90 105 """
91 106
92 107 super(MainWindow, self).__init__()
93 108 self._app = app
109 self.new_frontend_factory = new_frontend_factory
110 self.slave_frontend_factory = slave_frontend_factory
94 111
95 112 self.tab_widget = QtGui.QTabWidget(self)
96 113 self.tab_widget.setDocumentMode(True)
97 114 self.tab_widget.setTabsClosable(True)
98 115 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
99 116
100 117 self.setCentralWidget(self.tab_widget)
101 118 self.update_tab_bar_visibility()
102 119
103 120 def update_tab_bar_visibility(self):
104 121 """ update visibility of the tabBar depending of the number of tab
105 122
106 123 0 or 1 tab, tabBar hidden
107 124 2+ tabs, tabBar visible
108 125
109 126 send a self.close if number of tab ==0
110 127
111 128 need to be called explicitely, or be connected to tabInserted/tabRemoved
112 129 """
113 130 if self.tab_widget.count() <= 1:
114 131 self.tab_widget.tabBar().setVisible(False)
115 132 else:
116 133 self.tab_widget.tabBar().setVisible(True)
117 134 if self.tab_widget.count()==0 :
118 135 self.close()
119 136
120 137 @property
121 138 def active_frontend(self):
122 139 return self.tab_widget.currentWidget()
140
141 def create_tab_with_new_frontend(self):
142 """create a new frontend and attach it to a new tab"""
143 widget = self.new_frontend_factory()
144 self.add_tab_with_frontend(widget)
145
146 def create_tab_with_current_kernel(self):
147 """create a new frontend attached to the same kernel as the current tab"""
148 current_widget = self.tab_widget.currentWidget()
149 current_widget_index = self.tab_widget.indexOf(current_widget)
150 current_widget_name = self.tab_widget.tabText(current_widget_index)
151 widget = self.slave_frontend_factory(current_widget)
152 if 'slave' in current_widget_name:
153 # don't keep stacking slaves
154 name = current_widget_name
155 else:
156 name = str('('+current_widget_name+') slave')
157 self.add_tab_with_frontend(widget,name=name)
123 158
124 159 def close_tab(self,current_tab):
125 160 """ Called when you need to try to close a tab.
126 161
127 162 It takes the number of the tab to be closed as argument, or a referece
128 163 to the wiget insite this tab
129 164 """
130 165
131 166 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
132 167 # and a reference to the trontend to close
133 168 if type(current_tab) is not int :
134 169 current_tab = self.tab_widget.indexOf(current_tab)
135 170 closing_widget=self.tab_widget.widget(current_tab)
136 171
137 172
138 173 # when trying to be closed, widget might re-send a request to be closed again, but will
139 174 # be deleted when event will be processed. So need to check that widget still exist and
140 175 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
141 176 # re-send by this fonction on the master widget, which ask all slaves widget to exit
142 177 if closing_widget==None:
143 178 return
144 179
145 180 #get a list of all wwidget not owning the kernel.
146 181 slave_tabs=self.find_slaves_tabs(closing_widget)
147 182
148 183 keepkernel = None #Use the prompt by default
149 184 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
150 185 keepkernel = closing_widget._keep_kernel_on_exit
151 186 # If signal sent by exist magic (_keep_kernel_on_exit, exist and not None)
152 187 # we set local slave tabs._hidden to True to avoit prompting for kernel
153 188 # restart when they litt get the signal. and the "forward" the 'exit'
154 189 # to the main win
155 190 if keepkernel is not None:
156 191 for tab in slave_tabs:
157 192 tab._hidden = True
158 193 if closing_widget in slave_tabs :
159 194 try :
160 195 self.find_master_tab(closing_widget).execute('exit')
161 196 except AttributeError:
162 197 self.log.info("Master already closed or not local, closing only current tab")
163 198 self.tab_widget.removeTab(current_tab)
164 199 return
165 200
166 201 kernel_manager = closing_widget.kernel_manager
167 202
168 203 if keepkernel is None and not closing_widget._confirm_exit:
169 204 # don't prompt, just terminate the kernel if we own it
170 205 # or leave it alone if we don't
171 206 keepkernel = not closing_widget._existing
172 207
173 208 if keepkernel is None: #show prompt
174 209 if kernel_manager and kernel_manager.channels_running:
175 210 title = self.window().windowTitle()
176 211 cancel = QtGui.QMessageBox.Cancel
177 212 okay = QtGui.QMessageBox.Ok
178 213 if closing_widget._may_close:
179 214 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
180 215 info = "Would you like to quit the Kernel and all attached Consoles as well?"
181 216 justthis = QtGui.QPushButton("&No, just this Console", self)
182 217 justthis.setShortcut('N')
183 218 closeall = QtGui.QPushButton("&Yes, quit everything", self)
184 219 closeall.setShortcut('Y')
185 220 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
186 221 title, msg)
187 222 box.setInformativeText(info)
188 223 box.addButton(cancel)
189 224 box.addButton(justthis, QtGui.QMessageBox.NoRole)
190 225 box.addButton(closeall, QtGui.QMessageBox.YesRole)
191 226 box.setDefaultButton(closeall)
192 227 box.setEscapeButton(cancel)
193 228 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
194 229 box.setIconPixmap(pixmap)
195 230 reply = box.exec_()
196 231 if reply == 1: # close All
197 232 for slave in slave_tabs:
198 233 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
199 234 closing_widget.execute("exit")
200 235 self.tab_widget.removeTab(current_tab)
201 236 elif reply == 0: # close Console
202 237 if not closing_widget._existing:
203 238 # Have kernel: don't quit, just close the window
204 239 self._app.setQuitOnLastWindowClosed(False)
205 240 closing_widget.execute("exit True")
206 241 else:
207 242 reply = QtGui.QMessageBox.question(self, title,
208 243 "Are you sure you want to close this Console?"+
209 244 "\nThe Kernel and other Consoles will remain active.",
210 245 okay|cancel,
211 246 defaultButton=okay
212 247 )
213 248 if reply == okay:
214 249 self.tab_widget.removeTab(current_tab)
215 250 elif keepkernel: #close console but leave kernel running (no prompt)
216 251 if kernel_manager and kernel_manager.channels_running:
217 252 if not closing_widget._existing:
218 253 # I have the kernel: don't quit, just close the window
219 254 self.tab_widget.removeTab(current_tab)
220 255 else: #close console and kernel (no prompt)
221 256 if kernel_manager and kernel_manager.channels_running:
222 257 for slave in slave_tabs:
223 258 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
224 259 self.tab_widget.removeTab(current_tab)
225 260 kernel_manager.shutdown_kernel()
226 261 self.update_tab_bar_visibility()
227 262
228 263 def add_tab_with_frontend(self,frontend,name=None):
229 264 """ insert a tab with a given frontend in the tab bar, and give it a name
230 265
231 266 """
232 267 if not name:
233 268 name=str('kernel '+str(self.tab_widget.count()))
234 269 self.tab_widget.addTab(frontend,name)
235 270 self.update_tab_bar_visibility()
236 271 self.make_frontend_visible(frontend)
237 272 frontend.exit_requested.connect(self.close_tab)
238 273
239 274 def next_tab(self):
240 275 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
241 276
242 277 def prev_tab(self):
243 278 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
244 279
245 280 def make_frontend_visible(self,frontend):
246 281 widget_index=self.tab_widget.indexOf(frontend)
247 282 if widget_index > 0 :
248 283 self.tab_widget.setCurrentIndex(widget_index)
249 284
250 285 def find_master_tab(self,tab,as_list=False):
251 286 """
252 287 Try to return the frontend that own the kernel attached to the given widget/tab.
253 288
254 289 Only find frontend owed by the current application. Selection
255 290 based on port of the kernel, might be inacurate if several kernel
256 291 on different ip use same port number.
257 292
258 293 This fonction does the conversion tabNumber/widget if needed.
259 294 Might return None if no master widget (non local kernel)
260 295 Will crash IPython if more than 1 masterWidget
261 296
262 297 When asList set to True, always return a list of widget(s) owning
263 298 the kernel. The list might be empty or containing several Widget.
264 299 """
265 300
266 301 #convert from/to int/richIpythonWidget if needed
267 302 if type(tab) == int:
268 303 tab = self.tab_widget.widget(tab)
269 304 km=tab.kernel_manager;
270 305
271 306 #build list of all widgets
272 307 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
273 308
274 309 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
275 310 # And should have a _may_close attribute
276 311 filtred_widget_list = [ widget for widget in widget_list if
277 312 widget.kernel_manager.connection_file == km.connection_file and
278 313 hasattr(widget,'_may_close') ]
279 314 # the master widget is the one that may close the kernel
280 315 master_widget= [ widget for widget in filtred_widget_list if widget._may_close]
281 316 if as_list:
282 317 return master_widget
283 318 assert(len(master_widget)<=1 )
284 319 if len(master_widget)==0:
285 320 return None
286 321
287 322 return master_widget[0]
288 323
289 324 def find_slaves_tabs(self,tab):
290 325 """
291 326 Try to return all the frontend that do not own the kernel attached to the given widget/tab.
292 327
293 328 Only find frontend owed by the current application. Selection
294 329 based on port of the kernel, might be innacurate if several kernel
295 330 on different ip use same port number.
296 331
297 332 This fonction does the conversion tabNumber/widget if needed.
298 333 """
299 334 #convert from/to int/richIpythonWidget if needed
300 335 if type(tab) == int:
301 336 tab = self.tab_widget.widget(tab)
302 337 km=tab.kernel_manager;
303 338
304 339 #build list of all widgets
305 340 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
306 341
307 342 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
308 343 filtered_widget_list = ( widget for widget in widget_list if
309 344 widget.kernel_manager.connection_file == km.connection_file)
310 345 # Get a list of all widget owning the same kernel and removed it from
311 346 # the previous cadidate. (better using sets ?)
312 347 master_widget_list = self.find_master_tab(tab,as_list=True)
313 348 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
314 349
315 350 return slave_list
316 351
317 # MenuBar is always present on Mac Os, so let's populate it with possible
318 # action, don't do it on other platform as some user might not want the
319 # menu bar, or give them an option to remove it
352 # Populate the menu bar with common actions and shortcuts
353 def add_menu_action(self, menu, action):
354 """Add action to menu as well as self
355
356 So that when the menu bar is invisible, its actions are still available.
357 """
358 menu.addAction(action)
359 self.addAction(action)
360
320 361 def init_menu_bar(self):
321 362 #create menu in the order they should appear in the menu bar
363 self.init_file_menu()
364 self.init_edit_menu()
365 self.init_view_menu()
366 self.init_kernel_menu()
367 self.init_magic_menu()
368 self.init_window_menu()
369 self.init_help_menu()
370
371 def init_file_menu(self):
322 372 self.file_menu = self.menuBar().addMenu("&File")
323 self.edit_menu = self.menuBar().addMenu("&Edit")
324 self.kernel_menu = self.menuBar().addMenu("&Kernel")
325 self.window_menu = self.menuBar().addMenu("&Window")
326 self.magic_menu = self.menuBar().addMenu("&Magic")
327 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
328
329 # please keep the Help menu in Mac Os even if empty. It will
330 # automatically contain a search field to search inside menus and
331 # please keep it spelled in English, as long as Qt Doesn't support
332 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
333 # this search field fonctionnality
373
374 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
375 self,
376 shortcut="Ctrl+T",
377 triggered=self.create_tab_with_new_frontend)
378 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
334 379
335 self.help_menu = self.menuBar().addMenu("&Help")
380 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
381 self,
382 shortcut="Ctrl+Shift+T",
383 triggered=self.create_tab_with_current_kernel)
384 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
385
386 self.file_menu.addSeparator()
336 387
337 self.print_action = QtGui.QAction("&Print",
388 self.close_action=QtGui.QAction("&Close Tab",
338 389 self,
339 shortcut="Ctrl+P",
340 triggered=self.print_action_active_frontend)
341 self.file_menu.addAction(self.print_action)
390 shortcut="Ctrl+W",
391 triggered=self.close_active_frontend
392 )
393 self.add_menu_action(self.file_menu, self.close_action)
342 394
343 self.export_action=QtGui.QAction("E&xport",
395 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
344 396 self,
345 397 shortcut="Ctrl+S",
346 398 triggered=self.export_action_active_frontend
347 399 )
348 self.file_menu.addAction(self.export_action)
400 self.add_menu_action(self.file_menu, self.export_action)
349 401
350 self.select_all_action = QtGui.QAction("Select &All",
402 self.file_menu.addSeparator()
403
404 # Ctrl actually maps to Cmd on OSX, which avoids conflict with history
405 # action, which is already bound to true Ctrl+P
406 print_shortcut = "Ctrl+P" if sys.platform == 'darwin' else 'Ctrl+Shift+P'
407 self.print_action = QtGui.QAction("&Print",
351 408 self,
352 shortcut="Ctrl+A",
353 triggered=self.select_all_active_frontend
409 shortcut=print_shortcut,
410 triggered=self.print_action_active_frontend)
411 self.add_menu_action(self.file_menu, self.print_action)
412
413 if sys.platform == 'darwin':
414 # OSX always has Quit in the Application menu, only add it
415 # to the File menu elsewhere.
416
417 self.file_menu.addSeparator()
418
419 self.quit_action = QtGui.QAction("&Quit",
420 self,
421 shortcut=QtGui.QKeySequence.Quit,
422 triggered=self.close,
354 423 )
355 self.file_menu.addAction(self.select_all_action)
424 self.add_menu_action(self.file_menu, self.quit_action)
356 425
357 self.paste_action = QtGui.QAction("&Paste",
426
427 def init_edit_menu(self):
428 self.edit_menu = self.menuBar().addMenu("&Edit")
429
430 self.undo_action = QtGui.QAction("&Undo",
358 431 self,
359 shortcut=QtGui.QKeySequence.Paste,
360 triggered=self.paste_active_frontend
432 shortcut="Ctrl+Z",
433 statusTip="Undo last action if possible",
434 triggered=self.undo_active_frontend
435 )
436 self.add_menu_action(self.edit_menu, self.undo_action)
437
438 self.redo_action = QtGui.QAction("&Redo",
439 self,
440 shortcut="Ctrl+Shift+Z",
441 statusTip="Redo last action if possible",
442 triggered=self.redo_active_frontend)
443 self.add_menu_action(self.edit_menu, self.redo_action)
444
445 self.edit_menu.addSeparator()
446
447 self.cut_action = QtGui.QAction("&Cut",
448 self,
449 shortcut=QtGui.QKeySequence.Cut,
450 triggered=self.cut_active_frontend
361 451 )
362 self.edit_menu.addAction(self.paste_action)
452 self.add_menu_action(self.edit_menu, self.cut_action)
363 453
364 454 self.copy_action = QtGui.QAction("&Copy",
365 455 self,
366 456 shortcut=QtGui.QKeySequence.Copy,
367 457 triggered=self.copy_active_frontend
368 458 )
369 self.edit_menu.addAction(self.copy_action)
459 self.add_menu_action(self.edit_menu, self.copy_action)
370 460
371 461 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
372 462 self,
373 463 shortcut="Ctrl+Shift+C",
374 464 triggered=self.copy_raw_active_frontend
375 465 )
376 self.edit_menu.addAction(self.copy_raw_action)
466 self.add_menu_action(self.edit_menu, self.copy_raw_action)
377 467
378 self.cut_action = QtGui.QAction("&Cut",
468 self.paste_action = QtGui.QAction("&Paste",
379 469 self,
380 shortcut=QtGui.QKeySequence.Cut,
381 triggered=self.cut_active_frontend
470 shortcut=QtGui.QKeySequence.Paste,
471 triggered=self.paste_active_frontend
382 472 )
383 self.edit_menu.addAction(self.cut_action)
473 self.add_menu_action(self.edit_menu, self.paste_action)
384 474
385 475 self.edit_menu.addSeparator()
386 476
387 self.undo_action = QtGui.QAction("&Undo",
477 self.select_all_action = QtGui.QAction("Select &All",
388 478 self,
389 shortcut="Ctrl+Z",
390 statusTip="Undo last action if possible",
391 triggered=self.undo_active_frontend
479 shortcut="Ctrl+A",
480 triggered=self.select_all_active_frontend
392 481 )
393 self.edit_menu.addAction(self.undo_action)
482 self.add_menu_action(self.edit_menu, self.select_all_action)
394 483
395 self.redo_action = QtGui.QAction("&Redo",
484
485 def init_view_menu(self):
486 self.view_menu = self.menuBar().addMenu("&View")
487
488 if sys.platform != 'darwin':
489 # disable on OSX, where there is always a menu bar
490 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
491 self,
492 shortcut="Ctrl+Meta+M",
493 statusTip="Toggle visibility of menubar",
494 triggered=self.toggle_menu_bar)
495 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
496
497 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
498 self.full_screen_act = QtGui.QAction("&Full Screen",
396 499 self,
397 shortcut="Ctrl+Shift+Z",
398 statusTip="Redo last action if possible",
399 triggered=self.redo_active_frontend)
400 self.edit_menu.addAction(self.redo_action)
500 shortcut=fs_key,
501 statusTip="Toggle between Fullscreen and Normal Size",
502 triggered=self.toggleFullScreen)
503 self.add_menu_action(self.view_menu, self.full_screen_act)
401 504
402 self.window_menu.addSeparator()
505 self.view_menu.addSeparator()
403 506
404 self.increase_font_size = QtGui.QAction("&Increase Font Size",
507 self.increase_font_size = QtGui.QAction("Zoom &In",
405 508 self,
406 509 shortcut="Ctrl++",
407 510 triggered=self.increase_font_size_active_frontend
408 511 )
409 self.window_menu.addAction(self.increase_font_size)
512 self.add_menu_action(self.view_menu, self.increase_font_size)
410 513
411 self.decrease_font_size = QtGui.QAction("&Decrease Font Size",
514 self.decrease_font_size = QtGui.QAction("Zoom &Out",
412 515 self,
413 516 shortcut="Ctrl+-",
414 517 triggered=self.decrease_font_size_active_frontend
415 518 )
416 self.window_menu.addAction(self.decrease_font_size)
519 self.add_menu_action(self.view_menu, self.decrease_font_size)
417 520
418 self.reset_font_size = QtGui.QAction("&Reset Font Size",
521 self.reset_font_size = QtGui.QAction("Zoom &Reset",
419 522 self,
420 523 shortcut="Ctrl+0",
421 524 triggered=self.reset_font_size_active_frontend
422 525 )
423 self.window_menu.addAction(self.reset_font_size)
526 self.add_menu_action(self.view_menu, self.reset_font_size)
424 527
425 self.window_menu.addSeparator()
528 self.view_menu.addSeparator()
426 529
530 self.clear_action = QtGui.QAction("&Clear Screen",
531 self,
532 shortcut='Ctrl+L',
533 statusTip="Clear the console",
534 triggered=self.clear_magic_active_frontend)
535 self.add_menu_action(self.view_menu, self.clear_action)
536
537 def init_kernel_menu(self):
538 self.kernel_menu = self.menuBar().addMenu("&Kernel")
539 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
540 # keep the signal shortcuts to ctrl, rather than
541 # platform-default like we do elsewhere.
542
543 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
544
545 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
546 self,
547 triggered=self.interrupt_kernel_active_frontend,
548 shortcut=ctrl+"+C",
549 )
550 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
551
552 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
553 self,
554 triggered=self.restart_kernel_active_frontend,
555 shortcut=ctrl+"+.",
556 )
557 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
558
559 self.kernel_menu.addSeparator()
560
561 def init_magic_menu(self):
562 self.magic_menu = self.menuBar().addMenu("&Magic")
563 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
564
427 565 self.reset_action = QtGui.QAction("&Reset",
428 566 self,
429 567 statusTip="Clear all varible from workspace",
430 568 triggered=self.reset_magic_active_frontend)
431 self.magic_menu.addAction(self.reset_action)
569 self.add_menu_action(self.magic_menu, self.reset_action)
432 570
433 571 self.history_action = QtGui.QAction("&History",
434 572 self,
435 573 statusTip="show command history",
436 574 triggered=self.history_magic_active_frontend)
437 self.magic_menu.addAction(self.history_action)
575 self.add_menu_action(self.magic_menu, self.history_action)
438 576
439 577 self.save_action = QtGui.QAction("E&xport History ",
440 578 self,
441 579 statusTip="Export History as Python File",
442 580 triggered=self.save_magic_active_frontend)
443 self.magic_menu.addAction(self.save_action)
444
445 self.clear_action = QtGui.QAction("&Clear Screen",
446 self,
447 shortcut='Ctrl+L',
448 statusTip="Clear the console",
449 triggered=self.clear_magic_active_frontend)
450 self.window_menu.addAction(self.clear_action)
581 self.add_menu_action(self.magic_menu, self.save_action)
451 582
452 583 self.who_action = QtGui.QAction("&Who",
453 584 self,
454 585 statusTip="List interactive variable",
455 586 triggered=self.who_magic_active_frontend)
456 self.magic_menu.addAction(self.who_action)
587 self.add_menu_action(self.magic_menu, self.who_action)
457 588
458 589 self.who_ls_action = QtGui.QAction("Wh&o ls",
459 590 self,
460 591 statusTip="Return a list of interactive variable",
461 592 triggered=self.who_ls_magic_active_frontend)
462 self.magic_menu.addAction(self.who_ls_action)
593 self.add_menu_action(self.magic_menu, self.who_ls_action)
463 594
464 595 self.whos_action = QtGui.QAction("Who&s",
465 596 self,
466 597 statusTip="List interactive variable with detail",
467 598 triggered=self.whos_magic_active_frontend)
468 self.magic_menu.addAction(self.whos_action)
469
470 self.intro_active_frontend_action = QtGui.QAction("Intro to IPython",
471 self,
472 triggered=self.intro_active_frontend
473 )
474 self.help_menu.addAction(self.intro_active_frontend_action)
475
476 self.quickref_active_frontend_action = QtGui.QAction("IPython Cheat Sheet",
477 self,
478 triggered=self.quickref_active_frontend
479 )
480 self.help_menu.addAction(self.quickref_active_frontend_action)
481
482 self.guiref_active_frontend_action = QtGui.QAction("Qt Console",
483 self,
484 triggered=self.guiref_active_frontend
485 )
486 self.help_menu.addAction(self.guiref_active_frontend_action)
487
488 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
489 self,
490 triggered=self.interrupt_kernel_active_frontend
491 )
492 self.kernel_menu.addAction(self.interrupt_kernel_action)
493
494 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
495 self,
496 triggered=self.restart_kernel_active_frontend
497 )
498 self.kernel_menu.addAction(self.restart_kernel_action)
499 self.kernel_menu.addSeparator()
500
599 self.add_menu_action(self.magic_menu, self.whos_action)
600
601 # allmagics submenu:
602
501 603 #for now this is just a copy and paste, but we should get this dynamically
502 604 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
503 605 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
504 606 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
505 607 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
506 608 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
507 609 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
508 610 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
509 611 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
510 612 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
511 613 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
512 614
513 615 def make_dynamic_magic(i):
514 616 def inner_dynamic_magic():
515 617 self.active_frontend.execute(i)
516 618 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
517 619 return inner_dynamic_magic
518 620
519 621 for magic in magiclist:
520 622 xaction = QtGui.QAction(magic,
521 623 self,
522 624 triggered=make_dynamic_magic(magic)
523 625 )
524 626 self.all_magic_menu.addAction(xaction)
627
628 def init_window_menu(self):
629 self.window_menu = self.menuBar().addMenu("&Window")
630 if sys.platform == 'darwin':
631 # add min/maximize actions to OSX, which lacks default bindings.
632 self.minimizeAct = QtGui.QAction("Mini&mize",
633 self,
634 shortcut="Ctrl+m",
635 statusTip="Minimize the window/Restore Normal Size",
636 triggered=self.toggleMinimized)
637 # maximize is called 'Zoom' on OSX for some reason
638 self.maximizeAct = QtGui.QAction("&Zoom",
639 self,
640 shortcut="Ctrl+Shift+M",
641 statusTip="Maximize the window/Restore Normal Size",
642 triggered=self.toggleMaximized)
643
644 self.add_menu_action(self.window_menu, self.minimizeAct)
645 self.add_menu_action(self.window_menu, self.maximizeAct)
646 self.window_menu.addSeparator()
647
648 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgDown"
649 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
650 self,
651 shortcut=prev_key,
652 statusTip="Select previous tab",
653 triggered=self.prev_tab)
654 self.add_menu_action(self.window_menu, self.prev_tab_act)
655
656 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgUp"
657 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
658 self,
659 shortcut=next_key,
660 statusTip="Select next tab",
661 triggered=self.next_tab)
662 self.add_menu_action(self.window_menu, self.next_tab_act)
663
664 def init_help_menu(self):
665 # please keep the Help menu in Mac Os even if empty. It will
666 # automatically contain a search field to search inside menus and
667 # please keep it spelled in English, as long as Qt Doesn't support
668 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
669 # this search field fonctionality
670
671 self.help_menu = self.menuBar().addMenu("&Help")
672
673
674 # Help Menu
675
676 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
677 self,
678 triggered=self.intro_active_frontend
679 )
680 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
681
682 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
683 self,
684 triggered=self.quickref_active_frontend
685 )
686 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
687
688 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
689 self,
690 triggered=self.guiref_active_frontend
691 )
692 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
693
694 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
695 self,
696 triggered=self._open_online_help)
697 self.add_menu_action(self.help_menu, self.onlineHelpAct)
698
699 # minimize/maximize/fullscreen actions:
700
701 def toggle_menu_bar(self):
702 menu_bar = self.menuBar();
703 if menu_bar.isVisible():
704 menu_bar.setVisible(False)
705 else:
706 menu_bar.setVisible(True)
707
708 def toggleMinimized(self):
709 if not self.isMinimized():
710 self.showMinimized()
711 else:
712 self.showNormal()
713
714 def _open_online_help(self):
715 filename="http://ipython.org/ipython-doc/stable/index.html"
716 webbrowser.open(filename, new=1, autoraise=True)
717
718 def toggleMaximized(self):
719 if not self.isMaximized():
720 self.showMaximized()
721 else:
722 self.showNormal()
723
724 # Min/Max imizing while in full screen give a bug
725 # when going out of full screen, at least on OSX
726 def toggleFullScreen(self):
727 if not self.isFullScreen():
728 self.showFullScreen()
729 if sys.platform == 'darwin':
730 self.maximizeAct.setEnabled(False)
731 self.minimizeAct.setEnabled(False)
732 else:
733 self.showNormal()
734 if sys.platform == 'darwin':
735 self.maximizeAct.setEnabled(True)
736 self.minimizeAct.setEnabled(True)
737
738 def close_active_frontend(self):
739 self.close_tab(self.active_frontend)
525 740
526 741 def restart_kernel_active_frontend(self):
527 742 self.active_frontend.request_restart_kernel()
528 743
529 744 def interrupt_kernel_active_frontend(self):
530 745 self.active_frontend.request_interrupt_kernel()
531 746
532 747 def cut_active_frontend(self):
533 748 self.active_frontend.cut_action.trigger()
534 749
535 750 def copy_active_frontend(self):
536 751 self.active_frontend.copy_action.trigger()
537 752
538 753 def copy_raw_active_frontend(self):
539 754 self.active_frontend._copy_raw_action.trigger()
540 755
541 756 def paste_active_frontend(self):
542 757 self.active_frontend.paste_action.trigger()
543 758
544 759 def undo_active_frontend(self):
545 760 self.active_frontend.undo()
546 761
547 762 def redo_active_frontend(self):
548 763 self.active_frontend.redo()
549 764
550 765 def reset_magic_active_frontend(self):
551 766 self.active_frontend.execute("%reset")
552 767
553 768 def history_magic_active_frontend(self):
554 769 self.active_frontend.execute("%history")
555 770
556 771 def save_magic_active_frontend(self):
557 772 self.active_frontend.save_magic()
558 773
559 774 def clear_magic_active_frontend(self):
560 775 self.active_frontend.execute("%clear")
561 776
562 777 def who_magic_active_frontend(self):
563 778 self.active_frontend.execute("%who")
564 779
565 780 def who_ls_magic_active_frontend(self):
566 781 self.active_frontend.execute("%who_ls")
567 782
568 783 def whos_magic_active_frontend(self):
569 784 self.active_frontend.execute("%whos")
570 785
571 786 def print_action_active_frontend(self):
572 787 self.active_frontend.print_action.trigger()
573 788
574 789 def export_action_active_frontend(self):
575 790 self.active_frontend.export_action.trigger()
576 791
577 792 def select_all_active_frontend(self):
578 793 self.active_frontend.select_all_action.trigger()
579 794
580 795 def increase_font_size_active_frontend(self):
581 796 self.active_frontend.increase_font_size.trigger()
582 797
583 798 def decrease_font_size_active_frontend(self):
584 799 self.active_frontend.decrease_font_size.trigger()
585 800
586 801 def reset_font_size_active_frontend(self):
587 802 self.active_frontend.reset_font_size.trigger()
588 803
589 804 def guiref_active_frontend(self):
590 805 self.active_frontend.execute("%guiref")
591 806
592 807 def intro_active_frontend(self):
593 808 self.active_frontend.execute("?")
594 809
595 810 def quickref_active_frontend(self):
596 811 self.active_frontend.execute("%quickref")
597 812 #---------------------------------------------------------------------------
598 813 # QWidget interface
599 814 #---------------------------------------------------------------------------
600 815
601 816 def closeEvent(self, event):
602 817 """ Forward the close event to every tabs contained by the windows
603 818 """
604 819 # Do Not loop on the widget count as it change while closing
605 820 widget_list=[ self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
606 821 for widget in widget_list:
607 822 self.close_tab(widget)
608 823 event.accept()
609 824
610 825 #-----------------------------------------------------------------------------
611 826 # Aliases and Flags
612 827 #-----------------------------------------------------------------------------
613 828
614 829 flags = dict(ipkernel_flags)
615 830 qt_flags = {
616 831 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
617 832 "Connect to an existing kernel. If no argument specified, guess most recent"),
618 833 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
619 834 "Use a pure Python kernel instead of an IPython kernel."),
620 835 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
621 836 "Disable rich text support."),
622 837 }
623 838 qt_flags.update(boolean_flag(
624 839 'gui-completion', 'ConsoleWidget.gui_completion',
625 840 "use a GUI widget for tab completion",
626 841 "use plaintext output for completion"
627 842 ))
628 843 qt_flags.update(boolean_flag(
629 844 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
630 845 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
631 846 to force a direct exit without any confirmation.
632 847 """,
633 848 """Don't prompt the user when exiting. This will terminate the kernel
634 849 if it is owned by the frontend, and leave it alive if it is external.
635 850 """
636 851 ))
637 852 flags.update(qt_flags)
638 853
639 854 aliases = dict(ipkernel_aliases)
640 855
641 856 qt_aliases = dict(
642 857 hb = 'IPythonQtConsoleApp.hb_port',
643 858 shell = 'IPythonQtConsoleApp.shell_port',
644 859 iopub = 'IPythonQtConsoleApp.iopub_port',
645 860 stdin = 'IPythonQtConsoleApp.stdin_port',
646 861 ip = 'IPythonQtConsoleApp.ip',
647 862 existing = 'IPythonQtConsoleApp.existing',
648 863 f = 'IPythonQtConsoleApp.connection_file',
649 864
650 865 style = 'IPythonWidget.syntax_style',
651 866 stylesheet = 'IPythonQtConsoleApp.stylesheet',
652 867 colors = 'ZMQInteractiveShell.colors',
653 868
654 869 editor = 'IPythonWidget.editor',
655 870 paging = 'ConsoleWidget.paging',
656 871 ssh = 'IPythonQtConsoleApp.sshserver',
657 872 )
658 873 aliases.update(qt_aliases)
659 874
660 875
661 876 #-----------------------------------------------------------------------------
662 877 # IPythonQtConsole
663 878 #-----------------------------------------------------------------------------
664 879
665 880
666 881 class IPythonQtConsoleApp(BaseIPythonApplication):
667 882 name = 'ipython-qtconsole'
668 883 default_config_file_name='ipython_config.py'
669 884
670 885 description = """
671 886 The IPython QtConsole.
672 887
673 888 This launches a Console-style application using Qt. It is not a full
674 889 console, in that launched terminal subprocesses will not be able to accept
675 890 input.
676 891
677 892 The QtConsole supports various extra features beyond the Terminal IPython
678 893 shell, such as inline plotting with matplotlib, via:
679 894
680 895 ipython qtconsole --pylab=inline
681 896
682 897 as well as saving your session as HTML, and printing the output.
683 898
684 899 """
685 900 examples = _examples
686 901
687 902 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
688 903 flags = Dict(flags)
689 904 aliases = Dict(aliases)
690 905
691 906 kernel_argv = List(Unicode)
692 907
693 908 # create requested profiles by default, if they don't exist:
694 909 auto_create = CBool(True)
695 910 # connection info:
696 911 ip = Unicode(LOCALHOST, config=True,
697 912 help="""Set the kernel\'s IP address [default localhost].
698 913 If the IP address is something other than localhost, then
699 914 Consoles on other machines will be able to connect
700 915 to the Kernel, so be careful!"""
701 916 )
702 917
703 918 sshserver = Unicode('', config=True,
704 919 help="""The SSH server to use to connect to the kernel.""")
705 920 sshkey = Unicode('', config=True,
706 921 help="""Path to the ssh key to use for logging in to the ssh server.""")
707 922
708 923 hb_port = Int(0, config=True,
709 924 help="set the heartbeat port [default: random]")
710 925 shell_port = Int(0, config=True,
711 926 help="set the shell (XREP) port [default: random]")
712 927 iopub_port = Int(0, config=True,
713 928 help="set the iopub (PUB) port [default: random]")
714 929 stdin_port = Int(0, config=True,
715 930 help="set the stdin (XREQ) port [default: random]")
716 931 connection_file = Unicode('', config=True,
717 932 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
718 933
719 934 This file will contain the IP, ports, and authentication key needed to connect
720 935 clients to this kernel. By default, this file will be created in the security-dir
721 936 of the current profile, but can be specified by absolute path.
722 937 """)
723 938 def _connection_file_default(self):
724 939 return 'kernel-%i.json' % os.getpid()
725 940
726 941 existing = Unicode('', config=True,
727 942 help="""Connect to an already running kernel""")
728 943
729 944 stylesheet = Unicode('', config=True,
730 945 help="path to a custom CSS stylesheet")
731 946
732 947 pure = CBool(False, config=True,
733 948 help="Use a pure Python kernel instead of an IPython kernel.")
734 949 plain = CBool(False, config=True,
735 950 help="Use a plaintext widget instead of rich text (plain can't print/save).")
736 951
737 952 def _pure_changed(self, name, old, new):
738 953 kind = 'plain' if self.plain else 'rich'
739 954 self.config.ConsoleWidget.kind = kind
740 955 if self.pure:
741 956 self.widget_factory = FrontendWidget
742 957 elif self.plain:
743 958 self.widget_factory = IPythonWidget
744 959 else:
745 960 self.widget_factory = RichIPythonWidget
746 961
747 962 _plain_changed = _pure_changed
748 963
749 964 confirm_exit = CBool(True, config=True,
750 965 help="""
751 966 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
752 967 to force a direct exit without any confirmation.""",
753 968 )
754 969
755 970 # the factory for creating a widget
756 971 widget_factory = Any(RichIPythonWidget)
757 972
758 973 def parse_command_line(self, argv=None):
759 974 super(IPythonQtConsoleApp, self).parse_command_line(argv)
760 975 if argv is None:
761 976 argv = sys.argv[1:]
762 977
763 978 self.kernel_argv = list(argv) # copy
764 979 # kernel should inherit default config file from frontend
765 980 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
766 981 # Scrub frontend-specific flags
767 982 for a in argv:
768 983 if a.startswith('-') and a.lstrip('-') in qt_flags:
769 984 self.kernel_argv.remove(a)
770 985 swallow_next = False
771 986 for a in argv:
772 987 if swallow_next:
773 988 self.kernel_argv.remove(a)
774 989 swallow_next = False
775 990 continue
776 991 if a.startswith('-'):
777 992 split = a.lstrip('-').split('=')
778 993 alias = split[0]
779 994 if alias in qt_aliases:
780 995 self.kernel_argv.remove(a)
781 996 if len(split) == 1:
782 997 # alias passed with arg via space
783 998 swallow_next = True
784 999
785 1000 def init_connection_file(self):
786 1001 """find the connection file, and load the info if found.
787 1002
788 1003 The current working directory and the current profile's security
789 1004 directory will be searched for the file if it is not given by
790 1005 absolute path.
791 1006
792 1007 When attempting to connect to an existing kernel and the `--existing`
793 1008 argument does not match an existing file, it will be interpreted as a
794 1009 fileglob, and the matching file in the current profile's security dir
795 1010 with the latest access time will be used.
796 1011 """
797 1012 if self.existing:
798 1013 try:
799 1014 cf = find_connection_file(self.existing)
800 1015 except Exception:
801 1016 self.log.critical("Could not find existing kernel connection file %s", self.existing)
802 1017 self.exit(1)
803 1018 self.log.info("Connecting to existing kernel: %s" % cf)
804 1019 self.connection_file = cf
805 1020 # should load_connection_file only be used for existing?
806 1021 # as it is now, this allows reusing ports if an existing
807 1022 # file is requested
808 1023 try:
809 1024 self.load_connection_file()
810 1025 except Exception:
811 1026 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
812 1027 self.exit(1)
813 1028
814 1029 def load_connection_file(self):
815 1030 """load ip/port/hmac config from JSON connection file"""
816 1031 # this is identical to KernelApp.load_connection_file
817 1032 # perhaps it can be centralized somewhere?
818 1033 try:
819 1034 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
820 1035 except IOError:
821 1036 self.log.debug("Connection File not found: %s", self.connection_file)
822 1037 return
823 1038 self.log.debug(u"Loading connection file %s", fname)
824 1039 with open(fname) as f:
825 1040 s = f.read()
826 1041 cfg = json.loads(s)
827 1042 if self.ip == LOCALHOST and 'ip' in cfg:
828 1043 # not overridden by config or cl_args
829 1044 self.ip = cfg['ip']
830 1045 for channel in ('hb', 'shell', 'iopub', 'stdin'):
831 1046 name = channel + '_port'
832 1047 if getattr(self, name) == 0 and name in cfg:
833 1048 # not overridden by config or cl_args
834 1049 setattr(self, name, cfg[name])
835 1050 if 'key' in cfg:
836 1051 self.config.Session.key = str_to_bytes(cfg['key'])
837 1052
838 1053 def init_ssh(self):
839 1054 """set up ssh tunnels, if needed."""
840 1055 if not self.sshserver and not self.sshkey:
841 1056 return
842 1057
843 1058 if self.sshkey and not self.sshserver:
844 1059 # specifying just the key implies that we are connecting directly
845 1060 self.sshserver = self.ip
846 1061 self.ip = LOCALHOST
847 1062
848 1063 # build connection dict for tunnels:
849 1064 info = dict(ip=self.ip,
850 1065 shell_port=self.shell_port,
851 1066 iopub_port=self.iopub_port,
852 1067 stdin_port=self.stdin_port,
853 1068 hb_port=self.hb_port
854 1069 )
855 1070
856 1071 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
857 1072
858 1073 # tunnels return a new set of ports, which will be on localhost:
859 1074 self.ip = LOCALHOST
860 1075 try:
861 1076 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
862 1077 except:
863 1078 # even catch KeyboardInterrupt
864 1079 self.log.error("Could not setup tunnels", exc_info=True)
865 1080 self.exit(1)
866 1081
867 1082 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
868 1083
869 1084 cf = self.connection_file
870 1085 base,ext = os.path.splitext(cf)
871 1086 base = os.path.basename(base)
872 1087 self.connection_file = os.path.basename(base)+'-ssh'+ext
873 1088 self.log.critical("To connect another client via this tunnel, use:")
874 1089 self.log.critical("--existing %s" % self.connection_file)
875 1090
876 1091 def _new_connection_file(self):
877 1092 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
878 1093
879 1094 def init_kernel_manager(self):
880 1095 # Don't let Qt or ZMQ swallow KeyboardInterupts.
881 1096 signal.signal(signal.SIGINT, signal.SIG_DFL)
882 1097 sec = self.profile_dir.security_dir
883 1098 try:
884 1099 cf = filefind(self.connection_file, ['.', sec])
885 1100 except IOError:
886 1101 # file might not exist
887 1102 if self.connection_file == os.path.basename(self.connection_file):
888 1103 # just shortname, put it in security dir
889 1104 cf = os.path.join(sec, self.connection_file)
890 1105 else:
891 1106 cf = self.connection_file
892 1107
893 1108 # Create a KernelManager and start a kernel.
894 1109 self.kernel_manager = QtKernelManager(
895 1110 ip=self.ip,
896 1111 shell_port=self.shell_port,
897 1112 iopub_port=self.iopub_port,
898 1113 stdin_port=self.stdin_port,
899 1114 hb_port=self.hb_port,
900 1115 connection_file=cf,
901 1116 config=self.config,
902 1117 )
903 1118 # start the kernel
904 1119 if not self.existing:
905 1120 kwargs = dict(ipython=not self.pure)
906 1121 kwargs['extra_arguments'] = self.kernel_argv
907 1122 self.kernel_manager.start_kernel(**kwargs)
908 1123 elif self.sshserver:
909 1124 # ssh, write new connection file
910 1125 self.kernel_manager.write_connection_file()
911 1126 self.kernel_manager.start_channels()
912 1127
913 def create_tab_with_new_frontend(self):
914 """ Create new tab attached to new kernel, launched on localhost.
1128 def new_frontend_master(self):
1129 """ Create and return new frontend attached to new kernel, launched on localhost.
915 1130 """
916 1131 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
917 1132 kernel_manager = QtKernelManager(
918 1133 ip=ip,
919 1134 connection_file=self._new_connection_file(),
920 1135 config=self.config,
921 1136 )
922 1137 # start the kernel
923 1138 kwargs = dict(ipython=not self.pure)
924 1139 kwargs['extra_arguments'] = self.kernel_argv
925 1140 kernel_manager.start_kernel(**kwargs)
926 1141 kernel_manager.start_channels()
927 1142 widget = self.widget_factory(config=self.config,
928 1143 local_kernel=True)
929 1144 widget.kernel_manager = kernel_manager
930 widget._existing=False;
931 widget._confirm_exit=True;
932 widget._may_close=True;
933 self.window.add_tab_with_frontend(widget)
934
935 def create_tab_attached_to_current_tab_kernel(self):
936 current_widget = self.window.tab_widget.currentWidget()
937 current_widget_index = self.window.tab_widget.indexOf(current_widget)
938 current_widget.kernel_manager = current_widget.kernel_manager;
939 current_widget_name = self.window.tab_widget.tabText(current_widget_index);
1145 widget._existing=False
1146 widget._confirm_exit=True
1147 widget._may_close=True
1148 return widget
1149
1150 def new_frontend_slave(self, current_widget):
1151 """Create and return a new frontend attached to an existing kernel.
1152
1153 Parameters
1154 ----------
1155 current_widget : IPythonWidget
1156 The IPythonWidget whose kernel this frontend is to share
1157 """
940 1158 kernel_manager = QtKernelManager(
941 1159 connection_file=current_widget.kernel_manager.connection_file,
942 1160 config = self.config,
943 1161 )
944 1162 kernel_manager.load_connection_file()
945 1163 kernel_manager.start_channels()
946 1164 widget = self.widget_factory(config=self.config,
947 1165 local_kernel=False)
948 1166 widget._confirm_exit=True;
949 1167 widget._may_close=False;
950 1168 widget.kernel_manager = kernel_manager
951 self.window.add_tab_with_frontend(widget,name=str('('+current_widget_name+') slave'))
1169 return widget
952 1170
953 1171 def init_qt_elements(self):
954 1172 # Create the widget.
955 1173 self.app = QtGui.QApplication([])
956 1174
957 1175 base_path = os.path.abspath(os.path.dirname(__file__))
958 1176 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
959 1177 self.app.icon = QtGui.QIcon(icon_path)
960 1178 QtGui.QApplication.setWindowIcon(self.app.icon)
961 1179
962 1180 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
963 1181 self.widget = self.widget_factory(config=self.config,
964 1182 local_kernel=local_kernel)
965 1183 self.widget._existing = self.existing;
966 1184 self.widget._may_close = not self.existing;
967 1185 self.widget._confirm_exit = not self.existing;
968 1186
969 1187 self.widget.kernel_manager = self.kernel_manager
970 1188 self.window = MainWindow(self.app, self.widget, self.existing,
971 1189 may_close=local_kernel,
972 confirm_exit=self.confirm_exit)
1190 confirm_exit=self.confirm_exit,
1191 new_frontend_factory=self.new_frontend_master,
1192 slave_frontend_factory=self.new_frontend_slave,
1193 )
973 1194 self.window.log = self.log
974 1195 self.window.add_tab_with_frontend(self.widget)
975 1196 self.window.init_menu_bar()
976 1197 self.window.setWindowTitle('Python' if self.pure else 'IPython')
977 1198
978 1199 def init_colors(self):
979 1200 """Configure the coloring of the widget"""
980 1201 # Note: This will be dramatically simplified when colors
981 1202 # are removed from the backend.
982 1203
983 1204 if self.pure:
984 1205 # only IPythonWidget supports styling
985 1206 return
986 1207
987 1208 # parse the colors arg down to current known labels
988 1209 try:
989 1210 colors = self.config.ZMQInteractiveShell.colors
990 1211 except AttributeError:
991 1212 colors = None
992 1213 try:
993 1214 style = self.config.IPythonWidget.colors
994 1215 except AttributeError:
995 1216 style = None
996 1217
997 1218 # find the value for colors:
998 1219 if colors:
999 1220 colors=colors.lower()
1000 1221 if colors in ('lightbg', 'light'):
1001 1222 colors='lightbg'
1002 1223 elif colors in ('dark', 'linux'):
1003 1224 colors='linux'
1004 1225 else:
1005 1226 colors='nocolor'
1006 1227 elif style:
1007 1228 if style=='bw':
1008 1229 colors='nocolor'
1009 1230 elif styles.dark_style(style):
1010 1231 colors='linux'
1011 1232 else:
1012 1233 colors='lightbg'
1013 1234 else:
1014 1235 colors=None
1015 1236
1016 1237 # Configure the style.
1017 1238 widget = self.widget
1018 1239 if style:
1019 1240 widget.style_sheet = styles.sheet_from_template(style, colors)
1020 1241 widget.syntax_style = style
1021 1242 widget._syntax_style_changed()
1022 1243 widget._style_sheet_changed()
1023 1244 elif colors:
1024 1245 # use a default style
1025 1246 widget.set_default_style(colors=colors)
1026 1247 else:
1027 1248 # this is redundant for now, but allows the widget's
1028 1249 # defaults to change
1029 1250 widget.set_default_style()
1030 1251
1031 1252 if self.stylesheet:
1032 1253 # we got an expicit stylesheet
1033 1254 if os.path.isfile(self.stylesheet):
1034 1255 with open(self.stylesheet) as f:
1035 1256 sheet = f.read()
1036 1257 widget.style_sheet = sheet
1037 1258 widget._style_sheet_changed()
1038 1259 else:
1039 1260 raise IOError("Stylesheet %r not found."%self.stylesheet)
1040 1261
1041 1262 def initialize(self, argv=None):
1042 1263 super(IPythonQtConsoleApp, self).initialize(argv)
1043 1264 self.init_connection_file()
1044 1265 default_secure(self.config)
1045 1266 self.init_ssh()
1046 1267 self.init_kernel_manager()
1047 1268 self.init_qt_elements()
1048 1269 self.init_colors()
1049 self.init_window_shortcut()
1050
1051 def init_window_shortcut(self):
1052
1053 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
1054 self.window,
1055 shortcut="Ctrl+PgDown",
1056 statusTip="Cahange to next tab",
1057 triggered=self.window.prev_tab)
1058
1059 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
1060 self.window,
1061 shortcut="Ctrl+PgUp",
1062 statusTip="Cahange to next tab",
1063 triggered=self.window.next_tab)
1064
1065 self.fullScreenAct = QtGui.QAction("&Full Screen",
1066 self.window,
1067 shortcut="Ctrl+Meta+Space",
1068 statusTip="Toggle between Fullscreen and Normal Size",
1069 triggered=self.toggleFullScreen)
1070
1071
1072
1073 self.tabAndNewKernelAct =QtGui.QAction("Tab with &New kernel",
1074 self.window,
1075 shortcut="Ctrl+T",
1076 triggered=self.create_tab_with_new_frontend)
1077 self.window.kernel_menu.addAction(self.tabAndNewKernelAct)
1078
1079 self.tabSameKernalAct =QtGui.QAction("Tab with Sa&me kernel",
1080 self.window,
1081 shortcut="Ctrl+Shift+T",
1082 triggered=self.create_tab_attached_to_current_tab_kernel)
1083 self.window.kernel_menu.addAction(self.tabSameKernalAct)
1084 self.window.kernel_menu.addSeparator()
1085
1086 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
1087 self.window,
1088 triggered=self._open_online_help)
1089 self.window.help_menu.addAction(self.onlineHelpAct)
1090 # creating shortcut in menubar only for Mac OS as I don't
1091 # know the shortcut or if the windows manager assign it in
1092 # other platform.
1093 if sys.platform == 'darwin':
1094 self.minimizeAct = QtGui.QAction("Mini&mize",
1095 self.window,
1096 shortcut="Ctrl+m",
1097 statusTip="Minimize the window/Restore Normal Size",
1098 triggered=self.toggleMinimized)
1099 self.maximizeAct = QtGui.QAction("Ma&ximize",
1100 self.window,
1101 shortcut="Ctrl+Shift+M",
1102 statusTip="Maximize the window/Restore Normal Size",
1103 triggered=self.toggleMaximized)
1104
1105
1106 self.window_menu = self.window.window_menu
1107 self.kernel_menu = self.window.kernel_menu
1108
1109 self.kernel_menu.addAction(self.next_tab_act)
1110 self.kernel_menu.addAction(self.prev_tab_act)
1111 self.window_menu.addSeparator()
1112 self.window_menu.addAction(self.minimizeAct)
1113 self.window_menu.addAction(self.maximizeAct)
1114 self.window_menu.addSeparator()
1115 self.window_menu.addAction(self.fullScreenAct)
1116
1117 else:
1118 # if we don't put it in a menu, we add it to the window so
1119 # that it can still be triggerd by shortcut
1120 self.window.addAction(self.fullScreenAct)
1121
1122 # Don't activate toggleMenubar on mac, doen't work,
1123 # as toolbar always here
1124 self.toggle_menu_bar_act = QtGui.QAction("&Toggle Menu Bar",
1125 self.window,
1126 shortcut="Ctrl+Meta+H",
1127 statusTip="Toggle menubar betwin visible and not",
1128 triggered=self.toggle_menu_bar)
1129 self.window.window_menu.addAction(self.toggle_menu_bar_act)
1130
1131 def toggle_menu_bar(self):
1132 menu_bar = self.window.menuBar();
1133 if not menu_bar.isVisible():
1134 menu_bar.setVisible(False)
1135 else:
1136 menu_bar.setVisible(True)
1137
1138 def toggleMinimized(self):
1139 if not self.window.isMinimized():
1140 self.window.showMinimized()
1141 else:
1142 self.window.showNormal()
1143
1144 def _open_online_help(self):
1145 filename="http://ipython.org/ipython-doc/stable/index.html"
1146 webbrowser.open(filename, new=1, autoraise=True)
1147
1148 def toggleMaximized(self):
1149 if not self.window.isMaximized():
1150 self.window.showMaximized()
1151 else:
1152 self.window.showNormal()
1153
1154 # Min/Max imizing while in full screen give a bug
1155 # when going out of full screen, at least on OSX
1156 def toggleFullScreen(self):
1157 if not self.window.isFullScreen():
1158 self.window.showFullScreen()
1159 if sys.platform == 'darwin':
1160 self.maximizeAct.setEnabled(False)
1161 self.minimizeAct.setEnabled(False)
1162 else:
1163 self.window.showNormal()
1164 if sys.platform == 'darwin':
1165 self.maximizeAct.setEnabled(True)
1166 self.minimizeAct.setEnabled(True)
1167 1270
1168 1271 def start(self):
1169 1272
1170 1273 # draw the window
1171 1274 self.window.show()
1172 1275
1173 1276 # Start the application main loop.
1174 1277 self.app.exec_()
1175 1278
1176 1279 #-----------------------------------------------------------------------------
1177 1280 # Main entry point
1178 1281 #-----------------------------------------------------------------------------
1179 1282
1180 1283 def main():
1181 1284 app = IPythonQtConsoleApp()
1182 1285 app.initialize()
1183 1286 app.start()
1184 1287
1185 1288
1186 1289 if __name__ == '__main__':
1187 1290 main()
General Comments 0
You need to be logged in to leave comments. Login now