##// END OF EJS Templates
cleanup some closing logic...
MinRK -
Show More
@@ -1,782 +1,806 b''
1 1 """The Qt MainWindow for the QtConsole
2 2
3 3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
4 4 common actions.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14
15 15 """
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 # stdlib imports
22 22 import sys
23 23 import webbrowser
24 24
25 25 # System library imports
26 26 from IPython.external.qt import QtGui,QtCore
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Classes
30 30 #-----------------------------------------------------------------------------
31 31
32 32 class MainWindow(QtGui.QMainWindow):
33 33
34 34 #---------------------------------------------------------------------------
35 35 # 'object' interface
36 36 #---------------------------------------------------------------------------
37 37
38 def __init__(self, app, frontend, existing=False, may_close=True,
38 def __init__(self, app,
39 39 confirm_exit=True,
40 40 new_frontend_factory=None, slave_frontend_factory=None,
41 41 ):
42 42 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
43 43
44 44 Parameters
45 45 ----------
46 46
47 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 48 confirm_exit : bool, optional
56 49 Whether we should prompt on close of tabs
57 50 new_frontend_factory : callable
58 51 A callable that returns a new IPythonWidget instance, attached to
59 52 its own running kernel.
60 53 slave_frontend_factory : callable
61 54 A callable that takes an existing IPythonWidget, and returns a new
62 55 IPythonWidget instance, attached to the same kernel.
63 56 """
64 57
65 58 super(MainWindow, self).__init__()
66 59 self._app = app
60 self.confirm_exit = confirm_exit
67 61 self.new_frontend_factory = new_frontend_factory
68 62 self.slave_frontend_factory = slave_frontend_factory
69 63
70 64 self.tab_widget = QtGui.QTabWidget(self)
71 65 self.tab_widget.setDocumentMode(True)
72 66 self.tab_widget.setTabsClosable(True)
73 67 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
74 68
75 69 self.setCentralWidget(self.tab_widget)
76 self.update_tab_bar_visibility()
70 self.tab_widget.tabBar().setVisible(False)
77 71
78 72 def update_tab_bar_visibility(self):
79 73 """ update visibility of the tabBar depending of the number of tab
80 74
81 75 0 or 1 tab, tabBar hidden
82 76 2+ tabs, tabBar visible
83 77
84 78 send a self.close if number of tab ==0
85 79
86 80 need to be called explicitely, or be connected to tabInserted/tabRemoved
87 81 """
88 82 if self.tab_widget.count() <= 1:
89 83 self.tab_widget.tabBar().setVisible(False)
90 84 else:
91 85 self.tab_widget.tabBar().setVisible(True)
92 86 if self.tab_widget.count()==0 :
93 87 self.close()
94 88
95 89 @property
96 90 def active_frontend(self):
97 91 return self.tab_widget.currentWidget()
98 92
99 93 def create_tab_with_new_frontend(self):
100 94 """create a new frontend and attach it to a new tab"""
101 95 widget = self.new_frontend_factory()
102 96 self.add_tab_with_frontend(widget)
103 97
104 98 def create_tab_with_current_kernel(self):
105 99 """create a new frontend attached to the same kernel as the current tab"""
106 100 current_widget = self.tab_widget.currentWidget()
107 101 current_widget_index = self.tab_widget.indexOf(current_widget)
108 102 current_widget_name = self.tab_widget.tabText(current_widget_index)
109 103 widget = self.slave_frontend_factory(current_widget)
110 104 if 'slave' in current_widget_name:
111 105 # don't keep stacking slaves
112 106 name = current_widget_name
113 107 else:
114 108 name = str('('+current_widget_name+') slave')
115 109 self.add_tab_with_frontend(widget,name=name)
116 110
117 111 def close_tab(self,current_tab):
118 112 """ Called when you need to try to close a tab.
119 113
120 114 It takes the number of the tab to be closed as argument, or a referece
121 115 to the wiget insite this tab
122 116 """
123 117
124 118 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
125 119 # and a reference to the trontend to close
126 120 if type(current_tab) is not int :
127 121 current_tab = self.tab_widget.indexOf(current_tab)
128 122 closing_widget=self.tab_widget.widget(current_tab)
129 123
130 124
131 125 # when trying to be closed, widget might re-send a request to be closed again, but will
132 126 # be deleted when event will be processed. So need to check that widget still exist and
133 127 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
134 128 # re-send by this fonction on the master widget, which ask all slaves widget to exit
135 129 if closing_widget==None:
136 130 return
137 131
138 #get a list of all wwidget not owning the kernel.
139 slave_tabs=self.find_slaves_tabs(closing_widget)
132 #get a list of all slave widgets on the same kernel.
133 slave_tabs = self.find_slave_widgets(closing_widget)
140 134
141 135 keepkernel = None #Use the prompt by default
142 136 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
143 137 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
138 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
139 # we set local slave tabs._hidden to True to avoid prompting for kernel
140 # restart when they get the signal. and then "forward" the 'exit'
141 # to the main window
148 142 if keepkernel is not None:
149 143 for tab in slave_tabs:
150 144 tab._hidden = True
151 if closing_widget in slave_tabs :
145 if closing_widget in slave_tabs:
152 146 try :
153 147 self.find_master_tab(closing_widget).execute('exit')
154 148 except AttributeError:
155 149 self.log.info("Master already closed or not local, closing only current tab")
156 150 self.tab_widget.removeTab(current_tab)
151 self.update_tab_bar_visibility()
157 152 return
158 153
159 154 kernel_manager = closing_widget.kernel_manager
160 155
161 156 if keepkernel is None and not closing_widget._confirm_exit:
162 157 # don't prompt, just terminate the kernel if we own it
163 158 # or leave it alone if we don't
164 keepkernel = not closing_widget._existing
165
159 keepkernel = closing_widget._existing
166 160 if keepkernel is None: #show prompt
167 161 if kernel_manager and kernel_manager.channels_running:
168 162 title = self.window().windowTitle()
169 163 cancel = QtGui.QMessageBox.Cancel
170 164 okay = QtGui.QMessageBox.Ok
171 165 if closing_widget._may_close:
172 166 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)
167 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
168 justthis = QtGui.QPushButton("&No, just this Tab", self)
175 169 justthis.setShortcut('N')
176 closeall = QtGui.QPushButton("&Yes, quit everything", self)
170 closeall = QtGui.QPushButton("&Yes, close all", self)
177 171 closeall.setShortcut('Y')
178 172 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
179 173 title, msg)
180 174 box.setInformativeText(info)
181 175 box.addButton(cancel)
182 176 box.addButton(justthis, QtGui.QMessageBox.NoRole)
183 177 box.addButton(closeall, QtGui.QMessageBox.YesRole)
184 178 box.setDefaultButton(closeall)
185 179 box.setEscapeButton(cancel)
186 180 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
187 181 box.setIconPixmap(pixmap)
188 182 reply = box.exec_()
189 183 if reply == 1: # close All
190 184 for slave in slave_tabs:
191 185 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
192 186 closing_widget.execute("exit")
193 187 self.tab_widget.removeTab(current_tab)
194 188 elif reply == 0: # close Console
195 189 if not closing_widget._existing:
196 # Have kernel: don't quit, just close the window
197 self._app.setQuitOnLastWindowClosed(False)
190 # Have kernel: don't quit, just close the tab
198 191 closing_widget.execute("exit True")
192 self.tab_widget.removeTab(current_tab)
199 193 else:
200 194 reply = QtGui.QMessageBox.question(self, title,
201 195 "Are you sure you want to close this Console?"+
202 196 "\nThe Kernel and other Consoles will remain active.",
203 197 okay|cancel,
204 198 defaultButton=okay
205 199 )
206 200 if reply == okay:
207 201 self.tab_widget.removeTab(current_tab)
208 202 elif keepkernel: #close console but leave kernel running (no prompt)
203 self.tab_widget.removeTab(current_tab)
209 204 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)
205 kernel_manager.stop_channels()
213 206 else: #close console and kernel (no prompt)
207 self.tab_widget.removeTab(current_tab)
214 208 if kernel_manager and kernel_manager.channels_running:
209 kernel_manager.shutdown_kernel()
215 210 for slave in slave_tabs:
211 slave.kernel_manager.stop_channels()
216 212 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
217 self.tab_widget.removeTab(current_tab)
218 kernel_manager.shutdown_kernel()
213
219 214 self.update_tab_bar_visibility()
220 215
221 216 def add_tab_with_frontend(self,frontend,name=None):
222 217 """ insert a tab with a given frontend in the tab bar, and give it a name
223 218
224 219 """
225 220 if not name:
226 221 name=str('kernel '+str(self.tab_widget.count()))
227 222 self.tab_widget.addTab(frontend,name)
228 223 self.update_tab_bar_visibility()
229 224 self.make_frontend_visible(frontend)
230 225 frontend.exit_requested.connect(self.close_tab)
231 226
232 227 def next_tab(self):
233 228 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
234 229
235 230 def prev_tab(self):
236 231 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
237 232
238 233 def make_frontend_visible(self,frontend):
239 234 widget_index=self.tab_widget.indexOf(frontend)
240 235 if widget_index > 0 :
241 236 self.tab_widget.setCurrentIndex(widget_index)
242 237
243 238 def find_master_tab(self,tab,as_list=False):
244 239 """
245 240 Try to return the frontend that own the kernel attached to the given widget/tab.
246 241
247 242 Only find frontend owed by the current application. Selection
248 243 based on port of the kernel, might be inacurate if several kernel
249 244 on different ip use same port number.
250 245
251 246 This fonction does the conversion tabNumber/widget if needed.
252 247 Might return None if no master widget (non local kernel)
253 248 Will crash IPython if more than 1 masterWidget
254 249
255 250 When asList set to True, always return a list of widget(s) owning
256 251 the kernel. The list might be empty or containing several Widget.
257 252 """
258 253
259 254 #convert from/to int/richIpythonWidget if needed
260 if type(tab) == int:
255 if isinstance(tab, int):
261 256 tab = self.tab_widget.widget(tab)
262 km=tab.kernel_manager;
257 km=tab.kernel_manager
263 258
264 259 #build list of all widgets
265 260 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
266 261
267 262 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
268 263 # And should have a _may_close attribute
269 filtred_widget_list = [ widget for widget in widget_list if
264 filtered_widget_list = [ widget for widget in widget_list if
270 265 widget.kernel_manager.connection_file == km.connection_file and
271 266 hasattr(widget,'_may_close') ]
272 267 # 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]
268 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
274 269 if as_list:
275 270 return master_widget
276 271 assert(len(master_widget)<=1 )
277 272 if len(master_widget)==0:
278 273 return None
279 274
280 275 return master_widget[0]
281 276
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.
277 def find_slave_widgets(self,tab):
278 """return all the frontends that do not own the kernel attached to the given widget/tab.
285 279
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.
280 Only find frontends owned by the current application. Selection
281 based on connection file of the kernel.
289 282
290 This fonction does the conversion tabNumber/widget if needed.
283 This function does the conversion tabNumber/widget if needed.
291 284 """
292 285 #convert from/to int/richIpythonWidget if needed
293 if type(tab) == int:
286 if isinstance(tab, int):
294 287 tab = self.tab_widget.widget(tab)
295 km=tab.kernel_manager;
288 km=tab.kernel_manager
296 289
297 290 #build list of all widgets
298 291 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
299 292
300 293 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
301 294 filtered_widget_list = ( widget for widget in widget_list if
302 295 widget.kernel_manager.connection_file == km.connection_file)
303 296 # Get a list of all widget owning the same kernel and removed it from
304 297 # the previous cadidate. (better using sets ?)
305 master_widget_list = self.find_master_tab(tab,as_list=True)
298 master_widget_list = self.find_master_tab(tab, as_list=True)
306 299 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
307 300
308 301 return slave_list
309 302
310 303 # Populate the menu bar with common actions and shortcuts
311 304 def add_menu_action(self, menu, action):
312 305 """Add action to menu as well as self
313 306
314 307 So that when the menu bar is invisible, its actions are still available.
315 308 """
316 309 menu.addAction(action)
317 310 self.addAction(action)
318 311
319 312 def init_menu_bar(self):
320 313 #create menu in the order they should appear in the menu bar
321 314 self.init_file_menu()
322 315 self.init_edit_menu()
323 316 self.init_view_menu()
324 317 self.init_kernel_menu()
325 318 self.init_magic_menu()
326 319 self.init_window_menu()
327 320 self.init_help_menu()
328 321
329 322 def init_file_menu(self):
330 323 self.file_menu = self.menuBar().addMenu("&File")
331 324
332 325 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
333 326 self,
334 327 shortcut="Ctrl+T",
335 328 triggered=self.create_tab_with_new_frontend)
336 329 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
337 330
338 331 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
339 332 self,
340 333 shortcut="Ctrl+Shift+T",
341 334 triggered=self.create_tab_with_current_kernel)
342 335 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
343 336
344 337 self.file_menu.addSeparator()
345 338
346 339 self.close_action=QtGui.QAction("&Close Tab",
347 340 self,
348 341 shortcut="Ctrl+W",
349 342 triggered=self.close_active_frontend
350 343 )
351 344 self.add_menu_action(self.file_menu, self.close_action)
352 345
353 346 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
354 347 self,
355 348 shortcut="Ctrl+S",
356 349 triggered=self.export_action_active_frontend
357 350 )
358 351 self.add_menu_action(self.file_menu, self.export_action)
359 352
360 353 self.file_menu.addSeparator()
361 354
362 355 # Ctrl actually maps to Cmd on OSX, which avoids conflict with history
363 356 # action, which is already bound to true Ctrl+P
364 357 print_shortcut = "Ctrl+P" if sys.platform == 'darwin' else 'Ctrl+Shift+P'
365 358 self.print_action = QtGui.QAction("&Print",
366 359 self,
367 360 shortcut=print_shortcut,
368 361 triggered=self.print_action_active_frontend)
369 362 self.add_menu_action(self.file_menu, self.print_action)
370 363
371 364 if sys.platform == 'darwin':
372 365 # OSX always has Quit in the Application menu, only add it
373 366 # to the File menu elsewhere.
374 367
375 368 self.file_menu.addSeparator()
376 369
377 370 self.quit_action = QtGui.QAction("&Quit",
378 371 self,
379 372 shortcut=QtGui.QKeySequence.Quit,
380 373 triggered=self.close,
381 374 )
382 375 self.add_menu_action(self.file_menu, self.quit_action)
383 376
384 377
385 378 def init_edit_menu(self):
386 379 self.edit_menu = self.menuBar().addMenu("&Edit")
387 380
388 381 self.undo_action = QtGui.QAction("&Undo",
389 382 self,
390 383 shortcut="Ctrl+Z",
391 384 statusTip="Undo last action if possible",
392 385 triggered=self.undo_active_frontend
393 386 )
394 387 self.add_menu_action(self.edit_menu, self.undo_action)
395 388
396 389 self.redo_action = QtGui.QAction("&Redo",
397 390 self,
398 391 shortcut="Ctrl+Shift+Z",
399 392 statusTip="Redo last action if possible",
400 393 triggered=self.redo_active_frontend)
401 394 self.add_menu_action(self.edit_menu, self.redo_action)
402 395
403 396 self.edit_menu.addSeparator()
404 397
405 398 self.cut_action = QtGui.QAction("&Cut",
406 399 self,
407 400 shortcut=QtGui.QKeySequence.Cut,
408 401 triggered=self.cut_active_frontend
409 402 )
410 403 self.add_menu_action(self.edit_menu, self.cut_action)
411 404
412 405 self.copy_action = QtGui.QAction("&Copy",
413 406 self,
414 407 shortcut=QtGui.QKeySequence.Copy,
415 408 triggered=self.copy_active_frontend
416 409 )
417 410 self.add_menu_action(self.edit_menu, self.copy_action)
418 411
419 412 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
420 413 self,
421 414 shortcut="Ctrl+Shift+C",
422 415 triggered=self.copy_raw_active_frontend
423 416 )
424 417 self.add_menu_action(self.edit_menu, self.copy_raw_action)
425 418
426 419 self.paste_action = QtGui.QAction("&Paste",
427 420 self,
428 421 shortcut=QtGui.QKeySequence.Paste,
429 422 triggered=self.paste_active_frontend
430 423 )
431 424 self.add_menu_action(self.edit_menu, self.paste_action)
432 425
433 426 self.edit_menu.addSeparator()
434 427
435 428 self.select_all_action = QtGui.QAction("Select &All",
436 429 self,
437 430 shortcut="Ctrl+A",
438 431 triggered=self.select_all_active_frontend
439 432 )
440 433 self.add_menu_action(self.edit_menu, self.select_all_action)
441 434
442 435
443 436 def init_view_menu(self):
444 437 self.view_menu = self.menuBar().addMenu("&View")
445 438
446 439 if sys.platform != 'darwin':
447 440 # disable on OSX, where there is always a menu bar
448 441 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
449 442 self,
450 443 shortcut="Ctrl+Shift+M",
451 444 statusTip="Toggle visibility of menubar",
452 445 triggered=self.toggle_menu_bar)
453 446 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
454 447
455 448 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
456 449 self.full_screen_act = QtGui.QAction("&Full Screen",
457 450 self,
458 451 shortcut=fs_key,
459 452 statusTip="Toggle between Fullscreen and Normal Size",
460 453 triggered=self.toggleFullScreen)
461 454 self.add_menu_action(self.view_menu, self.full_screen_act)
462 455
463 456 self.view_menu.addSeparator()
464 457
465 458 self.increase_font_size = QtGui.QAction("Zoom &In",
466 459 self,
467 460 shortcut="Ctrl++",
468 461 triggered=self.increase_font_size_active_frontend
469 462 )
470 463 self.add_menu_action(self.view_menu, self.increase_font_size)
471 464
472 465 self.decrease_font_size = QtGui.QAction("Zoom &Out",
473 466 self,
474 467 shortcut="Ctrl+-",
475 468 triggered=self.decrease_font_size_active_frontend
476 469 )
477 470 self.add_menu_action(self.view_menu, self.decrease_font_size)
478 471
479 472 self.reset_font_size = QtGui.QAction("Zoom &Reset",
480 473 self,
481 474 shortcut="Ctrl+0",
482 475 triggered=self.reset_font_size_active_frontend
483 476 )
484 477 self.add_menu_action(self.view_menu, self.reset_font_size)
485 478
486 479 self.view_menu.addSeparator()
487 480
488 481 self.clear_action = QtGui.QAction("&Clear Screen",
489 482 self,
490 483 shortcut='Ctrl+L',
491 484 statusTip="Clear the console",
492 485 triggered=self.clear_magic_active_frontend)
493 486 self.add_menu_action(self.view_menu, self.clear_action)
494 487
495 488 def init_kernel_menu(self):
496 489 self.kernel_menu = self.menuBar().addMenu("&Kernel")
497 490 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
498 491 # keep the signal shortcuts to ctrl, rather than
499 492 # platform-default like we do elsewhere.
500 493
501 494 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
502 495
503 496 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
504 497 self,
505 498 triggered=self.interrupt_kernel_active_frontend,
506 499 shortcut=ctrl+"+C",
507 500 )
508 501 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
509 502
510 503 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
511 504 self,
512 505 triggered=self.restart_kernel_active_frontend,
513 506 shortcut=ctrl+"+.",
514 507 )
515 508 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
516 509
517 510 self.kernel_menu.addSeparator()
518 511
519 512 def init_magic_menu(self):
520 513 self.magic_menu = self.menuBar().addMenu("&Magic")
521 514 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
522 515
523 516 self.reset_action = QtGui.QAction("&Reset",
524 517 self,
525 518 statusTip="Clear all varible from workspace",
526 519 triggered=self.reset_magic_active_frontend)
527 520 self.add_menu_action(self.magic_menu, self.reset_action)
528 521
529 522 self.history_action = QtGui.QAction("&History",
530 523 self,
531 524 statusTip="show command history",
532 525 triggered=self.history_magic_active_frontend)
533 526 self.add_menu_action(self.magic_menu, self.history_action)
534 527
535 528 self.save_action = QtGui.QAction("E&xport History ",
536 529 self,
537 530 statusTip="Export History as Python File",
538 531 triggered=self.save_magic_active_frontend)
539 532 self.add_menu_action(self.magic_menu, self.save_action)
540 533
541 534 self.who_action = QtGui.QAction("&Who",
542 535 self,
543 536 statusTip="List interactive variable",
544 537 triggered=self.who_magic_active_frontend)
545 538 self.add_menu_action(self.magic_menu, self.who_action)
546 539
547 540 self.who_ls_action = QtGui.QAction("Wh&o ls",
548 541 self,
549 542 statusTip="Return a list of interactive variable",
550 543 triggered=self.who_ls_magic_active_frontend)
551 544 self.add_menu_action(self.magic_menu, self.who_ls_action)
552 545
553 546 self.whos_action = QtGui.QAction("Who&s",
554 547 self,
555 548 statusTip="List interactive variable with detail",
556 549 triggered=self.whos_magic_active_frontend)
557 550 self.add_menu_action(self.magic_menu, self.whos_action)
558 551
559 552 # allmagics submenu:
560 553
561 554 #for now this is just a copy and paste, but we should get this dynamically
562 555 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
563 556 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
564 557 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
565 558 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
566 559 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
567 560 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
568 561 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
569 562 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
570 563 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
571 564 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
572 565
573 566 def make_dynamic_magic(i):
574 567 def inner_dynamic_magic():
575 568 self.active_frontend.execute(i)
576 569 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
577 570 return inner_dynamic_magic
578 571
579 572 for magic in magiclist:
580 573 xaction = QtGui.QAction(magic,
581 574 self,
582 575 triggered=make_dynamic_magic(magic)
583 576 )
584 577 self.all_magic_menu.addAction(xaction)
585 578
586 579 def init_window_menu(self):
587 580 self.window_menu = self.menuBar().addMenu("&Window")
588 581 if sys.platform == 'darwin':
589 582 # add min/maximize actions to OSX, which lacks default bindings.
590 583 self.minimizeAct = QtGui.QAction("Mini&mize",
591 584 self,
592 585 shortcut="Ctrl+m",
593 586 statusTip="Minimize the window/Restore Normal Size",
594 587 triggered=self.toggleMinimized)
595 588 # maximize is called 'Zoom' on OSX for some reason
596 589 self.maximizeAct = QtGui.QAction("&Zoom",
597 590 self,
598 591 shortcut="Ctrl+Shift+M",
599 592 statusTip="Maximize the window/Restore Normal Size",
600 593 triggered=self.toggleMaximized)
601 594
602 595 self.add_menu_action(self.window_menu, self.minimizeAct)
603 596 self.add_menu_action(self.window_menu, self.maximizeAct)
604 597 self.window_menu.addSeparator()
605 598
606 599 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
607 600 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
608 601 self,
609 602 shortcut=prev_key,
610 603 statusTip="Select previous tab",
611 604 triggered=self.prev_tab)
612 605 self.add_menu_action(self.window_menu, self.prev_tab_act)
613 606
614 607 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
615 608 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
616 609 self,
617 610 shortcut=next_key,
618 611 statusTip="Select next tab",
619 612 triggered=self.next_tab)
620 613 self.add_menu_action(self.window_menu, self.next_tab_act)
621 614
622 615 def init_help_menu(self):
623 616 # please keep the Help menu in Mac Os even if empty. It will
624 617 # automatically contain a search field to search inside menus and
625 618 # please keep it spelled in English, as long as Qt Doesn't support
626 619 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
627 620 # this search field fonctionality
628 621
629 622 self.help_menu = self.menuBar().addMenu("&Help")
630 623
631 624
632 625 # Help Menu
633 626
634 627 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
635 628 self,
636 629 triggered=self.intro_active_frontend
637 630 )
638 631 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
639 632
640 633 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
641 634 self,
642 635 triggered=self.quickref_active_frontend
643 636 )
644 637 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
645 638
646 639 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
647 640 self,
648 641 triggered=self.guiref_active_frontend
649 642 )
650 643 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
651 644
652 645 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
653 646 self,
654 647 triggered=self._open_online_help)
655 648 self.add_menu_action(self.help_menu, self.onlineHelpAct)
656 649
657 650 # minimize/maximize/fullscreen actions:
658 651
659 652 def toggle_menu_bar(self):
660 menu_bar = self.menuBar();
653 menu_bar = self.menuBar()
661 654 if menu_bar.isVisible():
662 655 menu_bar.setVisible(False)
663 656 else:
664 657 menu_bar.setVisible(True)
665 658
666 659 def toggleMinimized(self):
667 660 if not self.isMinimized():
668 661 self.showMinimized()
669 662 else:
670 663 self.showNormal()
671 664
672 665 def _open_online_help(self):
673 666 filename="http://ipython.org/ipython-doc/stable/index.html"
674 667 webbrowser.open(filename, new=1, autoraise=True)
675 668
676 669 def toggleMaximized(self):
677 670 if not self.isMaximized():
678 671 self.showMaximized()
679 672 else:
680 673 self.showNormal()
681 674
682 675 # Min/Max imizing while in full screen give a bug
683 676 # when going out of full screen, at least on OSX
684 677 def toggleFullScreen(self):
685 678 if not self.isFullScreen():
686 679 self.showFullScreen()
687 680 if sys.platform == 'darwin':
688 681 self.maximizeAct.setEnabled(False)
689 682 self.minimizeAct.setEnabled(False)
690 683 else:
691 684 self.showNormal()
692 685 if sys.platform == 'darwin':
693 686 self.maximizeAct.setEnabled(True)
694 687 self.minimizeAct.setEnabled(True)
695 688
696 689 def close_active_frontend(self):
697 690 self.close_tab(self.active_frontend)
698 691
699 692 def restart_kernel_active_frontend(self):
700 693 self.active_frontend.request_restart_kernel()
701 694
702 695 def interrupt_kernel_active_frontend(self):
703 696 self.active_frontend.request_interrupt_kernel()
704 697
705 698 def cut_active_frontend(self):
706 699 self.active_frontend.cut_action.trigger()
707 700
708 701 def copy_active_frontend(self):
709 702 self.active_frontend.copy_action.trigger()
710 703
711 704 def copy_raw_active_frontend(self):
712 705 self.active_frontend._copy_raw_action.trigger()
713 706
714 707 def paste_active_frontend(self):
715 708 self.active_frontend.paste_action.trigger()
716 709
717 710 def undo_active_frontend(self):
718 711 self.active_frontend.undo()
719 712
720 713 def redo_active_frontend(self):
721 714 self.active_frontend.redo()
722 715
723 716 def reset_magic_active_frontend(self):
724 717 self.active_frontend.execute("%reset")
725 718
726 719 def history_magic_active_frontend(self):
727 720 self.active_frontend.execute("%history")
728 721
729 722 def save_magic_active_frontend(self):
730 723 self.active_frontend.save_magic()
731 724
732 725 def clear_magic_active_frontend(self):
733 726 self.active_frontend.execute("%clear")
734 727
735 728 def who_magic_active_frontend(self):
736 729 self.active_frontend.execute("%who")
737 730
738 731 def who_ls_magic_active_frontend(self):
739 732 self.active_frontend.execute("%who_ls")
740 733
741 734 def whos_magic_active_frontend(self):
742 735 self.active_frontend.execute("%whos")
743 736
744 737 def print_action_active_frontend(self):
745 738 self.active_frontend.print_action.trigger()
746 739
747 740 def export_action_active_frontend(self):
748 741 self.active_frontend.export_action.trigger()
749 742
750 743 def select_all_active_frontend(self):
751 744 self.active_frontend.select_all_action.trigger()
752 745
753 746 def increase_font_size_active_frontend(self):
754 747 self.active_frontend.increase_font_size.trigger()
755 748
756 749 def decrease_font_size_active_frontend(self):
757 750 self.active_frontend.decrease_font_size.trigger()
758 751
759 752 def reset_font_size_active_frontend(self):
760 753 self.active_frontend.reset_font_size.trigger()
761 754
762 755 def guiref_active_frontend(self):
763 756 self.active_frontend.execute("%guiref")
764 757
765 758 def intro_active_frontend(self):
766 759 self.active_frontend.execute("?")
767 760
768 761 def quickref_active_frontend(self):
769 762 self.active_frontend.execute("%quickref")
770 763 #---------------------------------------------------------------------------
771 764 # QWidget interface
772 765 #---------------------------------------------------------------------------
773 766
774 767 def closeEvent(self, event):
775 768 """ Forward the close event to every tabs contained by the windows
776 769 """
770 if self.tab_widget.count() == 0:
771 # no tabs, just close
772 event.accept()
773 return
777 774 # 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)
775 title = self.window().windowTitle()
776 cancel = QtGui.QMessageBox.Cancel
777 okay = QtGui.QMessageBox.Ok
778
779 if self.confirm_exit:
780 msg = "Close all tabs, stop all kernels, and Quit?"
781 closeall = QtGui.QPushButton("&Yes, quit everything", self)
782 closeall.setShortcut('Y')
783 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
784 title, msg)
785 # box.setInformativeText(info)
786 box.addButton(cancel)
787 box.addButton(closeall, QtGui.QMessageBox.YesRole)
788 box.setDefaultButton(closeall)
789 box.setEscapeButton(cancel)
790 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
791 box.setIconPixmap(pixmap)
792 reply = box.exec_()
793 else:
794 reply = okay
795
796 if reply == cancel:
797 return
798 if reply == okay:
799 while self.tab_widget.count() >= 1:
800 # prevent further confirmations:
801 widget = self.active_frontend
802 widget._confirm_exit = False
803 self.close_tab(widget)
804
781 805 event.accept()
782 806
@@ -1,538 +1,538 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 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14
15 15 """
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 # stdlib imports
22 22 import json
23 23 import os
24 24 import signal
25 25 import sys
26 26 import uuid
27 27
28 28 # System library imports
29 29 from IPython.external.qt import QtGui
30 30
31 31 # Local imports
32 32 from IPython.config.application import boolean_flag
33 33 from IPython.core.application import BaseIPythonApplication
34 34 from IPython.core.profiledir import ProfileDir
35 35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 39 from IPython.frontend.qt.console import styles
40 40 from IPython.frontend.qt.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 71 # Aliases and Flags
72 72 #-----------------------------------------------------------------------------
73 73
74 74 flags = dict(ipkernel_flags)
75 75 qt_flags = {
76 76 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
77 77 "Connect to an existing kernel. If no argument specified, guess most recent"),
78 78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
79 79 "Use a pure Python kernel instead of an IPython kernel."),
80 80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
81 81 "Disable rich text support."),
82 82 }
83 83 qt_flags.update(boolean_flag(
84 84 'gui-completion', 'ConsoleWidget.gui_completion',
85 85 "use a GUI widget for tab completion",
86 86 "use plaintext output for completion"
87 87 ))
88 88 qt_flags.update(boolean_flag(
89 89 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
90 90 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
91 91 to force a direct exit without any confirmation.
92 92 """,
93 93 """Don't prompt the user when exiting. This will terminate the kernel
94 94 if it is owned by the frontend, and leave it alive if it is external.
95 95 """
96 96 ))
97 97 flags.update(qt_flags)
98 98
99 99 aliases = dict(ipkernel_aliases)
100 100
101 101 qt_aliases = dict(
102 102 hb = 'IPythonQtConsoleApp.hb_port',
103 103 shell = 'IPythonQtConsoleApp.shell_port',
104 104 iopub = 'IPythonQtConsoleApp.iopub_port',
105 105 stdin = 'IPythonQtConsoleApp.stdin_port',
106 106 ip = 'IPythonQtConsoleApp.ip',
107 107 existing = 'IPythonQtConsoleApp.existing',
108 108 f = 'IPythonQtConsoleApp.connection_file',
109 109
110 110 style = 'IPythonWidget.syntax_style',
111 111 stylesheet = 'IPythonQtConsoleApp.stylesheet',
112 112 colors = 'ZMQInteractiveShell.colors',
113 113
114 114 editor = 'IPythonWidget.editor',
115 115 paging = 'ConsoleWidget.paging',
116 116 ssh = 'IPythonQtConsoleApp.sshserver',
117 117 )
118 118 aliases.update(qt_aliases)
119 119
120 120 #-----------------------------------------------------------------------------
121 121 # Classes
122 122 #-----------------------------------------------------------------------------
123 123
124 124 #-----------------------------------------------------------------------------
125 125 # IPythonQtConsole
126 126 #-----------------------------------------------------------------------------
127 127
128 128
129 129 class IPythonQtConsoleApp(BaseIPythonApplication):
130 130 name = 'ipython-qtconsole'
131 131 default_config_file_name='ipython_config.py'
132 132
133 133 description = """
134 134 The IPython QtConsole.
135 135
136 136 This launches a Console-style application using Qt. It is not a full
137 137 console, in that launched terminal subprocesses will not be able to accept
138 138 input.
139 139
140 140 The QtConsole supports various extra features beyond the Terminal IPython
141 141 shell, such as inline plotting with matplotlib, via:
142 142
143 143 ipython qtconsole --pylab=inline
144 144
145 145 as well as saving your session as HTML, and printing the output.
146 146
147 147 """
148 148 examples = _examples
149 149
150 150 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
151 151 flags = Dict(flags)
152 152 aliases = Dict(aliases)
153 153
154 154 kernel_argv = List(Unicode)
155 155
156 156 # create requested profiles by default, if they don't exist:
157 157 auto_create = CBool(True)
158 158 # connection info:
159 159 ip = Unicode(LOCALHOST, config=True,
160 160 help="""Set the kernel\'s IP address [default localhost].
161 161 If the IP address is something other than localhost, then
162 162 Consoles on other machines will be able to connect
163 163 to the Kernel, so be careful!"""
164 164 )
165 165
166 166 sshserver = Unicode('', config=True,
167 167 help="""The SSH server to use to connect to the kernel.""")
168 168 sshkey = Unicode('', config=True,
169 169 help="""Path to the ssh key to use for logging in to the ssh server.""")
170 170
171 171 hb_port = Int(0, config=True,
172 172 help="set the heartbeat port [default: random]")
173 173 shell_port = Int(0, config=True,
174 174 help="set the shell (XREP) port [default: random]")
175 175 iopub_port = Int(0, config=True,
176 176 help="set the iopub (PUB) port [default: random]")
177 177 stdin_port = Int(0, config=True,
178 178 help="set the stdin (XREQ) port [default: random]")
179 179 connection_file = Unicode('', config=True,
180 180 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
181 181
182 182 This file will contain the IP, ports, and authentication key needed to connect
183 183 clients to this kernel. By default, this file will be created in the security-dir
184 184 of the current profile, but can be specified by absolute path.
185 185 """)
186 186 def _connection_file_default(self):
187 187 return 'kernel-%i.json' % os.getpid()
188 188
189 189 existing = Unicode('', config=True,
190 190 help="""Connect to an already running kernel""")
191 191
192 192 stylesheet = Unicode('', config=True,
193 193 help="path to a custom CSS stylesheet")
194 194
195 195 pure = CBool(False, config=True,
196 196 help="Use a pure Python kernel instead of an IPython kernel.")
197 197 plain = CBool(False, config=True,
198 198 help="Use a plaintext widget instead of rich text (plain can't print/save).")
199 199
200 200 def _pure_changed(self, name, old, new):
201 201 kind = 'plain' if self.plain else 'rich'
202 202 self.config.ConsoleWidget.kind = kind
203 203 if self.pure:
204 204 self.widget_factory = FrontendWidget
205 205 elif self.plain:
206 206 self.widget_factory = IPythonWidget
207 207 else:
208 208 self.widget_factory = RichIPythonWidget
209 209
210 210 _plain_changed = _pure_changed
211 211
212 212 confirm_exit = CBool(True, config=True,
213 213 help="""
214 214 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
215 215 to force a direct exit without any confirmation.""",
216 216 )
217 217
218 218 # the factory for creating a widget
219 219 widget_factory = Any(RichIPythonWidget)
220 220
221 221 def parse_command_line(self, argv=None):
222 222 super(IPythonQtConsoleApp, self).parse_command_line(argv)
223 223 if argv is None:
224 224 argv = sys.argv[1:]
225 225
226 226 self.kernel_argv = list(argv) # copy
227 227 # kernel should inherit default config file from frontend
228 228 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
229 229 # Scrub frontend-specific flags
230 230 for a in argv:
231 231 if a.startswith('-') and a.lstrip('-') in qt_flags:
232 232 self.kernel_argv.remove(a)
233 233 swallow_next = False
234 234 for a in argv:
235 235 if swallow_next:
236 236 self.kernel_argv.remove(a)
237 237 swallow_next = False
238 238 continue
239 239 if a.startswith('-'):
240 240 split = a.lstrip('-').split('=')
241 241 alias = split[0]
242 242 if alias in qt_aliases:
243 243 self.kernel_argv.remove(a)
244 244 if len(split) == 1:
245 245 # alias passed with arg via space
246 246 swallow_next = True
247 247
248 248 def init_connection_file(self):
249 249 """find the connection file, and load the info if found.
250 250
251 251 The current working directory and the current profile's security
252 252 directory will be searched for the file if it is not given by
253 253 absolute path.
254 254
255 255 When attempting to connect to an existing kernel and the `--existing`
256 256 argument does not match an existing file, it will be interpreted as a
257 257 fileglob, and the matching file in the current profile's security dir
258 258 with the latest access time will be used.
259 259 """
260 260 if self.existing:
261 261 try:
262 262 cf = find_connection_file(self.existing)
263 263 except Exception:
264 264 self.log.critical("Could not find existing kernel connection file %s", self.existing)
265 265 self.exit(1)
266 266 self.log.info("Connecting to existing kernel: %s" % cf)
267 267 self.connection_file = cf
268 268 # should load_connection_file only be used for existing?
269 269 # as it is now, this allows reusing ports if an existing
270 270 # file is requested
271 271 try:
272 272 self.load_connection_file()
273 273 except Exception:
274 274 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
275 275 self.exit(1)
276 276
277 277 def load_connection_file(self):
278 278 """load ip/port/hmac config from JSON connection file"""
279 279 # this is identical to KernelApp.load_connection_file
280 280 # perhaps it can be centralized somewhere?
281 281 try:
282 282 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
283 283 except IOError:
284 284 self.log.debug("Connection File not found: %s", self.connection_file)
285 285 return
286 286 self.log.debug(u"Loading connection file %s", fname)
287 287 with open(fname) as f:
288 288 s = f.read()
289 289 cfg = json.loads(s)
290 290 if self.ip == LOCALHOST and 'ip' in cfg:
291 291 # not overridden by config or cl_args
292 292 self.ip = cfg['ip']
293 293 for channel in ('hb', 'shell', 'iopub', 'stdin'):
294 294 name = channel + '_port'
295 295 if getattr(self, name) == 0 and name in cfg:
296 296 # not overridden by config or cl_args
297 297 setattr(self, name, cfg[name])
298 298 if 'key' in cfg:
299 299 self.config.Session.key = str_to_bytes(cfg['key'])
300 300
301 301 def init_ssh(self):
302 302 """set up ssh tunnels, if needed."""
303 303 if not self.sshserver and not self.sshkey:
304 304 return
305 305
306 306 if self.sshkey and not self.sshserver:
307 307 # specifying just the key implies that we are connecting directly
308 308 self.sshserver = self.ip
309 309 self.ip = LOCALHOST
310 310
311 311 # build connection dict for tunnels:
312 312 info = dict(ip=self.ip,
313 313 shell_port=self.shell_port,
314 314 iopub_port=self.iopub_port,
315 315 stdin_port=self.stdin_port,
316 316 hb_port=self.hb_port
317 317 )
318 318
319 319 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
320 320
321 321 # tunnels return a new set of ports, which will be on localhost:
322 322 self.ip = LOCALHOST
323 323 try:
324 324 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
325 325 except:
326 326 # even catch KeyboardInterrupt
327 327 self.log.error("Could not setup tunnels", exc_info=True)
328 328 self.exit(1)
329 329
330 330 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
331 331
332 332 cf = self.connection_file
333 333 base,ext = os.path.splitext(cf)
334 334 base = os.path.basename(base)
335 335 self.connection_file = os.path.basename(base)+'-ssh'+ext
336 336 self.log.critical("To connect another client via this tunnel, use:")
337 337 self.log.critical("--existing %s" % self.connection_file)
338 338
339 339 def _new_connection_file(self):
340 340 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
341 341
342 342 def init_kernel_manager(self):
343 343 # Don't let Qt or ZMQ swallow KeyboardInterupts.
344 344 signal.signal(signal.SIGINT, signal.SIG_DFL)
345 345 sec = self.profile_dir.security_dir
346 346 try:
347 347 cf = filefind(self.connection_file, ['.', sec])
348 348 except IOError:
349 349 # file might not exist
350 350 if self.connection_file == os.path.basename(self.connection_file):
351 351 # just shortname, put it in security dir
352 352 cf = os.path.join(sec, self.connection_file)
353 353 else:
354 354 cf = self.connection_file
355 355
356 356 # Create a KernelManager and start a kernel.
357 357 self.kernel_manager = QtKernelManager(
358 358 ip=self.ip,
359 359 shell_port=self.shell_port,
360 360 iopub_port=self.iopub_port,
361 361 stdin_port=self.stdin_port,
362 362 hb_port=self.hb_port,
363 363 connection_file=cf,
364 364 config=self.config,
365 365 )
366 366 # start the kernel
367 367 if not self.existing:
368 368 kwargs = dict(ipython=not self.pure)
369 369 kwargs['extra_arguments'] = self.kernel_argv
370 370 self.kernel_manager.start_kernel(**kwargs)
371 371 elif self.sshserver:
372 372 # ssh, write new connection file
373 373 self.kernel_manager.write_connection_file()
374 374 self.kernel_manager.start_channels()
375 375
376 376 def new_frontend_master(self):
377 377 """ Create and return new frontend attached to new kernel, launched on localhost.
378 378 """
379 379 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
380 380 kernel_manager = QtKernelManager(
381 381 ip=ip,
382 382 connection_file=self._new_connection_file(),
383 383 config=self.config,
384 384 )
385 385 # start the kernel
386 386 kwargs = dict(ipython=not self.pure)
387 387 kwargs['extra_arguments'] = self.kernel_argv
388 388 kernel_manager.start_kernel(**kwargs)
389 389 kernel_manager.start_channels()
390 390 widget = self.widget_factory(config=self.config,
391 391 local_kernel=True)
392 392 widget.kernel_manager = kernel_manager
393 widget._existing=False
394 widget._confirm_exit=True
395 widget._may_close=True
393 widget._existing = False
394 widget._may_close = True
395 widget._confirm_exit = self.confirm_exit
396 396 return widget
397 397
398 398 def new_frontend_slave(self, current_widget):
399 399 """Create and return a new frontend attached to an existing kernel.
400 400
401 401 Parameters
402 402 ----------
403 403 current_widget : IPythonWidget
404 404 The IPythonWidget whose kernel this frontend is to share
405 405 """
406 406 kernel_manager = QtKernelManager(
407 407 connection_file=current_widget.kernel_manager.connection_file,
408 408 config = self.config,
409 409 )
410 410 kernel_manager.load_connection_file()
411 411 kernel_manager.start_channels()
412 412 widget = self.widget_factory(config=self.config,
413 413 local_kernel=False)
414 widget._confirm_exit=True;
415 widget._may_close=False;
414 widget._existing = True
415 widget._may_close = False
416 widget._confirm_exit = False
416 417 widget.kernel_manager = kernel_manager
417 418 return widget
418 419
419 420 def init_qt_elements(self):
420 421 # Create the widget.
421 422 self.app = QtGui.QApplication([])
422 423
423 424 base_path = os.path.abspath(os.path.dirname(__file__))
424 425 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
425 426 self.app.icon = QtGui.QIcon(icon_path)
426 427 QtGui.QApplication.setWindowIcon(self.app.icon)
427 428
428 429 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
429 430 self.widget = self.widget_factory(config=self.config,
430 431 local_kernel=local_kernel)
431 self.widget._existing = self.existing;
432 self.widget._may_close = not self.existing;
433 self.widget._confirm_exit = not self.existing;
432 self.widget._existing = self.existing
433 self.widget._may_close = not self.existing
434 self.widget._confirm_exit = self.confirm_exit
434 435
435 436 self.widget.kernel_manager = self.kernel_manager
436 self.window = MainWindow(self.app, self.widget, self.existing,
437 may_close=local_kernel,
437 self.window = MainWindow(self.app,
438 438 confirm_exit=self.confirm_exit,
439 439 new_frontend_factory=self.new_frontend_master,
440 440 slave_frontend_factory=self.new_frontend_slave,
441 441 )
442 442 self.window.log = self.log
443 443 self.window.add_tab_with_frontend(self.widget)
444 444 self.window.init_menu_bar()
445 445 self.window.setWindowTitle('Python' if self.pure else 'IPython')
446 446
447 447 def init_colors(self):
448 448 """Configure the coloring of the widget"""
449 449 # Note: This will be dramatically simplified when colors
450 450 # are removed from the backend.
451 451
452 452 if self.pure:
453 453 # only IPythonWidget supports styling
454 454 return
455 455
456 456 # parse the colors arg down to current known labels
457 457 try:
458 458 colors = self.config.ZMQInteractiveShell.colors
459 459 except AttributeError:
460 460 colors = None
461 461 try:
462 462 style = self.config.IPythonWidget.colors
463 463 except AttributeError:
464 464 style = None
465 465
466 466 # find the value for colors:
467 467 if colors:
468 468 colors=colors.lower()
469 469 if colors in ('lightbg', 'light'):
470 470 colors='lightbg'
471 471 elif colors in ('dark', 'linux'):
472 472 colors='linux'
473 473 else:
474 474 colors='nocolor'
475 475 elif style:
476 476 if style=='bw':
477 477 colors='nocolor'
478 478 elif styles.dark_style(style):
479 479 colors='linux'
480 480 else:
481 481 colors='lightbg'
482 482 else:
483 483 colors=None
484 484
485 485 # Configure the style.
486 486 widget = self.widget
487 487 if style:
488 488 widget.style_sheet = styles.sheet_from_template(style, colors)
489 489 widget.syntax_style = style
490 490 widget._syntax_style_changed()
491 491 widget._style_sheet_changed()
492 492 elif colors:
493 493 # use a default style
494 494 widget.set_default_style(colors=colors)
495 495 else:
496 496 # this is redundant for now, but allows the widget's
497 497 # defaults to change
498 498 widget.set_default_style()
499 499
500 500 if self.stylesheet:
501 501 # we got an expicit stylesheet
502 502 if os.path.isfile(self.stylesheet):
503 503 with open(self.stylesheet) as f:
504 504 sheet = f.read()
505 505 widget.style_sheet = sheet
506 506 widget._style_sheet_changed()
507 507 else:
508 508 raise IOError("Stylesheet %r not found."%self.stylesheet)
509 509
510 510 def initialize(self, argv=None):
511 511 super(IPythonQtConsoleApp, self).initialize(argv)
512 512 self.init_connection_file()
513 513 default_secure(self.config)
514 514 self.init_ssh()
515 515 self.init_kernel_manager()
516 516 self.init_qt_elements()
517 517 self.init_colors()
518 518
519 519 def start(self):
520 520
521 521 # draw the window
522 522 self.window.show()
523 523
524 524 # Start the application main loop.
525 525 self.app.exec_()
526 526
527 527 #-----------------------------------------------------------------------------
528 528 # Main entry point
529 529 #-----------------------------------------------------------------------------
530 530
531 531 def main():
532 532 app = IPythonQtConsoleApp()
533 533 app.initialize()
534 534 app.start()
535 535
536 536
537 537 if __name__ == '__main__':
538 538 main()
General Comments 0
You need to be logged in to leave comments. Login now