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